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:
GodopuandClaude Opus 5.5 committed 2026-10-04 13:54:57 +09:00
1 parent 150b4937f8
commit 65dc7fde82
28 files changed
+543 -25

No files matched your search

@@ -0,0 +1,45 @@
# 리뷰 리포트 — Job 40f4077c (PR #47, fix/issue-42-skills-link)
- 리뷰어: planner-reviewer-claude-01 (claude)
- 리뷰 identity: loop-20261004T043243Z-baf133 / base 6af4a79 / head e1ed270 / tree 2f781e0 — 검증 경로에서 `git rev-parse HEAD 'HEAD^{tree}'` 일치, 미커밋 변경 없음
## 결론 요약
수정은 두 배치(단일 링크 / 스킬별 링크 디렉터리)를 모두 올바르게 받고, 잘못된 배치는 실패시킨다. 차단 결함 없음. 비차단 관찰 2건.
## 1. 범위 (항목 3)
`git diff --stat 6af4a79 e1ed270`: tests/test_issue42_skill_blocks.py 한 파일, +10/−1 (test_claude_skills_link_resolves_to_agents_skills, :240-253). 제품 코드·버전 변경 없음.
## 2. 배치별 동작 (항목 1)
검증 경로 안에 임시 .claude/skills(.gitignore:40 의 `.claude/` 로 무시됨)를 만들어 이 테스트만 돌리고 매번 지웠다. 시작 전 .claude 가 없음을 확인했고, 끝난 뒤 `.claude` 잔여물 없음·`git status --short --ignored` 비어 있음·HEAD 불변을 확인했다. 본 체크아웃의 .claude/skills 는 `ls -la` 로 읽기만 했다(스킬 8개 링크 + lib_py·lib.sh 링크 = 10개 항목, 모두 ../../.agents/skills/<이름>).
| 배치 | 결과 | 판단 |
|---|---|---|
| A. 없음 | skip | 의도대로 |
| B. 단일 링크 → .agents/skills | pass | 의도대로 |
| C. 스킬별 링크 8개 + lib.sh·lib_py 링크(본 체크아웃과 같은 배치) | pass | 의도대로 |
| C-old. 같은 배치에서 base(6af4a79) 의 테스트 | **fail** | 수정 대상 결함 재현 |
| C2. C + 여분 디렉터리(foreign-skill) | pass | 여분 허용 |
| D. stop 링크가 loop 를 가리킴 | fail (`.claude/skills/multi-agent-mux-stop -> …/multi-agent-mux-loop`) | 의도대로 |
| E. 스킬 링크 하나 없음 | fail (`… is missing`) | 의도대로 |
| F. 스킬 링크 하나가 깨짐 | fail (`… is missing`) | 의도대로 |
| G. 링크 대신 복사본 디렉터리 | fail (resolve 가 .claude 쪽) | 의도대로 — 복사본은 드리프트하므로 막는 것이 맞다 |
| H. .claude/skills 가 일반 파일 | fail (`… is missing`) | 의도대로 |
| J. 단일 링크가 다른 곳(스킬 하나)을 가리킴 | fail | 의도대로 |
| K. 빈 디렉터리 | fail | 의도대로 |
| L. 스킬별 링크가 중간 링크를 거쳐 해석됨 | pass | resolve() 가 최종 대상을 비교하므로 맞다 |
| I. **단일 링크가 깨짐** (`.claude/skills -> ../nowhere`) | **skip** | 관찰 N1 |
- SKILL_NAMES 가 비면 스킬별 루프가 공집합이라 통과하겠지만, 같은 파일의 test_inventory_covers_eight_skills 가 `len(SKILL_NAMES) == 8` 을 단언하므로(파일 상단 SKILL_NAMES 정의와 함께) 놓치지 않는다.
## 3. 엄격성 (항목 2)
- 여분 항목(lib_py, lib.sh, 다른 스킬 디렉터리)은 허용된다(C, C2). Claude Code 가 스킬로 읽는 것은 SKILL.md 를 가진 하위 디렉터리이므로 여덟 MAM 스킬이 .agents/skills 의 것인지만 보는 현재 기준이 적절하다. 다른 스킬을 같이 두는 정상 사용을 막지 않는다.
- lib.sh·lib_py 링크는 검사하지 않는데, 진입 스크립트는 `$SCRIPT_DIR/../..` 에서 lib.sh 를 찾고 없으면 `${WORKSPACE_ROOT:-$PWD}/.agents/skills/lib.sh` 로 폴백하므로(예: reconcile.sh:20-21) 테스트의 필수 조건으로 둘 이유는 없다. 문서의 호출도 모두 `bash .agents/skills/...` 경로를 쓴다.
## 4. 비차단 관찰
- **N1 깨진 단일 링크가 skip 으로 숨는다** (tests/test_issue42_skill_blocks.py:245-246): `link.exists()` 는 링크를 따라가므로 대상이 없는 단일 링크는 "없음"으로 보고 skip 한다. 이 경우 Claude Code 는 스킬을 하나도 못 읽는데 테스트는 통과(skip)한다. base 테스트도 같은 동작이었으므로 이 PR 의 회귀는 아니다. `if not link.exists() and not link.is_symlink(): skip` 으로 바꾸면 깨진 링크는 아래 resolve 비교에서 실패한다.
- **N2 실패 메시지**: G(복사본)의 메시지가 `-> <.claude 안 경로>` 로 나와 "링크가 아니라 복사본"이라는 원인이 바로 드러나지 않는다. 사소함.
## 5. 테스트
검증 경로에서 `tests/test_issue42_skill_blocks.py` 전체: **31 passed / 1 skipped** (skip = 이 테스트, 워크트리에 .claude 없음 — 정상). 배치 탐침은 §2.
[VERDICT: PASS]
@@ -0,0 +1,14 @@
{
"loop_id": "loop-20261004T044643Z-1b1440",
"base_sha": "e1ed2708d835d712f5f3a9dfd51b8f66ca1ac447",
"head_sha": "150b4937f816c6407be16fc4c37ab88e478927e7",
"tree_sha": "afce44d2e0f91720d81e8d7d484f0e1358dee680",
"review_commit": "150b4937f816c6407be16fc4c37ab88e478927e7",
"reviewer": "planner-reviewer-claude-01",
"job_id": "dcece792",
"verdict": "PASS",
"transport": "herdr",
"evidence": "review-tree",
"isolation": "worktree",
"verified_at": "2026-10-04T04:54:16Z"
}
@@ -0,0 +1,37 @@
# 리뷰 리포트 — Job dcece792 (PR #47 2회차 델타, e1ed270..150b493)
- 리뷰어: planner-reviewer-claude-01 (claude)
- 리뷰 identity: loop-20261004T044643Z-1b1440 / base e1ed270 / head 150b493 / tree afce44d — 검증 경로에서 `git rev-parse HEAD 'HEAD^{tree}'` 일치, 미커밋 변경 없음
## 결론 요약
codex R1(그리고 같은 내용인 본인 1회차 관찰 N1)이 닫혔다. 차단 결함 없음.
## 1. 범위
`git diff --stat e1ed270 HEAD` 와 `git diff --stat 6af4a79 HEAD` 모두 tests/test_issue42_skill_blocks.py 한 파일(누적 +59/−3). 제품 코드·버전 변경 없음.
## 2. R1 종결
| 지적 | 수정 (tests/test_issue42_skill_blocks.py) | 확인 |
|---|---|---|
| codex R1 / claude N1: 깨진 최상위 링크가 `Path.exists()` 때문에 skip | claude_skills_problems(:240-261): 부재 = `not exists() and not is_symlink()` 일 때만 None(skip). symlink 는 `exists() and resolve() == skills_dir.resolve()` 여야 함 | 검증 경로에 `.claude/skills -> ../nowhere` 를 만들어 실제 테스트 실행 → **fail** (1회차 head 에서는 skip 이었음) |
## 3. 실제 배치 탐침 (검증 경로 안 임시 .claude/skills, 매번 삭제)
| 배치 | 결과 |
|---|---|
| 스킬별 링크 8개 + lib.sh·lib_py 링크(본 체크아웃과 동일) | pass |
| 단일 링크 → ../.agents/skills | pass |
| 깨진 최상위 링크(codex 탐침) | fail |
| 스킬 링크 하나가 깨짐 | fail ("… missing or dangling") |
| .claude/skills 가 일반 파일 | fail (각 스킬 "missing or dangling") |
| 최상위 자기 참조 링크(`skills -> skills`) | fail (RuntimeError: Symlink loop, Python 3.9.6) — 관찰 참고 |
시작 전 .claude 부재, 끝난 뒤 잔여물 없음·`git status --short --ignored` 비어 있음·HEAD 불변 확인. 본 체크아웃의 .claude/skills 는 건드리지 않았다.
## 4. 합성 배치 테스트
- test_claude_skills_layouts(:271-300)는 실제 배치 검사(test_claude_skills_link_resolves_to_agents_skills, :264-268)와 **같은 함수** claude_skills_problems 를 부른다.
- tmp_path 아래 상대 링크만 쓰고 외부 상태·시간·네트워크에 의존하지 않으므로 결정적이다. 6개 배치(absent/single/per_skill/wrong_skill/broken_top/broken_entry)가 모두 워크트리에서도 돈다.
- 실행: 검증 경로에서 `tests/test_issue42_skill_blocks.py` **37 passed / 1 skipped** (1회차 31 + 합성 6; skip 은 실제 배치 테스트, 워크트리라 정상).
## 5. 비차단 관찰
- 최상위 링크가 순환하면 :252 의 메시지용 `link.resolve()` 가 Python 3.9 에서 RuntimeError 를 던져 assert 실패 대신 예외로 끝난다. 어느 쪽이든 테스트는 실패하므로 잘못된 배치를 놓치지 않는다. 메시지를 `os.readlink(link)` 로 만들면 깔끔해진다(선택).
- 복사본 디렉터리일 때 메시지가 원인을 직접 말하지 않는 점(1회차 N2)은 그대로이며 사소하다.
[VERDICT: PASS]