6 SVC 40 COMMITS 686 TESTS

Issue Linker. 흩어진 이슈를, 내 PC의 Claude로 MR 초안까지 잇는다.

Sentry · GitLab · WorxBoard · Notion · Jira · Grafana 이슈를 한 화면에서 보고, 이슈별로 Claude Code에게 코드를 고치게 한 뒤 로컬 커밋 → push · GitLab MR 초안까지. 머지는 사람이 한다. 6주 동안 만든 사내 도구의 구조와 판단을 끝까지 파고듭니다.

LAYER 01 · BRIEF

30초 요약부터

상세는 아래 챕터에 있습니다. 먼저 무엇을, 어떻게, 어디까지 했는지만 짧게.

무엇 · 어떻게 · 어디까지

  • 1한 화면 조회. 6개 서비스의 이슈를 사내 웹 작업함에서 봅니다. 웹은 조회 전용이고 토큰은 PC의 Keychain에만 있습니다.
  • 2이슈별 수동 코드 수정. PC 로컬 서비스가 claude -p를 터미널 CLI와 같은 설정·플러그인·MCP로 실행합니다. 분석(확인만) 모드는 샌드박스로 읽기 전용.
  • 3PR 올리기는 버튼으로. diff 확인 → 로컬 커밋 → 타겟 fetch·머지 커밋 → push·MR 초안. 담당자는 올린 사람, 리뷰어는 지정하지 않고, 머지는 사람이 합니다.

사용법 4단계

  • APC에서 pnpm start → 로컬 설정 화면에서 저장소 폴더·토큰을 슬롯(1~5)에 저장
  • BPC 화면에서 만든 5분짜리 연결 코드를 사내 웹에 입력 (pairing)
  • C이슈 상세 → 작업 대화에서 수정 요청 (이미지 ⌘V, / 스킬 자동완성)
  • DPR 올리기 → 확인 화면마다 버튼 → MR 초안 링크
6주기간09-03 첫 커밋 ~ 10-14
40건커밋 (머지 제외)9월 8 · 10월 32
6개연동 서비스연결 슬롯 3 → 5
686테스트 · 전부 통과node:test 96개 파일 (10-14)
+35,939추가된 줄−3,099줄 · 변경 파일 687
9일치작업 기록 문서docs/state 날짜별 + summary
처리 이슈 건수·절감 시간 같은 정량 효과는 아직 측정 전입니다. 이 문서의 숫자는 저장소 기록(2026-10-14 기준)뿐입니다.

LAYER 02 · ARCHITECTURE

웹은 보기만, 쓰기는 PC에서

사내 웹은 정적 작업함일 뿐이고, 모든 토큰과 실행은 PC의 로컬 서비스(127.0.0.1:43121)에 있습니다. 구조도 위에 렌즈를 대면 같은 자리의 보안 레이어 — 무엇이 어디에 머무는지 — 가 보입니다.

WEB조회 전용 · plain HTML/CSS/JSPCNode 기본 모듈만SECRETKeychain에만
마우스·손가락을 구조도 위에 대면 렌즈가 따라갑니다 · 노드를 누르면 아래에 상세
NODE
—
    근거 코드

    LAYER 03 · ISSUE → MR

    이슈 한 건이 MR이 되기까지

    여덟 정거장. 판단이 필요한 곳은 Claude가, 결정론적인 검사는 앱이, 되돌리기 어려운 쓰기는 사람이 버튼으로 합니다. 노란 점이 사람의 버튼이 있는 정거장입니다.

    —

    TRACE 정거장에서 일어나는 일 (재연)
    1 / 8

    LAYER 04 · MERGE --NO-FF

    rebase 대신 머지 커밋, 충돌은 Claude가 · 검사는 앱이

    PR 올리기는 선택한 타겟을 매번 fetch해 비교하고, 뒤처졌으면 확인 화면의 버튼으로 모든 저장소 공통 merge --no-ff를 합니다. 충돌이 나면 해소 Claude가 공통 기준으로 충돌 파일만 고치고, 앱이 범위·충돌 표시·HEAD를 검사한 뒤에야 머지 커밋을 만듭니다. 시나리오를 골라 한 단계씩 실행해 보세요.

    재연 · 파일 내용과 커밋은 연출용입니다. 검사 순서와 중단 문구는 실제 코드(mr-git.mjs)를 따릅니다.
    READY
    GRAPH feature ← target
    CONFLICT FILE ——
    APP CHECKS 0 / 0
      CONSOLE
      NEEDS-USER · 사용자 판단 필요
      실제 merge 호출 companion/mr-git.mjs · syncTarget
      // 사용자 결정(2026-10-14): 모든 저장소에서 타겟을 항상 머지 커밋으로 받는다
      this.git(root, ['-c', 'rerere.enabled=false',
        '-c', 'merge.autoStash=false',
        'merge', '--no-edit', '--no-autostash',
        '--no-squash', '--no-ff', '--commit',
        '-m', message, fresh.targetHead], 60000)
      // 충돌 → resolveMerge() → 해소 Claude → 앱 검사 → commit
      // push: '--porcelain', '--no-force', ... (다음 화면의 버튼)
      하지 않는 것rebaseresetforce push자동 재시도훅 우회자동 머지

      LAYER 05 · ENGINE SWAP

      Codex에서 Claude Code로, 그리고 CLI와 동치로

      전사 표준이 바뀌어(10-12 설계, 10-13 머지) 실행 엔진을 Claude Code CLI 하나로 바꿨습니다. 처음엔 좁게 묶었다가, 같은 날 사용자 결정 — CLI에서 쓰는 것은 똑같이 쓸 수 있어야 해 — 으로 수정 실행기를 터미널 CLI와 같은 설정·플러그인·MCP로 열었습니다. 대신 분석 모드만 샌드박스로 잠급니다.

      MIGRATION 별도 브랜치 feat/claude-only-migration → master 머지
        claudeArguments(root, mode) companion/issue-runner.mjs

        네 개의 Claude 권한 매트릭스
        역할파일 편집셸셸 네트워크Git 쓰기
        수정 Claude작업 대화 · CLI 동치
        분석 Claude확인만 · 샌드박스
        초안 Claude커밋·MR 문구 · JSON 스키마
        해소 Claudemerge 충돌 파일만
        허용범위 제한없음·차단
        실제 CLI 확인(분석 모드 쓰기 거부·편집·이미지 블록)은 격리 scratch 저장소에서만 했습니다 — 업무 저장소에서는 실행하지 않음

        LAYER 06 · OAUTH MCP

        토큰을 붙여넣던 자리에 로그인 버튼

        Notion과 WorxBoard 모두 공식 OAuth MCP로 옮겼습니다. companion 안에 자체 MCP Streamable HTTP 클라이언트와 PKCE 로그인을 직접 구현했고, CLI·모델 토큰은 쓰지 않습니다. 단계를 따라가 보세요.

        STEP 0 / 8
        대기

        STEP을 누르면 메타데이터 발견부터 시작합니다.

        
                  
        NOTION · 10-13

        자체 MCP 클라이언트 + OAuth(PKCE)

        Codex 전환과 함께 Notion 커넥터를 자체 MCP 클라이언트로 바꿨습니다. 독립 리뷰에서 나온 두 버그 — 끝나지 않는 SSE 스트림에서 응답을 기다리던 문제와 토큰 갱신 경쟁 — 를 TDD로 고쳤습니다(SSE 즉시 처리 · refresh single-flight). 만료되면 Notion 탭의 버튼 한 번으로 재인증합니다.

        WORXBOARD · 10-13

        403에서 회사 계정 로그인으로

        WorxBoard가 9/28에 개인 PAT의 통합 API 접근을 종료해 목록이 403으로 막혔습니다. 같은 OAuth 로그인 흐름을 서비스별 flow로 일반화해 회사 계정 OAuth MCP로 옮겼고, 읽기 도구 tasks.list·tasks.get만 허용합니다.

        기록 시점(10-13) 기준 WorxBoard 동적 등록·로그인·MCP 실조회는 미확인 — 메타데이터 발견만 확인

        LAYER 07 · GRAFANA PANELS

        대시보드에 들어가지 않고 오류 로그까지

        저장한 대시보드의 패널 쿼리를 그대로 실행해 요약 숫자 · 오류 로그 목록 · 오류 추이를 작업함 안에 그립니다. 오류 한 줄을 열면 그대로 이슈 분석으로 넘어가고, 기간 전체를 묶어 일괄 분석(읽기 전용)도 돌릴 수 있습니다.

        재연 · 아래 숫자·경로·로그는 가상 데이터입니다. 화면 구성(기간 탭·유형 버튼·한 줄씩/묶어 보기·막대 필터·민감 필드 숨김)만 실제와 같습니다.
        오류 추이 막대를 누르면 그 구간만
        PIPELINE grafana-panels.mjs
        1. 패널 고르기VictoriaLogs 패널만 역할별로: 요약 = stat, 추이 = 제목에 오류/error가 있는 시계열(최대 2), 목록 = | stats 없는 오류 표 1개.
        2. 변수 치환은 아는 형식만$X · ${X} · :pipe · $__all · __from/__to · $__interval 등. 모르는 형식이 있으면 그 패널은 실행하지 않습니다.
        3. 쿼리 실행1 + 최대 4개를 순차로. 패널별 실패는 skipped에 남기고 나머지는 표시.
        4. 민감 필드부터 버림authorization · cookie · token 계열 필드는 값을 읽기 전에 버리고, 본문의 비밀 키는 [숨김]. URL은 쿼리·해시 제거.
        5. 묶음과 정렬상태·메서드·정규화 경로(ID → {id})로 묶고 줄 수 순(같으면 5xx → 4xx).
        고친 것 둘
        • 오류 목록이 안 보임 — 응답 2.42MB가 2MB 한도를 넘어 조용히 skipped. 오류 목록 쿼리만 한도를 올리고 실패는 리스트 자리에 크게 표시.
        • 4xx가 안 보임 — 5xx 우선 정렬이 미리보기를 채움. 줄 수 순 정렬 + 유형별 버튼.

        LAYER 08 · BOUNDARY

        토큰은 Keychain 밖으로 나가지 않는다

        CORS만으로 인증을 대신하지 않습니다. loopback 바인딩, 일회용 5분 연결 코드, origin·Host 검사, 8시간 세션. 왼쪽 요청을 골라 로컬 서비스가 어떻게 답하는지 보세요.

        응답 문구는 companion/server.mjs의 실제 메시지입니다 · 요청 자체는 재연
        REQUEST 요청을 골라 보내기
        ···요청을 고르세요
        // companion/server.mjs (발췌)
        const safeEqual = (a, b) => typeof a === 'string'
          && Buffer.byteLength(a) === Buffer.byteLength(b)
          && timingSafeEqual(Buffer.from(a), Buffer.from(b));
        sessions.set(token, { role, origin, slot,
          expires: Date.now() + 8 * 60 * 60_000 });   // 8시간
        pairings.set(`${origin}|${slot}`, { code, origin, slot,
          expires: Date.now() + 5 * 60_000, attempts: 0 }); // 5분 · 5회
        토큰 원문은 Keychain에만사내 웹 서버·로그·URL·localStorage에 두지 않습니다. 문서·커밋에도 원문·연결 코드·세션 토큰·credentialRef를 남기지 않는 것이 인계 규칙입니다.
        임의 셸 명령 API 없음웹이 PC에 시킬 수 있는 일은 정해진 조회 API뿐. 코드 수정·커밋·push는 PC 화면에서만 시작됩니다.
        토큰 탐색은 알려진 위치만discovery.mjs가 정한 위치만 봅니다. 셸 설정을 source하거나 히스토리·홈 전체를 뒤지지 않습니다.
        이슈 자료는 신뢰하지 않는다Claude에게 넘기는 본문·댓글·이미지는 <issue-data> 안의 참고 데이터로만 취급하라고 명시합니다. 그 안의 명령·권한 변경·비밀 요청은 지시가 아닙니다.
        쓰기 전에는 사람이 본다로컬 커밋, 타겟 수신, push·MR이 각각 확인 화면 + 버튼. 체크박스 동의는 두지 않고 버튼 자체가 승인입니다.
        외부 쓰기는 요청이 있을 때만야간 실행·자동 시작 등록·GitLab/Notion 쓰기·MR 생성·push·배포를 사용자 요청 없이 시작하지 않습니다.

        LAYER 09 · SHELL

        모든 기록은 터미널로 통한다

        앱의 작업 대화가 amber CRT 셸 바텀시트(you@issue-linker:~$)인 것처럼, 이 문서의 내용도 터미널로 꺼내 볼 수 있습니다. 처음이면 help, 커밋 40건은 git log, 설계 판단은 why 1~why 8.

        시뮬레이션 · 이 터미널은 아무 것도 실행하지 않습니다. 출력은 저장소 기록에서 옮긴 내용입니다.
        you@issue-linker — il-sh
        CLICK TO TYPE · TAB 자동완성 · ↑↓ 기록

        LAYER 10 · HANDOFF

        세션이 끊겨도 기억은 남는다

        에이전트와 함께 일하는 세션은 매번 끊기고, 이전 대화를 기억한다고 가정할 수 없습니다. 그래서 기억을 저장소에 둡니다. 새 세션은 정해진 순서로 읽고, 사실이 바뀌면 지우지 않고 superseded로 남깁니다. 기능은 테스트부터(TDD).

        새 세션의 읽는 순서

        1AGENTS.md작업 경계와 인계 규칙 — 조회 전용 웹, 외부 쓰기 금지, 토큰 취급, 커밋 규칙
        2docs/state/summary.md현재 구현 · 결정 · 미완료 · 재개 순서 (약 1,084줄)
        3docs/state/<날짜>.md날짜별 작업 기록 9일치09-0709-0809-0909-1609-1709-1810-0710-1310-14
        4docs/state/items.yaml원자적 사실 목록. 바뀐 사실은 삭제 대신 status: superseded + superseded_by
        # items.yaml — 실제 항목 (fact는 줄임)
        - id: night-shift-claude-only-migration
          fact: "2026-10-13 실행 엔진을 Codex에서 Claude Code CLI 전용으로 교체 … 수정 acceptEdits · 분석 dontAsk+denyWrite …"
          status: superseded
          superseded_by: night-shift-runner-cli-parity-figma-20261013
        # ↑ 같은 날 "CLI와 같은 설정·플러그인·MCP" 결정으로 대체됨 — 이전 사실은 남는다

        테스트

        Codex 전환 머지 시점553
        2026-10-14686

        node:test 96개 파일 · 686개 테스트 전부 통과(10-14 기록). 서버·프로바이더 단위 테스트에 더해, 격리 Chrome으로 1440px · 500px 두 폭의 브라우저 테스트도 돌립니다. 기능마다 테스트를 먼저 쓰고(TDD), 변경 뒤에는 세 명령을 돌립니다.

        pnpm testpnpm checkpnpm web:build
        • Node 20+ 기본 모듈과 plain HTML/CSS/JS — React 같은 UI 의존성을 들이지 않음
        • pnpm 고정 — 다른 패키지 매니저는 설치 단계에서 거부
        • 실제 업무 저장소에서 검증용 동기화 금지 — CLI 확인은 격리 scratch 저장소에서만
        • companion 변경 = 재시작 필요 — 재시작하면 웹 세션이 만료된다는 사실까지 인계

        LAYER 11 · RUN LOG

        6주의 기록

        막대는 커밋 날짜 기준 커밋 수, 노란 점은 그날의 작업 기록 문서가 있는 날입니다. 막대를 누르면 그날 한 일이 나옵니다. 아래 카드 여덟 장은 이 도구를 지탱하는 설계 판단 — 눌러서 뒤집으면 근거.

        아직 안 한 것 정직하게

        야간 자동 실행 · 예약러너 코어는 이관했지만 웹·서비스에서 트리거하지 않습니다. 이름(Night Shift)의 출발점이었던 기능이 아직 연결 전입니다.
        Sentry 자동 트리아지미이관.
        서명된 배포 설치기미구현. 지금의 pnpm install:local은 개발용 자동 시작 등록입니다.
        일부 실서버 반영 미확인Jira 상태·댓글 등록, WorxBoard OAuth 로그인의 실서버 동작은 일부 확인 전입니다.
        정량 효과 측정처리 이슈 건수 · 시간 절감은 아직 측정하지 않았습니다.