release(v4.8.4): bump framework and eight skills to 4.8.4 in lockstep; archive PR #47 reviews (two rounds)
The .claude/skills check accepts both local layouts and fails on a dangling link. Review: round 1 (@e1ed270) claude agy grok PASS, codex NOT PASS (a dangling top-level link was skipped); fixed in 150b493; round 2 4/4 PASS. Reports and the round-2 identity sidecars go to .agents/reports/<session>/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
1 parent
150b4937f8
commit
65dc7fde82
28 files changed
+543
-25
No files matched your search
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261004T044643Z-1b1440",
|
||||
"base_sha": "e1ed2708d835d712f5f3a9dfd51b8f66ca1ac447",
|
||||
"head_sha": "150b4937f816c6407be16fc4c37ab88e478927e7",
|
||||
"tree_sha": "afce44d2e0f91720d81e8d7d484f0e1358dee680",
|
||||
"review_commit": "150b4937f816c6407be16fc4c37ab88e478927e7",
|
||||
"reviewer": "planner-reviewer-codex-01",
|
||||
"job_id": "5f1a5a29",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-04T04:54:16Z"
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
# PR #47 2회차 델타 리뷰 — 5f1a5a29
|
||||
|
||||
**PASS**. 이전 R1(깨진 최상위 skills 링크의 skip)이 해결됐고, 실제 검사와 같은 함수를 사용하는 합성 배치 테스트가 회귀를 잡는다. 새 반려 사유는 발견하지 못했다.
|
||||
|
||||
## 검증 identity와 범위
|
||||
|
||||
- loop: `loop-20261004T044643Z-1b1440`
|
||||
- base: `e1ed2708d835d712f5f3a9dfd51b8f66ca1ac447`
|
||||
- head: `150b4937f816c6407be16fc4c37ab88e478927e7`
|
||||
- tree: `afce44d2e0f91720d81e8d7d484f0e1358dee680`
|
||||
- 검증 cwd: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261004T044643Z-1b1440/1`
|
||||
|
||||
HEAD/tree 일치, 시작·종료 시 git status clean. 이번 한 커밋의 변경은 `tests/test_issue42_skill_blocks.py` 하나(59 insertions, 12 deletions)이며 제품 코드·버전 변경은 없다. `git diff --check e1ed270 HEAD` 통과.
|
||||
|
||||
## 이전 지적 판정
|
||||
|
||||
| 지적 | 판정 | 근거 |
|
||||
|---|---|---|
|
||||
| R1: `.claude/skills` 자체가 dangling symlink일 때 skip | **종결** | `tests/test_issue42_skill_blocks.py:248`이 exists와 is_symlink가 모두 False인 경우에만 None을 반환한다. 깨진 링크는 :250–252의 symlink 검사에서 문제 목록을 반환하고, 실제 테스트 :264–267이 이를 실패로 처리한다. |
|
||||
|
||||
## 검증 결과
|
||||
|
||||
검증 cwd에서 실행:
|
||||
|
||||
```sh
|
||||
PYTHONDONTWRITEBYTECODE=1 /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python -m pytest -p no:cacheprovider -q --basetemp=/tmp/5f1a5a29-selected tests/test_issue42_skill_blocks.py
|
||||
```
|
||||
|
||||
**37 passed, 1 skipped in 0.06s**, rc 0. skip은 검증 트리에 원래 로컬 `.claude/skills`가 없기 때문이며, 새 합성 배치 6건은 모두 실행됐다.
|
||||
|
||||
- **같은 실제 경로로 전후 비교:** 검증 트리 안에 임시 `.claude/skills`를 존재하지 않는 대상의 symlink로 만들었다. base e1ed270에서 추출한 기존 함수는 같은 경로에서 SKIP했다. 현재 실제 pytest node는 **1 failed in 0.04s, rc 1**을 반환했다. 이 실패는 의도한 부정 탐침 결과이며 지정 모듈의 실패가 아니다.
|
||||
- **회귀 테스트의 감도:** 현재 helper의 부재 조건만 메모리상 이전 `if not link.exists():`로 되돌린 뒤, 새 `test_claude_skills_layouts(..., "broken_top", False)`를 호출하면 AssertionError가 발생했다. 즉 새 회귀 테스트가 이전 결함을 검출한다.
|
||||
- **정상 배치 유지:** 합성 `single`과 `per_skill`은 통과한다. 본 체크아웃의 스킬별 링크 배치에도 현재 helper를 읽기 전용으로 적용해 문제 목록 `[]`를 확인했다.
|
||||
- **정리:** 임시 링크와 이번 탐침이 만든 부모 디렉터리를 finally에서 제거했다. 검증 트리의 로컬 배치 부재와 git clean 상태를 다시 확인했다.
|
||||
|
||||
독립 탐침은 `/tmp/5f1a5a29-probes.py`에 있다. 위 검증 cwd에서 `PYTHONDONTWRITEBYTECODE=1 <위 Python> /tmp/5f1a5a29-probes.py`로 재현한다. 전체 탐침 실행은 rc 0이며 base SKIP/current FAIL/조건 복원 시 합성 테스트 FAIL을 각각 확인한다. 체크아웃 소스는 변경하지 않았다.
|
||||
|
||||
## 합성 테스트와 범위 판단
|
||||
|
||||
`claude_skills_problems()`(:240–260)가 실제 로컬 배치 테스트(:264)와 합성 테스트(:294)의 공통 검사 함수다. 합성 테스트(:270–300)는 각각의 tmp_path 안에서 두 디렉터리 및 상대 symlink를 구성하고, 없음/단일 링크/개별 링크/잘못된 대상/깨진 최상위 링크/깨진 개별 링크를 직접 검사한다. 프로세스·시간·실제 사용자 배치에 의존하지 않아 결정적이다. 테스트 대상이 파일·디렉터리의 경로 해석이므로 합성 대상에 SKILL.md 본문을 만들지 않은 것도 적절하다.
|
||||
|
||||
기존 모듈의 8개 스킬 inventory 보장과 링크별 대상 비교는 유지됐다. 새로운 helper는 테스트 전용이며 제품 동작을 바꾸지 않는다. 기본 검사가 워크트리에서 skip되어도 합성 6건이 실행되므로 이번 수정의 핵심 조건은 더 이상 로컬 배치 유무에 가려지지 않는다.
|
||||
|
||||
본 체크아웃은 배치 읽기에만 사용했다. 실제 .mam.env·agent 세션·herdr 서버는 건드리지 않았고, 실제 .mam 쓰기는 지시된 lifecycle과 이 리포트에 한정했다. 재계획 승격은 필요 없다.
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,70 @@
|
||||
# PR #47 리뷰 — f275a259
|
||||
|
||||
**NOT PASS**. 단일 링크와 스킬별 링크 디렉터리를 모두 받는 수정은 동작한다. 다만 `.claude/skills` 자체가 깨진 심볼릭 링크이면 부재로 오인해 skip하는 경계 사례가 남아 있다. 작은 테스트 수정으로 해결할 수 있으며 제품 코드 변경이나 재계획은 필요 없다.
|
||||
|
||||
## Identity와 범위
|
||||
|
||||
- loop: `loop-20261004T043243Z-baf133`
|
||||
- base: `6af4a796b1bdce3b2e929b7a45a8a22d66b36855`
|
||||
- head: `e1ed2708d835d712f5f3a9dfd51b8f66ca1ac447`
|
||||
- tree: `2f781e0db4df9a3082b4f55e69c42118868b18a1`
|
||||
- 검증 cwd: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261004T043243Z-baf133/1`
|
||||
|
||||
HEAD/tree 일치, 시작·종료 시 git status clean. 변경 파일은 `tests/test_issue42_skill_blocks.py` 하나이며 제품 코드·버전 변경은 없다. `git diff --check 6af4a79 HEAD` 통과.
|
||||
|
||||
## R1 — P2: 깨진 최상위 링크를 skip하지 말 것
|
||||
|
||||
**근거:** `tests/test_issue42_skill_blocks.py:245`–`:248`.
|
||||
|
||||
`Path.exists()`는 링크 자체가 아니라 대상을 따라 검사하므로 dangling symlink에서 False다. 따라서 `.claude/skills`가 존재하는 잘못된 링크여도 :245의 부재 분기가 실행돼 :247의 symlink 검증까지 도달하지 않는다. 정상적인 “이 워크트리에 로컬 배치가 없음”과 “로컬 배치가 있으나 깨짐”을 구별하지 못한다.
|
||||
|
||||
**재현:** 검증 트리의 원래 `.claude/skills`가 없음을 확인하고, 그 경로에 존재하지 않는 `absent-skills-target`을 가리키는 임시 링크를 만든 뒤 해당 pytest node를 실행했다. 실행 후 링크를 삭제했다.
|
||||
|
||||
```sh
|
||||
PYTHONDONTWRITEBYTECODE=1 /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python -m pytest -p no:cacheprovider -q --basetemp=/tmp/f275a259-broken_single tests/test_issue42_skill_blocks.py::test_claude_skills_link_resolves_to_agents_skills
|
||||
```
|
||||
|
||||
- 기대: 잘못된 로딩 경로이므로 fail, nonzero.
|
||||
- 실제: **1 skipped, rc 0**. 반면 디렉터리 안 깨진 개별 링크는 정상적으로 fail한다.
|
||||
- 영향: 모든 프로젝트 스킬의 진입 경로가 끊겨도 “워크트리에 없음”으로 숨겨져 CI/검증이 성공한다.
|
||||
- 수정 방향: 부재 skip을 `if not link.exists() and not link.is_symlink():`로 한정하거나 symlink 검증을 부재 검사보다 먼저 수행한다. 완전 부재는 계속 skip하고, dangling root link는 실패해야 한다. 부재/정상 단일 링크/정상 개별 링크/깨진 최상위 링크를 회귀 사례로 남긴다.
|
||||
|
||||
메모리상으로 위 조건만 바꾼 같은 함수에 대해 **완전 부재는 SKIP, 깨진 링크는 AssertionError**가 되는 것을 확인했다. 체크아웃 코드는 수정하지 않았다.
|
||||
|
||||
이 `exists()` 선행 분기는 이전 버전에도 있었다. 이번 변경이 새로 만든 회귀라고 주장하지 않는다. 그러나 브리프가 명시적으로 요청한 깨진 링크 검토에서 확인된 허점이고, 이 함수가 두 배치의 경로 무결성을 검증하도록 수정되는 범위 안에서 해결할 수 있으므로 반려 사유로 남긴다.
|
||||
|
||||
## 실행 결과
|
||||
|
||||
원래 워크트리 배치(로컬 `.claude/skills` 없음)에서 지정 모듈 실행:
|
||||
|
||||
```sh
|
||||
PYTHONDONTWRITEBYTECODE=1 /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python -m pytest -p no:cacheprovider -q --basetemp=/tmp/f275a259-selected tests/test_issue42_skill_blocks.py
|
||||
```
|
||||
|
||||
**31 passed, 1 skipped in 0.06s**, rc 0. 이 기본 실행은 로컬 링크 분기를 검증하지 않으므로 아래 임시 배치 탐침을 별도로 실행했다.
|
||||
|
||||
| 임시 배치 | 실제 pytest 결과 | 판단 |
|
||||
|---|---|---|
|
||||
| `.claude/skills` 완전 부재 | 1 skipped, rc 0 | 정상 |
|
||||
| 8개 스킬별 올바른 링크 | 1 passed, rc 0 | 정상; base 함수는 같은 배치에서 AssertionError |
|
||||
| 위 배치 + lib.sh/lib_py 링크 및 notes.txt | 1 passed, rc 0 | 정상 |
|
||||
| 개별 링크 하나가 다른 스킬을 가리킴 | 1 failed, rc 1 | 정상 거부 |
|
||||
| 개별 링크 하나가 깨짐 | 1 failed, rc 1 | 정상 거부 |
|
||||
| 단일 링크 → `.agents/skills` | 1 passed, rc 0 | 정상 |
|
||||
| 단일 링크 → 다른 기존 디렉터리 | 1 failed, rc 1 | 정상 거부 |
|
||||
| 단일 링크 → 존재하지 않는 대상 | 1 skipped, rc 0 | **R1** |
|
||||
| 일반 파일 | 1 failed, rc 1 | 정상 거부 |
|
||||
| 빈 디렉터리 | 1 failed, rc 1 | 정상 거부 |
|
||||
|
||||
탐침 스크립트: `/tmp/f275a259-layout-probes.py`; 결과: `/tmp/f275a259-layout-probes.log`. 위 cwd에서 `PYTHONDONTWRITEBYTECODE=1 <위 Python> /tmp/f275a259-layout-probes.py`로 재현한다. 스크립트는 원래 경로가 없는지 먼저 확인하고 자신이 만든 임시 배치만 finally에서 정리한다. 기대한 부정 사례의 pytest 실패는 제품 스위트 실패 수치에 합산하지 않았다.
|
||||
|
||||
## 나머지 검토 항목
|
||||
|
||||
- **실제 본 체크아웃:** 읽기 전용 조사에서 `.claude/skills`는 디렉터리이며 8개 스킬 링크와 lib.sh/lib_py 링크가 있다. 8개 스킬의 resolve 결과는 각각 본 체크아웃의 `.agents/skills/<skill>`과 정확히 일치했다. 수정의 동기가 된 정상 배치를 지원한다.
|
||||
- **빈 SKILL_NAMES:** 대상 함수만 메모리상 `SKILL_NAMES=[]`로 실행하면 for-loop가 비어 통과한다. 그러나 같은 모듈의 `test_inventory_covers_eight_skills`가 8개를 강제해 전체 모듈은 실패한다. 현재 모듈 단위 실행에는 침묵하는 성공이 없으므로 별도 반려 사유는 아니다. 대상 node를 단독 운영할 때까지 방어하려면 nonempty 단언을 추가할 수 있다.
|
||||
- **여분 항목:** framework 8개를 확인하는 테스트에서 lib.sh/lib_py 등 여분 항목을 금지하지 않는 것은 적절하다. Claude Code 공식 문서는 프로젝트 스킬을 `.claude/skills/<skill-name>/SKILL.md`로 설명하며 스킬 폴더의 symlink를 지원한다. 일반 helper 파일·SKILL.md 없는 helper 디렉터리를 framework 스킬로 취급할 필요는 없다. 다만 이 테스트는 다른 사용자 스킬 전체를 감사하지 않으므로 docstring의 “each skill Claude Code loads”는 “each framework skill” 정도로 좁히면 더 정확하다. [공식 Claude Code 스킬 로딩 문서](https://code.claude.com/docs/en/skills#choose-where-skills-load).
|
||||
- **과도한 엄격성:** 스킬 디렉터리의 내용 복사본은 같은 resolve 대상이 아니므로 거부된다. 브리프가 두 링크 배치만 지원하려는 계약에 맞는다. 다른 추가 사용자 스킬의 존재는 허용된다.
|
||||
|
||||
검증 트리 안의 임시 `.claude/skills`는 모두 삭제했다. 본 체크아웃의 배치를 변경하지 않았으며 제품 코드·실제 .mam.env·실제 agent/herdr 상태를 건드리지 않았다. 실제 `.mam` 쓰기는 지시된 잡 lifecycle과 이 최종 리포트에 한정했다.
|
||||
|
||||
[VERDICT: NOT PASS]
|
||||
Reference in new issue
Block a user