diff --git a/.agents/reports/planner-reviewer-claude-01/report-1fb2cefa.identity.json b/.agents/reports/planner-reviewer-claude-01/report-1fb2cefa.identity.json new file mode 100644 index 0000000..e47a9dd --- /dev/null +++ b/.agents/reports/planner-reviewer-claude-01/report-1fb2cefa.identity.json @@ -0,0 +1,14 @@ +{ + "loop_id": "loop-20261019T015801Z-05463c", + "base_sha": "1d7b7dab58a10fe86fd747b7290d47674ca93230", + "head_sha": "6a37c0019baa31ee28b32a086e57b4c8239413c4", + "tree_sha": "87013e7ef7f5239f1bf6ea38a2cbda214257be3c", + "review_commit": "6a37c0019baa31ee28b32a086e57b4c8239413c4", + "reviewer": "planner-reviewer-claude-01", + "job_id": "1fb2cefa", + "verdict": "PASS", + "transport": "herdr", + "evidence": "review-tree", + "isolation": "worktree", + "verified_at": "2026-10-19T02:37:22Z" +} \ No newline at end of file diff --git a/.agents/reports/planner-reviewer-claude-01/report-1fb2cefa.md b/.agents/reports/planner-reviewer-claude-01/report-1fb2cefa.md new file mode 100644 index 0000000..3176201 --- /dev/null +++ b/.agents/reports/planner-reviewer-claude-01/report-1fb2cefa.md @@ -0,0 +1,71 @@ +# PR #62 리뷰 2회차 — Issue #60 Linux 확인 후속 (job 1fb2cefa) + +- 리뷰어: planner-reviewer-claude-01 +- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T015801Z-05463c/1` +- identity: HEAD `6a37c0019baa31ee28b32a086e57b4c8239413c4`, tree `87013e7ef7f5239f1bf6ea38a2cbda214257be3c` 가 일치한다. 작업 트리는 clean 이고 `.mam` 은 없다. +- 범위: base `1d7b7da`..head 9커밋. 판정은 base..head 전체에 대한 것이다. 1회차 이후 바뀐 것은 6a37c00 하나이고, `opencode.py`·`status.sh`·`reconcile.sh`·`test_issue60_linux_fixes.py` 4개 파일만 바뀌었다. +- 1회차(@6a4e385)의 확인은 6a37c00 이 건드리지 않은 코드라 그대로 유효하다. 1회차에 확인한 것: 임시 DB 에서 `_ro_connect` 세 갈래, `_DB_PATHS` 키, `auth-ok` CLI, stop 문구. +- 환경: macOS, Python 3.9.6, sqlite 3.51.0. 실제 opencode 데이터는 `ls`·`grep -c`·`ps`·`lsof` 로만 봤다. + +## 결론 + +1회차의 공통 차단(B1)과 codex·agy 지적이 모두 해결됐고, 새 회귀는 찾지 못했다. base..head 전체를 병합 가능으로 판단한다. + +## 1회차 지적별 확인 + +**B1 (clean 상태 5개가 macOS 에서 실패) — 해결.** +- `make("clean")` 이 close 뒤 남은 `-wal`·`-shm` 을 지운다(`tests/test_issue60_linux_fixes.py:158-162`). 1회차 사본에서 내가 검증한 수정과 같다. +- 검증 경로에서 이 모듈이 모두 통과한다(아래 실행 결과). +- 뮤테이션 i1(immutable 분기를 `mode=ro` 로)이 이제 이 Mac 에서 5 failed 로 잡힌다. 1회차에는 fixture 가 먼저 실패해서 이 뮤테이션이 가려졌었다. + +**안내 문구 패턴이 사용자가 친 `Ask anything…` 를 지움(codex) — 해결.** +- 패턴이 `Ask anything(?:…|\.\.\.) "[^"]*"?` 가 되어 따옴표 예시가 필수다(`opencode.py:80`). +- 두 fixture 에 넣어 직접 확인한 결과: + - `Ask anything…`, `Ask anything...` 는 친 그대로 읽힌다. + - 홈 화면 원본, 잘린 예시, `Ask anything… "` 는 빈 입력으로 읽힌다. +- 회귀 테스트 2개가 추가됐다(`:84-85`). 뮤테이션 p1(예시를 다시 선택 사항으로)은 4 failed 로 잡힌다. + +**auth_ok 가 실행 불가한 `~/.opencode/bin/opencode` 도 인정(codex) — 해결.** +- 이제 `isfile and access(X_OK)` 로 본다(`opencode.py:322-323`). +- 권한 644 파일과 디렉터리는 모두 False 로 고정됐다(`test_a_home_install_that_cannot_run_is_not_enough`). 뮤테이션 a1(`exists` 로 되돌림)은 1 failed 로 잡힌다. +- `shutil.which` 가 이미 실행 가능 여부를 보므로 PATH 쪽은 그대로다. + +**status.sh·reconcile.sh 의 `sqlite3.connect(odb)`(agy) — 해결.** +- status 의 opencode 두 블록(`status.sh:115-117`, `:316-318`)과 reconcile drift C(`reconcile.sh:953-955`)가 `_ro_connect` 를 쓴다. +- 두 스크립트는 lib_py 를 이미 import 하는 경로에서 돌기 때문에 import 가 된다(status 는 `env_python`/PYTHONPATH, reconcile 은 `env_python`). +- status 블록에서 `import sqlite3` 를 지웠지만 영향이 없다. 같은 함수의 다른 블록(hermes `:90`, codex `:132`)은 각자 `import sqlite3` 를 한다. +- 행동 확인: git archive 사본에 임시 e2e 를 넣었다. 순서는 이렇다. + 1. mam_sandbox+mock 에서 create → 자식 세션 추가 → `journal_mode=wal` → 사이드카 삭제(Linux 의 닫힌 상태) + 2. `reconcile.sh --once --emit-diff` → `status.sh --json` 과 plain status +- 결과(head): rc 는 모두 0. 부모 id 로 고정됐고 `resume_state == "yes"` 이다. DB 디렉터리는 실행 전후 모두 `['opencode.db']` 였다. +- 대조(두 스크립트를 `sqlite3.connect(odb)` 로 되돌린 사본): reconcile 뒤 `opencode.db-shm`·`opencode.db-wal` 이 생겨 같은 테스트가 실패했다. +- 참고: mock opencode 가 만드는 DB 는 WAL 모드가 아니다. 그래서 WAL 로 바꾸지 않으면 이 차이가 드러나지 않는다. + +## 테스트 실행 (검증 경로) + +- 조건: `env -i`, 임시 HOME, `XDG_DATA_HOME=`, basetemp 는 scratchpad, `-p no:cacheprovider`, `PYTHONDONTWRITEBYTECODE=1`. +- 대상 12개 모듈: issue60_linux_fixes, issue60_opencode_v2, a4, tier1, tier2, issue31, issue55, issue57, issue21_worktree_prereqs, issue50_dispatch, issue30_stall_watch, c1_tui_readiness. +- 결과: **463 passed, 2 skipped, 실패 0**, 16분. + - skip 1: `/proc` 전용 테스트. + - skip 2: PATH 에 opencode 가 없어 건너뛴 실바이너리 계약 테스트(1회차·이전 이슈에서 따로 확인함). +- 부작용 확인: + - scratchpad XDG 디렉터리는 실행 뒤에도 비어 있었다. + - 실제 `~/.local/share/opencode` 의 목록은 전후가 같았다. + - 실제 log 의 `debug` 줄 수는 2 → 2 였다. + - 남은 opencode 프로세스는 사용자 서비스 하나뿐이다(pid 23630, 시작 08:13, 이 잡 이전). + - #55 가드 오탐은 없었다. +- 린트: + - `git diff --check 1d7b7da..HEAD` 는 깨끗하다. + - 바뀐 Python 은 3.9 로 파싱된다. + - `status.sh`·`reconcile.sh` 는 `/bin/bash -n`(3.2)을 통과한다. + +## 비차단 + +- **N1.** 새 `test_status_and_reconcile_open_the_database_read_only` 는 소스 문자열만 센다. `sqlite3.connect(odb, timeout=…)` 같은 변형은 지나간다. 위의 임시 e2e(WAL DB, 사이드카 없음 → reconcile·status → 목록 불변)를 tier2 opencode e2e 에 붙이면 행동으로 고정할 수 있다. +- **N2.** 1회차 비차단은 의도된 완화로 그대로 남아 있다(브리프에도 기록됨): + - immutable 경쟁 창(ms 수준) + - `_DB_PATHS` 의 프로세스 수명 캐시 + - create 사전 검사의 완화 +- **N3.** 아주 좁은 화면에서 예시의 여는 따옴표까지 잘리면 `Ask anything…` 만 남는다. 이때는 친 초안으로 읽힌다(안전한 쪽). 실제 캡처로는 확인하지 않았다. + +[VERDICT: PASS] diff --git a/.agents/reports/planner-reviewer-claude-01/report-dd5c8c62.md b/.agents/reports/planner-reviewer-claude-01/report-dd5c8c62.md new file mode 100644 index 0000000..4b2db81 --- /dev/null +++ b/.agents/reports/planner-reviewer-claude-01/report-dd5c8c62.md @@ -0,0 +1,93 @@ +# PR #62 리뷰 — Issue #60 Linux 확인 후속 (job dd5c8c62) + +- 리뷰어: planner-reviewer-claude-01 +- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T000805Z-975c4c/1` +- identity: HEAD `6a4e385a78c8a561d5cf3644336de6a352ea9271`, tree `afa2c55925f93820337b69699c670c0b0ca90980` 일치. 작업 트리 clean, `.mam` 없음. base `1d7b7da`..head 8커밋. 판정은 base..head 전체에 대한 것이다. +- 환경: macOS, `/usr/bin/python3` 3.9.6 와 `.venv` python 3.9.6, 둘 다 sqlite 3.51.0. 실제 opencode 데이터는 `ls`·`grep -c` 로만 봤다. 실제 DB 는 sqlite 로 열지 않았다. + +## 결론 + +제품 코드에서는 차단 결함을 찾지 못했다. 다만 새 테스트 5개가 이 Mac 에서 항상 실패한다(B1). 이 리뷰의 중점이 macOS 정합성이고, 오케스트레이터의 전체 스위트도 이 Mac 에서 돈다. 그래서 NOT PASS 로 둔다. 고칠 곳은 fixture 몇 줄이고 재설계는 필요 없다. + +## 차단 + +**B1. `test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-*]` 5개가 macOS 에서 실패한다.** +- 위치: `tests/test_issue60_linux_fixes.py:153-154`(`make("clean")`)과 `:175-181`. +- 원인: macOS 시스템 SQLite 는 persistent WAL 로 동작해서, 마지막 연결을 닫아도 `-wal`·`-shm` 을 지우지 않는다. 임시 DB 에서 쓰기 연결을 닫은 뒤의 목록이 `['o.db', 'o.db-shm', 'o.db-wal']` 였다. 그래서 `_wal_writer(db, ws).close()` 뒤의 목록이 기대값 `["opencode.db"]` 와 다르고, 첫 assert(`make(state) == listing`)에서 실패한다. 제품 코드까지 가지도 않는다. +- 재현: 검증 경로에서 `env -i …` 로 `tests/test_issue60_linux_fixes.py` 를 단독 실행하면 5 failed, 40 passed. 관련 18개 모듈 실행(아래)에서도 같은 5개만 실패했다. +- 제품 코드는 맞다. 이 테스트의 `clean` 상태는 Linux 에서 닫힌 DB 의 모습(사이드카 없음)이다. git archive 사본에서 이 상태를 만들도록 close 뒤에 `-wal`·`-shm` 을 지워 보았다. 그러자 해당 서브셋 33개가 모두 통과했고, 아래 뮤테이션 6개도 모두 잡혔다. +- 권고: `clean` 분기에서 close 뒤 남은 `-wal`·`-shm` 을 지운다. close 가 이미 checkpoint 하므로 행은 본 파일에 있다. 또는 Mac 의 실제 상태인 "쓰는 쪽 없이 사이드카 둘 다"는 이미 `leftover` 가 다루므로, 그 사실을 주석으로 남긴다. +- 주의: 지금 상태의 Mac 에서는 `clean` 이 첫 assert 에서 멈춘다. 그래서 immutable 분기를 없애는 뮤테이션(M1)이 이 Mac 에서는 드러나지 않는다. + +## 중점 항목 확인 + +**1. `_ro_connect` 세 갈래 (`opencode.py:13-36`).** 검증 경로 코드를 3.9 로 import 해 임시 DB 에서 확인했다. 사이드카를 직접 지워 Linux 와 같은 상태를 만들었다. + +| 상태 | 결과 | +|---|---| +| 사이드카 없음 | 같은 상태에서 base 의 평범한 `mode=ro` 는 `unable to open database file` 로 실패한다. 그래서 base 의 `_descendants`·`db_rows_naming` 은 Mac 에서도 빈 결과였다. `_ro_connect`(immutable)는 행을 읽고 아무 파일도 만들지 않는다. | +| writer 가 살아 있음(둘 다 있음) | WAL 에만 있는 행까지 읽는다. 목록은 그대로다. | +| `-shm` 없는 `-wal` | 커밋된 행을 읽는다. 원본 두 파일의 mtime 은 그대로였고 `-shm` 도 생기지 않았다. 같은 상태에서 평범한 `mode=ro` 는 `-shm` 을 만들었다. 디렉터리가 읽기 전용(0555)이어도 읽힌다. | +| DB 없음 | `OperationalError`. 파일은 생기지 않는다. base 의 `sqlite3.connect(db)` 는 빈 DB 를 만들었으므로 개선이다. | + +- 실제 Mac 의 상태: 실제 디렉터리에는 서비스가 떠 있어 `opencode.db`·`-shm`·`-wal` 이 모두 있다(`ls`). persistent WAL 이 기본인 Mac 에서는 "둘 다 있음 → mode=ro" 갈래가 대부분일 것이다. 다만 bun 이 시스템 SQLite 를 쓰는지는 확인하지 않았다. + +**2. `_DB_PATHS` 캐시 (`opencode.py:39-40, 139-161`).** +- 키는 `(bin, ctx.home_dir, _data_home(ctx))` 다. `_run` 이 넘기는 HOME·XDG_DATA_HOME 과 같은 값이라, 다른 HOME 이나 XDG 에 다른 경로를 돌려주지 않는다. +- 실패한 `debug paths` 는 저장되지 않는다. 바이너리가 없으면(`bin_path` None) 캐시를 쓰지 않는다. +- 테스트 간 누출: 이 모듈은 autouse fixture 로 캐시를 비운다. 다른 모듈은 tmp_path 별 HOME 이라 키가 겹치지 않는다. 18개 모듈을 한 프로세스에서 실행했을 때 B1 외의 실패는 없었다. + +**3. auth_ok (`opencode.py:317-321`, `__main__.py:120-127`, `create_session.sh:145-151`).** +- 다른 에이전트의 분기와 adapter 는 바뀌지 않았다. `auth-ok` 를 부르는 곳은 opencode 분기뿐이다. +- `python -m lib_py.agents auth-ok opencode` 를 `/usr/bin/python3` 3.9 로 실행했다. PATH 에 opencode 가 없으면 rc 1, `~/.opencode/bin` 이 있으면 rc 0, 인자가 없으면 rc 1(`unknown agent ''`). +- PYTHONPATH 는 lib.sh 가 export 한다(`lib.sh:25`). 같은 파일의 `facts`·`spawn-spec` 호출과 같은 방식이다. +- create 의 사전 검사로 충분하다. 바로 위 `command -v "$AGENT"`(`:117`)가 이미 PATH 의 바이너리를 요구한다. + +**4. `input_placeholder_pattern` (`opencode.py:75-79`, `input_region.py:83-85`).** +- 홈 화면 fixture 두 개(140열, 잘린 예시가 있는 60열)는 모두 빈 입력으로 읽힌다. 1.x 캡처도 그대로 빈 입력이다. 다른 adapter 는 None 이다(테스트로 고정). +- fullmatch 라 `Ask anything… "x" and fix it` 처럼 뒤에 글이 붙은 입력은 그대로 읽힌다. + +**5. stop 문구 (`stop_session.sh:242`).** 메시지만 바뀌었고 동작은 같다. 테스트 2개(exit 됨 / 살아 있어 kill)가 통과했다. + +**6. Python 3.9 와 셸.** +- 바뀐 Python 5개 파일이 3.9 에서 파싱되고, 제품 모듈은 3.9 로 import 해 실행했다. 새 테스트는 `from __future__ import annotations` 를 쓴다. +- `create_session.sh`·`stop_session.sh` 가 `/bin/bash -n`(3.2)을 통과한다. `git diff --check 1d7b7da..HEAD` 는 깨끗하다. + +## 테스트 실행 (검증 경로) + +조건: `env -i`, 임시 HOME, `XDG_DATA_HOME=`, basetemp 는 scratchpad, `-p no:cacheprovider`, `PYTHONDONTWRITEBYTECODE=1`. + +대상 18개 모듈: issue60_linux_fixes, issue60_opencode_v2, a4, issue50_dispatch, issue30_stall_watch(+_stop), issue31, tier1, tier2, c1_tui_readiness, issue5_phase0/phase2b, issue24, issue6, uuid_target, issue55, issue57, issue21_worktree_prereqs. + +- 결과: **5 failed(B1 의 5개뿐), 623 passed, 2 skipped**, 29분. +- skip 2개: `/proc` 전용 테스트와, PATH 에 opencode 가 없어 건너뛴 실바이너리 계약 테스트. +- 부작용 확인: + - scratchpad XDG 디렉터리는 실행 뒤에도 비어 있었다. + - 실제 `~/.local/share/opencode` 목록은 실행 전후가 같았다. + - 실제 log 의 `debug` 줄 수는 2 → 2. + - 남은 opencode 프로세스는 없었다(사용자 서비스 제외). + - #55 가드 오탐도 없었다. + +뮤테이션: git archive 사본에 B1 fixture 수정을 넣고 진행했다. 대상은 issue60_linux_fixes 와 a4 이며, 실제 프로세스를 띄우는 stop·create 2개는 뺐다. 모두 잡혔다. + +| 뮤테이션 | 결과 | +|---|---| +| M1 immutable 분기를 `mode=ro` 로 | 5 failed | +| M2 `wal and shm` 를 `wal` 로 | 5 failed | +| M3 캐시 사용 끔 | 1 failed | +| M4 잘린 예시 허용(`"?`) 제거 | 2 failed | +| M5 fullmatch 를 match 로 | 4 failed | +| M6 auth_ok 를 항상 True 로 | 3 failed | + +## 비차단 + +- **N1. immutable 경쟁.** `-wal` 없음을 판단한 뒤 연결이 쓰이는 동안 writer 가 생기면 결과가 틀릴 수 있다. 임시 DB 에서 재현했다. + - (a) 판단 직후 writer 가 한 행을 커밋하면, immutable 연결은 1/2 만 본다. + - (b) 열린 immutable 커서 아래에서 writer 가 지우고 다시 쓰고 checkpoint 하면, 예외 없이 옛 행과 새 행이 섞인 20119행이 나왔다(옛 20000행, 새 30000행). + - 쿼리가 짧아(LIMIT 20, 단건 조회) 실제 창은 ms 수준이다. persistent WAL 인 Mac 에서는 이 갈래 자체가 드물다. + - 원하면 이렇게 줄일 수 있다: 쿼리 뒤 `-wal` 이 여전히 없고 본 파일의 mtime·size 가 그대로인지 확인하고, 아니면 `mode=ro` 로 한 번 다시 읽는다. +- **N2. 캐시는 프로세스 수명 동안 유지된다.** 같은 경로에서 바이너리를 업그레이드한 경우, 오래 도는 `reconcile --subscribe` 는 예전 `debug paths` 결과를 계속 쓴다. 경로 규칙이 바뀌는 업그레이드에서만 문제가 된다. +- **N3. 사전 검사가 약해졌다.** create 의 opencode 검사는 사실상 `command -v` 와 같아졌다. 깨진 바이너리도 통과하고, 실패는 이후 준비 대기 단계에서 드러난다. 2.x 에 로그인이 없다는 전제와 맞는 완화로 본다. +- **N4. 안내 문구와 같은 모양의 초안.** 사용자가 정확히 그 모양으로 친 입력(`Ask anything...`, `Ask anything… "fix the tests"`)은 빈 입력으로 읽힌다. 확인한 결과 둘 다 `''` 였다. 실사용에서 생길 가능성은 매우 낮다. + +[VERDICT: NOT PASS] diff --git a/.agents/reports/planner-reviewer-codex-01/report-40dda79c.md b/.agents/reports/planner-reviewer-codex-01/report-40dda79c.md new file mode 100644 index 0000000..934ec6f --- /dev/null +++ b/.agents/reports/planner-reviewer-codex-01/report-40dda79c.md @@ -0,0 +1,19 @@ +# PR #62 / Issue #60 Linux follow-up review + +**Scope.** Reviewed base `1d7b7dab58a10fe86fd747b7290d47674ca93230` through head `6a4e385a78c8a561d5cf3644336de6a352ea9271` in the assigned review-only worktree. HEAD and tree matched the brief; the worktree was clean. No source files, branches, services, or user data were changed. + +**NOT PASS findings** + +1. **`auth_ok()` accepts a non-executable home install.** At `.agents/skills/lib_py/agents/adapters/opencode.py:317-321`, the `PATH` candidate is checked by `shutil.which`, but the home candidate is accepted by `os.path.exists` alone. Reproduction: with a temporary HOME containing `.opencode/bin/opencode` mode `0644`, an empty PATH, and no XDG variables, `adapter.auth_ok()` returned `True`. `create_session.sh` now relies on this result as its preflight, so it can report the installation as functional and proceed although the CLI cannot be executed. Check that the home candidate is a regular executable file (e.g. `os.path.isfile` and `os.access(..., os.X_OK)`), and cover a non-executable file and directory in the auth tests. + +2. **The placeholder regex can discard a real draft.** `.agents/skills/lib_py/agents/adapters/opencode.py:76-79` makes the example suffix optional, so the pattern matches the exact user text `Ask anything…`. Replaced the captured prompt line in the 2.x home fixture with that text and `extract_input_region()` returned `''`. Thus the existing input-empty signal loses this exact draft and can treat a non-empty prompt as idle. The observed 2.x placeholder fixtures include an opening quote and example, including the truncated narrow capture; constrain the pattern to that observed shape and add the exact-short-text draft as a regression case. Other tested similar strings are retained. + +3. **Five newly added Mac tests fail before exercising `_ro_connect`.** In `tests/test_issue60_linux_fixes.py:153-155`, the `clean` fixture closes a WAL-mode connection and assumes macOS SQLite removes both sidecars. On this Mac the files remain after close, so `make("clean")` returns `opencode.db`, `opencode.db-shm`, and `opencode.db-wal` and the assertion at `:181` fails before the query runs. This affects the five `_descendants`, `_has_session`, `discover`, `residual_artifacts`, and `verify_artifact` parameterizations. Normalize the fixture to a genuine no-WAL state after closing/checkpointing the writer (or construct the clean database in rollback-journal mode) before asserting the directory contents. + +**Validation.** On macOS / Python 3.9.6, ran the focused suite with temporary HOME/TMPDIR, XDG variables unset, the required environment scrub, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, and an external temporary basetemp: + +`tests/test_issue60_linux_fixes.py tests/test_a4_adapter_contract.py tests/test_issue31_purge_residual.py tests/test_issue50_dispatch.py` + +Result: **189 passed, 5 failed** (136.27s). All five failures share the clean-fixture setup issue above; the other WAL states and remaining focused tests passed. A separate no-WAL temporary DB probe exercised `verify_artifact`, `discover`, `_has_session`, `_descendants`, and `residual_artifacts`; each returned the expected result and left only `opencode.db`. The non-executable-file probe reproduced finding 1; the captured-pane probe reproduced finding 2. `git diff --check` and `bash -n` on the changed create/stop scripts passed. No actual OpenCode service or real data was accessed. Linux was not independently rerun in this turn. + +[VERDICT: NOT PASS] diff --git a/.agents/reports/planner-reviewer-codex-01/report-68ebbff1.identity.json b/.agents/reports/planner-reviewer-codex-01/report-68ebbff1.identity.json new file mode 100644 index 0000000..76348bc --- /dev/null +++ b/.agents/reports/planner-reviewer-codex-01/report-68ebbff1.identity.json @@ -0,0 +1,14 @@ +{ + "loop_id": "loop-20261019T015801Z-05463c", + "base_sha": "1d7b7dab58a10fe86fd747b7290d47674ca93230", + "head_sha": "6a37c0019baa31ee28b32a086e57b4c8239413c4", + "tree_sha": "87013e7ef7f5239f1bf6ea38a2cbda214257be3c", + "review_commit": "6a37c0019baa31ee28b32a086e57b4c8239413c4", + "reviewer": "planner-reviewer-codex-01", + "job_id": "68ebbff1", + "verdict": "PASS", + "transport": "herdr", + "evidence": "review-tree", + "isolation": "worktree", + "verified_at": "2026-10-19T02:37:22Z" +} \ No newline at end of file diff --git a/.agents/reports/planner-reviewer-codex-01/report-68ebbff1.md b/.agents/reports/planner-reviewer-codex-01/report-68ebbff1.md new file mode 100644 index 0000000..7f07353 --- /dev/null +++ b/.agents/reports/planner-reviewer-codex-01/report-68ebbff1.md @@ -0,0 +1,21 @@ +# PR #62 / Issue #60 Linux follow-up review, round 2 + +**Scope.** Reviewed base `1d7b7dab58a10fe86fd747b7290d47674ca93230` through head `6a37c0019baa31ee28b32a086e57b4c8239413c4` in the assigned review-only worktree. HEAD and tree matched the brief, and `git status --short` was empty. No source changes, commits, branch operations, or real service/data access were performed. + +The three findings from round 1 are addressed: + +- `.agents/skills/lib_py/agents/adapters/opencode.py:76-80` now requires the quote/example portion of the observed placeholder. `tests/test_issue60_linux_fixes.py:79-91` verifies that both the bare `Ask anything…` and `Ask anything...` typed drafts are retained, while the wide and truncated-narrow UI placeholders still read as empty. +- `opencode.py:318-324` accepts the home install only when it is a regular executable file; the test at `tests/test_issue60_linux_fixes.py:334-341` rejects both a non-executable file and a directory. +- `tests/test_issue60_linux_fixes.py:156-164` removes macOS-persistent WAL sidecars only after closing the last writer, to normalize the no-WAL fixture. The clean-state queries then pass, confirming committed rows remain available after fixture setup. The other three sidecar states still exercise the intended code paths. + +The follow-up also replaces direct writable SQLite opens in the two status branches (`.agents/skills/multi-agent-mux-status/scripts/status.sh:115-119,316-320`) and reconciler (`.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh:953-959`) with the adapter's `_ro_connect`, avoiding sidecar creation/removal in these readers. Their scripts receive the skill directory through the existing `env_python` bootstrap, so the new adapter import resolves in the normal execution path. The change preserves the prior status result mapping and query semantics. + +No new blocking issue found. The `_DB_PATHS` cache remains scoped to binary path and effective HOME/data-home, with its test fixture clearing the cache between tests; the previously noted narrow immutable/WAL race remains a transient-read caveat rather than a new data mutation path. No live OpenCode command, service, or user database was accessed. + +**Validation.** On macOS / Python 3.9.6, ran the related suite from the review worktree with temporary HOME/TMPDIR, XDG variables unset, the required environment scrub, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, and an external temporary basetemp: + +`tests/test_issue60_linux_fixes.py tests/test_a4_adapter_contract.py tests/test_issue31_purge_residual.py tests/test_issue50_dispatch.py` + +Result: **201 passed** (136.01s). `git diff --check` and `bash -n` on the changed status, reconcile, create, and stop scripts also passed. Linux was not independently rerun in this reviewer turn; the brief records the author's Linux run. + +[VERDICT: PASS] diff --git a/.agents/reports/reviewer-agy-01/report-ba63f0b5.md b/.agents/reports/reviewer-agy-01/report-ba63f0b5.md new file mode 100644 index 0000000..1f37fbe --- /dev/null +++ b/.agents/reports/reviewer-agy-01/report-ba63f0b5.md @@ -0,0 +1,93 @@ +# PR #62 Code Review Report — Issue #60 Linux Follow-up (`1d7b7da..6a4e385`) + +- **Job ID**: `ba63f0b5` +- **Reviewer**: `agy` (`herdr:reviewer-agy-01`) +- **Branch**: `fix/issue-60-linux` +- **Base .. Head**: `1d7b7dab58a10fe86fd747b7290d47674ca93230` .. `6a4e385a78c8a561d5cf3644336de6a352ea9271` (8 commits) +- **Verification Worktree**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T000805Z-975c4c/1` + +--- + +## 1. Executive Summary + +All production code changes in PR #62 (`_ro_connect`, `_DB_PATHS` cache, `input_placeholder_pattern`, `auth_ok` / `auth-ok`, `stop_session.sh` log text, and `test_u1e`) are well-designed and behave as intended on macOS (Python 3.9.6, Apple SQLite 3.51.0, opencode 2.0.32). + +However, **5 test cases in `tests/test_issue60_linux_fixes.py` fail on macOS** (`test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-listing0-*]`) due to a macOS vs. Linux SQLite difference in `wal_home`'s `make("clean")` fixture helper: +- On Linux (upstream SQLite default `SQLITE_FCNTL_PERSIST_WAL = 0`), closing the last connection to a WAL database (`_wal_writer(db, ws).close()`) checkpoints the WAL into `opencode.db` and unlinks `opencode.db-wal` and `opencode.db-shm`. +- On macOS (`/usr/bin/python3` linked against Apple's `/usr/lib/libsqlite3.dylib`, which sets `SQLITE_FCNTL_PERSIST_WAL = 1` by default), `_wal_writer(db, ws).close()` checkpoints the WAL into `opencode.db` and truncates `opencode.db-wal` to 0 bytes, **but leaves `opencode.db-wal` and `opencode.db-shm` on disk**. +- Consequently, `assert make(state) == listing` at `tests/test_issue60_linux_fixes.py:181` fails with `AssertionError: assert ['opencode.db', 'opencode.db-shm', 'opencode.db-wal'] == ['opencode.db']` before `_ro_connect` is even called. + +Unlinking `opencode.db-wal` and `opencode.db-shm` after `_wal_writer(db, ws).close()` in `make("clean")` makes all 20 `(query × state)` cases pass on macOS as well. + +--- + +## 2. Findings + +### `[P1]` Blocking — `wal_home` fixture `make("clean")` fails on macOS due to Apple SQLite `SQLITE_FCNTL_PERSIST_WAL=1` + +- **Location**: `tests/test_issue60_linux_fixes.py:157-159, 181` +- **Symptom**: + Running `pytest tests/test_issue60_linux_fixes.py` on macOS (`/usr/bin/python3` 3.9.6, `sqlite3.sqlite_version == '3.51.0'`) fails 5 tests: + - `FAILED tests/test_issue60_linux_fixes.py::test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-listing0-_descendants]` + - `FAILED tests/test_issue60_linux_fixes.py::test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-listing0-_has_session]` + - `FAILED tests/test_issue60_linux_fixes.py::test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-listing0-discover]` + - `FAILED tests/test_issue60_linux_fixes.py::test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-listing0-residual_artifacts]` + - `FAILED tests/test_issue60_linux_fixes.py::test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-listing0-verify_artifact]` +- **Root Cause**: + Apple's system `libsqlite3.dylib` enables `SQLITE_FCNTL_PERSIST_WAL` by default. When `_wal_writer(db, ws).close()` runs on macOS, SQLite checkpoints the committed WAL frames into `opencode.db` (`8192` bytes) and truncates `opencode.db-wal` to `0` bytes, but keeps `opencode.db-wal` and `opencode.db-shm` on disk instead of unlinking them. +- **Verification & Fix**: + In `make(state)` (`tests/test_issue60_linux_fixes.py:157-159`), removing any remaining sidecars after `_wal_writer(db, ws).close()` checkpoints the DB: + ```python + def make(state): + if state == "clean": + _wal_writer(db, ws).close() + for suffix in ("-wal", "-shm"): + if os.path.exists(str(db) + suffix): + os.unlink(str(db) + suffix) + ``` + leaves `opencode.db` with its WAL header and checkpointed rows and no sidecar files (`["opencode.db"]`). Verified on macOS that all 20 `(query × state)` combinations pass with this change, and that `_ro_connect(db)`'s `mode=ro&immutable=1` branch is crucial on macOS (where a plain `mode=ro` open on a sidecar-free WAL-mode DB raises `sqlite3.OperationalError: unable to open database file`, and a plain read-write `sqlite3.connect(db)` creates and leaves `-wal` and `-shm`). + +--- + +### `[P2]` Non-blocking — `status.sh` and `reconcile.sh` still open `opencode.db` with read-write `sqlite3.connect(odb)` + +- **Locations**: + - `.agents/skills/multi-agent-mux-status/scripts/status.sh:116, 316` + - `.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh:953` +- **Observation**: + While `OpenCodeAgentAdapter` methods (`verify_artifact`, `discover`, `_has_session`, `_descendants`, `residual_artifacts`) now use `_ro_connect(db)`, `status.sh` (both `--json` and text mode) and `reconcile.sh` (drift C, which `status.sh` also runs via `--dry-run`) still call `sqlite3.connect(odb)` directly: + - On macOS, calling `sqlite3.connect(odb)` on a sidecar-free WAL-mode `opencode.db` creates `opencode.db-wal` and `opencode.db-shm` and leaves them on disk after `.close()`. + - On Linux, calling `sqlite3.connect(odb)` on a `leftover` WAL database checkpoints the WAL and deletes `opencode.db-wal` and `opencode.db-shm` on `.close()`. +- **Suggestion**: + In `status.sh:108-120, 308-320`, replace the raw `sqlite3.connect(odb)` block with `get_adapter('opencode')._has_session(odb, u)` (which already uses `_ro_connect`), and in `reconcile.sh:953`, use `_ro_connect(odb)` from `lib_py.agents.adapters.opencode`. + +--- + +## 3. Detailed Review by Checklist Item + +1. **`_ro_connect` on macOS (`.agents/skills/lib_py/agents/adapters/opencode.py:13-36`)**: + - **Both `-wal` and `-shm` present (`leftover` / `writer`)**: `sqlite3.connect(f"file:{quote(path)}?mode=ro", uri=True)` reads uncommitted-to-main-file (WAL-committed) rows through the existing WAL index without creating or removing sidecars. + - **No `-wal` (`clean`)**: `sqlite3.connect(f"file:{quote(path)}?mode=ro&immutable=1", uri=True)` reads `opencode.db` directly without creating `-wal`/`-shm` (and avoids `sqlite3.OperationalError: unable to open database file` that plain `mode=ro` raises on macOS when opening a WAL-header DB without sidecars). Because every query opens, fetches, and immediately closes `conn`, any race with a writer starting right after `os.path.exists(path + "-wal")` only sees the pre-WAL snapshot for that single short read and never holds an immutable handle across writes. + - **`-wal` without `-shm` (`walonly`)**: Copies `opencode.db` and `opencode.db-wal` into a `tempfile.TemporaryDirectory(prefix="mam-opencode-")`, opens the private copy so SQLite can rebuild `-shm` and recover the committed WAL frames, backs it up to `:memory:`, and cleans up the temp dir. Verified on macOS that committed rows in `-wal` are returned and the source directory remains `['opencode.db', 'opencode.db-wal']`. +2. **`_DB_PATHS` process-local cache (`opencode.py:40, 144-160`)**: + - Keyed by `(bin_path, ctx.home_dir, self._data_home(ctx))` (or `(bin_path, HOME, XDG_DATA_HOME)` when `ctx.home_dir` is unset). + - Different `HOME`, `XDG_DATA_HOME`, or binary paths produce distinct cache keys; failed `debug paths` runs (`returncode != 0` or no `db` line) are not cached. +3. **`auth_ok` & `create_session.sh` (`opencode.py:317-321`, `__main__.py:120-127`, `create_session.sh:146-151`)**: + - Checks binary existence on `PATH` or `/.opencode/bin/opencode` without spawning `opencode --version` (preventing log writes to the real opencode data dir). Other agents' branches in `create_session.sh` are untouched. +4. **`input_placeholder_pattern` (`opencode.py:75-79`, `base.py:212-217`, `input_region.py:80-81`)**: + - `re.fullmatch(r'Ask anything(?:…|\.\.\.)(?: "[^"]*"?)?', head.strip())` strips both full and narrow-truncated 2.x home screen placeholders while preserving user input starting with `Ask anything…` (such as `'Ask anything… "x" and fix it'`). Other adapters return `None`. +5. **Python 3.9 & macOS compatibility**: + - All syntax, type annotations, and shell scripts are compatible with Python 3.9.6 and macOS bash 3.2 (`set -euo pipefail`). `git diff --check` is clean. + +--- + +## 4. Test Execution Summary + +Executed in `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T000805Z-975c4c/1` with isolated `HOME`, external `--basetemp`, `PYTHONDONTWRITEBYTECODE=1`, and `-p no:cacheprovider`: +- `tests/test_issue60_opencode_v2.py`: 15 passed +- `tests/test_a4_adapter_contract.py`: 33 passed +- `tests/test_issue50_dispatch.py`: 49 passed +- `tests/test_issue55_agent_homes.py`: 11 passed +- `tests/test_issue60_linux_fixes.py`: 40 passed, **5 failed** (`test_a_query_reads_right_and_leaves_the_sidecars_alone[clean-listing0-*]`) + +[VERDICT: NOT PASS] diff --git a/.agents/reports/reviewer-agy-01/report-fe8fd3b9.identity.json b/.agents/reports/reviewer-agy-01/report-fe8fd3b9.identity.json new file mode 100644 index 0000000..091c5ad --- /dev/null +++ b/.agents/reports/reviewer-agy-01/report-fe8fd3b9.identity.json @@ -0,0 +1,14 @@ +{ + "loop_id": "loop-20261019T015801Z-05463c", + "base_sha": "1d7b7dab58a10fe86fd747b7290d47674ca93230", + "head_sha": "6a37c0019baa31ee28b32a086e57b4c8239413c4", + "tree_sha": "87013e7ef7f5239f1bf6ea38a2cbda214257be3c", + "review_commit": "6a37c0019baa31ee28b32a086e57b4c8239413c4", + "reviewer": "reviewer-agy-01", + "job_id": "fe8fd3b9", + "verdict": "PASS", + "transport": "herdr", + "evidence": "review-tree", + "isolation": "worktree", + "verified_at": "2026-10-19T02:37:22Z" +} \ No newline at end of file diff --git a/.agents/reports/reviewer-agy-01/report-fe8fd3b9.md b/.agents/reports/reviewer-agy-01/report-fe8fd3b9.md new file mode 100644 index 0000000..802c39a --- /dev/null +++ b/.agents/reports/reviewer-agy-01/report-fe8fd3b9.md @@ -0,0 +1,58 @@ +# PR #62 Code Review Report — Round 2 (`1d7b7da..6a37c00`) + +- **Job ID**: `fe8fd3b9` +- **Reviewer**: `agy` (`herdr:reviewer-agy-01`) +- **Branch**: `fix/issue-60-linux` +- **Base .. Head**: `1d7b7dab58a10fe86fd747b7290d47674ca93230` .. `6a37c0019baa31ee28b32a086e57b4c8239413c4` (9 commits; Round 2 delta: `6a37c00`) +- **Verification Worktree**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T015801Z-05463c/1` + +--- + +## 1. Executive Summary + +Commit `6a37c00` resolves all blocking and non-blocking findings from Round 1 (`6a4e385`): + +1. **macOS SQLite persistent WAL in `wal_home` fixture (`tests/test_issue60_linux_fixes.py:155-162`)**: + - After `_wal_writer(db, ws).close()` checkpoints the committed WAL rows into `opencode.db`, `make("clean")` removes `opencode.db-wal` and `opencode.db-shm` if present (which Apple's system `libsqlite3.dylib` keeps on disk due to `SQLITE_FCNTL_PERSIST_WAL=1`). + - All 20 `(query × state)` combinations in `test_a_query_reads_right_and_leaves_the_sidecars_alone` now pass on macOS. +2. **Quoted example required in `input_placeholder_pattern` (`.agents/skills/lib_py/agents/adapters/opencode.py:76-79`)**: + - Pattern updated to `r'Ask anything(?:…|\.\.\.) "[^"]*"?'` so a bare `Ask anything…` or `Ask anything...` typed by a user is preserved as draft input (`test_typed_text_is_read_as_typed`), while the 2.x home screen placeholder (both full and narrow-truncated) is stripped. +3. **Executable check for `~/.opencode/bin/opencode` in `auth_ok()` (`.agents/skills/lib_py/agents/adapters/opencode.py:321-323`)**: + - `auth_ok()` now checks `os.path.isfile(local) and os.access(local, os.X_OK)` for `/.opencode/bin/opencode` (`test_a_home_install_that_cannot_run_is_not_enough`). +4. **Read-only `_ro_connect` in `status.sh` and `reconcile.sh` (`.agents/skills/multi-agent-mux-status/scripts/status.sh:115-117, 316-318`, `.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh:953-955`)**: + - Both scripts now open `opencode.db` via `_ro_connect(odb)` instead of raw `sqlite3.connect(odb)`, pinned by `test_status_and_reconcile_open_the_database_read_only`. + +--- + +## 2. Verification of Cumulative Changes (`1d7b7da..6a37c00`) + +| Area | Verification Result | +| :--- | :--- | +| **`_ro_connect` (`opencode.py:13-36`)** | Verified all 3 branches (`wal and shm` -> `mode=ro`, `not wal` -> `mode=ro&immutable=1`, `wal and not shm` -> temp copy + `.backup(:memory:)`) across all 5 adapter queries and 4 DB states (`clean`, `leftover`, `walonly`, `writer`) on macOS Python 3.9.6 / Apple SQLite 3.51.0. Sidecars are neither created nor deleted. | +| **`_DB_PATHS` cache (`opencode.py:40, 145-166`)** | Process-local cache keyed by `(bin_path, HOME, XDG_DATA_HOME)` caches successful `debug paths` lookups and does not cache failures. | +| **`input_placeholder_pattern` (`opencode.py:75-79`, `base.py:212-217`, `input_region.py:80-81`)** | Matches `Ask anything(?:…|\.\.\.) "[^"]*"?` via `re.fullmatch` on the first non-blank line of the input box only; preserves user drafts including bare `Ask anything…` / `Ask anything...`. | +| **`auth_ok` & `auth-ok` CLI (`opencode.py:319-323`, `__main__.py:120-127`, `create_session.sh:146-151`)** | Verifies `shutil.which("opencode")` or executable `/.opencode/bin/opencode` without running `opencode --version`. | +| **`status.sh`, `reconcile.sh`, `stop_session.sh`** | `status.sh` and `reconcile.sh` use `_ro_connect(odb)`; `stop_session.sh` logs unconfirmed `/exit` delivery accurately without altering exit codes or kill-chain behavior. | + +--- + +## 3. Test Execution Summary + +Executed in `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T015801Z-05463c/1` with isolated `HOME`, external `--basetemp`, `PYTHONDONTWRITEBYTECODE=1`, and `-p no:cacheprovider`: + +- `tests/test_issue60_linux_fixes.py`: 52 passed +- `tests/test_issue60_opencode_v2.py`: 15 passed +- `tests/test_a4_adapter_contract.py`: 33 passed +- `tests/test_issue50_dispatch.py`: 49 passed +- `tests/test_issue55_agent_homes.py`: 11 passed +- `tests/test_tier2_component.py`: 42 passed +- **Total**: `202 passed in 377.34s` +- **Post-check**: Verification worktree is clean (`git status -s` empty, `git diff --check` clean, no `.mam` directory), and no stray `opencode` test processes remain. + +--- + +## 4. Findings + +No blocking or non-blocking issues remaining. PR #62 (`fix/issue-60-linux`, `1d7b7da..6a37c00`) is ready to merge. + +[VERDICT: PASS] diff --git a/.agents/reports/reviewer-grok-01/report-85982c2d.md b/.agents/reports/reviewer-grok-01/report-85982c2d.md new file mode 100644 index 0000000..074d358 --- /dev/null +++ b/.agents/reports/reviewer-grok-01/report-85982c2d.md @@ -0,0 +1,59 @@ +# Review 85982c2d — PR #62, Issue #60 Linux follow-up + +NOT PASS. Five new tests fail on this Mac before they call the query. The read-only open itself, measured on temporary databases, returns the committed rows and leaves the directory names as they were. + +Reviewed only the snapshot `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T000805Z-975c4c/1` (`loop-20261019T000805Z-975c4c`). Head `6a4e385a78c8a561d5cf3644336de6a352ea9271`, tree `afa2c55925f93820337b69699c670c0b0ca90980`, base `1d7b7dab58a10fe86fd747b7290d47674ca93230` is an ancestor and is the control-plane HEAD. Eight commits, 11 files, +515/−31. `git diff --check` is clean. Snapshot porcelain is empty and the snapshot has no `.mam`. Control-plane porcelain is empty. No product file was edited. The source worktree was not run. + +Commits, newest first: `6a4e385` (narrow fixture ends at its last drawn line), `d17cd80` (auth is the binary; create uses `auth-ok`), `7803d47` (a `-wal` without its `-shm` is read from a private copy), `463817e` (unconfirmed `/exit` wording), `614ab14` (`debug paths` cached per binary and home), `ac5f115` (queries go through `_ro_connect`), `14d7a46` (2.x home placeholder), `4d49abb` (`DELEGATE_JOB_PYTHON` for the u1e wrapper). + +Host tools: control-plane venv Python 3.9.6, sqlite 3.51.0. `bash -n` accepts `create_session.sh` and `stop_session.sh`. `py_compile` accepts `opencode.py`, `base.py`, `input_region.py`, `__main__.py`, and `tests/test_issue60_linux_fixes.py`. The opencode binary was not launched. Linux was not executed here. + +## The five failures + +`tests/test_issue60_linux_fixes.py::test_a_query_reads_right_and_leaves_the_sidecars_alone` with state `clean` fails for all five queries (`verify_artifact`, `discover`, `_has_session`, `_descendants`, `residual_artifacts`). + +`make("clean")` closes a WAL connection and requires the directory to be exactly `["opencode.db"]` (`test_issue60_linux_fixes.py:154` and `:173-181`). The assertion is that listing, and it runs before the query. On sqlite 3.51.0 here, closing the last connection leaves `opencode.db-wal` at 0 bytes and `opencode.db-shm` at 32768 bytes. `PRAGMA wal_checkpoint(TRUNCATE)` then close leaves the same two names. The main file grows from 4096 to 8192 bytes on that close, which is the checkpoint into the main file. The pytest failure is `['opencode.db', 'opencode.db-shm', 'opencode.db-wal']` against `['opencode.db']`. + +That is the state a just-closed WAL database has on this host. `_ro_connect` then sees both sidecars and takes the `mode=ro` branch. A temporary database in that state returned the inserted row, and the three names and their sizes were the same after the connection closed. + +The no-wal branch was measured by removing the sidecars after the checkpoint. `_ro_connect` (`opencode.py:13-36`) opened `file:?mode=ro&immutable=1`, returned the row, and the directory stayed a single file with the same size and hash. A path containing a space behaved the same way. + +The fixture has to remove the sidecars this sqlite keeps before it can assert a one-file listing and exercise that branch. As written, the five clean cases are red on the review host, so the related file is red here. + +The other three states of the same test passed: both sidecars left by a writer that is gone, a `-wal` copied without its `-shm`, and a live writer. Each returned the expected rows (`ses_main`, the child id, or the residual `session_v2` count the test asks for) and the directory names after the query matched the names before it. + +## The three open modes, on temporary databases + +Both `-wal` and `-shm` present: `mode=ro`. The live writer's uncheckpointed row was visible. The `-wal` size and hash stayed the same. The `-shm` hash changed while the reader was open and was still the new hash after the reader closed (32768 bytes, same inode). The directory names stayed the three sidecar names. In a second sample the writer then inserted another row; `wal_checkpoint(TRUNCATE)` returned `(0, 0, 0)`. + +No `-wal`: `immutable=1`. Covered above. An existing `-shm` with no `-wal` was left in place, and the row in the main file was visible. + +`-wal` present and `-shm` absent: the function copies the db and the wal into a `mam-opencode-` temporary directory, backups that copy into memory, and drops the temporary directory before returning the memory connection. Both rows that lived in the wal were visible. The original db and wal kept their size and hash. No `-shm` appeared beside the original. + +`db_rows_naming` takes the optional `connect` and the opencode residual path passes `_ro_connect` (`base.py:111-119`, `opencode.py:273-274`). `verify_artifact`, `_descendants`, `_has_session`, and `discover` call `_ro_connect`. `purge_artifacts` still uses `sqlite3.connect(db)` for the 1.x `DELETE` statements (`opencode.py:218`). That line is unchanged in this range. + +An immutable open that is issued after the no-wal check has already been recorded, and after a writer has then committed a new row into a new `-wal`, returns the rows that were in the main file. `_ro_connect` called once both sidecars exist returns the new row as well. The gap is between `os.path.exists` (`opencode.py:21`) and `sqlite3.connect` (`opencode.py:25`). A writer that already has both sidecars is read with `mode=ro`. + +## The other five items + +The 2.x placeholder pattern is `Ask anything(?:…|\.\.\.)(?: "[^"]*"?)?` (`opencode.py:76-79`). `extract_input_region` applies it with `re.fullmatch` to the first non-blank line of the box only (`input_region.py:76-86`). Both new fixtures, wide and narrow, read as an empty box. The narrow fixture's example line contains a quote and does not end with a quote. A first line that is exactly `Ask anything…` or `Ask anything… ""` (ellipsis or three dots) reads as empty. A first line with text after the closing quote is kept, and a later line with the placeholder shape is kept. `input_placeholder` stays `''`, which is what `lib.sh` greps. The other adapters' `input_placeholder_pattern` is `None`. Those tests passed. + +`auth_ok` for opencode is `shutil.which("opencode")` or `/.opencode/bin/opencode` (`opencode.py:317-321`). Keys and `auth.json` are outside that check. `__main__.py` `auth-ok` exits 0 or 1 from the adapter, and exits 1 for an unknown agent. `create_session.sh` calls `auth-ok opencode` through `_delegate_py_bin`. The diff among adapters is only `opencode.py`. Claude, agy, grok, codex, and hermes keep their previous `auth_ok` bodies. `test_adapter_auth_ok`, the binary-only cases, and `test_create_decides_opencode_auth_without_running_it` passed. The create run records no `--version` call. + +`_DB_PATHS` keys are `(binary, home, data home)` (`opencode.py:139-161`). A hit returns the stored path. A failed `debug paths` is not stored. The new tests replace the dict for each test (`test_issue60_linux_fixes.py:48-50`). `test_debug_paths_runs_once_per_home` and `test_a_failed_debug_paths_is_not_remembered` passed. The same key keeps its path for the life of the process, including after the binary at that path is replaced. + +`stop_session.sh` still sends the exit key, waits, and runs `kill-session` when the session is still there (`stop_session.sh:242-250`). The unconfirmed-delivery line now says it is waiting for the session to exit. `test_the_graceful_stop_reports_what_happened` passed for a pane that exits on `/exit` and for one that stays up. The staying-up case still contains `still alive → kill-session`. + +`test_u1e_a_dry_run_record_is_not_an_open_job` passes `DELEGATE_JOB_PYTHON=sys.executable`. That test passed. + +## Suite + +Pytest used the control-plane venv, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, and `--basetemp` `/tmp/mam859-85982c2d-pybase`. Inherited `HERDR_*`, `MAM_*`, `MQTT_*`, `SKS_*`, `DELEGATE_JOB_LOGS_DIR`, `HOME_DIR`, `CLAUDE_PROJECT_DIR`, `CLAUDE_CONFIG_DIR`, `CLAUDE_CODE_TMPDIR`, `CODEX_HOME`, `PYTHONPATH`, `WORKSPACE_ROOT`, `XDG_STATE_HOME`, `XDG_CONFIG_HOME`, `XDG_CACHE_HOME`, and `XDG_DATA_HOME` were dropped. + +5 failed, 42 passed in 59.38 s. The failures are the five `clean` cases named above. The rest of `tests/test_issue60_linux_fixes.py`, `tests/test_a4_adapter_contract.py::test_adapter_auth_ok`, and `tests/test_issue50_dispatch.py::test_u1e_a_dry_run_record_is_not_an_open_job` passed. No error from the session guards. + +The user's background service stayed up. Its pid is 23630 and the command is `opencode serve --service`. The real database was not opened. Its mtime stayed 1792367390. `~/.config/opencode/service.json` stayed 1792246838 and `~/.local/state/opencode/service.json` stayed 1792365227. The real `opencode.log` mtime stayed 1792368889 and its size stayed 2636652. The herdr config mtime stayed 1784456394. + +After the report, snapshot porcelain is still empty, the snapshot still has no `.mam`, and control-plane porcelain is still empty at `1d7b7dab`. The herdr config mtime is still 1784456394. The opencode database mtime is still 1792367390, the opencode log mtime is still 1792368889 and its size is still 2636652, and service pid 23630 is still `opencode serve --service`. + +[VERDICT: NOT PASS] diff --git a/.agents/reports/reviewer-grok-01/report-8f95cc77.identity.json b/.agents/reports/reviewer-grok-01/report-8f95cc77.identity.json new file mode 100644 index 0000000..f463a61 --- /dev/null +++ b/.agents/reports/reviewer-grok-01/report-8f95cc77.identity.json @@ -0,0 +1,14 @@ +{ + "loop_id": "loop-20261019T015801Z-05463c", + "base_sha": "1d7b7dab58a10fe86fd747b7290d47674ca93230", + "head_sha": "6a37c0019baa31ee28b32a086e57b4c8239413c4", + "tree_sha": "87013e7ef7f5239f1bf6ea38a2cbda214257be3c", + "review_commit": "6a37c0019baa31ee28b32a086e57b4c8239413c4", + "reviewer": "reviewer-grok-01", + "job_id": "8f95cc77", + "verdict": "PASS", + "transport": "herdr", + "evidence": "review-tree", + "isolation": "worktree", + "verified_at": "2026-10-19T02:37:22Z" +} \ No newline at end of file diff --git a/.agents/reports/reviewer-grok-01/report-8f95cc77.md b/.agents/reports/reviewer-grok-01/report-8f95cc77.md new file mode 100644 index 0000000..48823f5 --- /dev/null +++ b/.agents/reports/reviewer-grok-01/report-8f95cc77.md @@ -0,0 +1,41 @@ +# Review 8f95cc77 — PR #62, second pass + +PASS. Commit `6a37c00` fixes the five Mac failures from `6a4e385` and the two extra findings from that round. Related tests: 53 passed. + +Reviewed only the snapshot `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T015801Z-05463c/1` (`loop-20261019T015801Z-05463c`). Head `6a37c0019baa31ee28b32a086e57b4c8239413c4`, tree `87013e7ef7f5239f1bf6ea38a2cbda214257be3c`, base `1d7b7dab58a10fe86fd747b7290d47674ca93230` is an ancestor and is the control-plane HEAD. Nine commits, 13 files, +553/−36. `git diff --check` is clean. Snapshot porcelain is empty and the snapshot has no `.mam`. Control-plane porcelain is empty. No product file was edited. The source worktree was not run. + +The new commit on top of `6a4e385` is `6a37c00`. It changes four files (+40/−7): `opencode.py` (placeholder pattern and `auth_ok`), `status.sh`, `reconcile.sh`, and `tests/test_issue60_linux_fixes.py`. + +Host tools: control-plane venv Python 3.9.6, sqlite 3.51.0. `bash -n` accepts `status.sh`, `reconcile.sh`, `create_session.sh`, and `stop_session.sh`. The changed Python modules parse. The opencode binary was not launched. Linux was not executed here. The real database was not opened. + +## The round-1 blocker + +On this host, closing the last WAL connection leaves `opencode.db-wal` at 0 bytes and `opencode.db-shm` at 32768 bytes, and the checkpoint has already written the rows into the main file. `make("clean")` now removes those two sidecars when they exist (`test_issue60_linux_fixes.py:158-162`). The five `clean` cases of `test_a_query_reads_right_and_leaves_the_sidecars_alone` then see a directory of exactly `opencode.db`, read the expected rows through `mode=ro&immutable=1`, and the directory is still that one file afterward. Those five cases passed. The leftover, wal-only, and live-writer cases passed again. + +On Linux the close already removes the sidecars, so the new `os.path.exists` unlink is a no-op there. The same rows are what the immutable open reads. + +## Placeholder and auth + +The pattern is now `Ask anything(?:…|\.\.\.) "[^"]*"?` (`opencode.py:79`). The quoted example is required, so a first line that is only `Ask anything…` or `Ask anything...` stays typed text. The wide and narrow home fixtures still read as an empty box, including the narrow line whose example has no closing quote. A first line with text after the closing quote stays, and so does a later line. `test_typed_text_is_read_as_typed` covers the two bare lines on both fixtures and passed. `input_placeholder` stays `''`. The other adapters' pattern property stays `None`. + +A first line that is exactly `Ask anything… ""` is still cleared. That is the home-screen shape. + +`auth_ok` accepts `shutil.which("opencode")`, or `/.opencode/bin/opencode` when that path is a file and `os.access(..., os.X_OK)` is true (`opencode.py:321-323`). A mode `0644` file and a directory named `opencode` both fail. `test_a_home_install_that_cannot_run_is_not_enough` and `test_adapter_auth_ok` passed. Keys and `auth.json` still do not count. Create still asks `auth-ok` and does not run `--version`. The other agents' `auth_ok` bodies are outside this commit. + +## status and reconcile + +Both copies of `resume_on_disk` in `status.sh` (`:117` and `:318`) and the opencode drift-C open in `reconcile.sh` (`:955`) call `_ro_connect(odb)`. `sqlite3.connect(odb)` is gone from both scripts. `test_status_and_reconcile_open_the_database_read_only` checks those counts and passed. `env_python` puts the skill directory on `PYTHONPATH`, which is the path `get_adapter` already uses a few lines above each open. A failed open still returns `MISSING` in status and is swallowed in reconcile. Hermes and codex in those scripts still open their own databases with `sqlite3.connect`. + +## Suite + +Pytest used the control-plane venv, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, and `--basetemp` `/tmp/mam8f95-8f95cc77-pybase`. Inherited `HERDR_*`, `MAM_*`, `MQTT_*`, `SKS_*`, `DELEGATE_JOB_LOGS_DIR`, `HOME_DIR`, `CLAUDE_PROJECT_DIR`, `CLAUDE_CONFIG_DIR`, `CLAUDE_CODE_TMPDIR`, `CODEX_HOME`, `PYTHONPATH`, `WORKSPACE_ROOT`, `XDG_STATE_HOME`, `XDG_CONFIG_HOME`, `XDG_CACHE_HOME`, and `XDG_DATA_HOME` were dropped. + +53 passed in 59.23 s: `tests/test_issue60_linux_fixes.py` and `tests/test_a4_adapter_contract.py::test_adapter_auth_ok`. No skip and no error. + +The earlier commits are unchanged in this pass: `_ro_connect` still picks `mode=ro` when both sidecars exist, `immutable=1` when there is no `-wal`, and a private memory copy when a `-wal` has no `-shm`. The gap between the `exists` check and the immutable open is still there. `_DB_PATHS` still keeps a path for the life of the process, and each test replaces that dict. Create still treats an executable binary as configured. Those three were left in place on purpose. + +The user's background service stayed up. Its pid is 23630 and the command is `opencode serve --service`. The real database mtime stayed 1792367390. `~/.config/opencode/service.json` stayed 1792246838 and `~/.local/state/opencode/service.json` stayed 1792365227. The real `opencode.log` mtime stayed 1792368889 and its size stayed 2636652. The herdr config mtime stayed 1784456394. + +After the report, snapshot porcelain is still empty, the snapshot still has no `.mam`, and control-plane porcelain is still empty at `1d7b7dab`. The herdr config mtime is still 1784456394. The opencode database mtime is still 1792367390, the opencode log mtime is still 1792368889 and its size is still 2636652, and service pid 23630 is still `opencode serve --service`. + +[VERDICT: PASS] diff --git a/.agents/skills/lib.sh b/.agents/skills/lib.sh index 4a39d7d..543370d 100644 --- a/.agents/skills/lib.sh +++ b/.agents/skills/lib.sh @@ -29,7 +29,7 @@ export WORKSPACE_ROOT # Framework semantic version. Single runtime source of truth; kept in lockstep # with VERSIONS.md and the 8 SKILL.md frontmatters by tests/test_version_consistency.py. # NOTE: unlike other MAM_* variables this one is intentionally NOT env-overridable. -MAM_VERSION="4.9.6" +MAM_VERSION="4.9.7" export MAM_VERSION AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}" diff --git a/.agents/skills/lib_py/loop/eager.py b/.agents/skills/lib_py/loop/eager.py index 0be6d07..38b41f5 100644 --- a/.agents/skills/lib_py/loop/eager.py +++ b/.agents/skills/lib_py/loop/eager.py @@ -13,7 +13,7 @@ import sys from typing import List, Sequence # Framework version locked with framework constants -FRAMEWORK_VERSION = "4.9.6" +FRAMEWORK_VERSION = "4.9.7" CORE_MODULES: Sequence[str] = ( # Standard library modules diff --git a/.agents/skills/multi-agent-mux-create/SKILL.md b/.agents/skills/multi-agent-mux-create/SKILL.md index e3faec8..73f858c 100644 --- a/.agents/skills/multi-agent-mux-create/SKILL.md +++ b/.agents/skills/multi-agent-mux-create/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-create description: "Create a new agent session (claude, antigravity/agy) in a dedicated herdr session for context-preserving long-running work. Always creates a herdr session — never backgrounds with nohup/disown. Writes the new session to .mam/agent-sessions.yaml. Use when you want to start a fresh agent (no prior UUID) for a new project workspace." -version: 4.9.6 +version: 4.9.7 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-delegate-job/SKILL.md b/.agents/skills/multi-agent-mux-delegate-job/SKILL.md index 22bab77..c7dca38 100644 --- a/.agents/skills/multi-agent-mux-delegate-job/SKILL.md +++ b/.agents/skills/multi-agent-mux-delegate-job/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-delegate-job description: "Delegate a unit of work to any autonomous agent (claude-code, hermes, agy, grok-build, codex, or a human) and observe it asynchronously over an MQTT event channel. Supported roles include orchestrator, worker, and reviewer." -version: 4.9.6 +version: 4.9.7 author: godopu license: MIT platforms: [linux, macos, windows] diff --git a/.agents/skills/multi-agent-mux-loop/SKILL.md b/.agents/skills/multi-agent-mux-loop/SKILL.md index 3e37ec4..0e74e58 100644 --- a/.agents/skills/multi-agent-mux-loop/SKILL.md +++ b/.agents/skills/multi-agent-mux-loop/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-loop description: "Run an autonomous planning-execution-review loop using multiple agents (Planner, Creator, Reviewers) in the workspace. Automatically orchestrates plan discussion, code changes, and peer reviews until a unanimous PASS is achieved or the maximum iteration limit is reached." -version: 4.9.6 +version: 4.9.7 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-monitor/SKILL.md b/.agents/skills/multi-agent-mux-monitor/SKILL.md index 5e2604d..0c3731b 100644 --- a/.agents/skills/multi-agent-mux-monitor/SKILL.md +++ b/.agents/skills/multi-agent-mux-monitor/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-monitor description: "Run a long-lived reconciler that watches .mam/agent-sessions.yaml against the actual herdr/agent runtime state and reconciles them. Use when you want live visibility into which agent sessions are running, which are dead, which have stale YAML entries, and which have new session ids that haven't been recorded yet. Runs as a persistent loop (`reconcile.sh --subscribe`) that keeps going until it times out, idles out, or is interrupted." -version: 4.9.6 +version: 4.9.7 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-orc-onboard/SKILL.md b/.agents/skills/multi-agent-mux-orc-onboard/SKILL.md index be467eb..93bce4f 100644 --- a/.agents/skills/multi-agent-mux-orc-onboard/SKILL.md +++ b/.agents/skills/multi-agent-mux-orc-onboard/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-orc-onboard description: "Register current or specified orchestrator session UUID into agent-sessions.yaml orchestrator_uuids list to prevent sub-agent discovery capture." -version: 4.9.6 +version: 4.9.7 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-resume/SKILL.md b/.agents/skills/multi-agent-mux-resume/SKILL.md index 4155c69..2113ec9 100644 --- a/.agents/skills/multi-agent-mux-resume/SKILL.md +++ b/.agents/skills/multi-agent-mux-resume/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-resume description: "Resume an existing agent (claude, antigravity/agy) conversation by UUID into a herdr session. Reads .mam/agent-sessions.yaml for the saved session/conversation id, spawns (or reuses) a herdr session of the matching name, and runs `claude -r ` or `agy --conversation ` inside. Use when you want to reattach to a previous session's context, or revive a session whose herdr died but the agent's conversation is still on disk." -version: 4.9.6 +version: 4.9.7 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-status/SKILL.md b/.agents/skills/multi-agent-mux-status/SKILL.md index 82eee31..83e83cf 100644 --- a/.agents/skills/multi-agent-mux-status/SKILL.md +++ b/.agents/skills/multi-agent-mux-status/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-status description: "Read-only instant snapshot of all agent herdr sessions — name, YAML status, herdr alive, pane cmd/cwd, resume UUID on disk, and any drift. No mutation. Reuses reconcile.sh --dry-run for the diff logic. Use when you want to know 'what's running RIGHT NOW' without spinning up the monitor loop." -version: 4.9.6 +version: 4.9.7 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-stop/SKILL.md b/.agents/skills/multi-agent-mux-stop/SKILL.md index 98b3a49..3ac0f9b 100644 --- a/.agents/skills/multi-agent-mux-stop/SKILL.md +++ b/.agents/skills/multi-agent-mux-stop/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-stop description: "Stop an agent herdr session (claude, antigravity/agy) and update .mam/agent-sessions.yaml. Default stops gracefully and marks status=stopped with conversation preserved for resume. Does NOT delete on-disk conversation artifacts (jsonl/db) — those are preserved unless --purge-conversation is passed. Use when ending a work session, switching to a different one, or cleaning up before a fresh start." -version: 4.9.6 +version: 4.9.7 author: godopu license: MIT platforms: [linux, macos] diff --git a/BOOTSTRAP.ko.md b/BOOTSTRAP.ko.md index 96b0747..6e3c706 100644 --- a/BOOTSTRAP.ko.md +++ b/BOOTSTRAP.ko.md @@ -2,7 +2,7 @@ 본 문서는 `herdr_agent_orchestration` 오케스트레이션 및 메시징 백플레인 워크플로우를 새로운 프로젝트에 도입하여 이식하고, 새로운 개발자/에이전트가 초기 가동을 시작할 때 수행해야 하는 환경 설정 및 구축 절차를 안내합니다. -[![Version: v4.9.6](https://img.shields.io/badge/version-v4.9.6-blue.svg)](VERSIONS.md) +[![Version: v4.9.7](https://img.shields.io/badge/version-v4.9.7-blue.svg)](VERSIONS.md) 새로운 에이전트는 이 안내서에 기술된 절차를 순차적으로 실행하여 초기 환경을 안정적으로 설정할 수 있습니다. diff --git a/BOOTSTRAP.md b/BOOTSTRAP.md index 6fb5b0f..bfddba0 100644 --- a/BOOTSTRAP.md +++ b/BOOTSTRAP.md @@ -2,7 +2,7 @@ This document guides you through the setup and initialization procedures required to adopt the `herdr_agent_orchestration` orchestration and messaging backplane workflow in a new project, enabling a new developer or agent to get up and running quickly. -[![Version: v4.9.6](https://img.shields.io/badge/version-v4.9.6-blue.svg)](VERSIONS.md) +[![Version: v4.9.7](https://img.shields.io/badge/version-v4.9.7-blue.svg)](VERSIONS.md) A new agent can follow the steps in this guide sequentially to establish a stable and reliable initial environment. diff --git a/MESSAGING.md b/MESSAGING.md index 6948b2b..690fff1 100644 --- a/MESSAGING.md +++ b/MESSAGING.md @@ -1,6 +1,6 @@ # Messaging System Technical Analysis & Architecture Report -Applies to: MAM v4.9.6 +Applies to: MAM v4.9.7 This report provides a comprehensive, deep-dive analysis of the messaging system implemented in the `multi-agent-mux-delegate-job` skill. It covers the MQTT broker architecture, event protocols, job lifecycles, codebase internals, cross-system integration, and a list of known limitations along with production recommendations. diff --git a/README.ko.md b/README.ko.md index 8dbf7d7..cd56c2b 100644 --- a/README.ko.md +++ b/README.ko.md @@ -2,7 +2,7 @@ [herdr](https://herdr.dev)와 MQTT 위에 만든 **다중 에이전트 오케스트레이션·메시징 백플레인**입니다. 장시간 실행되는 코딩 에이전트(`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`)를 격리된 herdr pane 에서 돌리고, 작업을 추적되는 잡으로 위임하며, 모든 리뷰어가 통과시킬 때까지 planner / creator / reviewer 루프를 돌립니다. -[![Version: v4.9.6](https://img.shields.io/badge/version-v4.9.6-blue.svg)](VERSIONS.md) +[![Version: v4.9.7](https://img.shields.io/badge/version-v4.9.7-blue.svg)](VERSIONS.md) --- diff --git a/README.md b/README.md index d61050e..c163da3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A **Multi-Agent Orchestration & Messaging Backplane** built on [herdr](https://herdr.dev) and MQTT. It runs long-lived coding agents (`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`) in isolated herdr panes, delegates work to them as tracked jobs, and drives planner / creator / reviewer loops until every reviewer passes the change. -[![Version: v4.9.6](https://img.shields.io/badge/version-v4.9.6-blue.svg)](VERSIONS.md) +[![Version: v4.9.7](https://img.shields.io/badge/version-v4.9.7-blue.svg)](VERSIONS.md) --- diff --git a/VERSIONS.md b/VERSIONS.md index 824a6c4..7c25b99 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -6,8 +6,8 @@ ## 📌 현재 버전 개요 (Current Release) -- **프레임워크 버전**: `v4.9.6` -- **최신 릴리스 일시**: 2026-10-18 (KST) +- **프레임워크 버전**: `v4.9.7` +- **최신 릴리스 일시**: 2026-10-19 (KST) - **기준 브랜치**: `main` - **핵심 아키텍처**: - **Terminal 상태 단방향 불변성 (`engine.py`, Issue #6 Phase 1)**: `completed`, `error`, `cancelled` 터미널 상태 진입 후 후속 이벤트나 업데이트에 의한 덮어쓰기를 엄격히 차단(Terminal Immutability). `registry.py close` 서브커맨드 및 fail-closed 조기 종료 도입. @@ -24,23 +24,54 @@ ## 🧭 스킬 패키지 버전 매트릭스 (Skills Version Matrix) -모든 8개 스킬은 YAML frontmatter 메타데이터(`author`, `version`, `platforms`, `environments`) 표준화를 통해 `v4.9.6`로 동기화되어 배포됩니다. +모든 8개 스킬은 YAML frontmatter 메타데이터(`author`, `version`, `platforms`, `environments`) 표준화를 통해 `v4.9.7`로 동기화되어 배포됩니다. | 스킬명 | 버전 | 역할 및 주요 책임 | 상태 | | :--- | :---: | :--- | :---: | -| **`multi-agent-mux-create`** | `4.9.6` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 | -| **`multi-agent-mux-stop`** | `4.9.6` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 | -| **`multi-agent-mux-resume`** | `4.9.6` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 | -| **`multi-agent-mux-status`** | `4.9.6` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 | -| **`multi-agent-mux-monitor`** | `4.9.6` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 | -| **`multi-agent-mux-delegate-job`** | `4.9.6` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 | -| **`multi-agent-mux-loop`** | `4.9.6` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 | -| **`multi-agent-mux-orc-onboard`** | `4.9.6` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 | +| **`multi-agent-mux-create`** | `4.9.7` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 | +| **`multi-agent-mux-stop`** | `4.9.7` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 | +| **`multi-agent-mux-resume`** | `4.9.7` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 | +| **`multi-agent-mux-status`** | `4.9.7` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 | +| **`multi-agent-mux-monitor`** | `4.9.7` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 | +| **`multi-agent-mux-delegate-job`** | `4.9.7` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 | +| **`multi-agent-mux-loop`** | `4.9.7` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 | +| **`multi-agent-mux-orc-onboard`** | `4.9.7` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 | --- ## 📋 버전별 상세 변경 내역 (Changelog) +### 🚀 `v4.9.7` — opencode 2.x 의 Linux 확인 후속: 안내 문구, 읽기 전용 조회, 인증 확인 (Issue #60, PR #62) (2026-10-19) + +> **패치 (PATCH Release)**: v4.9.6 을 Linux(Ubuntu, Python 3.14, opencode 2.0.32, herdr 0.9.3)에서 확인했다. 경로·XDG·`--standalone` 자식의 수명·거부되는 플래그·`/exit`·status 는 Mac 과 같았다. 아래는 그 확인에서 찾은 결함으로, 특정 OS 에 한정되지 않는다. Issue #60 은 이 릴리스로 닫힌다. + +#### 🌟 주요 개선 사항 +1. **2.x 홈 화면 안내 문구는 입력이 아니다**: 입력 칸의 `Ask anything… "<예시>"` 를 입력 글자로 읽어 `stall_watch.input_empty()` 가 빈 칸을 보지 못했다. 어댑터에 `input_placeholder_pattern`(입력 칸 첫 줄 전체 일치)을 두었다. opencode 패턴은 좁은 화면에서 잘린 예시도 받고, 따옴표로 시작하는 예시가 있어야 한다(사용자가 친 `Ask anything…` 는 입력이다). 다른 에이전트는 기존 `input_placeholder` 정확 일치 그대로다. 실제 2.x 캡처 fixture 2개(140열, 좁은 화면)를 추가했다. +2. **opencode DB 조회는 DB 옆에 아무것도 만들거나 지우지 않는다**: `_ro_connect` 가 사이드카 상태로 여는 방식을 고른다. + - `-wal`·`-shm` 둘 다(쓰는 쪽이 있음): `mode=ro` + - `-wal` 없음: `mode=ro&immutable=1` + - `-shm` 없는 `-wal`(남겨진 파일): 커밋된 행이 있으므로 임시 복사본을 메모리로 읽는다 + + 어댑터의 verify·discover·`_has_session`·자손 조회·residual 과 `status.sh`·`reconcile.sh`(drift C, Mac 리뷰)가 이것을 쓴다. `db_rows_naming` 은 선택적 `connect` 를 받는다(opencode 만 넘긴다). purge 의 의도된 쓰기(`session delete`, 옛 행 정리)는 그대로다. +3. **`debug paths` 는 (바이너리, HOME, XDG_DATA_HOME) 마다 한 번**: 실행마다 opencode 로그에 한 줄을 남기므로 프로세스 안에서 결과를 기억한다. 실패한 결과는 기억하지 않는다. +4. **인증 확인은 바이너리**: 2.x 에는 `auth.json` 이 없고 새 HOME 은 무료 기본 모델로 돈다. opencode 의 `auth_ok` 는 PATH 의 `opencode` 또는 실행 가능한 `~/.opencode/bin/opencode` 이다(Mac 리뷰: 실행 권한 없는 파일·폴더는 아님). `create_session.sh` 는 새 `python -m lib_py.agents auth-ok ` 로 어댑터에 묻고, 실제 로그에 기록하던 `opencode --version` 호출이 없어졌다. +5. **stop 문구**: `/exit` 전달이 확인되지 않을 때 "kill chain 으로 넘어간다" 대신 세션 종료를 기다린다고 출력한다. 종료 판단과 kill chain 은 그대로다. +6. **테스트**: `test_u1e` 가 `DELEGATE_JOB_PYTHON=sys.executable` 을 넘겨 PATH 의 python3 에 의존하지 않는다. + +#### ✅ 품질 검증 +- **회귀**: `tests/test_issue60_linux_fixes.py`. base(v4.9.6)에 새 테스트만 얹으면 46개 중 23개가 실패했다(작성자 측정). Mac 리뷰 뒤 macOS persistent WAL 상태, `Ask anything…` 입력, 실행 불가 설치, status·reconcile 읽기 전용 확인을 더했다. +- **Linux (작성자)**: 리뷰 2회차 4/4 PASS. 전체 2018 passed, 20 skipped, 12 failed(알려진 `test_issue10_13_purge.py` agy 실패, #28). 실제 opencode 데이터·상태·설정 폴더와 로그는 바뀌지 않았다. +- **리뷰 (`--review-only`, `--all-reviewer`, 이 Mac 정합성 중심)**: + - 1회차(@6a4e385): 4/4 NOT PASS. 공통 차단은 새 테스트의 `clean` 상태가 macOS 에서 항상 실패한 것이다(macOS SQLite 는 닫아도 `-wal`·`-shm` 을 남김; 제품 코드는 맞음). codex 는 실행 불가 설치를 인정하는 `auth_ok` 와 `Ask anything…` 입력을 지우는 패턴을, agy 는 `status.sh`·`reconcile.sh` 의 쓰기 가능 열기를 지적했다. 오케스트레이터가 6a37c00 에서 모두 고쳤다. + - 2회차(@6a37c00): **4/4 PASS**. 리포트는 `.agents/reports//` 에 있다. +- **전체 스위트 (macOS, Python 3.9.6, @6a37c00)**: 2001 passed, 56 skipped, 실패 0. 실제 `~/.claude`·`/private/tmp/claude-`·opencode 의 data·state·config 폴더·herdr 서버 pane 목록에 이 실행이 남긴 것은 없었고, 사용자의 opencode 서비스는 그대로였다. +- **알려진 비차단**: + - `-wal` 없음을 판단한 직후 쓰는 쪽이 생기면 immutable 읽기가 틀릴 수 있다(창은 ms 수준, 단건·LIMIT 20 조회). + - `debug paths` 결과는 프로세스 수명 동안 유지된다. 같은 경로의 바이너리를 경로 규칙이 바뀌는 버전으로 올리면 오래 도는 `reconcile --subscribe` 는 다시 시작해야 한다. + - create 의 opencode 사전 검사는 바이너리 존재뿐이다. 깨진 바이너리는 준비 대기 단계에서 드러난다. + - 2.x 대화 화면의 안내 문구는 캡처하지 않았다(메시지를 보내면 모델을 호출한다). + - agy(#28)·codex·hermes 는 아직 DB 를 쓰기 가능으로 연다. macOS 에서 열기만 해도 사이드카가 남을 수 있다. + ### 🚀 `v4.9.6` — opencode 2.x 전용 지원 (Issue #60) (2026-10-18) > **패치 (PATCH Release)**: opencode 가 1.x 에서 2.x 로 올라가면서 TUI 가 `--agent`·`-m` 을 거부하고(실행 즉시 종료), 세션 저장소가 `session_v2` 로 바뀌고, `opencode db path` 가 없어졌다. MAM 은 opencode **2.x 만** 지원한다(1.x 호환 경로 없음). 사실은 이 Mac 에서 opencode 2.0.32 를 임시 HOME·TMPDIR 로 실측해 확인했다.