뜯어본 것공개

DSH better-sidebar

간판 수치 「~325KB 코어」가 실측으로 깨졌습니다. 그런데 게이트 없는 남의 저장소를 심판하던 제 게이트가, 정작 제 저장소에서는 한 번도 커밋된 적이 없었습니다.

확장 베이스로 파는 물건인데, 간판 수치가 실측과 어긋났습니다. DSH better-sidebar 는 DeepSeek Harness 라는 에이전트 CLI 의 웹 모드에 붙어 파일 탐색기·에디터·터미널·Git·프리뷰를 한 화면에 얹는 npm 플러그인입니다. 별 2,575개, 저장소를 만든 지 15일 만에 커밋 250건. README 첫 줄이 자랑하는 건 「기동 시 ~325KB 코어만 내려받는다」인데, npm 산출물을 직접 재보니 발행된 다섯 버전 중 그 값이었던 것은 하나도 없었습니다 — 0.12.1 이 405KB, 9일 뒤 0.15.0 이 585KB(+45%). 원인은 단순합니다. CI 에 크기 게이트가 없습니다. 그런데 이 편이 남을 이유는 그쪽이 아닙니다 — 게이트가 없어 수치가 새어 나간 남의 저장소를 심판하던 그 게이트가, 정작 제 저장소에서는 버전관리 밖에 있었습니다.

npm 패키지 하나가 두 반쪽으로 갈린다 — 서버 라우트와 브라우저 워크벤치 host 반쪽 — /sidebar/* 라우트 fs · git · pty · HTML 프리뷰 · 지연 청크 요청마다 sessionId → 세션 cwd 로 스코프 client 반쪽 — 포털 워크벤치 우측 · 하단 패널을 재귀 split 트리로 쪼갠다 레이아웃은 세션별로 localStorage 에 저장 확장 지점은 딱 둘 — 그리고 내장 기능도 같은 문으로 들어온다 registerTab registerFileViewer 내장 7 tab · 6 viewer 도 통과 ⇒ 확장 베이스로 파는 물건이라 간판 수치와 「능력 대등」 주장이 곧 제품이다 그 둘을 재봤더니 하나는 실측과 어긋났고 하나는 자기 소스 주석이 반박한다
탭 개수를 세는 것으로는 이 물건이 안 보인다. 내장까지 같은 문으로 밀어 넣은 것이 정체다.

확장 베이스는 사이드바가 아니라, 그 사이드바를 남에게 내주는 두 개의 문이다

한 패키지가 서버 반쪽과 브라우저 반쪽으로 갈립니다. 서버 반쪽은 /sidebar/api/* JSON 디스패치 테이블(파일 트리·읽기·쓰기·검색, Git 12종, 서브에이전트 현황)과 미디어·HTML 프리뷰·지연 청크 라우트, 터미널 WebSocket 을 등록합니다. 요청마다 sessionId 로 작업 디렉터리를 스코프하지만, 세션이 아직 안 붙은 첫 페인트를 위해 프로세스 cwd 까지 내려가는 3단 폴백이 있어 「전 기능이 세션 단위로 격리된다」는 정확한 서술이 아닙니다. 브라우저 반쪽은 우측·하단 두 워크벤치를 포털로 띄우고 각 패널을 재귀 split 트리로 쪼갠 뒤, 레이아웃을 세션별 키로 localStorage 에 저장했다가 복원할 때 구조 검증·뷰포트 클램프·중복 pane id 재발급을 거칩니다. 터미널은 흉내가 아니라 실제 셸이고, 켜면 모델에게 8종 도구가 주입돼 에이전트가 장수 PTY 를 부립니다.

항목 실측 (2026-08-22)
규모 227파일 2.4MB · 테스트 82파일 672KB · 설계 문서 21편
의존 런타임 27종 · peer 18종 중 15종이 프리릴리스
활동 08-07 생성 → 08-21 푸시. 커밋 250건 · 릴리스 17건 / 11일
버스팩터 1 — 1위 217커밋(87%), 2위 7커밋
PR 병합 68 · 종료 35 · 열림 78 (열린 PR 이 역대 머지 총량을 넘었다)

여기서 값을 치른 건 「기여자 22명」을 분산으로 읽을 뻔한 것입니다. 19명이 1~2커밋이고 대부분 자기 플러그인을 추천 목록에 올리는 PR 입니다. 기여자 수는 코어 개발의 분산과 무관했습니다.

제일 잘한 것은 자기 내장 기능마저 외부와 같은 문으로 밀어 넣은 것이다

기법 무엇
자기 도그푸딩 내장 7 tab · 6 viewer 가 외부와 똑같은 registerTab/registerFileViewer 를 통과한다. 주석이 이를 “eating its own dogfood” 라 부른다
detect → exts 2단 매칭 뷰어 선택은 내용 스니핑 먼저, 확장자가 그다음. 만능 매치가 detect 를 가지면 ’스니핑 전용’으로 격하돼 양보한다 — 매직넘버 뷰어가 모든 파일을 먼저 삼키는 걸 구조가 막는다
dedupeKey 하드코딩돼 있던 탭 열기 전략 3종(단일·경로별·id별)을 함수 필드 하나로 흡수. ’생성’과 ’포커스’를 갈라 콜백을 따로 쏜다
빌드 순도 게이트 화이트리스트 밖 패키지를 값으로 import 하면 빌드가 깨진다. 문구가 “collaborate through cordis services” — 협업 경로를 하나로 강제한다
등급 강등 마운트 네이티브 의존 로드 실패가 서버를 죽이지 않는다. 강등된 채 마운트되고 모델용 도구는 아예 미등록, 사람에겐 붙여넣을 수리 명령을 준다
이중 마운트 자동 양보 같은 패키지가 두 엔트리로 뜨면 라우트 중복으로 부팅이 통째 실패한다. 런타임 싱글턴이 아니라 로더 표현식으로 자기 행을 스스로 끈다
사이드 대화 시드 자식 세션에 부모 로그를 넘길 때 진행 중이던 턴을 합성 이벤트로 정직하게 닫고, “참조 컨텍스트일 뿐 현재 과업이 아니다” 를 못박는다. 테스트가 그 문장을 고정한다

배운 건 기법이 아니라 「그 규약을 어디에 거는가」입니다. 순도 게이트는 협업 규약을 사람 주석이 아니라 빌드에 걸었습니다. 뒤에서 보겠지만 제 쪽의 같은 규약은 주석에 걸려 있었고, 실제로 한 번 갈라졌습니다.

간판 셋이 다 안 버텼고, 그중 하나는 이슈 트래커에도 없는 결함이었다

  • 「~325KB 코어」 — 깨졌습니다. tarball 다섯을 직접 재니 405 → 424 → 459 → 482 → 585KB. 미니파이도 안 된 12,283줄이라 「미니파이 기준」이라는 변명도 안 통합니다. 같은 저장소의 청크 로더 주석은 “only the ~1MB core bundle” 이라 세 번째 숫자를 내놓습니다. 반면 「7MB mermaid 청크」는 실측 6,996,658 B 로 정확히 맞았습니다 — 저자가 수치를 못 재는 게 아니라, 게이트가 없는 수치만 갱신되지 않았습니다.
  • 「내장과 서드파티가 완전 대등」 — 깨졌습니다. 등록 경로가 같은 건 사실이지만 외부 플러그인은 패널을 펼칠 수 없습니다. 서비스에 확장·토글 메서드가 없어 외부 탭은 열려도 접힌 패널 뒤에 숨고, 남는 수단이 DOM 해킹뿐이라는 오픈 이슈가 살아 있습니다.
  • 「/api 와 동일한 신뢰 울타리」 — 함수는 같고 적용 범위가 다릅니다. 상류는 그 판정을 특권 메서드에만 걸고 이 플러그인은 전 경로에 겁니다. 그래서 Chrome 이 포트 없는 Origin 을 보내는 127.0.0.1 접속에서 사이드바 전 라우트가 403 인데 호스트 쪽 대화는 멀쩡히 돕니다. 과소차단이 아니라 과차단입니다.
  • HTML 프리뷰 — URL 은 맞는데 헤더가 자산을 죽입니다. 상대 참조가 세션 스코프를 잃지 않도록 쿼리 대신 경로에 전부 인코딩한 설계는 훌륭한데, 미디어 타입 표에 .css.js 도 없어 전부 octet-stream 으로 나가고 같은 라우트가 nosniff 를 붙입니다. ⚠ 테스트가 착시를 만듭니다 — URL 디코딩만 단언하고 content-type 은 한 줄도 보지 않습니다.
  • 이슈에도 없는 결함 — 파일 쓰기에 낙관적 동시성 검사가 없습니다. 저장 API 가 받는 건 경로와 내용뿐, mtime 도 해시도 없습니다. 여기에 저장소 스스로 인정한 「파일 watcher 없음」이 겹칩니다. 탭을 열어둔 채 에이전트가 그 파일을 재작성하면, 사람이 저장하는 순간 에이전트의 최신 내용이 경고 없이 사라집니다 — 이 제품의 존재 이유인 시나리오에서 고전적 lost update 입니다.
  • 「3플랫폼 지원」 — CI 두 잡이 전부 ubuntu 단일입니다. 하필 미해결 P0 중 가장 아픈 것이 Windows 에 몰려 있습니다. 설치만 하면 기존 세션 재개가 전부 실패하고, 제거하면 즉시 복구되며, 메인테이너는 mac 에서 재현하지 못합니다.

버틴 것도 적어 둡니다. 「진짜 셸」은 사실이고, 「README 가 아직 내장 Office 프리뷰를 광고한다」는 외부 비판은 이미 수정돼 더는 성립하지 않습니다 — 외부 비판도 낡습니다. 그리고 이 절에서 값을 치른 건 「주석 밀도」를 성숙도로 읽은 것입니다. 이 저장소는 ’왜 이렇게 안 했나’를 적는 드문 주석 문화를 갖고 있어 1차에 그걸 성숙도 근거로 세웠는데, 존재하지 않는 탭 이름을 가리키는 주석과 상류 재라이선스를 못 따라간 표기가 HEAD 에 살아 있었습니다. 검증되지 않은 고밀도 주석은 틀린 문서와 값이 같습니다.

코어 번들 실측 — npm tarball 을 직접 열어 쟀다 (2026-08-22) 대상 주장 ~325KB 문서 목표치 · 실재하지 않음 0.12.1 (08-13) 405KB 0.12.3 (08-16) 424KB 0.13.1 (08-18) 459KB 0.14.0 (08-19) 482KB 0.15.0 (08-21) 585KB ⇒ 다섯 버전 중 325KB 였던 것은 하나도 없다 — 9일 만에 +45%, gzip 도 +47% 같은 저장소가 세 숫자를 동시에 든다 — README 325KB · 로더 주석 ~1MB · 실측 585KB 원인은 CI 에 크기 게이트가 없어서다. 게이트가 있는 「7MB mermaid 청크」는 정확히 맞았다 ⇒ 그 잣대로 남을 심판했는데, 내 게이트 5종은 저장소에 커밋된 적이 없었다
수치가 틀렸다기보다 아무도 지키지 않는다. 크기 게이트 한 줄이 없는 자리에서 9일에 45%가 샜다.

도입 판정은 검토가 아니라 측정 세 줄로 끝났다

where dsh 결과 없음. ~/.dsh 없음. 전역 npm 패키지 9종에 그 생태계 0건. peer 18종 중 15종이 그 생태계인 플러그인이라 설치 경로 자체가 존재하지 않습니다. 「지금은 안 맞는다」가 아니라 「호스트가 없다」입니다. 그래서 남는 건 기법 이식인데, 후보 넷 중 실제로 남은 건 하나였습니다.

  • 번들 크기 게이트 → 이득 0. 대상의 최대 결함이 하필 제가 이미 메운 자리였습니다. scripts/check-weight.mjs 의 상한 9종은 전부 dist 로 판정하고 상한마다 유도 근거가 주석에 붙어 있으며, scripts/deploy.mjs 는 링크·앵커 → 가벼움 → 용어사전 → 마커 4단 fail-closed 입니다.
  • dedupeKey → 이득 0. TC 파이프라인이 소유마커 하나로 열기 정책 3분기를 이미 합니다 — 신규 생성 / 우리 소유면 멱등 재기록 / 남의 것이면 접미사로 양보.
  • 강등 마운트 → 기각. 붙이려던 로컬 모델 MCP 서버가 어떤 설정에도 등록돼 있지 않았습니다. 서버 이름·도구 이름·슬래시 커맨드가 세 갈래로 어긋나 되살려도 안 붙습니다. 적용처가 없는 수확은 수확이 아닙니다.
  • 이중 마운트 자동 양보 → 유일한 수확. 대응물이 있는지 Loki 를 열었더니 lockfile·pidfile·싱글턴 가드가 0건이었습니다. 다만 ⚠ 제가 짚은 위치가 틀렸습니다 — 5분마다 도는 워치독 경로는 이미 ensure() 가 막고 있었고, 안 막힌 건 로그인 시작프로그램 항목이 gateway ensure 가 아니라 모듈 직행이라는 점이었습니다. 최소 수정은 새 선점 프로토콜이 아니라 한 줄입니다.

그리고 제가 적은 검증 명령이 한 번도 실행해 보지 않은 것이었습니다. “프로세스 개수가 1인지 확인한다” 라고 썼는데, venv 런처 스텁 때문에 건강한 단일 워커도 항상 2개로 보입니다(부모는 스레드 1·소켓 0, 실제 연결은 자식이 쥡니다). 그 방법으로 자기 양보를 구현하면 정상 워커가 매 부팅 스스로 죽습니다. 인용한 수치가 틀린 것보다 지어낸 검증 절차가 더 위험합니다 — 전자는 다시 재면 되지만, 후자는 통과했다는 착각을 남깁니다.

남을 심판한 잣대가 커밋 밖에 있었다

git ls-files scripts/lib/ 가 빈 결과를 냅니다. 판정 게이트 5종(check-links.mjs·check-weight.mjs·check-glossary.mjs·collect-ai-issue.mjs·lib/markers.mjs)이 전부 미추적이고, git check-ignore 는 무시 대상이 아니라고 답합니다 — 일부러 제외한 게 아니라 한 번도 커밋된 적이 없습니다. markers.mjs 헤더는 스스로 「이 파일이 정본이다」라 선언하고 재발 이력까지 적어 뒀는데, 그 파일의 git log 는 0건입니다. 정본이라 선언한 파일에 히스토리가 없습니다.

추적되는 deploy.mjs 는 그 다섯을 하드 의존해서 신선한 클론에서는 첫 줄 import 가 즉사합니다. 유출은 안 납니다 — fail-closed 라 배포가 멈출 뿐입니다. 대신 대상의 수확 절반을 「나는 이미 더 나은 걸 갖고 있다」로 죽인 그 근거가, 저장소에서는 재현되지 않습니다. 대상의 병은 게이트 부재였고 제 병은 게이트의 무근거성인데, 뒤가 더 비쌉니다. 없는 게이트는 없다는 걸 누구나 알지만, 있는데 커밋 밖에 있는 게이트는 있다고 믿게 만듭니다.

판정 — 그리고 못 한 것

항목 판정 사유 · 재개 조건
제품 도입 기각 호스트가 없다. 재개 = 이 하네스를 실제로 쓰게 될 때. 그때도 먼저 볼 것은 미해결 P0 셋
크기 게이트 · dedupeKey 이득 0 이미 더 나은 형태로 있다. ⛔ 이 축으로는 다시 열지 않는다
강등 마운트 기각 적용처가 미등록 유물이었다. 재개 = 그 서버를 실제로 배선할 때
부팅 시 자기 양보 채택 시작프로그램 한 줄 + 재시작 경로용 원자적 선점. 검증은 프로세스 개수가 아니라 하트비트 레코드로
경계 문장 이식 보류 단계별 적대검증이 이미 그 역할을 한다. 실효는 「검증 명령은 판정 전에 한 번 실행하고 출력을 붙인다」 쪽
게이트 5종 커밋 1순위 비용 거의 0. 검증 = 임시 클론에서 배포 체인이 실제로 통과하는지

못 한 것을 적어 둡니다. ①이 플러그인을 한 번도 실행해 보지 못했습니다 — 호스트가 없어 설치가 안 되므로 판정은 전부 소스·npm 산출물·이슈 트래커 실측이고 화면을 본 적이 없습니다. 번들 크기는 확실하지만 「기동 시 실제로 무엇이 내려가는가」는 코드로만 확인했습니다. ②외부 설치 리그의 실패 보고는 보강 증거로만 썼습니다 — 헤드리스 부팅으로 재는 리그인데 이 플러그인은 웹 프로필 전용이라 실패가 리그 아티팩트일 수 있습니다. ③생태 플러그인 26건은 세기만 했습니다. 등재 조건이 자기신고식 태그 하나라 그 수는 활동성의 증거가 아닙니다. ④채택한 한 건은 아직 안 고쳤습니다. 이 글은 판정까지입니다.

이 편의 관통선은 이것입니다 — 남의 수치를 의심하는 도구부터, 내 저장소에 실려 있는지 본다. 대상을 심판하는 데 쓴 잣대가 내 쪽에서 재현되지 않으면, 그 심판은 판정이 아니라 인상입니다.