diff --git a/dist/directory/fullops-squad/.claude-plugin/plugin.json b/dist/directory/fullops-squad/.claude-plugin/plugin.json index 0dbd26a..c098a86 100644 --- a/dist/directory/fullops-squad/.claude-plugin/plugin.json +++ b/dist/directory/fullops-squad/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "fullops-squad", - "version": "1.8.5", + "version": "1.8.6", "repository": "https://github.com/tourbut/Fullops-Squad-plugin", "displayName": "FullOps Squad", "description": "Opt-in development harness that coordinates role-based AI workers in Orca with handovers, design documents, lint gates and pre-merge review.", diff --git a/dist/directory/fullops-squad/assets/repository/.fullops-squad/.gitignore b/dist/directory/fullops-squad/assets/repository/.fullops-squad/.gitignore index 7dafe84..d32cdaf 100644 --- a/dist/directory/fullops-squad/assets/repository/.fullops-squad/.gitignore +++ b/dist/directory/fullops-squad/assets/repository/.fullops-squad/.gitignore @@ -3,3 +3,5 @@ tools/frontend/ # 판정에 쓰지 않은 캡처·첫 실행 실패본·스냅샷·중간 SHA gate 로그 (rules/common/testing.md) docs/evaluations/qa-reports/**/raw/ +# 레포 작업용 도구·로그·임시 산출물 (rules/common/README.md `작업 파일 위치`) +local/ diff --git a/dist/directory/fullops-squad/assets/repository/.fullops-squad/handovers/_TEMPLATE.md b/dist/directory/fullops-squad/assets/repository/.fullops-squad/handovers/_TEMPLATE.md index 52b7575..2f60428 100644 --- a/dist/directory/fullops-squad/assets/repository/.fullops-squad/handovers/_TEMPLATE.md +++ b/dist/directory/fullops-squad/assets/repository/.fullops-squad/handovers/_TEMPLATE.md @@ -40,7 +40,7 @@ worker는 항목을 끝낼 때마다 이 목록에 체크한다. 대화가 요 관찰 가능한 성공 조건·실행할 검증·예상 실패 및 경계 조건을 적는다. 끝난 상태를 한 번에 적는다(예: 엔드포인트 이전 완료, 레거시 삭제, 테스트 통과). 증명 방법: 이 과제의 완료를 무엇으로 보이는지 적는다(테스트·명령·Jev 시나리오·확인할 흐름). 상위 의도의 성공 기준을 맡으면 그 기준을 어떻게 보이는지 연결한다. -검사별 담당·대상·실행 시점·통과 조건을 적고 worker 완료와 제품 최종 수락을 구분한다. 하네스 제약도 적는다: FullOps lint 명령의 전체 경로·cwd·`--from <기준 SHA>`, 검증 증거 파일 형식(`.fullops-squad/` 아래 `.md`는 front matter 필수 — lint DOC-003, 원시 로그·캡처 텍스트는 `.txt`/`.log`), 재검토 리뷰의 base 조건(이전 리뷰 SHA의 후손), 원격·격리 검증 환경의 스크립트 경로. 반복 범위와 증거 재사용은 [공통 테스트 기준](../rules/common/testing.md)의 `담당과 반복 범위`를 따른다. 검사·캡처·검수 역할은 테스트 레벨 범위 안에서 정한다. 캡처·영상은 필요한 판정 항목과 연결하고, 레벨이 요구하는 후속 QA·ART 검수가 있으면 담당과 인계 조건을 적는다. +검사별 담당·대상·실행 시점·통과 조건을 적고 worker 완료와 제품 최종 수락을 구분한다. 하네스 제약도 적는다: FullOps lint 명령의 전체 경로·cwd·`--from <기준 SHA>`, 검증 증거 파일 형식(`.fullops-squad/` 아래 `.md`는 front matter 필수 — lint DOC-003, 원시 로그·캡처 텍스트는 `.txt`/`.log`), 재검토 리뷰의 base 조건(이전 리뷰 SHA의 후손), 원격·격리 검증 환경의 스크립트 경로, 로그·임시 산출물 위치(`.fullops-squad/local/`, 판정 증거는 `qa-reports/<과제 키>-test/`). 반복 범위와 증거 재사용은 [공통 테스트 기준](../rules/common/testing.md)의 `담당과 반복 범위`를 따른다. 검사·캡처·검수 역할은 테스트 레벨 범위 안에서 정한다. 캡처·영상은 필요한 판정 항목과 연결하고, 레벨이 요구하는 후속 QA·ART 검수가 있으면 담당과 인계 조건을 적는다. worker는 이 조건을 모두 채울 때까지 중간 확인 없이 진행한다. 멈추고 묻는 경우는 `추가 승인이 필요한 행위`, 설명되지 않는 검증 실패, 제품 규칙 변경·범위 확대·공유 제품 기준의 불명확성이다. 제품/기술 책임 분리 레포의 DEV는 기술 계획·구조/API·버그 분석/수정을 같은 작업에서 수행하고 실제 구현에 맞게 기술 문서를 갱신한다. ### UI 작업의 디자인 기준과 검증 diff --git a/dist/directory/fullops-squad/assets/repository/.fullops-squad/project.md b/dist/directory/fullops-squad/assets/repository/.fullops-squad/project.md index 124582e..68b2de9 100644 --- a/dist/directory/fullops-squad/assets/repository/.fullops-squad/project.md +++ b/dist/directory/fullops-squad/assets/repository/.fullops-squad/project.md @@ -7,7 +7,7 @@ setup에서 이 레포를 직접 확인하고 채운다. 원본 템플릿의 기 | 제품 목적 | 미확정 | | 기본 브랜치 | 미확정 — Git에서 조회 | | 기술 스택·아키텍처 정본 | 미확정 — 기존 문서 연결 | -| 공통 개발 기준 | [rules/common/README.md](rules/common/README.md) — `fullops-common-0.3.5` | +| 공통 개발 기준 | [rules/common/README.md](rules/common/README.md) — `fullops-common-0.3.6` | | 보안·코딩 규칙 | 미확정 — 기존 문서 연결 | | 테스트·린트·빌드 명령 | 미확정 — 실행 가능한 명령과 실행 위치 기록. lint 명령은 `lint/lint.json`에 등록 | | 문서 언어 | 한국어 | diff --git a/dist/directory/fullops-squad/assets/repository/.fullops-squad/rules/common/README.md b/dist/directory/fullops-squad/assets/repository/.fullops-squad/rules/common/README.md index 951ac44..94bbe63 100644 --- a/dist/directory/fullops-squad/assets/repository/.fullops-squad/rules/common/README.md +++ b/dist/directory/fullops-squad/assets/repository/.fullops-squad/rules/common/README.md @@ -1,6 +1,6 @@ # FullOps 공통 개발 규칙 -규칙 묶음: `fullops-common-0.3.5` +규칙 묶음: `fullops-common-0.3.6` ## 적용 @@ -18,6 +18,10 @@ 비밀값 보호, 승인 범위, 리뷰 수락 기준은 작업 지시서나 외부 스킬로 임의 완화하지 않는다. 미해결 critical/high가 있으면 기존 FullOps 리뷰 검사를 통과할 수 없다. 실제 충돌이나 예외는 지시서에 경로·이유·승인 필요 여부를 기록하고, 승인이 필요한 예외는 승인 전 적용하지 않는다. +## 작업 파일 위치 + +레포 작업에 쓰는 도구·검증 로그·임시 산출물은 레포 안 `.fullops-squad/local/`에 둔다. `.fullops-squad/.gitignore`가 이 폴더를 제외한다. 여러 워크트리가 함께 쓰는 도구(휴대용 Node·make 등)는 기본 체크아웃(`git rev-parse --git-common-dir`의 상위 폴더)의 `.fullops-squad/local/`에 둔다. 레포 밖(`%LOCALAPPDATA%`·`%TEMP%`·홈 디렉터리)에는 레포 전용 파일을 만들지 않는다. 호스트가 주는 세션 scratchpad는 그 세션에서만 쓰는 임시 파일에 쓴다. 예외가 필요하면 사용자 승인을 받은 뒤 경로·용도·정리 시점을 `project.md`에 기록한다. 과제 판정 증거는 [테스트](testing.md)의 `qa-reports` 규칙을 따른다. + ## 전달과 검토 지시서의 `적용 기준과 예외`에 이 묶음의 식별자, 사용한 프로젝트 문서 경로와 기준 커밋을 적는다. 미커밋 문서는 명시한 스냅샷 경로로 식별한다. 규칙을 수정했다면 같은 식별자만으로 동일한 내용이라고 판단하지 않는다. diff --git a/dist/directory/fullops-squad/assets/repository/.fullops-squad/rules/common/testing.md b/dist/directory/fullops-squad/assets/repository/.fullops-squad/rules/common/testing.md index 86fa750..12368dc 100644 --- a/dist/directory/fullops-squad/assets/repository/.fullops-squad/rules/common/testing.md +++ b/dist/directory/fullops-squad/assets/repository/.fullops-squad/rules/common/testing.md @@ -8,7 +8,7 @@ - `full`: 안정된 통합 후보에서 전체 회귀와 필요한 통합·수락 검사를 담당자별로 한 번 수행한다. - `exhaustive`: full에 프로젝트에 필요한 E2E·성능·장시간·환경 검수를 더해 안정된 후보에서 한 번 수행한다. -보안·데이터 손실 방지·프로젝트 필수 검사·독립 리뷰(`review.py need`가 필요로 판정한 변경)·최종 수락 조건은 모든 레벨에서 유지한다. 지시서는 레벨 밖의 검사·캡처 폭·테마·검수 역할을 더하지 않는다. 더 필요하면 레벨을 올리거나 사용자에게 묻는다. 완료 조건에 선택 레벨·검사 담당·대상·시점을 기록하고 통과 뒤에는 새 변경·실패·근거 결함이 있을 때만 재검증한다. +보안·데이터 손실 방지·프로젝트 필수 검사·독립 리뷰(`review.py need`가 필요로 판정한 변경)·최종 수락 조건은 모든 레벨에서 유지한다. 지시서는 레벨 밖의 검사·캡처 폭·테마·검수 역할을 더하지 않는다. 더 필요하면 레벨을 올리거나 사용자에게 묻는다. 사용자가 승인한 수락 기준(기획·의도 문서)이 명시한 캡처·판정은 레벨 기본값보다 우선한다. 지시서는 그 수락 기준을 근거로 적고, 보고서는 그 판정 때문에 길어져도 된다. 완료 조건에 선택 레벨·검사 담당·대상·시점을 기록하고 통과 뒤에는 새 변경·실패·근거 결함이 있을 때만 재검증한다. lint.json의 test 명령에 최소 level을 명시하면 범위 밖 명령은 skipped와 사유를 남긴다. 레벨 없는 기존 명령과 required: true 명령은 항상 실행한다. 명시적 setup/update에서 실제 프로젝트 기준으로 전체 회귀의 level을 분류하고, 변경 경로용 기존 명령을 연결한다. 명령 문법·영향 범위를 추정하거나 기존 필수 검사를 낮추지 않는다. 기준 merge-base의 설정을 사용하며 검사 생략을 PASS로 보고하지 않는다. @@ -32,7 +32,7 @@ lint.json의 test 명령에 최소 level을 명시하면 범위 밖 명령은 sk 캡처는 시각 판정이 필요한 지정 항목에만 만든다. 영상은 모션·깜빡임·시간에 따른 변화처럼 정지 화면으로 판정할 수 없는 항목에만 만든다. 통합 후보마다 필요한 자료를 한 번 확보해 검수자와 공유하며, 자동 검사로 판정되는 값·상태는 실행 결과로 남긴다. -커밋하는 증거는 보고서의 판정 항목이 링크하는 파일만이다. 통과 항목의 캡처, 첫 실행 실패본, HTML·DOM 스냅샷, 완료 SHA 이전 중간 커밋의 gate 로그와 그 해시 목록은 `docs/evaluations/qa-reports/<과제 키>-test/raw/`에 둔다. 이 경로는 `.fullops-squad/.gitignore`가 제외한다. gate 로그(lint·test·integration)는 완료 SHA 1건만 보존한다. 같은 화면의 시각 검수 캡처는 지시서가 정한 검수 역할 하나(designer 또는 tester)가 만든다. 구현 담당자는 지시서가 자기 판정 항목으로 지정한 렌더만 만들고, 검수용 렌더를 대신 만들지 않는다. 증거를 만들려고 쓴 일회성 스크립트와 그 출력도 `raw/`에 둔다. 같은 과제의 수정·재검증·배포 확인은 새 보고서 폴더를 만들지 않고 기존 `report.md`에 절을 덧붙인다. +커밋하는 증거는 보고서의 판정 항목이 링크하는 파일만이다. 통과 항목의 캡처, 첫 실행 실패본, HTML·DOM 스냅샷, 완료 SHA 이전 중간 커밋의 gate 로그와 그 해시 목록은 `docs/evaluations/qa-reports/<과제 키>-test/raw/`에 둔다. 이 경로는 `.fullops-squad/.gitignore`가 제외한다. gate 로그(lint·test·integration)는 완료 SHA 1건만 보존한다. 같은 화면의 시각 검수 캡처는 지시서가 정한 검수 역할 하나(designer 또는 tester)가 만든다. 구현 담당자는 지시서가 자기 판정 항목으로 지정한 렌더만 만들고, 검수용 렌더를 대신 만들지 않는다. 증거를 만들려고 쓴 일회성 스크립트와 그 출력도 `raw/`에 둔다. 보고서는 `raw/`를 링크하지 않는다. 다른 과제에서 다시 쓸 검증 하네스(실행 스크립트·compose override·E2E 설정)는 `raw/`가 아니라 레포의 커밋되는 위치(예: `scripts/qa/`)에 두고, 비밀값 경로만 환경 변수로 받는다. 같은 과제의 수정·재검증·배포 확인은 새 보고서 폴더를 만들지 않고 기존 `report.md`에 절을 덧붙인다. UI/UX 품질은 과제 단위가 아니라 통합 후보·마일스톤의 큰 틀 검수에서 판단한다. 판정 기준은 `docs/planning/intents/`의 성공 기준이다. 주요 사용자 흐름은 `docs/evaluations/scenarios/`의 고정 시나리오 세트로 유지해 통합 후보마다 다시 돌리고, 발견한 결함은 시나리오로 추가한다. 지정 담당은 그 흐름을 처음부터 끝까지 실행하며 화면 사이의 흐름·일관성·정보 구조를 함께 본다. 결과는 우선순위를 매긴 개선 목록 하나로 남기고, 다음 개선 과제는 그 목록을 범위로 여러 화면을 함께 바꾸게 배정한다. 캡처는 이 검수에서 판정에 쓴 화면만 남긴다. diff --git a/dist/directory/fullops-squad/plugin.json b/dist/directory/fullops-squad/plugin.json index bf4070f..023b125 100644 --- a/dist/directory/fullops-squad/plugin.json +++ b/dist/directory/fullops-squad/plugin.json @@ -1,5 +1,5 @@ { "name": "fullops-squad", - "version": "1.8.5", + "version": "1.8.6", "description": "Orca worker 핸드오버와 개발 산출물을 관리하는 레포별 선택형 하네스" } diff --git a/dist/directory/fullops-squad/releases/1.8.6.md b/dist/directory/fullops-squad/releases/1.8.6.md new file mode 100644 index 0000000..d28315a --- /dev/null +++ b/dist/directory/fullops-squad/releases/1.8.6.md @@ -0,0 +1,36 @@ +# 1.8.6 — 작은 원인으로 큰 비용이 드는 지점 줄이기, 작업 파일 위치 규칙 + +근거: jevpet UX3-A 작업 회고(#94), KnowsLink 레포 밖 작업 파일 15G(#95). + +## 변경 + +### 반복 비용 줄이기 (#94) + +- **기록만 바뀐 커밋은 lint를 다시 돌리지 않는다**: lint 통과 HEAD 뒤 커밋이 완료 기록·검증 보고(`.fullops-squad/handovers/`·`.fullops-squad/docs/evaluations/`)만 바꿨으면 done-gate가 이전 통과를 그대로 인정한다. 바뀐 기록의 front matter는 done-gate가 직접 검사한다(DOC-002·DOC-003). 산출물 문서·설정·코드가 바뀌었으면 지금처럼 lint를 다시 요구한다. +- **실행 순서 안내**: 작업 트리가 깨끗하지 않아 lint가 거절할 때 걸린 파일과 순서(구현 커밋 → lint → 완료 보고 → `work.py finish` → 커밋)를 함께 알린다. `fullops-work` 스킬에도 같은 순서를 적었다. +- **`raw/` 링크 알림**: qa-reports 보고서가 커밋하지 않는 `raw/`를 링크하면 쓴 직후 한 번 알린다. 막지는 않는다. +- **패킷 불일치 오류가 원인을 말한다**: `packet … identity differs`가 task·role·attempt·지시서 중 무엇이 다른지 표시하고, 지시서가 다르면 front matter(`status` 등)를 되돌리거나 패킷을 다시 만들라고 안내한다. +- **수락 기준 우선**: 사용자가 승인한 수락 기준이 명시한 캡처·판정은 테스트 레벨 기본값보다 우선한다. 보고서 길이 알림도 그 경우는 그대로 두라고 안내한다. + +### 작업 파일 위치 (#95, fullops-common-0.3.6) + +- 레포 작업에 쓰는 도구·검증 로그·임시 산출물은 레포 안 `.fullops-squad/local/`(gitignore)에 둔다. 여러 워크트리가 함께 쓰는 도구는 기본 체크아웃의 `.fullops-squad/local/`에 둔다. 레포 밖(`%LOCALAPPDATA%`·`%TEMP%`·홈)에는 레포 전용 파일을 만들지 않는다. 예외는 사용자 승인 뒤 `project.md`에 경로·용도·정리 시점을 기록한다. +- 다시 쓸 검증 하네스(실행 스크립트·compose override·E2E 설정)는 `raw/`가 아니라 레포의 커밋되는 위치(예: `scripts/qa/`)에 두고 비밀값 경로만 환경 변수로 받는다. +- 지시서 템플릿의 `완료 기준과 검증`에 로그·임시 산출물 위치를 적었다. + +보류: 패킷 context 문서를 선택 항목으로 돌리는 안(#94-5)은 읽기 증거 계약을 약하게 해 보류한다. 레포 밖 쓰기를 감지하는 hook(#95-4)은 정상 설치·scratchpad와 구분이 어려워 넣지 않는다. + +## 필수 적용 + +update-fullops는 이 절의 항목을 해당 없음·보류로 분류하지 않는다. `update.py --verify`가 통과해야 `plugin_version`을 1.8.6으로 올린다. + +- 작업 파일 위치 규칙 — 확인: `.fullops-squad/rules/common/README.md`에 `작업 파일 위치` +- 작업 파일 폴더 제외 — 확인: `.fullops-squad/.gitignore`에 `local/` +- 수락 기준 우선·검증 하네스 위치 — 확인: `.fullops-squad/rules/common/testing.md`에 `검증 하네스` +- 지시서 템플릿의 작업 파일 위치 — 확인: `.fullops-squad/handovers/_TEMPLATE.md`에 `.fullops-squad/local/` + +## 기존 레포 적용 + +- 위 필수 적용: setup은 기존 파일을 바꾸지 않는다. `rules/common/README.md`·`rules/common/testing.md`는 새 설치 패키지의 `assets/repository/.fullops-squad/rules/common/` 같은 파일과 비교해 통합한다(레포에서 고친 내용은 보존). `.fullops-squad/.gitignore`에 `local/` 두 줄을 더하고, `handovers/_TEMPLATE.md`의 `완료 기준과 검증` 문장을 새 판으로 바꾼다. `project.md`의 규칙 묶음 표기를 `fullops-common-0.3.6`으로 올린다. 끝나면 `update.py --verify`로 확인한다. +- 레포 밖에 이미 만든 작업 폴더가 있으면 `.fullops-squad/local/`로 옮기고 규칙 문서의 참조를 고친다. 원본 삭제는 사용자가 확인한 뒤 한다. 다른 경로(예: `.devtools/`)를 이미 쓰고 있으면 옮기는 대신 `project.md`에 예외로 기록해도 된다. +- 나머지(lint 재사용·알림·오류 문구)는 플러그인 업데이트 뒤 새 세션에서 적용된다. diff --git a/dist/directory/fullops-squad/scripts/done_gate.py b/dist/directory/fullops-squad/scripts/done_gate.py index e3f24eb..bf04a29 100644 --- a/dist/directory/fullops-squad/scripts/done_gate.py +++ b/dist/directory/fullops-squad/scripts/done_gate.py @@ -13,7 +13,7 @@ import subprocess import sys -from lint import C_STYLE, CONFIG, DEFAULT, HASH, PY +from lint import C_STYLE, CONFIG, DEFAULT, HASH, PY, blob, deliverable_violations import deliverables import policy from storage import write_json @@ -128,6 +128,23 @@ def code(path): return sorted(filter(code, committed)), sorted(filter(code, dirty)) +RECORDS = ('.fullops-squad/handovers/', '.fullops-squad/docs/evaluations/') + + +def records_only(root, passed, head): + """lint 통과 HEAD 뒤 커밋이 완료 기록·검증 보고만 바꿨는지. 그러면 명령 검사를 다시 돌리지 않고 문서 형식만 본다(#94)""" + try: + subprocess.run(['git', '-C', str(root), 'merge-base', '--is-ancestor', passed, head], check=True, capture_output=True) + changed = git(root, 'diff', '--name-only', passed, head).splitlines() + if not changed or not all(p.startswith(RECORDS) for p in changed): + return False + index = deliverables.index_rows(blob(root, head, '.fullops-squad/' + deliverables.INDEX) or '') + kept = git(root, 'diff', '--name-only', '--diff-filter=d', passed, head).splitlines() + return not any(deliverable_violations(p, blob(root, head, p) or '', index) for p in kept) + except (OSError, ValueError, subprocess.CalledProcessError): + return False + + def expected_base(root, state): roles = json.loads((root / '.fullops-squad/fullops.json').read_text()).get('roles', {}) branch = git(root, 'rev-parse', '--abbrev-ref', 'HEAD') @@ -197,7 +214,8 @@ def main(event=None): write_json(session, state) # 성공 Stop 뒤 finish가 인박스를 비워도 기준을 보존한다. try: record = json.loads((gate / 'pass.json').read_text()) - passed = record.get('head') == head and (not expected or record.get('base') == expected) + passed = (not expected or record.get('base') == expected) and bool(record.get('head')) and ( + record['head'] == head or records_only(root, record['head'], head)) except (OSError, ValueError, AttributeError): passed = False if passed and not dirty: diff --git a/dist/directory/fullops-squad/scripts/flow_gate.py b/dist/directory/fullops-squad/scripts/flow_gate.py index 8ebe095..0796108 100644 --- a/dist/directory/fullops-squad/scripts/flow_gate.py +++ b/dist/directory/fullops-squad/scripts/flow_gate.py @@ -704,7 +704,7 @@ def main(event=None): state.pop('sending', None) import harness_gate command, files = targets(field(event, 'tool_input') or {}) - notes = harness_gate.advice(root, files, config['test_level'], HANDOVER) + notes = harness_gate.advice(root, files, config['test_level'], HANDOVER) + harness_gate.raw_links(root, files) if primary(root, state): # 흐름 그래프 경계 알림(docs/flow-graph.md E7·E11·E15) notes += [n for n in [integration.merge_drift(root, shell_commands(command)), harness_gate.repeat_budget(root, shell_commands(command))] if n] diff --git a/dist/directory/fullops-squad/scripts/harness_gate.py b/dist/directory/fullops-squad/scripts/harness_gate.py index e6ef736..f03b83c 100644 --- a/dist/directory/fullops-squad/scripts/harness_gate.py +++ b/dist/directory/fullops-squad/scripts/harness_gate.py @@ -135,7 +135,28 @@ def advice(root, files, level, handover_pattern): lines = len(text.splitlines()) if lines > REPORT_LINES: notes.append(f'보고서 {relative}가 {lines}줄입니다. 테스트 레벨 {level}의 보고서는 판정 항목과 결과 표, 실패·미실행·한계만 적고 ' - '30줄 안팎으로 둡니다. 통과한 검사는 명령·SHA·종료코드 한 줄이면 됩니다.') + '30줄 안팎으로 둡니다. 통과한 검사는 명령·SHA·종료코드 한 줄이면 됩니다. 승인된 수락 기준이나 지시서가 요구한 판정 때문에 길어졌으면 그대로 둡니다.') + return notes + + +RAW_LINK = re.compile(r'\]\(\s*]+)') + + +def raw_links(root, files): + """방금 쓴 qa-reports 보고서가 Git이 제외하는 `raw/`를 링크하면 알릴 문장(#94)""" + notes = [] + for name, _ in files: + relative = _relative(root, name) + if not _folder(relative) or not relative.endswith('.md') or '/raw/' in relative: + continue + try: + text = (root / relative).read_text(encoding='utf-8') + except (OSError, UnicodeDecodeError): + continue + links = sorted({link for link in RAW_LINK.findall(text) if '://' not in link and re.search(r'(^|/)raw/', link)}) + if links: + notes.append(f"보고서 {relative}가 커밋하지 않는 `raw/`를 링크합니다({', '.join(links[:3])}). 판정 근거면 파일을 보고서 폴더로 옮기고, " + '아니면 링크를 빼세요. 다른 체크아웃에서는 열리지 않습니다(rules/common/testing.md).') return notes diff --git a/dist/directory/fullops-squad/scripts/jev_packet.py b/dist/directory/fullops-squad/scripts/jev_packet.py index 2ebe39d..c95806a 100644 --- a/dist/directory/fullops-squad/scripts/jev_packet.py +++ b/dist/directory/fullops-squad/scripts/jev_packet.py @@ -264,8 +264,13 @@ def read(relative): result = json.loads(read(relative)) meta = front_matter(text) or {} sha = instruction_digest(text) - if result.get('task_key') != key or result.get('role') != role or not meta.get('attempt') or result.get('attempt') != meta['attempt'] or result.get('instruction_sha256') != sha: - raise ValueError('packet task/role/attempt/instruction identity differs') + differs = [name for name, same in (('task', result.get('task_key') == key), ('role', result.get('role') == role), + ('attempt', bool(meta.get('attempt')) and result.get('attempt') == meta['attempt']), + ('instruction', result.get('instruction_sha256') == sha)) if not same] + if differs: + raise ValueError(f"packet {'/'.join(differs)} identity differs" + ( + '; 패킷을 만든 뒤 지시서가 바뀌었습니다. front matter(status 등)나 본문을 고쳤으면 되돌리고, ' + '지시가 바뀌었으면 패킷을 다시 만드세요' if 'instruction' in differs else '')) current_head = head or git(repo, 'rev-parse', 'HEAD') if completion: subprocess.run(['git', '-C', str(repo), 'merge-base', '--is-ancestor', result['head'], current_head], check=True, capture_output=True, env=git_env()) diff --git a/dist/directory/fullops-squad/scripts/lint.py b/dist/directory/fullops-squad/scripts/lint.py index ab7ecab..64446f4 100644 --- a/dist/directory/fullops-squad/scripts/lint.py +++ b/dist/directory/fullops-squad/scripts/lint.py @@ -289,8 +289,11 @@ def lint(repo, base_ref): (gate / 'lint-result.json').unlink(missing_ok=True) attempt = uuid.uuid4().hex write_json(gate / 'lint-attempt.json', {'attempt': attempt}) - if git(repo, 'status', '--porcelain'): - raise ValueError('작업 트리가 깨끗하지 않습니다. 커밋 후 실행하세요') + dirty = git(repo, 'status', '--porcelain') + if dirty: + files = ', '.join(line.split(maxsplit=1)[-1] for line in dirty.splitlines()[:3]) + raise ValueError(f'작업 트리가 깨끗하지 않습니다({files}). 커밋 후 실행하세요. 순서: 구현 커밋 → lint → 완료 보고 → ' + 'work.py finish → 커밋. 통과 뒤 완료 기록·검증 보고만 바꾼 커밋은 lint를 다시 돌리지 않아도 됩니다') head = git(repo, 'rev-parse', 'HEAD') base = git(repo, 'rev-parse', '--verify', base_ref + '^{commit}') merge_base = git(repo, 'merge-base', base, head) diff --git a/dist/directory/fullops-squad/skills/fullops-work/SKILL.md b/dist/directory/fullops-squad/skills/fullops-work/SKILL.md index 5ffd937..ffed361 100644 --- a/dist/directory/fullops-squad/skills/fullops-work/SKILL.md +++ b/dist/directory/fullops-squad/skills/fullops-work/SKILL.md @@ -64,7 +64,7 @@ worker는 지시서의 완료 기준을 채울 때까지 진행 보고나 승인 새 코드 파일은 맨 위 docstring·주석 1~3줄에 무엇을 하는지 적는다. 이 설명이 Jev 코드 지도가 되며, 없으면 lint DOC-001 WARNING이다. 변경 파일과 산출물을 커밋해 브랜치·SHA를 확보한다. 커밋이 허가되지 않았거나 실패하면 미커밋 상태를 명시한다. -코드를 바꿨으면 커밋한 뒤 이 스킬 기준 `../../scripts/lint.py`로 `python3 --repo <자기 체크아웃 루트> --from <지시서의 기준 ref>`를 실행한다. 규약은 `.fullops-squad/lint/README.md`를 따른다. ERROR를 모두 고치고 다시 커밋해 통과시킨다. 완료 보고의 검증 항목에 HEAD·ERROR·WARNING·실행 불가와 사유를 적는다. 보존하는 gate 로그는 완료 SHA 1건뿐이다. 중간 커밋의 로그·해시 목록과 자기 판정 항목이 아닌 렌더는 `qa-reports/<과제 키>-test/raw/`(gitignore)에 두거나 만들지 않는다. 시각 검수 캡처는 designer·tester 몫이다. 같은 실수가 반복되면 `lint.json`의 `rules`에 규칙을 추가하도록 제안한다. worker 브랜치에서 바꾼 `lint.json`은 병합 전까지 적용되지 않는다. 테스트 실행 증거는 명령 자신의 종료코드를 남긴다. `| tail`처럼 파이프로 종료코드를 가린 로그는 통과 근거로 쓰지 않는다(`set -o pipefail` 또는 파일 저장 후 확인). +순서는 구현 커밋 → lint → 완료 보고 → `work.py finish` → 커밋이다. lint 통과 뒤 완료 기록·검증 보고(`handovers/`·`docs/evaluations/`)만 바꾼 커밋은 lint를 다시 돌리지 않아도 done-gate가 통과시킨다. 코드를 바꿨으면 커밋한 뒤 이 스킬 기준 `../../scripts/lint.py`로 `python3 --repo <자기 체크아웃 루트> --from <지시서의 기준 ref>`를 실행한다. 규약은 `.fullops-squad/lint/README.md`를 따른다. ERROR를 모두 고치고 다시 커밋해 통과시킨다. 완료 보고의 검증 항목에 HEAD·ERROR·WARNING·실행 불가와 사유를 적는다. 보존하는 gate 로그는 완료 SHA 1건뿐이다. 중간 커밋의 로그·해시 목록과 자기 판정 항목이 아닌 렌더는 `qa-reports/<과제 키>-test/raw/`(gitignore)에 두거나 만들지 않는다. 시각 검수 캡처는 designer·tester 몫이다. 같은 실수가 반복되면 `lint.json`의 `rules`에 규칙을 추가하도록 제안한다. worker 브랜치에서 바꾼 `lint.json`은 병합 전까지 적용되지 않는다. 테스트 실행 증거는 명령 자신의 종료코드를 남긴다. `| tail`처럼 파이프로 종료코드를 가린 로그는 통과 근거로 쓰지 않는다(`set -o pipefail` 또는 파일 저장 후 확인). 지시서의 `갱신할 산출물`이 있으면 `fullops-deliverables`로 해당 원천 문서를 갱신하고 front matter는 `deliverables.py --stamp --task <과제 키>`로 쓴 뒤, 완료 보고의 산출물 항목에 바꾼 ID를 적는다. worker는 지시서의 복귀 주소로 `fullops-orca` report를 직접 실행한다. 전송 실패 시 보존한 로그는 유지하고 회신 재시도를 남긴다. 검토자는 같은 과제를 다시 아카이빙하지 않는다. diff --git a/dist/native/fullops-squad/.claude-plugin/plugin.json b/dist/native/fullops-squad/.claude-plugin/plugin.json index bb76e66..22dfa87 100644 --- a/dist/native/fullops-squad/.claude-plugin/plugin.json +++ b/dist/native/fullops-squad/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "fullops-squad", - "version": "1.8.5", + "version": "1.8.6", "description": "Orca worker 핸드오버와 개발 산출물을 관리하는 레포별 선택형 하네스", "author": { "name": "tourbut" diff --git a/dist/native/fullops-squad/.codex-plugin/plugin.json b/dist/native/fullops-squad/.codex-plugin/plugin.json index ce16248..d6ac5d0 100644 --- a/dist/native/fullops-squad/.codex-plugin/plugin.json +++ b/dist/native/fullops-squad/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "fullops-squad", - "version": "1.8.5", + "version": "1.8.6", "description": "Orca worker 핸드오버와 개발 산출물을 관리하는 레포별 선택형 하네스", "author": { "name": "tourbut" diff --git a/dist/native/fullops-squad/assets/repository/.fullops-squad/.gitignore b/dist/native/fullops-squad/assets/repository/.fullops-squad/.gitignore index 7dafe84..d32cdaf 100644 --- a/dist/native/fullops-squad/assets/repository/.fullops-squad/.gitignore +++ b/dist/native/fullops-squad/assets/repository/.fullops-squad/.gitignore @@ -3,3 +3,5 @@ tools/frontend/ # 판정에 쓰지 않은 캡처·첫 실행 실패본·스냅샷·중간 SHA gate 로그 (rules/common/testing.md) docs/evaluations/qa-reports/**/raw/ +# 레포 작업용 도구·로그·임시 산출물 (rules/common/README.md `작업 파일 위치`) +local/ diff --git a/dist/native/fullops-squad/assets/repository/.fullops-squad/handovers/_TEMPLATE.md b/dist/native/fullops-squad/assets/repository/.fullops-squad/handovers/_TEMPLATE.md index 52b7575..2f60428 100644 --- a/dist/native/fullops-squad/assets/repository/.fullops-squad/handovers/_TEMPLATE.md +++ b/dist/native/fullops-squad/assets/repository/.fullops-squad/handovers/_TEMPLATE.md @@ -40,7 +40,7 @@ worker는 항목을 끝낼 때마다 이 목록에 체크한다. 대화가 요 관찰 가능한 성공 조건·실행할 검증·예상 실패 및 경계 조건을 적는다. 끝난 상태를 한 번에 적는다(예: 엔드포인트 이전 완료, 레거시 삭제, 테스트 통과). 증명 방법: 이 과제의 완료를 무엇으로 보이는지 적는다(테스트·명령·Jev 시나리오·확인할 흐름). 상위 의도의 성공 기준을 맡으면 그 기준을 어떻게 보이는지 연결한다. -검사별 담당·대상·실행 시점·통과 조건을 적고 worker 완료와 제품 최종 수락을 구분한다. 하네스 제약도 적는다: FullOps lint 명령의 전체 경로·cwd·`--from <기준 SHA>`, 검증 증거 파일 형식(`.fullops-squad/` 아래 `.md`는 front matter 필수 — lint DOC-003, 원시 로그·캡처 텍스트는 `.txt`/`.log`), 재검토 리뷰의 base 조건(이전 리뷰 SHA의 후손), 원격·격리 검증 환경의 스크립트 경로. 반복 범위와 증거 재사용은 [공통 테스트 기준](../rules/common/testing.md)의 `담당과 반복 범위`를 따른다. 검사·캡처·검수 역할은 테스트 레벨 범위 안에서 정한다. 캡처·영상은 필요한 판정 항목과 연결하고, 레벨이 요구하는 후속 QA·ART 검수가 있으면 담당과 인계 조건을 적는다. +검사별 담당·대상·실행 시점·통과 조건을 적고 worker 완료와 제품 최종 수락을 구분한다. 하네스 제약도 적는다: FullOps lint 명령의 전체 경로·cwd·`--from <기준 SHA>`, 검증 증거 파일 형식(`.fullops-squad/` 아래 `.md`는 front matter 필수 — lint DOC-003, 원시 로그·캡처 텍스트는 `.txt`/`.log`), 재검토 리뷰의 base 조건(이전 리뷰 SHA의 후손), 원격·격리 검증 환경의 스크립트 경로, 로그·임시 산출물 위치(`.fullops-squad/local/`, 판정 증거는 `qa-reports/<과제 키>-test/`). 반복 범위와 증거 재사용은 [공통 테스트 기준](../rules/common/testing.md)의 `담당과 반복 범위`를 따른다. 검사·캡처·검수 역할은 테스트 레벨 범위 안에서 정한다. 캡처·영상은 필요한 판정 항목과 연결하고, 레벨이 요구하는 후속 QA·ART 검수가 있으면 담당과 인계 조건을 적는다. worker는 이 조건을 모두 채울 때까지 중간 확인 없이 진행한다. 멈추고 묻는 경우는 `추가 승인이 필요한 행위`, 설명되지 않는 검증 실패, 제품 규칙 변경·범위 확대·공유 제품 기준의 불명확성이다. 제품/기술 책임 분리 레포의 DEV는 기술 계획·구조/API·버그 분석/수정을 같은 작업에서 수행하고 실제 구현에 맞게 기술 문서를 갱신한다. ### UI 작업의 디자인 기준과 검증 diff --git a/dist/native/fullops-squad/assets/repository/.fullops-squad/project.md b/dist/native/fullops-squad/assets/repository/.fullops-squad/project.md index 124582e..68b2de9 100644 --- a/dist/native/fullops-squad/assets/repository/.fullops-squad/project.md +++ b/dist/native/fullops-squad/assets/repository/.fullops-squad/project.md @@ -7,7 +7,7 @@ setup에서 이 레포를 직접 확인하고 채운다. 원본 템플릿의 기 | 제품 목적 | 미확정 | | 기본 브랜치 | 미확정 — Git에서 조회 | | 기술 스택·아키텍처 정본 | 미확정 — 기존 문서 연결 | -| 공통 개발 기준 | [rules/common/README.md](rules/common/README.md) — `fullops-common-0.3.5` | +| 공통 개발 기준 | [rules/common/README.md](rules/common/README.md) — `fullops-common-0.3.6` | | 보안·코딩 규칙 | 미확정 — 기존 문서 연결 | | 테스트·린트·빌드 명령 | 미확정 — 실행 가능한 명령과 실행 위치 기록. lint 명령은 `lint/lint.json`에 등록 | | 문서 언어 | 한국어 | diff --git a/dist/native/fullops-squad/assets/repository/.fullops-squad/rules/common/README.md b/dist/native/fullops-squad/assets/repository/.fullops-squad/rules/common/README.md index 951ac44..94bbe63 100644 --- a/dist/native/fullops-squad/assets/repository/.fullops-squad/rules/common/README.md +++ b/dist/native/fullops-squad/assets/repository/.fullops-squad/rules/common/README.md @@ -1,6 +1,6 @@ # FullOps 공통 개발 규칙 -규칙 묶음: `fullops-common-0.3.5` +규칙 묶음: `fullops-common-0.3.6` ## 적용 @@ -18,6 +18,10 @@ 비밀값 보호, 승인 범위, 리뷰 수락 기준은 작업 지시서나 외부 스킬로 임의 완화하지 않는다. 미해결 critical/high가 있으면 기존 FullOps 리뷰 검사를 통과할 수 없다. 실제 충돌이나 예외는 지시서에 경로·이유·승인 필요 여부를 기록하고, 승인이 필요한 예외는 승인 전 적용하지 않는다. +## 작업 파일 위치 + +레포 작업에 쓰는 도구·검증 로그·임시 산출물은 레포 안 `.fullops-squad/local/`에 둔다. `.fullops-squad/.gitignore`가 이 폴더를 제외한다. 여러 워크트리가 함께 쓰는 도구(휴대용 Node·make 등)는 기본 체크아웃(`git rev-parse --git-common-dir`의 상위 폴더)의 `.fullops-squad/local/`에 둔다. 레포 밖(`%LOCALAPPDATA%`·`%TEMP%`·홈 디렉터리)에는 레포 전용 파일을 만들지 않는다. 호스트가 주는 세션 scratchpad는 그 세션에서만 쓰는 임시 파일에 쓴다. 예외가 필요하면 사용자 승인을 받은 뒤 경로·용도·정리 시점을 `project.md`에 기록한다. 과제 판정 증거는 [테스트](testing.md)의 `qa-reports` 규칙을 따른다. + ## 전달과 검토 지시서의 `적용 기준과 예외`에 이 묶음의 식별자, 사용한 프로젝트 문서 경로와 기준 커밋을 적는다. 미커밋 문서는 명시한 스냅샷 경로로 식별한다. 규칙을 수정했다면 같은 식별자만으로 동일한 내용이라고 판단하지 않는다. diff --git a/dist/native/fullops-squad/assets/repository/.fullops-squad/rules/common/testing.md b/dist/native/fullops-squad/assets/repository/.fullops-squad/rules/common/testing.md index 86fa750..12368dc 100644 --- a/dist/native/fullops-squad/assets/repository/.fullops-squad/rules/common/testing.md +++ b/dist/native/fullops-squad/assets/repository/.fullops-squad/rules/common/testing.md @@ -8,7 +8,7 @@ - `full`: 안정된 통합 후보에서 전체 회귀와 필요한 통합·수락 검사를 담당자별로 한 번 수행한다. - `exhaustive`: full에 프로젝트에 필요한 E2E·성능·장시간·환경 검수를 더해 안정된 후보에서 한 번 수행한다. -보안·데이터 손실 방지·프로젝트 필수 검사·독립 리뷰(`review.py need`가 필요로 판정한 변경)·최종 수락 조건은 모든 레벨에서 유지한다. 지시서는 레벨 밖의 검사·캡처 폭·테마·검수 역할을 더하지 않는다. 더 필요하면 레벨을 올리거나 사용자에게 묻는다. 완료 조건에 선택 레벨·검사 담당·대상·시점을 기록하고 통과 뒤에는 새 변경·실패·근거 결함이 있을 때만 재검증한다. +보안·데이터 손실 방지·프로젝트 필수 검사·독립 리뷰(`review.py need`가 필요로 판정한 변경)·최종 수락 조건은 모든 레벨에서 유지한다. 지시서는 레벨 밖의 검사·캡처 폭·테마·검수 역할을 더하지 않는다. 더 필요하면 레벨을 올리거나 사용자에게 묻는다. 사용자가 승인한 수락 기준(기획·의도 문서)이 명시한 캡처·판정은 레벨 기본값보다 우선한다. 지시서는 그 수락 기준을 근거로 적고, 보고서는 그 판정 때문에 길어져도 된다. 완료 조건에 선택 레벨·검사 담당·대상·시점을 기록하고 통과 뒤에는 새 변경·실패·근거 결함이 있을 때만 재검증한다. lint.json의 test 명령에 최소 level을 명시하면 범위 밖 명령은 skipped와 사유를 남긴다. 레벨 없는 기존 명령과 required: true 명령은 항상 실행한다. 명시적 setup/update에서 실제 프로젝트 기준으로 전체 회귀의 level을 분류하고, 변경 경로용 기존 명령을 연결한다. 명령 문법·영향 범위를 추정하거나 기존 필수 검사를 낮추지 않는다. 기준 merge-base의 설정을 사용하며 검사 생략을 PASS로 보고하지 않는다. @@ -32,7 +32,7 @@ lint.json의 test 명령에 최소 level을 명시하면 범위 밖 명령은 sk 캡처는 시각 판정이 필요한 지정 항목에만 만든다. 영상은 모션·깜빡임·시간에 따른 변화처럼 정지 화면으로 판정할 수 없는 항목에만 만든다. 통합 후보마다 필요한 자료를 한 번 확보해 검수자와 공유하며, 자동 검사로 판정되는 값·상태는 실행 결과로 남긴다. -커밋하는 증거는 보고서의 판정 항목이 링크하는 파일만이다. 통과 항목의 캡처, 첫 실행 실패본, HTML·DOM 스냅샷, 완료 SHA 이전 중간 커밋의 gate 로그와 그 해시 목록은 `docs/evaluations/qa-reports/<과제 키>-test/raw/`에 둔다. 이 경로는 `.fullops-squad/.gitignore`가 제외한다. gate 로그(lint·test·integration)는 완료 SHA 1건만 보존한다. 같은 화면의 시각 검수 캡처는 지시서가 정한 검수 역할 하나(designer 또는 tester)가 만든다. 구현 담당자는 지시서가 자기 판정 항목으로 지정한 렌더만 만들고, 검수용 렌더를 대신 만들지 않는다. 증거를 만들려고 쓴 일회성 스크립트와 그 출력도 `raw/`에 둔다. 같은 과제의 수정·재검증·배포 확인은 새 보고서 폴더를 만들지 않고 기존 `report.md`에 절을 덧붙인다. +커밋하는 증거는 보고서의 판정 항목이 링크하는 파일만이다. 통과 항목의 캡처, 첫 실행 실패본, HTML·DOM 스냅샷, 완료 SHA 이전 중간 커밋의 gate 로그와 그 해시 목록은 `docs/evaluations/qa-reports/<과제 키>-test/raw/`에 둔다. 이 경로는 `.fullops-squad/.gitignore`가 제외한다. gate 로그(lint·test·integration)는 완료 SHA 1건만 보존한다. 같은 화면의 시각 검수 캡처는 지시서가 정한 검수 역할 하나(designer 또는 tester)가 만든다. 구현 담당자는 지시서가 자기 판정 항목으로 지정한 렌더만 만들고, 검수용 렌더를 대신 만들지 않는다. 증거를 만들려고 쓴 일회성 스크립트와 그 출력도 `raw/`에 둔다. 보고서는 `raw/`를 링크하지 않는다. 다른 과제에서 다시 쓸 검증 하네스(실행 스크립트·compose override·E2E 설정)는 `raw/`가 아니라 레포의 커밋되는 위치(예: `scripts/qa/`)에 두고, 비밀값 경로만 환경 변수로 받는다. 같은 과제의 수정·재검증·배포 확인은 새 보고서 폴더를 만들지 않고 기존 `report.md`에 절을 덧붙인다. UI/UX 품질은 과제 단위가 아니라 통합 후보·마일스톤의 큰 틀 검수에서 판단한다. 판정 기준은 `docs/planning/intents/`의 성공 기준이다. 주요 사용자 흐름은 `docs/evaluations/scenarios/`의 고정 시나리오 세트로 유지해 통합 후보마다 다시 돌리고, 발견한 결함은 시나리오로 추가한다. 지정 담당은 그 흐름을 처음부터 끝까지 실행하며 화면 사이의 흐름·일관성·정보 구조를 함께 본다. 결과는 우선순위를 매긴 개선 목록 하나로 남기고, 다음 개선 과제는 그 목록을 범위로 여러 화면을 함께 바꾸게 배정한다. 캡처는 이 검수에서 판정에 쓴 화면만 남긴다. diff --git a/dist/native/fullops-squad/plugin.json b/dist/native/fullops-squad/plugin.json index bf4070f..023b125 100644 --- a/dist/native/fullops-squad/plugin.json +++ b/dist/native/fullops-squad/plugin.json @@ -1,5 +1,5 @@ { "name": "fullops-squad", - "version": "1.8.5", + "version": "1.8.6", "description": "Orca worker 핸드오버와 개발 산출물을 관리하는 레포별 선택형 하네스" } diff --git a/dist/native/fullops-squad/releases/1.8.6.md b/dist/native/fullops-squad/releases/1.8.6.md new file mode 100644 index 0000000..d28315a --- /dev/null +++ b/dist/native/fullops-squad/releases/1.8.6.md @@ -0,0 +1,36 @@ +# 1.8.6 — 작은 원인으로 큰 비용이 드는 지점 줄이기, 작업 파일 위치 규칙 + +근거: jevpet UX3-A 작업 회고(#94), KnowsLink 레포 밖 작업 파일 15G(#95). + +## 변경 + +### 반복 비용 줄이기 (#94) + +- **기록만 바뀐 커밋은 lint를 다시 돌리지 않는다**: lint 통과 HEAD 뒤 커밋이 완료 기록·검증 보고(`.fullops-squad/handovers/`·`.fullops-squad/docs/evaluations/`)만 바꿨으면 done-gate가 이전 통과를 그대로 인정한다. 바뀐 기록의 front matter는 done-gate가 직접 검사한다(DOC-002·DOC-003). 산출물 문서·설정·코드가 바뀌었으면 지금처럼 lint를 다시 요구한다. +- **실행 순서 안내**: 작업 트리가 깨끗하지 않아 lint가 거절할 때 걸린 파일과 순서(구현 커밋 → lint → 완료 보고 → `work.py finish` → 커밋)를 함께 알린다. `fullops-work` 스킬에도 같은 순서를 적었다. +- **`raw/` 링크 알림**: qa-reports 보고서가 커밋하지 않는 `raw/`를 링크하면 쓴 직후 한 번 알린다. 막지는 않는다. +- **패킷 불일치 오류가 원인을 말한다**: `packet … identity differs`가 task·role·attempt·지시서 중 무엇이 다른지 표시하고, 지시서가 다르면 front matter(`status` 등)를 되돌리거나 패킷을 다시 만들라고 안내한다. +- **수락 기준 우선**: 사용자가 승인한 수락 기준이 명시한 캡처·판정은 테스트 레벨 기본값보다 우선한다. 보고서 길이 알림도 그 경우는 그대로 두라고 안내한다. + +### 작업 파일 위치 (#95, fullops-common-0.3.6) + +- 레포 작업에 쓰는 도구·검증 로그·임시 산출물은 레포 안 `.fullops-squad/local/`(gitignore)에 둔다. 여러 워크트리가 함께 쓰는 도구는 기본 체크아웃의 `.fullops-squad/local/`에 둔다. 레포 밖(`%LOCALAPPDATA%`·`%TEMP%`·홈)에는 레포 전용 파일을 만들지 않는다. 예외는 사용자 승인 뒤 `project.md`에 경로·용도·정리 시점을 기록한다. +- 다시 쓸 검증 하네스(실행 스크립트·compose override·E2E 설정)는 `raw/`가 아니라 레포의 커밋되는 위치(예: `scripts/qa/`)에 두고 비밀값 경로만 환경 변수로 받는다. +- 지시서 템플릿의 `완료 기준과 검증`에 로그·임시 산출물 위치를 적었다. + +보류: 패킷 context 문서를 선택 항목으로 돌리는 안(#94-5)은 읽기 증거 계약을 약하게 해 보류한다. 레포 밖 쓰기를 감지하는 hook(#95-4)은 정상 설치·scratchpad와 구분이 어려워 넣지 않는다. + +## 필수 적용 + +update-fullops는 이 절의 항목을 해당 없음·보류로 분류하지 않는다. `update.py --verify`가 통과해야 `plugin_version`을 1.8.6으로 올린다. + +- 작업 파일 위치 규칙 — 확인: `.fullops-squad/rules/common/README.md`에 `작업 파일 위치` +- 작업 파일 폴더 제외 — 확인: `.fullops-squad/.gitignore`에 `local/` +- 수락 기준 우선·검증 하네스 위치 — 확인: `.fullops-squad/rules/common/testing.md`에 `검증 하네스` +- 지시서 템플릿의 작업 파일 위치 — 확인: `.fullops-squad/handovers/_TEMPLATE.md`에 `.fullops-squad/local/` + +## 기존 레포 적용 + +- 위 필수 적용: setup은 기존 파일을 바꾸지 않는다. `rules/common/README.md`·`rules/common/testing.md`는 새 설치 패키지의 `assets/repository/.fullops-squad/rules/common/` 같은 파일과 비교해 통합한다(레포에서 고친 내용은 보존). `.fullops-squad/.gitignore`에 `local/` 두 줄을 더하고, `handovers/_TEMPLATE.md`의 `완료 기준과 검증` 문장을 새 판으로 바꾼다. `project.md`의 규칙 묶음 표기를 `fullops-common-0.3.6`으로 올린다. 끝나면 `update.py --verify`로 확인한다. +- 레포 밖에 이미 만든 작업 폴더가 있으면 `.fullops-squad/local/`로 옮기고 규칙 문서의 참조를 고친다. 원본 삭제는 사용자가 확인한 뒤 한다. 다른 경로(예: `.devtools/`)를 이미 쓰고 있으면 옮기는 대신 `project.md`에 예외로 기록해도 된다. +- 나머지(lint 재사용·알림·오류 문구)는 플러그인 업데이트 뒤 새 세션에서 적용된다. diff --git a/dist/native/fullops-squad/scripts/done_gate.py b/dist/native/fullops-squad/scripts/done_gate.py index e3f24eb..bf04a29 100644 --- a/dist/native/fullops-squad/scripts/done_gate.py +++ b/dist/native/fullops-squad/scripts/done_gate.py @@ -13,7 +13,7 @@ import subprocess import sys -from lint import C_STYLE, CONFIG, DEFAULT, HASH, PY +from lint import C_STYLE, CONFIG, DEFAULT, HASH, PY, blob, deliverable_violations import deliverables import policy from storage import write_json @@ -128,6 +128,23 @@ def code(path): return sorted(filter(code, committed)), sorted(filter(code, dirty)) +RECORDS = ('.fullops-squad/handovers/', '.fullops-squad/docs/evaluations/') + + +def records_only(root, passed, head): + """lint 통과 HEAD 뒤 커밋이 완료 기록·검증 보고만 바꿨는지. 그러면 명령 검사를 다시 돌리지 않고 문서 형식만 본다(#94)""" + try: + subprocess.run(['git', '-C', str(root), 'merge-base', '--is-ancestor', passed, head], check=True, capture_output=True) + changed = git(root, 'diff', '--name-only', passed, head).splitlines() + if not changed or not all(p.startswith(RECORDS) for p in changed): + return False + index = deliverables.index_rows(blob(root, head, '.fullops-squad/' + deliverables.INDEX) or '') + kept = git(root, 'diff', '--name-only', '--diff-filter=d', passed, head).splitlines() + return not any(deliverable_violations(p, blob(root, head, p) or '', index) for p in kept) + except (OSError, ValueError, subprocess.CalledProcessError): + return False + + def expected_base(root, state): roles = json.loads((root / '.fullops-squad/fullops.json').read_text()).get('roles', {}) branch = git(root, 'rev-parse', '--abbrev-ref', 'HEAD') @@ -197,7 +214,8 @@ def main(event=None): write_json(session, state) # 성공 Stop 뒤 finish가 인박스를 비워도 기준을 보존한다. try: record = json.loads((gate / 'pass.json').read_text()) - passed = record.get('head') == head and (not expected or record.get('base') == expected) + passed = (not expected or record.get('base') == expected) and bool(record.get('head')) and ( + record['head'] == head or records_only(root, record['head'], head)) except (OSError, ValueError, AttributeError): passed = False if passed and not dirty: diff --git a/dist/native/fullops-squad/scripts/flow_gate.py b/dist/native/fullops-squad/scripts/flow_gate.py index 8ebe095..0796108 100644 --- a/dist/native/fullops-squad/scripts/flow_gate.py +++ b/dist/native/fullops-squad/scripts/flow_gate.py @@ -704,7 +704,7 @@ def main(event=None): state.pop('sending', None) import harness_gate command, files = targets(field(event, 'tool_input') or {}) - notes = harness_gate.advice(root, files, config['test_level'], HANDOVER) + notes = harness_gate.advice(root, files, config['test_level'], HANDOVER) + harness_gate.raw_links(root, files) if primary(root, state): # 흐름 그래프 경계 알림(docs/flow-graph.md E7·E11·E15) notes += [n for n in [integration.merge_drift(root, shell_commands(command)), harness_gate.repeat_budget(root, shell_commands(command))] if n] diff --git a/dist/native/fullops-squad/scripts/harness_gate.py b/dist/native/fullops-squad/scripts/harness_gate.py index e6ef736..f03b83c 100644 --- a/dist/native/fullops-squad/scripts/harness_gate.py +++ b/dist/native/fullops-squad/scripts/harness_gate.py @@ -135,7 +135,28 @@ def advice(root, files, level, handover_pattern): lines = len(text.splitlines()) if lines > REPORT_LINES: notes.append(f'보고서 {relative}가 {lines}줄입니다. 테스트 레벨 {level}의 보고서는 판정 항목과 결과 표, 실패·미실행·한계만 적고 ' - '30줄 안팎으로 둡니다. 통과한 검사는 명령·SHA·종료코드 한 줄이면 됩니다.') + '30줄 안팎으로 둡니다. 통과한 검사는 명령·SHA·종료코드 한 줄이면 됩니다. 승인된 수락 기준이나 지시서가 요구한 판정 때문에 길어졌으면 그대로 둡니다.') + return notes + + +RAW_LINK = re.compile(r'\]\(\s*]+)') + + +def raw_links(root, files): + """방금 쓴 qa-reports 보고서가 Git이 제외하는 `raw/`를 링크하면 알릴 문장(#94)""" + notes = [] + for name, _ in files: + relative = _relative(root, name) + if not _folder(relative) or not relative.endswith('.md') or '/raw/' in relative: + continue + try: + text = (root / relative).read_text(encoding='utf-8') + except (OSError, UnicodeDecodeError): + continue + links = sorted({link for link in RAW_LINK.findall(text) if '://' not in link and re.search(r'(^|/)raw/', link)}) + if links: + notes.append(f"보고서 {relative}가 커밋하지 않는 `raw/`를 링크합니다({', '.join(links[:3])}). 판정 근거면 파일을 보고서 폴더로 옮기고, " + '아니면 링크를 빼세요. 다른 체크아웃에서는 열리지 않습니다(rules/common/testing.md).') return notes diff --git a/dist/native/fullops-squad/scripts/jev_packet.py b/dist/native/fullops-squad/scripts/jev_packet.py index 2ebe39d..c95806a 100644 --- a/dist/native/fullops-squad/scripts/jev_packet.py +++ b/dist/native/fullops-squad/scripts/jev_packet.py @@ -264,8 +264,13 @@ def read(relative): result = json.loads(read(relative)) meta = front_matter(text) or {} sha = instruction_digest(text) - if result.get('task_key') != key or result.get('role') != role or not meta.get('attempt') or result.get('attempt') != meta['attempt'] or result.get('instruction_sha256') != sha: - raise ValueError('packet task/role/attempt/instruction identity differs') + differs = [name for name, same in (('task', result.get('task_key') == key), ('role', result.get('role') == role), + ('attempt', bool(meta.get('attempt')) and result.get('attempt') == meta['attempt']), + ('instruction', result.get('instruction_sha256') == sha)) if not same] + if differs: + raise ValueError(f"packet {'/'.join(differs)} identity differs" + ( + '; 패킷을 만든 뒤 지시서가 바뀌었습니다. front matter(status 등)나 본문을 고쳤으면 되돌리고, ' + '지시가 바뀌었으면 패킷을 다시 만드세요' if 'instruction' in differs else '')) current_head = head or git(repo, 'rev-parse', 'HEAD') if completion: subprocess.run(['git', '-C', str(repo), 'merge-base', '--is-ancestor', result['head'], current_head], check=True, capture_output=True, env=git_env()) diff --git a/dist/native/fullops-squad/scripts/lint.py b/dist/native/fullops-squad/scripts/lint.py index ab7ecab..64446f4 100644 --- a/dist/native/fullops-squad/scripts/lint.py +++ b/dist/native/fullops-squad/scripts/lint.py @@ -289,8 +289,11 @@ def lint(repo, base_ref): (gate / 'lint-result.json').unlink(missing_ok=True) attempt = uuid.uuid4().hex write_json(gate / 'lint-attempt.json', {'attempt': attempt}) - if git(repo, 'status', '--porcelain'): - raise ValueError('작업 트리가 깨끗하지 않습니다. 커밋 후 실행하세요') + dirty = git(repo, 'status', '--porcelain') + if dirty: + files = ', '.join(line.split(maxsplit=1)[-1] for line in dirty.splitlines()[:3]) + raise ValueError(f'작업 트리가 깨끗하지 않습니다({files}). 커밋 후 실행하세요. 순서: 구현 커밋 → lint → 완료 보고 → ' + 'work.py finish → 커밋. 통과 뒤 완료 기록·검증 보고만 바꾼 커밋은 lint를 다시 돌리지 않아도 됩니다') head = git(repo, 'rev-parse', 'HEAD') base = git(repo, 'rev-parse', '--verify', base_ref + '^{commit}') merge_base = git(repo, 'merge-base', base, head) diff --git a/dist/native/fullops-squad/skills/fullops-work/SKILL.md b/dist/native/fullops-squad/skills/fullops-work/SKILL.md index 5ffd937..ffed361 100644 --- a/dist/native/fullops-squad/skills/fullops-work/SKILL.md +++ b/dist/native/fullops-squad/skills/fullops-work/SKILL.md @@ -64,7 +64,7 @@ worker는 지시서의 완료 기준을 채울 때까지 진행 보고나 승인 새 코드 파일은 맨 위 docstring·주석 1~3줄에 무엇을 하는지 적는다. 이 설명이 Jev 코드 지도가 되며, 없으면 lint DOC-001 WARNING이다. 변경 파일과 산출물을 커밋해 브랜치·SHA를 확보한다. 커밋이 허가되지 않았거나 실패하면 미커밋 상태를 명시한다. -코드를 바꿨으면 커밋한 뒤 이 스킬 기준 `../../scripts/lint.py`로 `python3 --repo <자기 체크아웃 루트> --from <지시서의 기준 ref>`를 실행한다. 규약은 `.fullops-squad/lint/README.md`를 따른다. ERROR를 모두 고치고 다시 커밋해 통과시킨다. 완료 보고의 검증 항목에 HEAD·ERROR·WARNING·실행 불가와 사유를 적는다. 보존하는 gate 로그는 완료 SHA 1건뿐이다. 중간 커밋의 로그·해시 목록과 자기 판정 항목이 아닌 렌더는 `qa-reports/<과제 키>-test/raw/`(gitignore)에 두거나 만들지 않는다. 시각 검수 캡처는 designer·tester 몫이다. 같은 실수가 반복되면 `lint.json`의 `rules`에 규칙을 추가하도록 제안한다. worker 브랜치에서 바꾼 `lint.json`은 병합 전까지 적용되지 않는다. 테스트 실행 증거는 명령 자신의 종료코드를 남긴다. `| tail`처럼 파이프로 종료코드를 가린 로그는 통과 근거로 쓰지 않는다(`set -o pipefail` 또는 파일 저장 후 확인). +순서는 구현 커밋 → lint → 완료 보고 → `work.py finish` → 커밋이다. lint 통과 뒤 완료 기록·검증 보고(`handovers/`·`docs/evaluations/`)만 바꾼 커밋은 lint를 다시 돌리지 않아도 done-gate가 통과시킨다. 코드를 바꿨으면 커밋한 뒤 이 스킬 기준 `../../scripts/lint.py`로 `python3 --repo <자기 체크아웃 루트> --from <지시서의 기준 ref>`를 실행한다. 규약은 `.fullops-squad/lint/README.md`를 따른다. ERROR를 모두 고치고 다시 커밋해 통과시킨다. 완료 보고의 검증 항목에 HEAD·ERROR·WARNING·실행 불가와 사유를 적는다. 보존하는 gate 로그는 완료 SHA 1건뿐이다. 중간 커밋의 로그·해시 목록과 자기 판정 항목이 아닌 렌더는 `qa-reports/<과제 키>-test/raw/`(gitignore)에 두거나 만들지 않는다. 시각 검수 캡처는 designer·tester 몫이다. 같은 실수가 반복되면 `lint.json`의 `rules`에 규칙을 추가하도록 제안한다. worker 브랜치에서 바꾼 `lint.json`은 병합 전까지 적용되지 않는다. 테스트 실행 증거는 명령 자신의 종료코드를 남긴다. `| tail`처럼 파이프로 종료코드를 가린 로그는 통과 근거로 쓰지 않는다(`set -o pipefail` 또는 파일 저장 후 확인). 지시서의 `갱신할 산출물`이 있으면 `fullops-deliverables`로 해당 원천 문서를 갱신하고 front matter는 `deliverables.py --stamp --task <과제 키>`로 쓴 뒤, 완료 보고의 산출물 항목에 바꾼 ID를 적는다. worker는 지시서의 복귀 주소로 `fullops-orca` report를 직접 실행한다. 전송 실패 시 보존한 로그는 유지하고 회신 재시도를 남긴다. 검토자는 같은 과제를 다시 아카이빙하지 않는다. diff --git a/docs/releases/1.8.6.md b/docs/releases/1.8.6.md new file mode 100644 index 0000000..d28315a --- /dev/null +++ b/docs/releases/1.8.6.md @@ -0,0 +1,36 @@ +# 1.8.6 — 작은 원인으로 큰 비용이 드는 지점 줄이기, 작업 파일 위치 규칙 + +근거: jevpet UX3-A 작업 회고(#94), KnowsLink 레포 밖 작업 파일 15G(#95). + +## 변경 + +### 반복 비용 줄이기 (#94) + +- **기록만 바뀐 커밋은 lint를 다시 돌리지 않는다**: lint 통과 HEAD 뒤 커밋이 완료 기록·검증 보고(`.fullops-squad/handovers/`·`.fullops-squad/docs/evaluations/`)만 바꿨으면 done-gate가 이전 통과를 그대로 인정한다. 바뀐 기록의 front matter는 done-gate가 직접 검사한다(DOC-002·DOC-003). 산출물 문서·설정·코드가 바뀌었으면 지금처럼 lint를 다시 요구한다. +- **실행 순서 안내**: 작업 트리가 깨끗하지 않아 lint가 거절할 때 걸린 파일과 순서(구현 커밋 → lint → 완료 보고 → `work.py finish` → 커밋)를 함께 알린다. `fullops-work` 스킬에도 같은 순서를 적었다. +- **`raw/` 링크 알림**: qa-reports 보고서가 커밋하지 않는 `raw/`를 링크하면 쓴 직후 한 번 알린다. 막지는 않는다. +- **패킷 불일치 오류가 원인을 말한다**: `packet … identity differs`가 task·role·attempt·지시서 중 무엇이 다른지 표시하고, 지시서가 다르면 front matter(`status` 등)를 되돌리거나 패킷을 다시 만들라고 안내한다. +- **수락 기준 우선**: 사용자가 승인한 수락 기준이 명시한 캡처·판정은 테스트 레벨 기본값보다 우선한다. 보고서 길이 알림도 그 경우는 그대로 두라고 안내한다. + +### 작업 파일 위치 (#95, fullops-common-0.3.6) + +- 레포 작업에 쓰는 도구·검증 로그·임시 산출물은 레포 안 `.fullops-squad/local/`(gitignore)에 둔다. 여러 워크트리가 함께 쓰는 도구는 기본 체크아웃의 `.fullops-squad/local/`에 둔다. 레포 밖(`%LOCALAPPDATA%`·`%TEMP%`·홈)에는 레포 전용 파일을 만들지 않는다. 예외는 사용자 승인 뒤 `project.md`에 경로·용도·정리 시점을 기록한다. +- 다시 쓸 검증 하네스(실행 스크립트·compose override·E2E 설정)는 `raw/`가 아니라 레포의 커밋되는 위치(예: `scripts/qa/`)에 두고 비밀값 경로만 환경 변수로 받는다. +- 지시서 템플릿의 `완료 기준과 검증`에 로그·임시 산출물 위치를 적었다. + +보류: 패킷 context 문서를 선택 항목으로 돌리는 안(#94-5)은 읽기 증거 계약을 약하게 해 보류한다. 레포 밖 쓰기를 감지하는 hook(#95-4)은 정상 설치·scratchpad와 구분이 어려워 넣지 않는다. + +## 필수 적용 + +update-fullops는 이 절의 항목을 해당 없음·보류로 분류하지 않는다. `update.py --verify`가 통과해야 `plugin_version`을 1.8.6으로 올린다. + +- 작업 파일 위치 규칙 — 확인: `.fullops-squad/rules/common/README.md`에 `작업 파일 위치` +- 작업 파일 폴더 제외 — 확인: `.fullops-squad/.gitignore`에 `local/` +- 수락 기준 우선·검증 하네스 위치 — 확인: `.fullops-squad/rules/common/testing.md`에 `검증 하네스` +- 지시서 템플릿의 작업 파일 위치 — 확인: `.fullops-squad/handovers/_TEMPLATE.md`에 `.fullops-squad/local/` + +## 기존 레포 적용 + +- 위 필수 적용: setup은 기존 파일을 바꾸지 않는다. `rules/common/README.md`·`rules/common/testing.md`는 새 설치 패키지의 `assets/repository/.fullops-squad/rules/common/` 같은 파일과 비교해 통합한다(레포에서 고친 내용은 보존). `.fullops-squad/.gitignore`에 `local/` 두 줄을 더하고, `handovers/_TEMPLATE.md`의 `완료 기준과 검증` 문장을 새 판으로 바꾼다. `project.md`의 규칙 묶음 표기를 `fullops-common-0.3.6`으로 올린다. 끝나면 `update.py --verify`로 확인한다. +- 레포 밖에 이미 만든 작업 폴더가 있으면 `.fullops-squad/local/`로 옮기고 규칙 문서의 참조를 고친다. 원본 삭제는 사용자가 확인한 뒤 한다. 다른 경로(예: `.devtools/`)를 이미 쓰고 있으면 옮기는 대신 `project.md`에 예외로 기록해도 된다. +- 나머지(lint 재사용·알림·오류 문구)는 플러그인 업데이트 뒤 새 세션에서 적용된다. diff --git a/plugins/fullops-squad/assets/repository/.fullops-squad/.gitignore b/plugins/fullops-squad/assets/repository/.fullops-squad/.gitignore index 7dafe84..d32cdaf 100644 --- a/plugins/fullops-squad/assets/repository/.fullops-squad/.gitignore +++ b/plugins/fullops-squad/assets/repository/.fullops-squad/.gitignore @@ -3,3 +3,5 @@ tools/frontend/ # 판정에 쓰지 않은 캡처·첫 실행 실패본·스냅샷·중간 SHA gate 로그 (rules/common/testing.md) docs/evaluations/qa-reports/**/raw/ +# 레포 작업용 도구·로그·임시 산출물 (rules/common/README.md `작업 파일 위치`) +local/ diff --git a/plugins/fullops-squad/assets/repository/.fullops-squad/handovers/_TEMPLATE.md b/plugins/fullops-squad/assets/repository/.fullops-squad/handovers/_TEMPLATE.md index 52b7575..2f60428 100644 --- a/plugins/fullops-squad/assets/repository/.fullops-squad/handovers/_TEMPLATE.md +++ b/plugins/fullops-squad/assets/repository/.fullops-squad/handovers/_TEMPLATE.md @@ -40,7 +40,7 @@ worker는 항목을 끝낼 때마다 이 목록에 체크한다. 대화가 요 관찰 가능한 성공 조건·실행할 검증·예상 실패 및 경계 조건을 적는다. 끝난 상태를 한 번에 적는다(예: 엔드포인트 이전 완료, 레거시 삭제, 테스트 통과). 증명 방법: 이 과제의 완료를 무엇으로 보이는지 적는다(테스트·명령·Jev 시나리오·확인할 흐름). 상위 의도의 성공 기준을 맡으면 그 기준을 어떻게 보이는지 연결한다. -검사별 담당·대상·실행 시점·통과 조건을 적고 worker 완료와 제품 최종 수락을 구분한다. 하네스 제약도 적는다: FullOps lint 명령의 전체 경로·cwd·`--from <기준 SHA>`, 검증 증거 파일 형식(`.fullops-squad/` 아래 `.md`는 front matter 필수 — lint DOC-003, 원시 로그·캡처 텍스트는 `.txt`/`.log`), 재검토 리뷰의 base 조건(이전 리뷰 SHA의 후손), 원격·격리 검증 환경의 스크립트 경로. 반복 범위와 증거 재사용은 [공통 테스트 기준](../rules/common/testing.md)의 `담당과 반복 범위`를 따른다. 검사·캡처·검수 역할은 테스트 레벨 범위 안에서 정한다. 캡처·영상은 필요한 판정 항목과 연결하고, 레벨이 요구하는 후속 QA·ART 검수가 있으면 담당과 인계 조건을 적는다. +검사별 담당·대상·실행 시점·통과 조건을 적고 worker 완료와 제품 최종 수락을 구분한다. 하네스 제약도 적는다: FullOps lint 명령의 전체 경로·cwd·`--from <기준 SHA>`, 검증 증거 파일 형식(`.fullops-squad/` 아래 `.md`는 front matter 필수 — lint DOC-003, 원시 로그·캡처 텍스트는 `.txt`/`.log`), 재검토 리뷰의 base 조건(이전 리뷰 SHA의 후손), 원격·격리 검증 환경의 스크립트 경로, 로그·임시 산출물 위치(`.fullops-squad/local/`, 판정 증거는 `qa-reports/<과제 키>-test/`). 반복 범위와 증거 재사용은 [공통 테스트 기준](../rules/common/testing.md)의 `담당과 반복 범위`를 따른다. 검사·캡처·검수 역할은 테스트 레벨 범위 안에서 정한다. 캡처·영상은 필요한 판정 항목과 연결하고, 레벨이 요구하는 후속 QA·ART 검수가 있으면 담당과 인계 조건을 적는다. worker는 이 조건을 모두 채울 때까지 중간 확인 없이 진행한다. 멈추고 묻는 경우는 `추가 승인이 필요한 행위`, 설명되지 않는 검증 실패, 제품 규칙 변경·범위 확대·공유 제품 기준의 불명확성이다. 제품/기술 책임 분리 레포의 DEV는 기술 계획·구조/API·버그 분석/수정을 같은 작업에서 수행하고 실제 구현에 맞게 기술 문서를 갱신한다. ### UI 작업의 디자인 기준과 검증 diff --git a/plugins/fullops-squad/assets/repository/.fullops-squad/project.md b/plugins/fullops-squad/assets/repository/.fullops-squad/project.md index 124582e..68b2de9 100644 --- a/plugins/fullops-squad/assets/repository/.fullops-squad/project.md +++ b/plugins/fullops-squad/assets/repository/.fullops-squad/project.md @@ -7,7 +7,7 @@ setup에서 이 레포를 직접 확인하고 채운다. 원본 템플릿의 기 | 제품 목적 | 미확정 | | 기본 브랜치 | 미확정 — Git에서 조회 | | 기술 스택·아키텍처 정본 | 미확정 — 기존 문서 연결 | -| 공통 개발 기준 | [rules/common/README.md](rules/common/README.md) — `fullops-common-0.3.5` | +| 공통 개발 기준 | [rules/common/README.md](rules/common/README.md) — `fullops-common-0.3.6` | | 보안·코딩 규칙 | 미확정 — 기존 문서 연결 | | 테스트·린트·빌드 명령 | 미확정 — 실행 가능한 명령과 실행 위치 기록. lint 명령은 `lint/lint.json`에 등록 | | 문서 언어 | 한국어 | diff --git a/plugins/fullops-squad/assets/repository/.fullops-squad/rules/common/README.md b/plugins/fullops-squad/assets/repository/.fullops-squad/rules/common/README.md index 951ac44..94bbe63 100644 --- a/plugins/fullops-squad/assets/repository/.fullops-squad/rules/common/README.md +++ b/plugins/fullops-squad/assets/repository/.fullops-squad/rules/common/README.md @@ -1,6 +1,6 @@ # FullOps 공통 개발 규칙 -규칙 묶음: `fullops-common-0.3.5` +규칙 묶음: `fullops-common-0.3.6` ## 적용 @@ -18,6 +18,10 @@ 비밀값 보호, 승인 범위, 리뷰 수락 기준은 작업 지시서나 외부 스킬로 임의 완화하지 않는다. 미해결 critical/high가 있으면 기존 FullOps 리뷰 검사를 통과할 수 없다. 실제 충돌이나 예외는 지시서에 경로·이유·승인 필요 여부를 기록하고, 승인이 필요한 예외는 승인 전 적용하지 않는다. +## 작업 파일 위치 + +레포 작업에 쓰는 도구·검증 로그·임시 산출물은 레포 안 `.fullops-squad/local/`에 둔다. `.fullops-squad/.gitignore`가 이 폴더를 제외한다. 여러 워크트리가 함께 쓰는 도구(휴대용 Node·make 등)는 기본 체크아웃(`git rev-parse --git-common-dir`의 상위 폴더)의 `.fullops-squad/local/`에 둔다. 레포 밖(`%LOCALAPPDATA%`·`%TEMP%`·홈 디렉터리)에는 레포 전용 파일을 만들지 않는다. 호스트가 주는 세션 scratchpad는 그 세션에서만 쓰는 임시 파일에 쓴다. 예외가 필요하면 사용자 승인을 받은 뒤 경로·용도·정리 시점을 `project.md`에 기록한다. 과제 판정 증거는 [테스트](testing.md)의 `qa-reports` 규칙을 따른다. + ## 전달과 검토 지시서의 `적용 기준과 예외`에 이 묶음의 식별자, 사용한 프로젝트 문서 경로와 기준 커밋을 적는다. 미커밋 문서는 명시한 스냅샷 경로로 식별한다. 규칙을 수정했다면 같은 식별자만으로 동일한 내용이라고 판단하지 않는다. diff --git a/plugins/fullops-squad/assets/repository/.fullops-squad/rules/common/testing.md b/plugins/fullops-squad/assets/repository/.fullops-squad/rules/common/testing.md index 86fa750..12368dc 100644 --- a/plugins/fullops-squad/assets/repository/.fullops-squad/rules/common/testing.md +++ b/plugins/fullops-squad/assets/repository/.fullops-squad/rules/common/testing.md @@ -8,7 +8,7 @@ - `full`: 안정된 통합 후보에서 전체 회귀와 필요한 통합·수락 검사를 담당자별로 한 번 수행한다. - `exhaustive`: full에 프로젝트에 필요한 E2E·성능·장시간·환경 검수를 더해 안정된 후보에서 한 번 수행한다. -보안·데이터 손실 방지·프로젝트 필수 검사·독립 리뷰(`review.py need`가 필요로 판정한 변경)·최종 수락 조건은 모든 레벨에서 유지한다. 지시서는 레벨 밖의 검사·캡처 폭·테마·검수 역할을 더하지 않는다. 더 필요하면 레벨을 올리거나 사용자에게 묻는다. 완료 조건에 선택 레벨·검사 담당·대상·시점을 기록하고 통과 뒤에는 새 변경·실패·근거 결함이 있을 때만 재검증한다. +보안·데이터 손실 방지·프로젝트 필수 검사·독립 리뷰(`review.py need`가 필요로 판정한 변경)·최종 수락 조건은 모든 레벨에서 유지한다. 지시서는 레벨 밖의 검사·캡처 폭·테마·검수 역할을 더하지 않는다. 더 필요하면 레벨을 올리거나 사용자에게 묻는다. 사용자가 승인한 수락 기준(기획·의도 문서)이 명시한 캡처·판정은 레벨 기본값보다 우선한다. 지시서는 그 수락 기준을 근거로 적고, 보고서는 그 판정 때문에 길어져도 된다. 완료 조건에 선택 레벨·검사 담당·대상·시점을 기록하고 통과 뒤에는 새 변경·실패·근거 결함이 있을 때만 재검증한다. lint.json의 test 명령에 최소 level을 명시하면 범위 밖 명령은 skipped와 사유를 남긴다. 레벨 없는 기존 명령과 required: true 명령은 항상 실행한다. 명시적 setup/update에서 실제 프로젝트 기준으로 전체 회귀의 level을 분류하고, 변경 경로용 기존 명령을 연결한다. 명령 문법·영향 범위를 추정하거나 기존 필수 검사를 낮추지 않는다. 기준 merge-base의 설정을 사용하며 검사 생략을 PASS로 보고하지 않는다. @@ -32,7 +32,7 @@ lint.json의 test 명령에 최소 level을 명시하면 범위 밖 명령은 sk 캡처는 시각 판정이 필요한 지정 항목에만 만든다. 영상은 모션·깜빡임·시간에 따른 변화처럼 정지 화면으로 판정할 수 없는 항목에만 만든다. 통합 후보마다 필요한 자료를 한 번 확보해 검수자와 공유하며, 자동 검사로 판정되는 값·상태는 실행 결과로 남긴다. -커밋하는 증거는 보고서의 판정 항목이 링크하는 파일만이다. 통과 항목의 캡처, 첫 실행 실패본, HTML·DOM 스냅샷, 완료 SHA 이전 중간 커밋의 gate 로그와 그 해시 목록은 `docs/evaluations/qa-reports/<과제 키>-test/raw/`에 둔다. 이 경로는 `.fullops-squad/.gitignore`가 제외한다. gate 로그(lint·test·integration)는 완료 SHA 1건만 보존한다. 같은 화면의 시각 검수 캡처는 지시서가 정한 검수 역할 하나(designer 또는 tester)가 만든다. 구현 담당자는 지시서가 자기 판정 항목으로 지정한 렌더만 만들고, 검수용 렌더를 대신 만들지 않는다. 증거를 만들려고 쓴 일회성 스크립트와 그 출력도 `raw/`에 둔다. 같은 과제의 수정·재검증·배포 확인은 새 보고서 폴더를 만들지 않고 기존 `report.md`에 절을 덧붙인다. +커밋하는 증거는 보고서의 판정 항목이 링크하는 파일만이다. 통과 항목의 캡처, 첫 실행 실패본, HTML·DOM 스냅샷, 완료 SHA 이전 중간 커밋의 gate 로그와 그 해시 목록은 `docs/evaluations/qa-reports/<과제 키>-test/raw/`에 둔다. 이 경로는 `.fullops-squad/.gitignore`가 제외한다. gate 로그(lint·test·integration)는 완료 SHA 1건만 보존한다. 같은 화면의 시각 검수 캡처는 지시서가 정한 검수 역할 하나(designer 또는 tester)가 만든다. 구현 담당자는 지시서가 자기 판정 항목으로 지정한 렌더만 만들고, 검수용 렌더를 대신 만들지 않는다. 증거를 만들려고 쓴 일회성 스크립트와 그 출력도 `raw/`에 둔다. 보고서는 `raw/`를 링크하지 않는다. 다른 과제에서 다시 쓸 검증 하네스(실행 스크립트·compose override·E2E 설정)는 `raw/`가 아니라 레포의 커밋되는 위치(예: `scripts/qa/`)에 두고, 비밀값 경로만 환경 변수로 받는다. 같은 과제의 수정·재검증·배포 확인은 새 보고서 폴더를 만들지 않고 기존 `report.md`에 절을 덧붙인다. UI/UX 품질은 과제 단위가 아니라 통합 후보·마일스톤의 큰 틀 검수에서 판단한다. 판정 기준은 `docs/planning/intents/`의 성공 기준이다. 주요 사용자 흐름은 `docs/evaluations/scenarios/`의 고정 시나리오 세트로 유지해 통합 후보마다 다시 돌리고, 발견한 결함은 시나리오로 추가한다. 지정 담당은 그 흐름을 처음부터 끝까지 실행하며 화면 사이의 흐름·일관성·정보 구조를 함께 본다. 결과는 우선순위를 매긴 개선 목록 하나로 남기고, 다음 개선 과제는 그 목록을 범위로 여러 화면을 함께 바꾸게 배정한다. 캡처는 이 검수에서 판정에 쓴 화면만 남긴다. diff --git a/plugins/fullops-squad/plugin.json b/plugins/fullops-squad/plugin.json index fc2146a..7ad87e1 100644 --- a/plugins/fullops-squad/plugin.json +++ b/plugins/fullops-squad/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "fullops-squad", - "version": "1.8.5", + "version": "1.8.6", "description": "Orca worker 핸드오버와 개발 산출물을 관리하는 레포별 선택형 하네스", "author": { "name": "tourbut" diff --git a/plugins/fullops-squad/scripts/done_gate.py b/plugins/fullops-squad/scripts/done_gate.py index e3f24eb..bf04a29 100644 --- a/plugins/fullops-squad/scripts/done_gate.py +++ b/plugins/fullops-squad/scripts/done_gate.py @@ -13,7 +13,7 @@ import subprocess import sys -from lint import C_STYLE, CONFIG, DEFAULT, HASH, PY +from lint import C_STYLE, CONFIG, DEFAULT, HASH, PY, blob, deliverable_violations import deliverables import policy from storage import write_json @@ -128,6 +128,23 @@ def code(path): return sorted(filter(code, committed)), sorted(filter(code, dirty)) +RECORDS = ('.fullops-squad/handovers/', '.fullops-squad/docs/evaluations/') + + +def records_only(root, passed, head): + """lint 통과 HEAD 뒤 커밋이 완료 기록·검증 보고만 바꿨는지. 그러면 명령 검사를 다시 돌리지 않고 문서 형식만 본다(#94)""" + try: + subprocess.run(['git', '-C', str(root), 'merge-base', '--is-ancestor', passed, head], check=True, capture_output=True) + changed = git(root, 'diff', '--name-only', passed, head).splitlines() + if not changed or not all(p.startswith(RECORDS) for p in changed): + return False + index = deliverables.index_rows(blob(root, head, '.fullops-squad/' + deliverables.INDEX) or '') + kept = git(root, 'diff', '--name-only', '--diff-filter=d', passed, head).splitlines() + return not any(deliverable_violations(p, blob(root, head, p) or '', index) for p in kept) + except (OSError, ValueError, subprocess.CalledProcessError): + return False + + def expected_base(root, state): roles = json.loads((root / '.fullops-squad/fullops.json').read_text()).get('roles', {}) branch = git(root, 'rev-parse', '--abbrev-ref', 'HEAD') @@ -197,7 +214,8 @@ def main(event=None): write_json(session, state) # 성공 Stop 뒤 finish가 인박스를 비워도 기준을 보존한다. try: record = json.loads((gate / 'pass.json').read_text()) - passed = record.get('head') == head and (not expected or record.get('base') == expected) + passed = (not expected or record.get('base') == expected) and bool(record.get('head')) and ( + record['head'] == head or records_only(root, record['head'], head)) except (OSError, ValueError, AttributeError): passed = False if passed and not dirty: diff --git a/plugins/fullops-squad/scripts/flow_gate.py b/plugins/fullops-squad/scripts/flow_gate.py index 8ebe095..0796108 100644 --- a/plugins/fullops-squad/scripts/flow_gate.py +++ b/plugins/fullops-squad/scripts/flow_gate.py @@ -704,7 +704,7 @@ def main(event=None): state.pop('sending', None) import harness_gate command, files = targets(field(event, 'tool_input') or {}) - notes = harness_gate.advice(root, files, config['test_level'], HANDOVER) + notes = harness_gate.advice(root, files, config['test_level'], HANDOVER) + harness_gate.raw_links(root, files) if primary(root, state): # 흐름 그래프 경계 알림(docs/flow-graph.md E7·E11·E15) notes += [n for n in [integration.merge_drift(root, shell_commands(command)), harness_gate.repeat_budget(root, shell_commands(command))] if n] diff --git a/plugins/fullops-squad/scripts/harness_gate.py b/plugins/fullops-squad/scripts/harness_gate.py index e6ef736..f03b83c 100644 --- a/plugins/fullops-squad/scripts/harness_gate.py +++ b/plugins/fullops-squad/scripts/harness_gate.py @@ -135,7 +135,28 @@ def advice(root, files, level, handover_pattern): lines = len(text.splitlines()) if lines > REPORT_LINES: notes.append(f'보고서 {relative}가 {lines}줄입니다. 테스트 레벨 {level}의 보고서는 판정 항목과 결과 표, 실패·미실행·한계만 적고 ' - '30줄 안팎으로 둡니다. 통과한 검사는 명령·SHA·종료코드 한 줄이면 됩니다.') + '30줄 안팎으로 둡니다. 통과한 검사는 명령·SHA·종료코드 한 줄이면 됩니다. 승인된 수락 기준이나 지시서가 요구한 판정 때문에 길어졌으면 그대로 둡니다.') + return notes + + +RAW_LINK = re.compile(r'\]\(\s*]+)') + + +def raw_links(root, files): + """방금 쓴 qa-reports 보고서가 Git이 제외하는 `raw/`를 링크하면 알릴 문장(#94)""" + notes = [] + for name, _ in files: + relative = _relative(root, name) + if not _folder(relative) or not relative.endswith('.md') or '/raw/' in relative: + continue + try: + text = (root / relative).read_text(encoding='utf-8') + except (OSError, UnicodeDecodeError): + continue + links = sorted({link for link in RAW_LINK.findall(text) if '://' not in link and re.search(r'(^|/)raw/', link)}) + if links: + notes.append(f"보고서 {relative}가 커밋하지 않는 `raw/`를 링크합니다({', '.join(links[:3])}). 판정 근거면 파일을 보고서 폴더로 옮기고, " + '아니면 링크를 빼세요. 다른 체크아웃에서는 열리지 않습니다(rules/common/testing.md).') return notes diff --git a/plugins/fullops-squad/scripts/jev_packet.py b/plugins/fullops-squad/scripts/jev_packet.py index 2ebe39d..c95806a 100644 --- a/plugins/fullops-squad/scripts/jev_packet.py +++ b/plugins/fullops-squad/scripts/jev_packet.py @@ -264,8 +264,13 @@ def read(relative): result = json.loads(read(relative)) meta = front_matter(text) or {} sha = instruction_digest(text) - if result.get('task_key') != key or result.get('role') != role or not meta.get('attempt') or result.get('attempt') != meta['attempt'] or result.get('instruction_sha256') != sha: - raise ValueError('packet task/role/attempt/instruction identity differs') + differs = [name for name, same in (('task', result.get('task_key') == key), ('role', result.get('role') == role), + ('attempt', bool(meta.get('attempt')) and result.get('attempt') == meta['attempt']), + ('instruction', result.get('instruction_sha256') == sha)) if not same] + if differs: + raise ValueError(f"packet {'/'.join(differs)} identity differs" + ( + '; 패킷을 만든 뒤 지시서가 바뀌었습니다. front matter(status 등)나 본문을 고쳤으면 되돌리고, ' + '지시가 바뀌었으면 패킷을 다시 만드세요' if 'instruction' in differs else '')) current_head = head or git(repo, 'rev-parse', 'HEAD') if completion: subprocess.run(['git', '-C', str(repo), 'merge-base', '--is-ancestor', result['head'], current_head], check=True, capture_output=True, env=git_env()) diff --git a/plugins/fullops-squad/scripts/lint.py b/plugins/fullops-squad/scripts/lint.py index ab7ecab..64446f4 100644 --- a/plugins/fullops-squad/scripts/lint.py +++ b/plugins/fullops-squad/scripts/lint.py @@ -289,8 +289,11 @@ def lint(repo, base_ref): (gate / 'lint-result.json').unlink(missing_ok=True) attempt = uuid.uuid4().hex write_json(gate / 'lint-attempt.json', {'attempt': attempt}) - if git(repo, 'status', '--porcelain'): - raise ValueError('작업 트리가 깨끗하지 않습니다. 커밋 후 실행하세요') + dirty = git(repo, 'status', '--porcelain') + if dirty: + files = ', '.join(line.split(maxsplit=1)[-1] for line in dirty.splitlines()[:3]) + raise ValueError(f'작업 트리가 깨끗하지 않습니다({files}). 커밋 후 실행하세요. 순서: 구현 커밋 → lint → 완료 보고 → ' + 'work.py finish → 커밋. 통과 뒤 완료 기록·검증 보고만 바꾼 커밋은 lint를 다시 돌리지 않아도 됩니다') head = git(repo, 'rev-parse', 'HEAD') base = git(repo, 'rev-parse', '--verify', base_ref + '^{commit}') merge_base = git(repo, 'merge-base', base, head) diff --git a/plugins/fullops-squad/skills/fullops-work/SKILL.md b/plugins/fullops-squad/skills/fullops-work/SKILL.md index 5ffd937..ffed361 100644 --- a/plugins/fullops-squad/skills/fullops-work/SKILL.md +++ b/plugins/fullops-squad/skills/fullops-work/SKILL.md @@ -64,7 +64,7 @@ worker는 지시서의 완료 기준을 채울 때까지 진행 보고나 승인 새 코드 파일은 맨 위 docstring·주석 1~3줄에 무엇을 하는지 적는다. 이 설명이 Jev 코드 지도가 되며, 없으면 lint DOC-001 WARNING이다. 변경 파일과 산출물을 커밋해 브랜치·SHA를 확보한다. 커밋이 허가되지 않았거나 실패하면 미커밋 상태를 명시한다. -코드를 바꿨으면 커밋한 뒤 이 스킬 기준 `../../scripts/lint.py`로 `python3 --repo <자기 체크아웃 루트> --from <지시서의 기준 ref>`를 실행한다. 규약은 `.fullops-squad/lint/README.md`를 따른다. ERROR를 모두 고치고 다시 커밋해 통과시킨다. 완료 보고의 검증 항목에 HEAD·ERROR·WARNING·실행 불가와 사유를 적는다. 보존하는 gate 로그는 완료 SHA 1건뿐이다. 중간 커밋의 로그·해시 목록과 자기 판정 항목이 아닌 렌더는 `qa-reports/<과제 키>-test/raw/`(gitignore)에 두거나 만들지 않는다. 시각 검수 캡처는 designer·tester 몫이다. 같은 실수가 반복되면 `lint.json`의 `rules`에 규칙을 추가하도록 제안한다. worker 브랜치에서 바꾼 `lint.json`은 병합 전까지 적용되지 않는다. 테스트 실행 증거는 명령 자신의 종료코드를 남긴다. `| tail`처럼 파이프로 종료코드를 가린 로그는 통과 근거로 쓰지 않는다(`set -o pipefail` 또는 파일 저장 후 확인). +순서는 구현 커밋 → lint → 완료 보고 → `work.py finish` → 커밋이다. lint 통과 뒤 완료 기록·검증 보고(`handovers/`·`docs/evaluations/`)만 바꾼 커밋은 lint를 다시 돌리지 않아도 done-gate가 통과시킨다. 코드를 바꿨으면 커밋한 뒤 이 스킬 기준 `../../scripts/lint.py`로 `python3 --repo <자기 체크아웃 루트> --from <지시서의 기준 ref>`를 실행한다. 규약은 `.fullops-squad/lint/README.md`를 따른다. ERROR를 모두 고치고 다시 커밋해 통과시킨다. 완료 보고의 검증 항목에 HEAD·ERROR·WARNING·실행 불가와 사유를 적는다. 보존하는 gate 로그는 완료 SHA 1건뿐이다. 중간 커밋의 로그·해시 목록과 자기 판정 항목이 아닌 렌더는 `qa-reports/<과제 키>-test/raw/`(gitignore)에 두거나 만들지 않는다. 시각 검수 캡처는 designer·tester 몫이다. 같은 실수가 반복되면 `lint.json`의 `rules`에 규칙을 추가하도록 제안한다. worker 브랜치에서 바꾼 `lint.json`은 병합 전까지 적용되지 않는다. 테스트 실행 증거는 명령 자신의 종료코드를 남긴다. `| tail`처럼 파이프로 종료코드를 가린 로그는 통과 근거로 쓰지 않는다(`set -o pipefail` 또는 파일 저장 후 확인). 지시서의 `갱신할 산출물`이 있으면 `fullops-deliverables`로 해당 원천 문서를 갱신하고 front matter는 `deliverables.py --stamp --task <과제 키>`로 쓴 뒤, 완료 보고의 산출물 항목에 바꾼 ID를 적는다. worker는 지시서의 복귀 주소로 `fullops-orca` report를 직접 실행한다. 전송 실패 시 보존한 로그는 유지하고 회신 재시도를 남긴다. 검토자는 같은 과제를 다시 아카이빙하지 않는다. diff --git a/tests/common-rules.py b/tests/common-rules.py index 6828bd5..311b2e0 100644 --- a/tests/common-rules.py +++ b/tests/common-rules.py @@ -75,7 +75,7 @@ def test_rules_have_bounded_scope_provenance_and_local_links(self): directory = ASSETS / 'rules/common' self.assertEqual({p.name for p in directory.iterdir()}, BUNDLE_NAMES) readme = (directory / 'README.md').read_text() - self.assertIn('fullops-common-0.3.5', readme) + self.assertIn('fullops-common-0.3.6', readme) self.assertIn('934195f955cf0da847d59fcd6f68856bce112d8b', readme) license_text = (directory / 'ECC-LICENSE.txt').read_text() self.assertIn('Copyright (c) 2026 Affaan Mustafa', license_text) diff --git a/tests/done-gate.py b/tests/done-gate.py index b3e3a3b..a19f488 100644 --- a/tests/done-gate.py +++ b/tests/done-gate.py @@ -98,6 +98,21 @@ def hook(mode, session='s1', cwd=tmp, **event): capture_output=True, text=True) assert recovered.returncode == 0 and hook('stop') == {} + # 통과 뒤 완료 기록·검증 보고만 바꾼 커밋은 lint를 다시 돌리지 않는다(#94). 다른 문서는 다시 돌린다. + report = repo / '.fullops-squad/docs/evaluations/qa-reports/K1-test/report.md' + report.parent.mkdir(parents=True) + report.write_text('---\ntitle: K1 QA\nstatus: draft\nupdated: 2026-10-24\nowner: dev\nsummary: K1 검증\n---\n\n# K1 QA\n', encoding='utf-8') + commit('qa report') + assert hook('stop') == {}, '기록만 바뀐 커밋에 lint 재실행을 요구함' + report.write_text('# K1 QA\n', encoding='utf-8') + commit('report without front matter') + assert hook('stop')['decision'] == 'block', '형식이 깨진 기록은 lint로 돌려보내야 함' + git('reset', '-q', '--hard', 'HEAD~1') + (repo / 'notes.md').write_text('설계 메모\n', encoding='utf-8') + commit('notes') + assert hook('stop')['decision'] == 'block' + git('reset', '-q', '--hard', 'HEAD~1') + # 다른 체크아웃에서 만든 커밋을 fast-forward로 받는 것은 이 세션의 코드 변경이 아니다. worker = repo.parent / f'{repo.name}-worker' git('worktree', 'add', '-q', '-b', 'fullops/dev-work', str(worker)) diff --git a/tests/flow-gate.py b/tests/flow-gate.py index 625c8cc..1c32bfe 100644 --- a/tests/flow-gate.py +++ b/tests/flow-gate.py @@ -278,6 +278,10 @@ def scenario(messages, workers): assert hook('post', session_id='ad', tool_name='Write', tool_input={'file_path': str(inbox)}) == {} # 같은 지적은 한 번 (task / 'report.md').write_text('\n'.join(['| 검사 | 통과 |'] * 45), encoding='utf-8') assert '45줄' in hook('post', session_id='ad', tool_name='Edit', tool_input={'file_path': str(task / 'report.md')})['hookSpecificOutput']['additionalContext'] + # 커밋하지 않는 raw/를 링크한 보고서는 레벨과 무관하게 쓴 직후 알린다(#94) + (task / 'report.md').write_text('# K1\n\n![](shots/a.png) [로그](raw/d.png) [외부](https://x.test/raw/y)\n', encoding='utf-8') + raw = hook('post', session_id='raw', tool_name='Write', tool_input={'file_path': str(task / 'report.md')})['hookSpecificOutput']['additionalContext'] + assert 'raw/d.png' in raw and 'shots/a.png' not in raw and 'x.test' not in raw, raw inbox.write_bytes(saved) # 반복 예산(docs/flow-graph.md E15): 같은 과제의 재오픈·차수 키가 2회를 넘으면 coordinator에게 한 번 알린다 git('checkout', '-q', 'main') diff --git a/tests/jev-packet.py b/tests/jev-packet.py index 8db3bae..9486080 100644 --- a/tests/jev-packet.py +++ b/tests/jev-packet.py @@ -83,6 +83,14 @@ def git(*args): else: raise AssertionError('전달 항목 누락을 통과시킴') outcomes.write_text(json.dumps(data, ensure_ascii=False), encoding='utf-8') + edited = text.replace('status: draft', 'status: done', 1) + assert edited != text + try: # 어느 값이 다른지 말한다(#94) + jev_packet.check(repo, 'dev', KEY, completion=True, text=edited) + except ValueError as error: + assert 'packet instruction identity differs' in str(error) and 'status' in str(error), error + else: + raise AssertionError('지시서 변경을 통과시킴') # finish는 완료 보고의 `남은 일`에서 다른 역할 요청을 PLANS.md에 등재한다. inbox.write_text(text.partition('## 완료 보고\n')[0] + '## 완료 보고\n브랜치 fullops/dev / SHA ' + result['head'][:12] diff --git a/tests/lint.py b/tests/lint.py index 4694b68..cfe74a5 100644 --- a/tests/lint.py +++ b/tests/lint.py @@ -341,7 +341,8 @@ def lint(*extra): git('reset', '-q', '--hard', base) (repo / 'dirty.py').write_text('d = 1\n') - assert lint().returncode == 2 # 커밋 전 실행 거부 + refused = lint() + assert refused.returncode == 2 and 'dirty.py' in refused.stdout + refused.stderr and 'finish' in refused.stdout + refused.stderr # 커밋 전 실행 거부 (repo / 'dirty.py').unlink() # review.py check의 lint 게이트 (OCR 없이 리뷰 기록을 구성한다) diff --git a/tests/update.py b/tests/update.py index c2ebd00..912b3c9 100644 --- a/tests/update.py +++ b/tests/update.py @@ -117,7 +117,7 @@ def test_packaged_cli_reads_bundled_notes_without_checkout(self): '--from', '0.9.9', '--json'], capture_output=True, text=True, encoding='utf-8', check=True) report = json.loads(result.stdout) - self.assertEqual([n['version'] for n in report['releases']], ['0.9.9', '0.9.10', '0.9.11', '0.9.12', '0.9.13', '0.9.14', '1.0.0', '1.1.0', '1.1.1', '1.1.2', '1.2.0', '1.2.1', '1.3.0', '1.4.0', '1.4.1', '1.4.2', '1.4.3', '1.4.4', '1.5.0', '1.6.0', '1.7.0', '1.7.1', '1.8.0', '1.8.1', '1.8.2', '1.8.3', '1.8.4', '1.8.5']) + self.assertEqual([n['version'] for n in report['releases']], ['0.9.9', '0.9.10', '0.9.11', '0.9.12', '0.9.13', '0.9.14', '1.0.0', '1.1.0', '1.1.1', '1.1.2', '1.2.0', '1.2.1', '1.3.0', '1.4.0', '1.4.1', '1.4.2', '1.4.3', '1.4.4', '1.5.0', '1.6.0', '1.7.0', '1.7.1', '1.8.0', '1.8.1', '1.8.2', '1.8.3', '1.8.4', '1.8.5', '1.8.6']) self.assertTrue(report['steps']) self.assertIn('담당과 반복 범위', (package / 'assets/repository/.fullops-squad/rules/common/testing.md').read_text(encoding='utf-8'))