GITHUB 레포 딥다이브 · 2026-08-04 · CHUSPEEISM/DASHI-TASKBOARD · Codex 에이전트를 위한 로컬 우선 칸반 보드

dashi-taskboard 딥다이브
— AI 코딩 에이전트에게 "할 일 목록"을 쥐여주는 칸반 보드

dashi-taskboard(코드 내부 이름 Codex Taskboard)는 브라우저에서 도는 로컬 우선(local-first) 이슈 보드다. 겉보기엔 Linear·Jira를 닮은 평범한 칸반이지만, 진짜 정체는 OpenAI의 Codex 코딩 에이전트가 집어 드는 "작업 큐(task queue)"다. 사람이 카드를 만들어 todo에 꽂아 두면, 에이전트가 그 카드를 집어 in_progress로 옮기고, 코드를 짜고, 검증한 뒤 in_review로 넘긴다.

기술적으로 특이한 점이 두 가지다. 첫째, 덩치가 놀랍도록 작다 — 손으로 짠 Node HTTP 서버 하나가 React UI·JSON API·CLI를 전부 서빙하고, 데이터는 npm 패키지가 아니라 Node 22.5에 내장된 node:sqlite 파일 하나에 담긴다. 런타임 의존성이 단 6개다. 둘째, Codex 데스크톱 앱 안으로 파고든다 — 크롬 개발자 프로토콜(CDP)로 ChatGPT 앱에 보드 패널을 주입해, 마치 원래 있던 기능처럼 사이드바에 끼워 넣는다.

(저장소 chuspeeism/dashi-taskboard · ★ 870 · 포크 56 · 이슈 3 · 언어 JavaScript/TypeScript · 2026-07-24 생성 · v0.1.0 · 런타임 의존성 단 6개 · Node 22.5+ · 라이선스 파일 없음 · TrendShift Daily #4)
목차
  1. 한 줄 정체 (+ 이름의 비밀)
  2. 왜 주목받는가 — 에이전트 시대의 "작업 큐"
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 — 손으로 짠 코어 + 낙관적 동시성
  5. 디렉토리 구조 해부
  6. 학습 포인트 — 기술별 배울 것
  7. 시스템 / 배포 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 로드맵 (주차별)
  10. 핵심 키워드 사전
  11. 참고 링크

1한 줄 정체 (+ 이름의 비밀)

이 프로젝트가 정확히 무엇인지 한 문장으로

dashi-taskboard는 "Codex 같은 AI 코딩 에이전트에게 일을 나눠 주고 진행 상황을 추적하는, SQLite 파일 하나로 도는 로컬 우선 칸반 보드"다. 브라우저에서 열리는 이슈 보드 UI, 그 UI와 CLI를 동시에 먹이는 HTTP API, 그리고 에이전트에게 "이 보드를 이렇게 다뤄라"를 가르치는 Codex 스킬(Skill)이 한 저장소에 들어 있다. 핵심 단위는 "카드(이슈)"이고, 카드마다 담당자를 사람으로도 에이전트로도 지정할 수 있다.

먼저 짚고 갈 것
이름이 두 개다 — 저장소는 dashi, 코드는 codex-taskboard

이 저장소의 GitHub 주소는 chuspeeism/dashi-taskboard지만, 정작 코드 안에는 "dashi"라는 단어가 한 번도 나오지 않는다. package.json의 이름은 codex-taskboard, 원저자 서명은 jadon7, 배포 도메인은 taskboard.dsxzai.com이다. 즉 이 레포는 Codex Taskboard라는 상류 프로젝트를 그대로 복제·개명해 올린 미러에 가깝다(GitHub의 Fork 버튼이 아니라 순수 clone). 그래서 이 문서는 내용상 "Codex Taskboard"를 설명하되, 저장소 별칭이 dashi-taskboard임을 함께 표기한다.

한 장의 비유

"신입에게 주는 화이트보드 — 그런데 그 신입이 AI다"

사무실에 새 인턴이 왔다고 하자. 당신은 화이트보드에 포스트잇을 붙인다: "로그인 버그 고치기", "README 다듬기". 인턴은 포스트잇 하나를 떼서 "진행 중" 칸으로 옮기고, 일을 하고, 끝나면 "검토 요청" 칸에 붙인다. 당신이 확인하고 OK 하면 그제서야 "완료"로 간다.

dashi-taskboard가 하는 일이 딱 이것이다. 다만 포스트잇은 SQLite 행(row)이고, 인턴은 Codex 에이전트이며, 칸반 카드 하나하나가 곧 에이전트에게 던지는 작업 지시서가 된다. 사람과 에이전트가 같은 보드를 공유하되, "누가 무엇을 언제 어떤 버전에서 집었는지"가 전부 기록으로 남는다.

용어
로컬 우선 (local-first)
데이터가 남의 클라우드가 아니라 내 컴퓨터에 먼저 있는 설계. dashi-taskboard는 기본적으로 127.0.0.1:47823에서 돌고 데이터는 .data/taskboard.sqlite 파일 하나에 쌓인다. 계정 가입도, 외부 서버도 필요 없다. 원하면 나중에 같은 데이터를 LAN이나 Cloudflare로 확장할 수 있지만, 출발점은 "내 노트북 안"이다.
용어
Codex / 코딩 에이전트
OpenAI가 만든 터미널·데스크톱용 AI 코딩 에이전트(codex 명령/앱). 사람 대신 코드를 읽고 고치고 명령을 실행한다. dashi-taskboard는 이 Codex를 "실행기"로 삼아, 보드의 카드를 Codex에게 넘겨 처리하게 한다. Claude Code·Cursor 같은 다른 에이전트와 같은 계열이다.

사용자가 카드 하나를 만들어 에이전트에게 맡겼을 때, dashi-taskboard 안에서 벌어지는 흐름을 압축하면 이렇다:

사람: 카드 생성 "결제 모듈 리팩터링" → todo 칸에 배치, 담당=에이전트 │ ▼ ① taskctl / UI 가 /api/tasks 로 카드 저장 (SQLite, version=1) ② Codex 스킬이 카드를 읽음 → 상태를 in_progress 로 이동 └ 이때 "내가 읽은 version=1" 을 함께 보냄 (낙관적 잠금) ③ 에이전트가 코드 작성 · 명령 실행 (전용 Git 브랜치/워크트리에서) ④ 진행 메모를 comment 로 남기고, 끝나면 in_review 로 이동 ⑤ SSE(server-sent events)로 열려 있는 모든 브라우저에 즉시 방송 ⑥ 사람이 검토 → "완료 처리해" 라고 명시적으로 승인해야 done 으로 감

여기서 이미 이 프로젝트의 성격이 드러난다 — 화려한 AI 마법이 아니라, 여러 주체(사람·에이전트)가 같은 데이터를 어긋남 없이 다루게 하는 "규율"이 핵심이다. 그 규율의 정체는 4장에서 자세히 본다.

2왜 주목받는가 — 에이전트 시대의 "작업 큐"

공개 며칠 만에 ★870, TrendShift Daily #4에 오른 이유

타이밍이 절묘하다. 2026년 중반은 "일하는 AI 에이전트(agent for work)"가 최대 격전지가 된 시점이다. 같은 TrendShift 목록에 코딩 에이전트 boldsoftware/meat, 조직용 에이전트 하네스 yc-software/qm, AI 오피스 genspark-ai/genoffice가 나란히 올라와 있다. 이 흐름에서 dashi-taskboard의 각도는 뚜렷하다 — 에이전트를 더 똑똑하게 만드는 게 아니라, 이미 있는 에이전트(Codex)에게 "무슨 일을 어떤 순서로 할지" 대주는 관제탑을 만든다. 에이전트가 늘어날수록 "그 에이전트들에게 일을 나눠 주고 결과를 추적하는 판"의 수요가 커진다는 데 베팅한 셈이다.

둘째 이유는 군더더기 없는 구현이다. 런타임 의존성이 단 6개, 서버는 프레임워크 없이 Node 표준 라이브러리만으로 손수 짰다. 웹 서버(Express)도, SQLite 드라이버(better-sqlite3)도, ORM도, 상태관리 라이브러리(Redux)도 없다. 그래서 코드베이스 전체(약 2.8만 줄)를 처음부터 끝까지 읽어 "이슈 트래커가 실제로 어떻게 작동하는가"를 통째로 이해할 수 있다. 배우기용 교재로도, 포크해서 내 입맛대로 고치기에도 이상적이다.

비유

기성품 자동차 대신, 부품이 다 보이는 키트카. Jira·Linear는 잘 만든 완성차다 — 편하지만 보닛을 열어도 뭐가 뭔지 모른다. dashi-taskboard는 엔진·배선이 그대로 드러난 키트카에 가깝다. 프레임워크라는 "이미 조립된 블랙박스"를 걷어내고 Node 기본기만 썼기 때문에, HTTP 라우팅부터 실시간 동기화까지 모든 부품을 눈으로 따라갈 수 있다. 대신 "시동 걸면 끝"인 편함은 일부 포기한다.

비슷한 것들과 뭐가 다른가

항목Jira / LinearTrello / 칸반 앱그냥 Codex CLI이 레포(dashi-taskboard)
호스팅벤더 SaaS대개 SaaS로컬로컬 우선 (SQLite 1개)
담당자사람사람-사람 또는 AI 에이전트
에이전트 연동플러그인·API거의 없음보드 없음내장 스킬 + CDP 주입
동시 작업 안전장치서버 락제한적없음낙관적 버전 잠금
워크플로 자동화유료 기능제한적없음React Flow 시각 빌더
덩치초대형중형-의존성 6개 · 손수 구현

셋째 이유는 "에이전트 앱 속으로 파고드는" 기법의 신선함이다. dashi-taskboard는 Codex 데스크톱 앱(macOS의 ChatGPT.app)을 크롬 개발자 프로토콜(CDP)로 붙잡아, 보드 패널을 그 앱 사이드바에 네이티브처럼 끼워 넣는다. 앱 설치 파일(app.asar)은 건드리지 않고, 실행 중인 앱에 스크립트만 주입하는 방식이다. "AI 앱을 내가 원하는 도구로 확장한다"는 발상 자체가 개발자들의 눈길을 끈다(자세한 원리는 4장).

균형 잡기
읽기 전에 알아둘 것 — v0.1.0, 라이선스 공백, 그리고 macOS 편향

분석 시점 기준 이 레포는 공개 2주 남짓, 버전 0.1.0이다. 커밋은 사실상 한 덩어리로 눌러 담겨(squash) 있고, API·스키마는 아직 얼마든지 바뀔 수 있다. 지금 실무에 얹는 건 얼리어답터의 영역이다.

또 하나 중요한 공백: LICENSE 파일이 없다. 공개 저장소지만 명시된 오픈소스 라이선스가 없으면 법적으로는 "모든 권리 보유(all rights reserved)" — 마음대로 재사용·재배포하기 전에 반드시 확인이 필요하다. 그리고 Codex 주입 기능은 macOS의 ChatGPT.app을 전제로 한다. 보드 자체(웹 UI·CLI·API)는 OS 무관하게 돌지만, "앱에 꽂는" 킬러 기능은 맥 사용자에게 치우쳐 있다.

3기술 스택 전체 지도

package.json·소스·wrangler.jsonc에서 읽어낸 실제 구성

스택이 인상적으로 얇다. 서버는 빌드 스텝조차 없이 .mjs(순수 ESM)를 Node가 직접 실행하고, 프론트만 Vite로 번들한다. "검증된 최신 조각을 최소한으로"라는 취향이 뚜렷하다.

① 백엔드 — 프레임워크 0, Node 표준만

② 프론트엔드 — React 19 + React Flow

패키지버전역할
react / react-dom^19.2보드 UI 전체. 상태관리 라이브러리 없이 훅만 사용
@xyflow/react^12.11React Flow — 워크플로 자동화 캔버스(노드·엣지 그래프)
react-markdown + remark-gfm^10 / ^4이슈 설명·댓글의 마크다운 렌더(GFM 지원)
@lobehub/icons-static-svg^1.94AI 공급자·브랜드 아이콘 세트
런타임 의존성은 이 6개가 전부다.

③ 빌드 · 클라우드 · 개발 도구 (devDependencies)

감상 포인트
"의존성을 늘리지 않는다"는 것도 하나의 설계 사상

요즘 웹 프로젝트는 npm install 한 번에 수백 개 패키지가 딸려 온다. dashi-taskboard는 정반대로 간다 — SQLite도 HTTP 서버도 실시간 채널도 마이그레이션도 전부 손으로 짰다. 덕분에 공급망 위험(supply-chain)이 작고, 업그레이드에 덜 휘둘리며, 무엇보다 "이 코드가 실제로 무슨 일을 하는지"가 전부 이 레포 안에 있다. 배우는 사람에게는 이게 가장 큰 선물이다.

4아키텍처 심화 — 손으로 짠 코어 + 낙관적 동시성

소스를 직접 읽고 재구성한 구조

전체를 한 문장으로 요약하면 "하나의 로컬 서비스가 여러 표면(UI·CLI·주입 패널)을 먹이고, 데이터는 SQLite 파일 하나에 두며, 여러 주체가 버전 잠금으로 안전하게 공유한다"이다. 그림으로 보면:

┌───────────── 표면(같은 HTTP API를 쓰는 세 입구) ─────────────┐ │ 브라우저 보드 UI taskctl CLI Codex 앱 주입 패널 │ │ (React 19 + Flow) (스킬이 호출) (CDP로 iframe 삽입) │ └──────────┬───────────────┬────────────────────┬─────────────┘ └───────────────┴──── HTTP + SSE ─────┘ │ ┌─────────────────────▼─────────────────────┐ │ 로컬 서비스 server/app.mjs │ │ 정규식 라우터 · SSE 방송 · 정적 파일 │ │ ▲ ▲ │ │ ▼ ▼ │ │ node:sqlite child_process │ │ (.data/*.sqlite) codex 실행/스트림 │ └────────┬────────────────────────┬──────────┘ │ │ ┌───────────▼──────────┐ ┌──────────▼───────────────┐ │ SQLite (로컬 기본) │ │ (선택) Cloudflare 클라우드│ │ tasks · comments │ │ Worker + D1 + R2 │ │ relations · workflow│ │ = 2인 공유 배포 │ │ global_revision │ │ Basic 인증 │ └──────────────────────┘ └──────────────────────────┘

핵심 패턴 ① — 낙관적 동시성(optimistic concurrency)

이 레포에서 가장 중요한 설계다. 모든 카드(task)와 댓글에는 version이라는 정수가 붙어 있다. 무언가를 바꾸려면 "내가 방금 읽은 버전이 N이다"를 함께 보내야 하고(If-Version 헤더 / --if-version 플래그), 그 사이 누군가 먼저 바꿔 버전이 올라갔으면 충돌로 거절된다. 여러 Codex 에이전트가 동시에 돌아도 같은 카드를 둘이 집는 사고가 구조적으로 막힌다. 스킬은 에이전트에게 "todo 카드를 정확한 버전과 함께 집어 in_progress로 옮겨라"를 규칙으로 가르친다.

용어
낙관적 잠금 vs 비관적 잠금
비관적은 "먼저 자물쇠를 걸고" 작업한다(다른 사람 대기). 낙관적은 자물쇠 없이 각자 진행하다가, 저장하는 순간 "그새 누가 바꿨나?"만 확인한다. 충돌이 드물면 낙관적이 훨씬 빠르고 단순하다. dashi-taskboard는 version 숫자 하나로 이걸 구현한다 — 버전이 안 맞으면 저장 거절.

핵심 패턴 ② — global_revision 트리거로 만든 실시간 동기화

데이터베이스에 global_revision이라는 단 한 줄짜리 카운터 테이블이 있다. 그리고 모든 테이블에 "INSERT/UPDATE/DELETE가 일어나면 이 카운터를 +1" 하는 SQL 트리거가 걸려 있다. 로컬 모드에서는 SSE로 변경을 즉시 방송하고, 클라우드 모드에서는 브라우저가 2초마다 "리비전 번호 바뀌었어?"만 물어보고, 바뀌었을 때만 새로고침한다. "무엇이 바뀌었는지"를 일일이 추적하는 대신 "뭔가 바뀌었다"는 신호 하나로 동기화를 단순하게 만든 영리한 트릭이다.

핵심 패턴 ③ — CDP 주입: 남의 앱에 내 패널 꽂기

scripts/codex-injector.mjs(약 1,335줄)가 이 레포의 화제성을 만든 기법이다. 순서는 이렇다:

① ChatGPT.app 을 --remote-debugging-port 옵션으로 실행 (크롬 개발자 프로토콜=CDP 포트를 연다) ② /json/list 로 앱 안의 렌더러(창)들을 찾는다 ③ Page.setBypassCSP 로 보안정책(CSP)을 잠깐 무력화 (안 그러면 외부 iframe 삽입이 막힌다) ④ addScriptToEvaluateOnNewDocument 로 사용자 스크립트를 문서 시작 시점에 주입 → 사이드바에 Taskboard 항목 추가 ⑤ Runtime.addBinding 으로 앱↔패널 양방향 통신 다리를 놓음 ("대화에서 열기" → 네이티브 작성창에 $manage-taskboard ID 채움)

중요한 절제: 이 도구는 앱 설치 파일을 수정하지 않고(app.asar 무손상), React를 패치하거나 fetch를 가로채지도 않는다. 실행 중인 앱에 스크립트를 얹을 뿐이다. README는 동시에 경고한다 — CDP 포트는 같은 컴퓨터의 다른 프로세스에 인증 없이 열리므로, 주입이 켜져 있는 동안엔 신뢰할 수 있는 로컬 코드만 돌리라고.

용어
CDP (Chrome DevTools Protocol)
크롬(및 Electron 앱)을 바깥에서 원격 조종하는 프로토콜. 개발자도구가 브라우저와 대화할 때 쓰는 그 통로다. 페이지 로드, 스크립트 주입, CSP 우회 같은 걸 코드로 시킬 수 있다. ChatGPT.app이 Electron(=크롬 기반)이라서 이 방법이 통한다. Puppeteer·Playwright도 내부적으로 CDP를 쓴다.

핵심 패턴 ④ — 내장 AI 채팅이 진짜 codex를 실행한다

보드 안에서 AI와 대화하면, 그 뒤에서 server/ai-chat-process.mjs가 실제 codex 실행 파일을 자식 프로세스로 띄운다(codex resume <스레드ID> 등). Codex가 뱉는 스트리밍 JSON(에이전트 메시지·명령 실행·파일 변경·MCP 도구 호출·웹 검색 등)을 파싱해 다시 SSE 이벤트로 UI에 흘려 보낸다. 샌드박스 수위는 read-only / workspace-write / danger-full-access 셋으로 고른다. "스레드당 실행 중 턴은 하나뿐"을 DB 부분 유니크 인덱스로 강제한다.

세 가지 배포 모드

모드저장소인증쓰임
로컬(기본)node:sqlite 파일없음(127.0.0.1)혼자 · 개발
LAN 공유같은 SQLite없음(신뢰 네트워크 가정)같은 사무실 팀
CloudflareD1 + R2HTTPS Basic(공유 비번)2인 원격 협업

클라우드 워커(cloud/src/index.mjs, 약 2,196줄)는 로컬과 같은 API를 D1·R2 위에 다시 구현한다. 인증은 공유 비밀번호를 SHA-256으로 해시해 비교하는 Basic 인증이고, 사용자명은 "표시용 이름"일 뿐 신원 검증은 아니다. 그리고 클라우드는 절대 로컬 SQLite에 이중 기록하지 않는다 — 각 기기의 프로젝트 경로 매핑은 로컬에만 남는다.

항상 기억할 것
LAN 모드에는 인증이 없다 — "신뢰 네트워크"를 실제로 신뢰할 수 있을 때만

npm start는 로컬 URL과 함께 LAN URL도 찍어 준다. 같은 네트워크의 동료가 그 주소로 보드를 읽고 쓸 수 있다는 뜻인데, 여기엔 계정 인증이 전혀 없다. 카페 와이파이나 공용 네트워크에서 이걸 열면 아무나 보드를 만질 수 있다. 공개 인터넷·클라우드로 나갈 때는 반드시 인증 경계(Cloudflare Basic 인증 등)를 두라고 README가 못 박는다.

5디렉토리 구조 해부

git clone 직후 보이는 것들 (약 2.8만 줄 · 테스트 38개)
codex-taskboard/ (저장소 이름은 dashi-taskboard) ├── package.json / wrangler.jsonc Node 22.5 · 의존성 6개 · Cloudflare 설정 ├── README.md / AGENTS.md 소개 + 에이전트 개발 규율(작게 고쳐라) ├── server/ ★ 로컬 Node 서비스 (프레임워크 0) │ ├── index.mjs 진입점: 서버 리슨 + 로컬·LAN URL 출력 │ ├── app.mjs (2087) 전체 HTTP 라우터 + SSE + 정적 서빙 │ ├── database.mjs(1586) node:sqlite 데이터층 + 자체 마이그레이션 │ ├── ai-chat*.mjs codex 자식 프로세스 실행·스트리밍 │ └── cloud-proxy.mjs 로컬 companion → 클라우드 워커 프록시 ├── cli/taskctl.mjs (855) 이슈·댓글·프로젝트·클라우드 CLI ├── shared/ ★ 서버·CLI·주입·웹이 공유하는 순수 로직 │ ├── domain.mjs 상태·우선순위 상수 │ ├── workflow-sequence.mjs 워크플로 단계 위상정렬 │ └── workflow-control-flow.mjs(723) 트리거·조건 분기 정의 ├── web/ ★ Vite + React 19 프론트 (TypeScript) │ ├── src/App.tsx (2357) 보드 최상위 — 이슈뷰 + 워크플로뷰 │ ├── src/api.ts 타입 붙은 fetch 클라이언트 + EventSource │ ├── src/types.ts 도메인 타입 전체 │ └── src/components/ 30+ 컴포넌트 (TaskCard·WorkflowBoard·AiChat…) ├── cloud/ ★ Cloudflare Worker 배포 │ ├── src/index.mjs (2196) D1+R2 위에 같은 API 재구현 │ └── migrations/0001_initial.sql D1 스키마(권위 데이터모델) ├── inject/codex-taskboard.user.js(1276) 앱에 주입되는 사용자 스크립트 ├── scripts/codex-injector.mjs(1335) CDP 실행·주입기 ├── skills/manage-taskboard/ ★ 번들된 Codex 스킬 │ └── SKILL.md · references/cli.md · agents/openai.yaml └── test/ node:test 38개 (server·cli·cloud·workflow·ai-chat…)

구조가 곧 아키텍처다. 이 트리를 위에서 아래로 훑는 것만으로 "로컬 서비스(server) → 프론트(web) → 클라우드 재구현(cloud) → 에이전트 연동(skills·inject)"의 네 세계가 또렷이 갈린다. 특히 shared/에 순수 로직을 몰아넣어 서버·CLI·주입 스크립트·웹이 같은 규칙을 공유하게 한 점, 그리고 클라우드 데이터모델의 "정본"을 cloud/migrations/0001_initial.sql 하나로 못박은 점이 눈에 띈다.

데이터모델 — SQL 스키마가 곧 도메인 사전

이 프로젝트가 무엇을 다루는지는 테이블 정의에 다 적혀 있다:

테이블핵심 컬럼 · 역할
projectsid · name · workspace_path(로컬 경로) · next_task_number
tasksidentifier(PROJ-12) · status(backlog…done) · priority · labels(JSON) · sort_order(실수, 드래그 정렬용) · thread_id(Codex) · creator/assignee(사람|에이전트) · version(낙관적 잠금)
task_relationsparent·blocks·related. 부모 순환을 막는 재귀 트리거 + "부모는 하나만" 유니크 인덱스
comments / attachments댓글 · 첨부(바이트는 로컬은 파일, 클라우드는 R2)
workflow_workspaces프로젝트별 React Flow 그래프(JSON, 버전 관리)
global_revision한 줄 카운터. 모든 테이블 변경 트리거가 +1 → 동기화 신호

6학습 포인트 — 기술별 배울 것

이 레포에서 뽑아 갈 수 있는 다섯 가지
배울 것 ①

프레임워크 없이 "이슈 트래커 한 채"를 통째로 짜보기

Express도 ORM도 없이 node:sqlite + Node http만으로 라우팅·SSE·마이그레이션·정적 서빙을 손수 구현한 코드다. 프레임워크라는 블랙박스 없이 이슈 트래커 한 채를 처음부터 끝까지 읽어낼 수 있다는 게 이 레포의 가장 큰 교육적 가치. server/app.mjs 한 파일을 정독하면 "웹 백엔드가 실제로 무슨 일을 하는가"의 밑바닥이 보인다.

배울 것 ②

낙관적 동시성으로 다중 주체를 조율하는 법

version 정수 하나로 "여러 에이전트·사람이 같은 데이터를 안전하게 나눠 쓰기"를 구현한 실전 사례. database.mjs에서 버전 체크 → 충돌 반환 흐름을 따라가고, 스킬이 에이전트에게 이 규칙을 어떻게 강제하는지 보면, 분산·협업 시스템의 핵심인 동시성 제어를 가장 단순한 형태로 익힐 수 있다.

배울 것 ③

CDP로 데스크톱(Electron) 앱을 확장하는 법

scripts/codex-injector.mjs는 크롬 개발자 프로토콜의 실전 교과서다. 포트 열기 → 타깃 찾기 → CSP 우회 → 문서 시작 스크립트 주입 → 호스트 바인딩으로 양방향 통신까지. Puppeteer·Playwright의 원리를 밑바닥에서 이해하고 싶거나, "설치 파일을 건드리지 않고 앱을 확장"하는 기법이 궁금하다면 최고의 예제다.

배울 것 ④

React Flow로 시각적 워크플로 빌더 만들기

트리거(이슈·RSS·PR·Git 상태) → 조건 분기 → AI 에이전트 단계(계획·리뷰·테스트) → 미디어 생성·배포 노드로 이어지는 Zapier/n8n 스타일 자동화 캔버스를 @xyflow/react로 구현했다. workflow-sequence.mjs(노드 위치·엣지 → 위상정렬)와 workflow-control-flow.mjs(중첩 조건 분기)를 읽으면 "노드 그래프를 실행 순서로 바꾸는" 컴파일 로직을 배운다.

배울 것 ⑤

같은 API를 로컬 SQLite와 서버리스 D1에 "이식"하는 법

로컬 서비스(server/)와 클라우드 워커(cloud/)가 동일한 API를 서로 다른 저장소(node:sqlite vs Cloudflare D1+R2) 위에 두 번 구현했다. 두 코드를 나란히 비교하면, 데이터 접근 계층을 어떻게 추상화해야 "온프레미스 ↔ 서버리스"를 오갈 수 있는지가 손에 잡힌다. global_revision 기반 동기화가 두 세계에서 각각 SSE·폴링으로 갈라지는 지점도 좋은 관전 포인트.

7시스템 / 배포 요구사항

README·package.json·wrangler.jsonc 기준
구성요소요구비고
런타임Node ≥ 22.5내장 node:sqlite 때문에 하한이 높다. 순수 ESM
로컬 DBSQLite 파일.data/taskboard.sqlite 자동 생성. 별도 설치 불필요
포트47823(서비스) · 5173(dev UI)CODEX_TASKBOARD_PORT로 변경 가능
Codex 주입(선택)macOS ChatGPT.app + CDP 포트Codex 26.715.52143 기준. 보드 자체는 OS 무관
클라우드(선택)Cloudflare 계정Workers + D1 + R2. wrangler로 배포·마이그레이션
환경변수HOST · PORT · DATA_DIR · URLHOST=127.0.0.1로 LAN 접근 차단 가능

로컬 실행 (가장 단순한 경로)

# 설치 → 빌드 → 실행
npm install
npm run build
npm start                 # http://127.0.0.1:47823

# 개발 모드(프론트 핫리로드, API 프록시)
npm run dev               # UI :5173 → /api 는 로컬 서비스로

# CLI 로 프로젝트·이슈 만들기
npm run taskctl -- project create --id my-proj --name "내 프로젝트" \
  --workspace-path /절대/경로/저장소
npm run taskctl -- issue create --project my-proj \
  --title "다음 작업" --status todo --priority high

# 전체 검증(타입체크 + 프론트 빌드 + 테스트)
npm run check

8직접 해볼 수 있는 실습 과제

쉬운 것부터 — 읽기 → 실행 → 확장 순
과제 1 난이도 ●○○○○

clone → README·AGENTS 정독 → 보드 띄우기

git clone --depth 1npm install && npm run build && npm start127.0.0.1:47823을 열어 카드 몇 개를 만들어 보자. AGENTS.md의 "가장 작은 변경을 하라, 확인 전엔 방어코드·테스트를 미리 넣지 말라"는 규율이 왜 나왔을지 생각해 보는 게 관전 포인트.

과제 2 난이도 ●●○○○

taskctl CLI로 만들고, SQLite 파일을 직접 열어보기

taskctl로 프로젝트·이슈를 만든 뒤 .data/taskboard.sqlitesqlite3로 열어 tasks 테이블을 조회해 보자. UI·CLI·DB가 같은 데이터를 본다는 걸 눈으로 확인하고, version·sort_order 컬럼이 어떻게 채워지는지 살핀다.

과제 3 난이도 ●●●○○

낙관적 잠금 충돌을 일부러 재현하기

같은 카드를 version=N으로 두 번 수정 요청하되, 첫 요청이 성공해 버전이 올라간 뒤 두 번째를 보내 충돌 거절을 받아 보자. server/database.mjsapp.mjs에서 버전 비교 코드를 찾아, "어디서 막히는지"를 실제 라인으로 짚는다.

과제 4 난이도 ●●●●○

워크플로 노드 하나를 추가하거나, 스킬로 카드 맡기기

(A) web/src/components/workflowCatalog.ts에 커스텀 노드 타입을 하나 더 정의해 캔버스에 띄워 보거나, (B) skills/manage-taskboard를 Codex 스킬 디렉토리에 심볼릭 링크하고 카드 하나를 에이전트에게 맡겨, in_progress → in_review 이동이 규칙대로 일어나는지 관찰한다.

과제 5 난이도 ●●●●●

Cloudflare에 D1+R2로 배포하고 데이터 옮기기

wrangler로 D1 데이터베이스·R2 버킷을 만들고 cloud:migrate로 스키마를 올린 뒤 cloud:deploy. 그다음 migrate-to-cloud로 로컬 SQLite 데이터를 클라우드로 옮겨 본다. 로컬과 클라우드가 같은 API·다른 저장소임을 두 코드로 비교하는 게 최종 목표 — 반드시 테스트용 계정에서.

9관련 기술 심화 로드맵 (주차별)

이 레포를 계기로 공부 계획을 짠다면
주차주제구체적으로
1주Node 밑바닥 백엔드node:sqlite, http 모듈 라우팅, SSE, WAL·트랜잭션. 프레임워크 없이 CRUD 서버 만들어 보기
2주동시성·실시간 동기화낙관적 vs 비관적 잠금, 버전 벡터, SSE vs 폴링 vs WebSocket, 리비전 카운터 패턴
3주React 19 + React Flow훅 기반 상태, 노드/엣지 그래프 UI, 위상정렬로 실행 순서 만들기, 프랙셔널 인덱싱 드래그 정렬
4주CDP · Electron 확장Chrome DevTools Protocol, Puppeteer/Playwright, CSP, document-start 주입, 앱 자동화 윤리·보안
5주(선택)서버리스 이식Cloudflare Workers·D1·R2, wrangler·miniflare, 온프레미스 API를 서버리스로 옮길 때의 트레이드오프

10핵심 키워드 사전

이 문서에 나온 개념 압축 정리
용어
로컬 우선 (local-first)
데이터가 내 컴퓨터에 먼저 있는 설계. 기본은 127.0.0.1 + .data/taskboard.sqlite 파일 하나. 계정·외부 서버 없이 즉시 동작한다.
용어
node:sqlite
Node 22.5부터 표준 내장된 동기식 SQLite 모듈. better-sqlite3 같은 네이티브 패키지 없이 import만으로 DB를 쓴다. 이 레포가 Node 22.5+를 요구하는 이유.
용어
낙관적 동시성 / version
자물쇠 없이 각자 진행하다 저장 순간에만 "그새 바뀌었나?"를 version 숫자로 확인하는 방식. 안 맞으면 저장 거절. 다중 에이전트가 같은 카드를 겹쳐 집는 걸 막는다.
용어
SSE (Server-Sent Events)
서버가 클라이언트로 한 방향 이벤트를 계속 흘려 보내는 표준 기술(text/event-stream). WebSocket보다 단순하다. 로컬 모드에서 보드 변경을 즉시 방송하는 데 쓴다.
용어
global_revision 트리거
한 줄짜리 카운터 테이블 + 모든 테이블의 변경 트리거(+1). "뭔가 바뀌었다"는 신호 하나로 동기화를 단순화한다. 클라우드는 이 번호만 2초마다 폴링.
용어
CDP 주입 (Chrome DevTools Protocol)
실행 중인 크롬/Electron 앱을 바깥에서 조종하는 프로토콜. dashi-taskboard는 이걸로 ChatGPT.app에 CSP 우회 + 문서시작 스크립트를 얹어 보드 패널을 주입한다(앱 파일은 무손상).
용어
React Flow (@xyflow/react)
노드·엣지로 그래프 UI를 만드는 React 라이브러리. 이 레포의 워크플로 자동화 캔버스(트리거→조건→에이전트·미디어·배포 노드)가 이 위에 얹혀 있다.
용어
taskctl / Codex 스킬
taskctl은 보드를 조작하는 CLI. skills/manage-taskboard는 Codex에게 "이 CLI로 카드를 집고 상태를 옮기고 검증하라"를 가르치는 스킬 문서다. 에이전트가 보드를 다루는 계약.
용어
브랜치 / 워크트리 바인딩
카드마다 Git 브랜치 하나 또는 워크트리 하나를 묶을 수 있다. 여러 에이전트가 서로 다른 작업 디렉토리에서 병렬로 일하되 충돌하지 않게 하는 장치.
용어
D1 / R2 (Cloudflare)
D1 = Cloudflare의 서버리스 SQLite, R2 = S3 호환 오브젝트 스토리지(첨부 바이트 저장). 클라우드 모드에서 로컬 SQLite·파일을 대체하지만, API 표면은 동일하다.
용어
프랙셔널 정렬 (sort_order REAL)
카드 순서를 정수가 아닌 실수로 저장해, 두 카드 사이에 끼워 넣을 때 그 사이 값(예: 1과 2 사이 1.5)만 갱신하면 되는 기법. 드래그 재정렬을 값 하나 수정으로 끝낸다.

11참고 링크

더 파고들 때