파이프라인 3단계(Planning) 산출물 · 2026-09-16 · v1.1(Design 반영 2026-09-16) — Analysis Gate PASS 후 Design 리뷰가 요구한 부연 5건(FR-206 · FR-302 · FR-406 · FR-501 · FR-505 · FR-606)을 추가했다. Seed 를 뒤집지 않는 elaboration 이며 결정 일지 D-019 에 기록
근거: docs/seed-spec-fullstack-code-editor.md v1.1.1 LOCKED(hash 79991d04, Specification Gate PASS) · docs/planning-inputs-fullstack-code-editor.md · docs/discovery-fullstack-code-editor.md · docs/decision-log-fullstack-code-editor.md(D-001~D-018) · docs/spec-evaluation-fullstack-code-editor.md round table(패널 지적 16건)
이 문서의 지위
Seed Spec 이 잠긴 상태이므로 이 PRD 가 인용하는 C-nn · AC-nn 은 확정 계약이다. PRD 는 Seed 를 부연하며 뒤집지 않는다 — 어긋나면 Seed 가 이긴다. 상위 파이프라인 항목은 cocode/AC-03 처럼 접두어를 붙인다.
두 가지를 먼저 밝힌다.
- A-01(풀스택 = Dart 풀스택)은 오너의 개별 확답 없이 "잠정 결정"으로 잠겼다(Seed Metadata 잠금 근거 · D-014). Breakdown 이 ZenHub 이슈를 만들기 전에 Q1 개별 확답을 받는다(OBJ-1-PM-3).
- 상위 파이프라인 결정 2건이 하드 의존이다 — D-013(롤백 사전검사 체인 불연속 조건, AC-22 · FR-703)과 D-018(기존 로컬 폴더 열기, 상위 UX-D-30 미결). 둘 다 Development 착수 전 확정돼야 한다.
수치 규칙
요구사항 수치는 Seed §4 "수치의 출처"와 planning-inputs §3 실측에 근거가 있는 것만 쓴다. 근거 없는 값은 쓰지 않고 §10 미정값 표에 규칙만 둔다. 스토리 포인트(§6)는 요구사항 값이 아니라 추정치이며 Breakdown 이 재산정한다.
1. 제품 개요 · 목표 · 범위 · 비즈니스 모델 (Product Brief)
1.1 제품 개요
무엇인가
cocode ADE(Dart/Flutter 전용 데스크톱 Agent Development Environment)의 개발자 표면 — 사람이 직접 코드를 편집 · 실행 · 검사하는 창. Flutter 클라이언트 · Serverpod 서버 · Jaspr 웹 · PostgreSQL 로 이루어진 Dart 풀스택 워크스페이스(Seed §0)를 한 창에서 다룬다. 같은 창에서 에이전트에게 맡긴 편집을 검토 · 되돌리는 ADE 의 위임 표면(상위 Project #2 로 착지)과 나란히 산다.
어떤 부재를 메우는가 (Seed §1 Core Problem)
- cocode ADE 앱 셸(
app/cocode/lib/main.dart, PR #289)의 editorStack · terminal 슬롯은 ColoredBox 플레이스홀더이고 editor · terminal · lsp 는 앱에 연결되지 않았다(Discovery §1.3). 상위 cocode/AC-03 은 이 표면을 3-OS MVP 출시 조건으로 둔다 — 이 표면 없이 ADE 는 출시되지 않는다.
- Dart 서버(Serverpod)를 1급으로 두는 에디터는 시장에 없다(Discovery §2.1). Flutter 를 1급 템플릿으로 뒀던 Firebase Studio 는 2027-03-22 종료가 공지됐다(Discovery §2.2).
왜 기존 도구로 안 되는가
| 대안 |
한계 |
출처 |
| VS Code + Dart-Code |
서버 · DB 는 외부 도구, 에이전트 편집의 롤백 · 잠금 · 승인이 없다 |
Discovery §2.1 |
| Firebase Studio |
2027-03-22 종료, 후계자(Antigravity · AI Studio)는 Flutter 특화가 아니다 |
Discovery §2.2 |
| flutter_ide |
macOS 한정 · 탭 전환 시 편집 소실 · 셸 인젝션 · LICENSE 부재 → 패턴만 차용 |
Discovery §4.1 |
| 클라우드 풀스택 빌더(Lovable · Bolt · Replit) |
JS/Python 중심, Flutter · Dart 부재 |
Discovery §2.1 |
1.2 목표와 성공 기준
합격선
Seed §4 의 Must 8건(AC-01 · 03 · 04 · 06 · 07 · 08 · 09 · 10) + Must-Not 5건(AC-11 · 12 · 13 · 22 · 23) 전량 충족이 첫 릴리스의 합격선이다. 현재 13건 모두 Status = PENDING. May 9건(AC-14~21 · AC-24)은 합격선이 아니다.
첫 릴리스의 "검사"란 무엇인가 (OBJ-1-PM-2)
Seed §1 의 "편집 · 실행 · 검사"에서 첫 릴리스의 검사는 Dart 진단(AC-07)과 터미널 · 실행 구성 세션의 출력(AC-08 · AC-09) 이다. DB 브라우저(AC-14) · API 클라이언트(AC-15) · 디버거(AC-16)는 May 이며 첫 릴리스에 없다. 이 문서와 마케팅 문안은 첫 릴리스에서 그 이상을 약속하지 않는다.
North Star Metric — 미확정 (상위 승계)
상위 PRD §1.2 가 NSM 을 "지금 정하지 않는다"로 뒀고 이 문서도 같다. Discovery §3.2 의 후보 3종(풀스택 워크스페이스 주간 활성 · 에디터 내 핫 리로드 사이클 완주 비율 · 도구 전환 없이 끝난 세션 비율)은 후보로만 유지한다.
도그푸딩 실험(Discovery §1.5)
- 가설: 셸 통합 + 에디터 코어 +
serverpod start 실행 구성만으로 내부 팀이 unibook 작업 하루를 cocode ADE 안에서 끝낼 수 있다.
- 측정: ⓐ 다른 에디터 · 터미널 앱으로 전환한 횟수와 사유 ⓑ 에디터를 떠나게 만든 기능 부재 목록(빈도순) ⓒ 데이터 손실 · 크래시 건수. 판정값은 첫 실측 뒤 오너가 정한다(상위 D-065 와 같은 순서). ⓑ 의 상위 항목이 May 9건의 순서가 된다(D-003).
- 전제: 기존 로컬 폴더(unibook)를 워크스페이스로 열 수 있어야 한다 — D-018(상위 미결)에 종속.
1.3 범위 (첫 릴리스)
안 — Seed §4 Must · Must-Not 가 규정하는 것
| 트랙 |
내용 |
AC |
| A 셸 통합 |
앱 셸 다섯 슬롯에 실제 뷰, 패키지 8종 배선, 계약 부트스트랩, CI 스코프 편입 |
AC-01 |
| B 에디터 코어 |
문서 버퍼 · 탭 · 저장 · 외부 변경 · 닫기 처분 · 실행 취소 · 디렉터리 트리 · 전역 검색/치환 · Dart LSP 토큰 · 진단 · 비-Dart 구문 강조 |
AC-01 · 03 · 04 · 06 · 07 |
| C 풀스택 표면(일부) |
실행 구성(템플릿 3종 · 단일 런처 · 세션 · 종료) · PTY 터미널(다중 세션 · 크기 · 키 전달 · 상한) |
AC-08 · 09 |
| 교차 |
실행 중 작업 대상 파일의 사람 편집(표시 · 비차단 · 스냅샷 없음) · 체인 불연속 충돌 · 무음 실패 금지 · 범위 가드 |
AC-10 · 22 · 23 · 11 · 12 · 13 |
밖
| 항목 |
지위 |
근거 |
| DB 브라우저 · HTTP API 클라이언트 · DAP 디버거 · git 사이드바 · pub.dev 사이드바 · 에디터 분할 · 세션 복원 · 대체 화면 터미널 · 비-Dart 언어 서버 |
May(AC-14~21 · AC-24) — 도그푸딩 ⓑ 결과로 순서 결정 |
D-003 |
| 비-Dart 1급 프로젝트 타입 · 앱 명령의 터미널 stdin 타이핑 · 사람 편집의 EditSnapshot · 사람 편집 구간을 건너뛰는 자동 롤백 · 무음 실패 |
Must-Not(AC-11 · 12 · 13 · 22 · 23) |
C-02 · C-05 · C-03 |
| 드리머 모드 · 워크트리 격리 · co-brick · 네이티브 윈도잉 · 창 분리 |
상위 Must-Not · D-025 |
Seed Metadata 상속 |
| 워크스페이스 생성 · 기존 폴더 열기 · 워크스페이스 생명주기 |
상위 파이프라인 소관(D-018) |
ux-spec §2.5 UX-D-30 |
범위 해석에서 틀리기 쉬운 세 지점
- AC-07 의 "비-Dart 파일 편집 지원"은 파일 이름 또는 확장자 기반 구문 강조 + 편집 · 저장까지다. 언어 서버 연결(AC-24)은 May 이고, 워크스페이스 생성 선택지 · 스캐폴딩 · 에이전트 도구 전제(1급의 세 요소)는 금지(AC-11).
- 사람 편집은 어떤 경우에도 EditSnapshot 을 만들지 않는다(AC-13). 실행 중 작업이 스냅샷을 남긴 파일을 사람이 저장해도 막지 않는다(AC-10). 대신 그 작업의 다음 스냅샷이 체인 불연속을 만들고, 롤백은 그 구간을 건너뛰지 못한다(AC-22 — 상위 D-013 종속).
- AC-08 의 실행 구성은 풀스택 워크스페이스뿐 아니라 프로젝트 디렉터리가 하나 이상인 모든 워크스페이스에서 동작한다(단일 Flutter 앱 포함).
1.4 비즈니스 모델
- 과금 요소 없음. 상위 D-034(BYOK: 모델 비용은 사용자 키, 과금 · 정산은 MVP 밖)를 그대로 승계한다. 이 모듈은 LLM 을 호출하지 않으므로 사용자 비용도 생기지 않는다.
- 비용 구조: 개발 인건비만. 외부 서비스 없음. 선택 의존
flutter_pty2(MIT) · re_highlight(MIT, pub.dev 2026-09-16 확인 — highlight.js v11.9.0 동기 규칙) 는 무상.
- 가치 제안(Discovery §2.3): ① Dart 풀스택을 한 창에서 ② ADE 안전망 위의 에디터(에이전트 편집만 스냅샷 · 잠금 · 승인, 사람 편집은 자유) ③ 결정론적 풀스택 골격(상위
bricks) ④ 가벼움(최소 셸 디스크 58.29 MiB 실측, docs/a06-shell-footprint-cocode.md).
- 검증 포인트: 도그푸딩 ⓑ(어떤 부재가 사용자를 떠나게 하는가) — May 우선순위와 이후 과금 설계의 입력. 상위 Discovery 의 "가격 빈 구간" 가설은 이 모듈이 아니라 상위 파이프라인이 검증한다.
| ID |
제약 |
이 PRD 에서의 결과 |
| 상속 |
상위 cocode/C-01~C-05 · Must-Not AC-04 · 05 · 21 · 24 |
3-OS · 개발자 인터페이스 우선 · Dart/Flutter 전용 · 비동기 위임 · 테스트 파일 보호를 그대로 전제 |
| C-02 |
Dart 풀스택. 1급 타입은 Flutter 앱 · Serverpod 서버 · Jaspr 웹 |
FR-407(비-Dart 강조) · FR-704(AC-11 가드) |
| C-03 |
사람 편집은 스냅샷 · 리스 없음, 저장 비차단, 체인 불연속 → 충돌 |
FR-209 · FR-701~703 |
| C-04 |
모든 새 텍스트 입력 표면은 상위 cocode/AC-16(IME) 판정 대상, 판정은 분리 |
NFR-02 |
| C-05 |
앱 자신의 명령은 단일 런처로만 |
FR-402 · 504 · 602 · 704 |
| 상위 DD-06 |
고빈도 이벤트는 Bloc 미통과 |
FR-208 · NFR-01 |
| 상위 DD-15 · DD-23a |
Process.start 는 런처 파일뿐 · 환경변수 화이트리스트는 사람 터미널에도 |
FR-504 · 602 · 603 |
| 상위 D-025 |
Flutter stable 3.47.x, 윈도잉 API 미사용 |
분할 뷰(May)는 페인 내부 |
| 상위 D-031 |
도너 자산 존치 · 분석 제외 |
FR-102 는 exclude 를 좁힐 뿐 파일을 지우지 않는다 |
1.6 이해관계자 · 승인
| 역할 |
주체 |
승인 대상 |
| Product Owner |
프로젝트 오너 |
Q1 개별 확답(A-01) · D-013 · D-018 |
| Engineering Lead |
상위 파이프라인(cocode ADE) 담당 |
상위 Story 라우팅(FR-703) · 계약 12→13 |
| 이 모듈 |
진행자(cc-product 파이프라인) |
이 PRD · BDD |
2. 기능 요구사항
표기: [경계] = Seed §4 AC 또는 §2 C 문면 · [불변식] = Seed §3 · [도출] = 상위 규약 · 패널 지적 · 실측에서 도출(Seed 문면에는 없음) · [이관] = 값 · 정책이 §10 미정 표 또는 Design 에 있음.
2.1 셸 통합 (FR-1xx) — AC-01
| ID |
요구사항 |
근거 |
| FR-101 |
app/cocode/pubspec.yaml 에 editor · terminal · lsp · toolchain · desktop_platform 워크스페이스 의존을 추가한다(현재 core · ui · workspace 만) |
[경계] §0 셸 통합 · Discovery §1.3 |
| FR-102 |
analysis_options.yaml:123 의 app/cocode/** 제외를 도너 파일(main_donor.dart · main_{development,staging,production}.dart · bootstrap.dart · app/** · core/**)로 좁혀 app/cocode/lib/main.dart 와 신설 코드가 CI analyze · test 스코프에 들어온다. 도너 파일은 삭제하지 않는다 |
[도출] OBJ-1-Analyst-4 · 상위 D-031(존치 + 제외) |
| FR-103 |
부트스트랩이 core 계약 목록 contractTypes(package/core/lib/src/contract/contract_registry.dart:12; 인터페이스 선언은 contracts.dart)의 계약 전수(현재 12종, FR-404 로 13종)를 정확히 1회 등록하고, DD-02a 완결성 테스트(소스 대조 · 실제 호출 2축)가 통과한다 |
[도출] 상위 architecture DD-02a |
| FR-104 |
_pane(app/cocode/lib/main.dart:69)의 슬롯 채움: navigation = 실제 디렉터리 트리(FR-301) · editorStack = 탭 스택 + CocodeEditorView(열린 파일 0 이면 CoUI EmptyState.compact, 상위 ux-spec §2.5 ⓐ행 계약) · terminal = 세션 탭 그룹 + CocodeTerminalView · workbench = 컨테이너(자체 내용 없음) · 슬롯 이름 텍스트 플레이스홀더 0 |
[경계] AC-01 |
| FR-105 |
하단 슬롯은 CoUI Tabs 로 [터미널] [문제] 탭 그룹을 둔다. 상위 ux-spec §2.1 의 [검증 로그] 탭은 상위 소관이며 이 문서는 자리를 비워 두지 않고 상위 위젯(CocodeVerificationLogPanel)을 배치만 한다(내용 · 동작은 상위; 배치 포함 여부는 Breakdown 에서 상위와 합의) |
[도출] 상위 ux-spec §2.1 |
| FR-106 |
메뉴바 File · View · Run 항목과 커맨드 팔레트 등재 — 1급 동작은 CocodeCommandRegistry 에 등록(K1 양방향 커버리지). 항목 표는 Design 이 정한다 |
[도출] 상위 K1 · UX-D-16 [이관] U-11 |
2.2 문서 버퍼 · 탭 · 저장 (FR-2xx) — AC-03 · AC-04 · AC-13
| ID |
요구사항 |
근거 |
| FR-201 |
앱은 워크스페이스당 DocumentBuffer 레지스트리를 두고, 같은 path_key 의 버퍼는 최대 1개다. 버퍼가 CodeLineEditingController 를 소유하고 CocodeEditorView 는 그것을 참조한다 |
[불변식] DocumentBuffer (1) · cocode_editor_view.dart:42-71 |
| FR-202 |
탭 스택(CoUI Tabs)의 탭은 버퍼를 참조할 뿐 소유하지 않는다. 탭이 닫혀도 dirty 버퍼는 사용자 처분 전에 사라지지 않는다 |
[불변식] (2) · [경계] AC-03 |
| FR-203 |
dirty = hash(content) ≠ disk_hash. disk_hash 는 로드 · 저장 시점에만 갱신된다 |
[경계] §0 dirty |
| FR-204 |
저장 명령은 파일을 디스크에 기록하고 disk_hash 를 갱신한다. 기록 실패는 실패 사유(OS 오류 메시지)와 함께 표시된다 |
[경계] AC-04 · AC-23 |
| FR-205 |
파일 감시가 열린 파일의 변경을 감지하면: 내용 변경 + clean → 디스크 내용으로 다시 읽기 · 내용 변경 + dirty → "다시 읽기 · 내 편집 유지" 선택 · 삭제 · 이름변경 → 버퍼 보존 + external_change_pending 표시, 다시 읽기 선택 없음, 닫기 시 AC-03 처분(clean 버퍼도 같다) |
[경계] AC-04 · [도출] OBJ-1-Analyst-1 [이관] 감지 지연 상한 U-3 |
| FR-206 |
닫기 처분: 탭 닫기 = 저장 · 폐기 · 취소 3선택. 워크스페이스 닫기 · 앱 종료 = dirty 버퍼 전체 목록과 실행 중 세션(사용자 셸 · 실행 구성) 목록을 한 다이얼로그에 제시, "계속" 을 고른 경우에만 세션을 전부 종료(terminate)한 뒤 진행한다 — 고아 프로세스 0. 일괄 저장 중 한 파일이라도 실패하면 진행하지 않고 실패 목록을 남긴다. 폐기 · "계속" 은 파괴적 동작으로 Enter 기본 활성화 금지(K4) |
[경계] AC-03 · [도출] 상위 K4 · flow-permutation E-C1 · E-C2 · architecture ED-16 |
| FR-207 |
실행 취소 · 다시 실행 이력은 버퍼(컨트롤러)에 귀속되어 탭 전환으로 사라지지 않는다 |
[경계] AC-03 |
| FR-208 |
버퍼 content · 커서 · 스크롤은 Bloc 을 통과하지 않는다. Bloc 은 열린 문서 목록 · dirty 여부 · 활성 탭 같은 저빈도 요약만 갖는다 |
[도출] 상위 DD-06 · OBJ-1-Architect-4 |
| FR-209 |
사람 편집 저장은 WorkspaceFileGate 의 에이전트 경로를 타지 않으며 EditSnapshot 을 만들지 않고 편집 리스를 획득하지 않는다. 저장 경로의 dart:io 쓰기는 상위 DD-15 허용 목록 안(workspace)에 둔다 |
[경계] C-03 · AC-13 · [도출] DD-15 |
| FR-210 |
바이너리 · 비 UTF-8 파일 열기 정책은 Design 이 정한다(읽기 전용 열기 또는 거부 + 사유 표시 중 하나) |
[이관] D-017(6) |
2.3 워크스페이스 탐색 · 검색 (FR-3xx) — AC-01 · AC-06
| ID |
요구사항 |
근거 |
| FR-301 |
디렉터리 트리는 CocodeFileTree.loadChildren(cocode_file_tree.dart:44)에 비동기 로더를 연결해 펼친 디렉터리의 자식만 그 시점에 Directory.list 로 읽는다. 전체 트리 선행 적재 없음 |
[경계] AC-01 |
| FR-302 |
워크스페이스가 git 저장소이면 .gitignore 판정의 정본은 git 의 판정(git check-ignore --stdin, 단일 런처 run)이며 루트 · 하위 · 전역 규칙을 포함한다. git 저장소가 아니면 루트 .gitignore 파일의 규칙을 순수 Dart matcher 로 적용한다(Seed AC-01 은 파일 규칙 기반 — 정본을 좁히지 않는다; matcher 선택은 U-14). .gitignore 파일도 없으면 제외 항목은 없다. git 저장소인데 git 실행 파일이 없거나 .git 을 읽을 수 없으면 루트 .gitignore 파일 규칙으로 후퇴하고 «git 판정 불가» 를 표시한다(E-I1). 판정 캐시의 무효화 시점은 U-14 |
[경계] AC-01 · AC-06 · [도출] OBJ-1-Analyst-5 |
| FR-303 |
무시 항목의 표시/숨김 전환은 워크스페이스 설정 항목이다 |
[경계] AC-01 |
| FR-304 |
전역 검색은 문자열 · 정규식을 받아 FR-302 의 규칙을 통과한 파일만 대상으로 하며, 검색은 isolate 에서 실행되어 결과가 스트리밍된다(메인 스레드 비차단) |
[경계] AC-06 · [도출] OBJ-1-Architect-5 |
| FR-305 |
결과 목록(파일 · 행 · 일치 구간)에서 항목을 선택하면 그 파일이 버퍼로 열리고 커서가 그 위치로 이동한다 |
[경계] AC-06 |
| FR-306 |
치환은 대상 파일을 버퍼로 열어 사람 편집으로 적용하고 FR-204 로 저장한다 — EditSnapshot 없음 |
[경계] AC-06 · C-03 |
2.4 언어 지원 (FR-4xx) — AC-07
| ID |
요구사항 |
근거 |
| FR-401 |
editor 에 TextDocumentSource(core/lib/src/contract/contracts.dart:362) 구현을 신설한다 — 열린 버퍼의 열기 · 변경을 CocodeLspClient 에 공급. 현재 구현 0건 |
[경계] AC-07 · [도출] 상위 C1 |
| FR-402 |
Dart 언어 서버(dart language-server)는 워크스페이스당 1개, 단일 런처(ToolchainProcessLauncher.start, process_launcher.dart:157)의 humanTerminal 프로파일로 기동한다 |
[경계] AC-07 · C-05 |
| FR-403 |
시맨틱 토큰은 기존 CocodeSemanticTokenSink → CocodeSyntaxScheme(LSP 3.17 23종) 경로로 구문 색상에 반영된다 |
[경계] AC-07 |
| FR-404 |
DiagnosticsSink 계약을 core contracts.dart 에 신설하고 contract_registry.dart:12 의 contractTypes 를 12→13 으로 갱신(DD-02a 테스트 갱신). lsp 는 publishDiagnostics 를 이 sink 로 전달하고(현재 세면서 버림, cocode_lsp_client.dart:124), 구현은 에디터 측이 보유한다. 이 sink 는 상위 자가 검증 ④ 의 입력이 아니며 toolchain 은 이를 의존하지 않는다 |
[경계] §0 진단 · [도출] OBJ-1-Architect-3 · 상위 C5 · 이슈 #60 AC3 |
| FR-405 |
진단은 하단 [문제] 탭(파일 · 행 · 심각도 · 메시지 목록, 선택 시 이동)과 에디터 거터 표식으로 표시된다 |
[경계] AC-07 |
| FR-406 |
언어 서버가 없거나(실행 파일 부재) 종료되면 [문제] 탭에 서버 상태(부재 · 종료 코드)가 표시되고 편집은 계속된다. 자동 재기동은 없고 사용자가 [언어 서버 다시 시작] 으로 수동 재기동한다 — 재기동 실패도 같은 자리에 사유로 표시된다 |
[경계] AC-07 · AC-23 · [도출] architecture ED-10 · UR-14 |
| FR-407 |
Dart 이외 파일의 구문 강조는 re_highlight 언어 정의를 파일 이름 또는 확장자로 매핑한다 — 최소 SQL(.sql) · YAML(.yaml · .yml) · Dockerfile(파일 이름 Dockerfile) · Markdown(.md). 편집 · 저장은 FR-2xx 와 같다 |
[경계] AC-07 · C-02 |
| FR-408 |
Dart 이외 언어 서버 연결은 May(AC-24). 설치 · 기동 정책은 §10 U-6 |
[이관] D-012(2) |
2.5 실행 구성 (FR-5xx) — AC-08
| ID |
요구사항 |
근거 |
| FR-501 |
RunConfiguration(id · name · template_kind · command_template · working_dir · env_overrides)을 워크스페이스에 영속한다. 저장 파일은 .cocode/editor/run_configurations.json(Design ED-07 확정 — .cocode/config/ 는 상위 #22 소유). 파일이 손상되어 읽을 수 없으면 실행 구성 목록은 비고 오류가 표시되며 파일을 덮어쓰거나 재생성하지 않는다 |
[불변식] RunConfiguration · architecture ED-07 · ED-17 |
| FR-502 |
프로젝트 디렉터리 판정: 워크스페이스 안의 pubspec.yaml(깊이 무관)을 읽어 flutter SDK 의존 + lib/main.dart → Flutter 앱, serverpod 의존 → Serverpod 서버, jaspr 의존 → Jaspr 웹. working_dir 은 판정된 디렉터리 중 하나 |
[경계] §0 · D-015 |
| FR-503 |
설치 직후 앱은 판정된 프로젝트 디렉터리마다 template_kind 별 기본 템플릿을 1건씩 제공한다(같은 종류의 디렉터리가 여럿이면 디렉터리 수만큼) — 정본은 아래 표(D-017(5) 를 이 PRD 가 확정). 각 템플릿은 실행 파일과 인자 목록이 비어 있지 않다 |
[경계] AC-08 |
| FR-504 |
실행은 단일 런처 start 의 humanTerminal 프로파일 · ProcessLaunchRequest(environmentOverrides) 로 기동하며, 환경변수는 상위 DD-23a 화이트리스트를 통과한 것만 전달된다 |
[경계] AC-08 · C-05 |
| FR-505 |
실행 중 프로세스는 세션 목록에 origin(실행 구성 이름)과 함께 나타나고, 세션의 [중지] 한 번으로 종료된다(종료 신호 → 유예 대기 → 강제 종료 → status=exited, terminate({grace})). flutterRun 이 디바이스 미지정이면 flutter devices 결과에서 선택을 요구하고 취소 시 실행하지 않는다. working_dir 이 사라진 구성은 실행을 거부하고 사유를 표시한다 |
[경계] AC-08 · architecture ED-15 · UX-E-32/33 |
| FR-506 |
실행 전제 검사(실행 전): 실행 파일이 PATH 또는 지정 경로에 존재 · flutterRun 은 디바이스 인자 지정(미지정 시 디바이스 선택 요구) · serverpodStart 는 serverpod CLI 가 4.x. 미충족 시 세션을 만들지 않고 사유를 표시한다 |
[경계] AC-08 |
| FR-507 |
기동 실패(실행 파일 없음 · 권한 없음) · 비정상 종료는 그 세션에 종료 코드 또는 오류 메시지로 표시된다 |
[경계] AC-23 |
| FR-508 |
Run 메뉴와 팔레트에 "실행 구성 실행 · 중지 · 편집" 명령을 등재한다 |
[도출] K1 |
기본 템플릿 3종 정본 (D-017(5) — 이 PRD 가 확정, 값은 설정으로 편집 가능)
| template_kind |
이름 |
실행 파일 · 인자 |
working_dir |
실행 전제 |
| flutterRun |
Flutter 앱 실행 |
flutter run -d <device> |
Flutter 앱 프로젝트 디렉터리 |
flutter 존재 · <device> 지정(미지정 시 flutter devices 결과에서 선택 요구) |
| serverpodStart |
Serverpod 풀스택 실행 |
serverpod start |
Serverpod 서버 프로젝트 디렉터리 |
serverpod 존재 · serverpod --version 이 4.x — 4.x 전용(미충족 시 사유 표시 + 아래 대체 템플릿 안내) |
| dartTest |
테스트 실행 |
Flutter 앱: flutter test / 그 외: dart test |
프로젝트 디렉터리 |
실행 파일 존재 |
| custom(기본 제공 1건) |
Serverpod 3.x 서버 실행 |
dart run bin/main.dart |
Serverpod 서버 프로젝트 디렉터리 |
dart 존재. OBJ-1-PM-4 처분: 3.x 사용자용 대체 경로. serverpodStart 를 3.x 로 바꾸지 않는다(4.x 전용 표기) |
2.6 터미널 세션 · PTY (FR-6xx) — AC-09
| ID |
요구사항 |
근거 |
| FR-601 |
CocodeTerminalBackend(cocode_terminal_backend.dart:62-85)의 두 번째 구현으로 PTY 백엔드를 둔다(isPseudoTerminal:true · canResize:true). flutter_pty2 1.0.2 채택 여부는 스파이크(§6 EPIC-6 S1)로 정한다 — 자체 FFI 는 대안 |
[경계] AC-09 · D-008 |
| FR-602 |
PTY 기동은 Process.start 가 아니므로 단일 런처 구현 파일에 PTY spawn 경로를 추가하고, check_process_launch_allowlist.py 를 Pty.start/FFI spawn 심볼까지 확장한다. terminal(L2) → toolchain(L1) 의존은 계층 규칙에 합법 |
[경계] C-05 · [도출] OBJ-1-Architect-1 |
| FR-603 |
명령 셸은 로그인 셸(macOS · Linux: $SHELL -l; Windows 의 셸 선택 규칙은 U-13)로 기동하고 환경변수는 DD-23a 화이트리스트 통과분이다 — 프로필이 PATH 를 복원해 통합 터미널에서 flutter/fvm 을 찾는다 |
[경계] AC-09 · [도출] OBJ-1-Architect-6 · flutter_ide §6-e |
| FR-604 |
뷰 크기 변경이 PTY 창 크기(rows · cols)로 프로세스에 전달된다 |
[경계] AC-09 |
| FR-605 |
사용자 키 입력(IME 확정 문자열 포함)이 실행 중인 프로세스에 그대로 전달된다 — flutter run 의 r · R · q 포함. 앱 자신의 명령은 쓰지 않는다 |
[경계] AC-09 · 불변식 TerminalSession (1) |
| FR-606 |
세션은 둘 이상 동시에 열리고 하단 탭 그룹에 각각 표시되며 개별로 종료된다. running 세션의 탭 × 는 비활성이며 종료는 명시적 [터미널 종료]/[중지](K4)로만 한다; exited 뒤에 × 로 탭을 닫으면 출력이 사라진다. 종료된 세션의 출력은 닫기 전까지 남는다 |
[경계] AC-09 · flow-permutation E-C3 · UX-E-20 |
| FR-607 |
scrollback 은 설치 직후 양의 유한한 상한(설정 항목)을 가지며 넘치면 오래된 줄부터 버린다. 기본값은 §10 U-2 |
[경계] AC-09 |
| FR-608 |
커서 이동 · 대체 화면 해석(vim · htop)은 May(AC-21). 첫 릴리스의 파서는 현행 스크롤백 뷰를 유지한다 |
[경계] AC-21 |
| FR-609 |
터미널 · 검색 패널 · 명령 팔레트 · 실행 구성 편집 입력은 상위 UX-D-18 I1~I3 규약을 따르고, IME 판정은 상위 cocode/AC-16 재생 워크플로(probe.ac16-ime-replay.yaml)에 표면을 추가해 분리 판정한다 |
[경계] C-04 |
| FR-610 |
PTY 생성 실패는 세션 자리에 사유와 함께 표시된다 |
[경계] AC-23 |
2.7 사람 편집 × 에이전트 작업 · 범위 가드 (FR-7xx) — AC-10 · AC-22 · AC-11 · AC-12 · AC-13 · AC-23
| ID |
요구사항 |
근거 |
| FR-701 |
실행 중인 DelegatedTask 가 EditSnapshot 을 남긴 파일을 열면 에디터(탭 · 상단 배너)에 "작업 {task_id} 편집 대상"이 표시된다. 데이터는 SnapshotStore 조회(snapshot_query_use_cases.dart) |
[경계] AC-10 |
| FR-702 |
그 파일의 사람 저장은 차단되지 않으며 EditSnapshot 을 만들지 않는다 |
[경계] AC-10 · AC-13 |
| FR-703 |
[상위 Story] 롤백 사전검사에 체인 불연속 조건을 추가한다 — 같은 작업의 연속 스냅샷에서 뒤.base ≠ 앞.result 이면 롤백 구간 안 불연속을 충돌로 제시. 구현 지점 RollbackTargetSet.structuralConflicts(rollback_targets.dart:459-480) · SnapshotChainStore.snapshotsOfTask. core · workspace 변경이라 상위 파이프라인 Story 로 라우팅한다. D-013 확정 전 AC-22 는 MET 불가 |
[경계] AC-22 · C-03 · [도출] OBJ-1-Architect-2 · OBJ-1-Analyst-3 |
| FR-704 |
Must-Not 가드(상위 D-072 형식): AC-11 = 워크스페이스 생성 선택지 · 템플릿 열거 테스트에 비-Dart 항목 부재 · AC-12 = TerminalSession.write 호출처가 사용자 입력 경로뿐임을 스캔 + 런처 allowlist 가드 · AC-13 = 사람 저장 경로에 SnapshotStore 호출 부재 스캔 · AC-23 = 실패 주입 테스트(기동 실패 · 서버 종료 · PTY 실패 · 저장 실패)에서 표시 단언 |
[경계] AC-11 · 12 · 13 · 23 · [도출] D-072 |
3. 비기능 요구사항
| ID |
범주 |
요구사항 |
근거 · 값의 상태 |
| NFR-01 |
성능 — 응답성 |
상위 cocode/AC-15(10만 줄 파일에서 조작이 순서대로 반영, 응답 불가 구간 없음)를 탭 N개 열림 조건에서도 유지한다. 벤치 ac15_responsiveness_bench.dart 를 그 조건으로 재실행한다 |
[경계] A-03 · [이관] N = U-1, 목표치 = 상위 D-065 절차. 실측: 단일 뷰 비용 평평(2~5 ms), 적재 선형(122 ms/10만 줄) |
| NFR-02 |
한글 IME |
새 텍스트 입력 표면 5종(에디터 본문 · 터미널 · 검색/치환 · 팔레트 · 실행 구성 편집)은 상위 cocode/AC-16 의 판정 대상이며 각 Must 의 판정과 분리한다 |
[경계] C-04 · 상위 D-069(6조합 미충족) |
| NFR-03 |
자원 상한 |
scrollback 상한 존재(FR-607) · 파일 감시 지연 상한 존재(FR-205) · 검색 · 트리 적재는 메인 isolate 를 막지 않는다 |
[경계] AC-09 · AC-04 · [이관] U-2 · U-3 |
| NFR-04 |
보안 |
앱 명령은 단일 런처(C-05) · DD-23a 화이트리스트 · 파일 · 폴더 이름에 경로 구분자와 .. 거부(flutter_ide §6-d 교훈) · 워크스페이스 밖 파일은 트리 · 검색 · 실행 구성의 대상이 아니다 |
[경계] C-05 · [도출] Discovery §4.1 |
| NFR-05 |
접근성 · 키보드 |
1급 동작은 팔레트 등재(K1) · 파괴적 동작 Enter 기본 활성화 금지(K4) · 히트영역 ≥ CocodeTouchTarget.pointer · 고정폭 서체 CocodeMonoFont 2:1 격자 |
[도출] 상위 K1 · K4 · 현황 브리프 §7.2 |
| NFR-06 |
유지보수 |
신설 패키지는 dependency_layers.yaml 명시 층 + workspace: 등록 + 순수 Dart 면 stage_coco_pure_packages.py · 신설 ui 위젯은 위젯북 use-case 동반 · 테스트 이름 should_when · 가드 스크립트 확장(FR-602 · FR-704) |
[도출] 현황 브리프 §4 · CLAUDE.md |
4. 사용자 흐름
흐름 1 — 워크스페이스가 열린 뒤 앱 셸 (AC-01)
- (전제 — 상위 소관 D-018) 워크스페이스가 열린다.
- 앱 셸이 그려진다: navigation 에 루트 디렉터리의 1단계 자식(FR-301,
.gitignore 제외 FR-302), editorStack 에 빈 상태(EmptyState.compact), terminal 에 세션 탭 그룹(세션 0 → "새 터미널" 동작), inspector 는 상위 Task Inspector.
- 디렉터리를 펼치면 그 시점에 자식을 읽는다. 파일을 선택하면 버퍼로 열려 탭이 생긴다.
흐름 2 — 편집 · 저장 · 외부 변경 (AC-03 · AC-04)
- 파일 A 를 편집(dirty) → 파일 B 탭으로 전환 → A 의 content · 실행 취소 이력 보존.
저장 → 디스크 기록 · disk_hash 갱신. 실패 → 사유 표시(AC-23).
- 외부에서 A 가 바뀜 → clean 이면 다시 읽기, dirty 면 "다시 읽기 · 내 편집 유지". A 가 삭제되면 버퍼 보존 + 표시.
- A 탭 닫기 → 저장 · 폐기 · 취소. 앱 종료 → dirty 목록 일괄 처분.
흐름 3 — 전역 검색 · 치환 (AC-06)
- 검색어(문자열/정규식) 입력 → isolate 가 FR-302 통과 파일을 스트리밍 검색 → 결과 목록.
- 항목 선택 → 파일이 버퍼로 열리고 커서 이동.
- 치환 → 대상 파일을 버퍼로 열어 사람 편집 적용 → 저장. EditSnapshot 0건.
흐름 4 — 실행 구성 (AC-08)
Run › Serverpod 풀스택 실행 → 전제 검사(serverpod 존재 · 4.x) → 미충족이면 사유 + 3.x 대체 템플릿 안내.
- 충족 → 단일 런처 humanTerminal 로 기동 → 세션 탭에 origin "Serverpod 풀스택 실행" 표시 → 출력 스트리밍.
[중지] → 종료 신호 → exited(code) 표시. 기동 실패 → 세션에 오류.
흐름 5 — 터미널 세션 (AC-09)
새 터미널 → PTY 로 로그인 셸 기동(런처 경유 · 화이트리스트 환경) → flutter run 타이핑 → r 키가 프로세스에 전달.
- 두 번째 터미널 → 병렬 유지. 첫 세션 종료 → 출력은 닫기 전까지 남음. scrollback 상한 초과 → 오래된 줄부터 폐기.
흐름 6 — 실행 중 작업 대상 파일의 사람 편집 (AC-10 · AC-22)
- 작업 T 가
lib/a.dart 에 S1 을 남김 → 사람이 a.dart 를 열면 "작업 T 편집 대상" 표시.
- 사람이 편집 · 저장 → 차단 없음, 스냅샷 없음.
- T 가 다시 편집 → S2(base = 사람 편집본 ≠ S1.result, 체인 불연속).
- 사용자가 T 를 시작 시점으로 롤백 → 상위 사전검사가 불연속을 충돌로 제시(자동 적용 없음). — D-013 확정 전에는 3~4 단계가 상위 현행 규칙(현재 해시 vs 마지막 result)으로만 동작해 사람 편집이 소멸할 수 있다: 그래서 하드 의존이다.
5. 데이터 모델 초안 (Seed §3 승계 + 영속 위치 제안)
| 엔티티 |
속성(Seed §3) |
영속 |
비고 |
| DocumentBuffer |
path_key · content · disk_hash · dirty · language_id · external_change_pending |
메모리(비영속). 세션 복원(AC-20)은 May |
content 는 CodeLineEditingController 가 보유(DD-06) |
| RunConfiguration |
id · name · template_kind · command_template · working_dir · env_overrides |
.cocode/config/run_configurations.json(제안, U-7) |
working_dir 은 프로젝트 디렉터리 중 하나 |
| TerminalSession |
id · backend_kind · process_id · cwd · scrollback · status · origin |
메모리(비영속) |
scrollback 상한 U-2 |
| 상위 참조 |
Workspace · DelegatedTask · EditSnapshot |
상위 .cocode/ |
체인 불연속은 EditSnapshot 두 개의 관계 — 새 엔티티 없음 |
모델 수준 미확정(Design 결정): RunConfiguration 영속 파일 스키마와 소유 패키지(U-7) · DiagnosticsSink 의 값 타입(U-6) · 프로젝트 디렉터리 판정 캐시의 무효화 시점.
6. Epic 구성 (Breakdown 입력 — 스토리 포인트는 추정치)
순서 = 트랙 의존. EPIC-1 이 나머지의 전제. 각 Story 는 단일 PR 크기(1~8 SP)를 목표로 잘랐다 — Plan Quality "scope appropriateness" 는 이 Story 단위로 판정한다(상위 Planning 이 MVP 전체를 단일 PR 기준으로 판정해 0.20 을 받은 구조적 실패의 재발 방지).
| Epic |
이름 |
FR |
Story 후보 (SP) |
합계 |
우선 |
의존 |
| EPIC-1 |
셸 통합 |
FR-101~106 |
S1 CI exclude 축소 + app/cocode/lib 스코프 편입 + 셸 테스트 1건(3) · S2 pubspec 의존 + 계약 부트스트랩 + DD-02a 테스트(5) · S3 editorStack 탭 스택 + CocodeEditorView + 빈 상태(5) · S4 하단 탭 그룹 + CocodeTerminalView(파이프 백엔드 임시)(3) · S5 navigation 실제 로더 연결(3) · S6 메뉴바 · 팔레트 항목 표 + 등재(3) |
22 |
p0 |
— |
| EPIC-2 |
버퍼 · 탭 · 저장 |
FR-201~210 |
S1 버퍼 레지스트리 + dirty/disk_hash(5) · S2 탭 닫기 3선택 + 워크스페이스 닫기/앱 종료 일괄 처분(5) · S3 저장 + 실패 표시(3) · S4 파일 감시 + 외부 변경 3분기(5) · S5 실행 취소 이력 귀속 테스트(2) · S6 DD-06 저빈도 요약 Bloc(3) |
23 |
p0 |
EPIC-1 S3 |
| EPIC-3 |
탐색 · 검색 |
FR-301~306 |
S1 지연 로더(3) · S2 git check-ignore 판정 + 캐시 + 비-git 폴백(5) · S3 전역 검색 isolate 스트리밍(8) · S4 결과 목록 → 에디터 점프(3) · S5 치환(사람 편집)(5) |
24 |
p0 |
EPIC-1 S5 · EPIC-2 S1 |
| EPIC-4 |
언어 지원 |
FR-401~408 |
S1 TextDocumentSource 구현(5) · S2 Dart 언어 서버 기동(런처 humanTerminal)(5) · S3 DiagnosticsSink 계약 신설 + 등록 + DD-02a 갱신(5) · S4 [문제] 패널 + 거터(5) · S5 서버 부재 · 종료 상태 표시(3) · S6 비-Dart 강조 매핑 4종(3) |
26 |
p0 |
EPIC-1 S2 · EPIC-2 S1 |
| EPIC-5 |
실행 구성 |
FR-501~508 |
S1 모델 + 영속 파일(5) · S2 프로젝트 디렉터리 판정(3) · S3 템플릿 3종 + custom 1 + 전제 검사(5) · S4 실행 → origin 세션 → 중지(5) · S5 Run 메뉴 · 팔레트(2) |
20 |
p0 |
EPIC-1 S4 · EPIC-6 S3(PTY 아닌 파이프로도 가능 — S4 는 파이프 우선, PTY 연동은 EPIC-6) |
| EPIC-6 |
PTY 터미널 |
FR-601~610 |
S1 flutter_pty2 3-OS 스파이크(5) · S2 런처 PTY 티켓 + allowlist 가드 확장(5) · S3a PTY 백엔드 구현(5) · S3b 로그인 셸 인자 + 화이트리스트 환경 적용(3) · S4 다중 세션 UI + 개별 종료 + exited 유지(5) · S5 scrollback 상한 설정(2) · S6 IME probe 표면 추가(3) |
28 |
p0 |
EPIC-1 S4 |
| EPIC-7 |
사람 편집 × 작업 · 가드 |
FR-701~704 |
S1 작업 대상 파일 표시(5) · S2 저장 비차단 + 스냅샷 부재 가드(3) · S3 Must-Not 가드 4종 + 실패 주입 테스트(5) · S4 [상위 라우팅] 체인 불연속 사전검사(8) |
21 |
p0 (S4 는 상위) |
EPIC-2 S3 · 상위 D-013 |
합계 39 Story · 164 SP(추정). May 항목(AC-14~21 · 24)은 Epic 을 만들지 않고 도그푸딩 ⓑ 뒤 별도 Breakdown 한다.
7. 의존성
내부
| 대상 |
내용 |
| 상위 결정 D-013 |
FR-703 · AC-22 — 하드 의존, 기각 시 새 Seed |
| 상위 결정 D-018 |
도그푸딩 · P1 ROI — Development 전 확정 |
| 오너 Q1 확답 |
A-01 — Breakdown 전 |
| 계약 12→13 |
FR-404 — core 변경 + DD-02a 테스트 갱신 |
| 가드 스크립트 |
check_process_launch_allowlist.py(FR-602) · analysis_options.yaml(FR-102) · check_mvp_scope_must_not.py 스캔 경로에 app/cocode/lib 자동 편입(명명 주의) |
외부(pub · 도구)
| 대상 |
버전 · 상태 |
용도 · 위험 |
re_editor / re_highlight |
0.10.0 / 0.0.3(현행) |
에디터 코어 · 비-Dart 강조. #254 PageUp/Down 미동작 |
flutter_pty2 |
1.0.2(sofluffy.io, MIT) |
PTY 후보 — 스파이크로 채택 결정(A-04) |
Dart SDK dart language-server |
프로젝트 FVM 3.47.3 |
FR-402 |
| Serverpod CLI |
4.x(public beta 2026-07-08) / 3.x 대체 템플릿 |
FR-503 · A-10 |
| git |
사용자 설치본 |
FR-302 |
CoUI Tabs · EmptyState · Resizable |
coui_flutter 0.130.1 핀 |
FR-104 · 105 |
8. 가정 (Seed §5 승계)
A-01(Dart 풀스택 — 잠정 결정) · A-02(기존 셸 위 — Verified) · A-03(다중 탭 응답성 — Unverified) · A-04(PTY · IME — Unverified, 반증 이력) · A-05(serverpod start 존재 — Verified) · A-07(사람 편집 비대상 — Verified) · A-10(템플릿이 beta/stable 차이 흡수 — Unverified). 검증 방법은 Seed §5.
9. 범위 밖
§1.3 "밖" 표 참조. 추가로: 에디터 테마 교체 UI · 플러그인 시스템 · MCP 통합 · 협업 편집 · 모바일 · 웹 타깃.
10. 미정값 · 미결 질문 (값을 지어내지 않는다)
| # |
대상 |
상태 |
확정 주체 · 시점 |
영향 |
| U-1 |
NFR-01 탭 N |
미정 |
Planning — 상위 D-065 절차(실측 분포 후 오너) |
벤치 조건 |
| U-2 |
scrollback 상한 기본값 |
미정(존재는 FR-607 강제) |
Design — 설정 기본값 |
BDD 는 규칙만 검증 |
| U-3 |
파일 감시 감지 지연 상한 |
미정(존재는 FR-205 강제) |
Design |
BDD 는 규칙만 검증 |
| U-4 |
기본 템플릿 정본 |
✅ 확정 — §2.5 표 |
— |
— |
| U-5 |
Serverpod 3.x 대체 |
✅ 확정 — custom 템플릿 1건 |
— |
— |
| U-6 |
DiagnosticsSink 값 타입 · 언어 서버 SandboxProfile · 외부 언어 서버 정책 |
미정 |
Design |
FR-404 · 408 |
| U-7 |
RunConfiguration 영속 파일 스키마 · 소유 패키지 |
미정(경로 제안만) |
Design |
FR-501 |
| U-8 |
PTY 패키지 채택(flutter_pty2 vs 자체 FFI) |
미정 |
EPIC-6 S1 스파이크 |
FR-601 |
| U-9 |
D-013 체인 불연속 |
상위 결정 대기 |
상위 파이프라인 — Development 전 |
FR-703 · AC-22 |
| U-10 |
D-018 기존 폴더 열기 |
상위 결정 대기 |
상위 파이프라인 — Development 전 |
도그푸딩 · 흐름 1 전제 |
| U-11 |
메뉴바 항목 표 · 키맵 |
미정 |
Design(상위 UX-D-16) |
FR-106 |
| U-12 |
바이너리 · 비 UTF-8 정책 |
미정 |
Design |
FR-210 |
| U-13 |
Windows 명령 셸 선택 규칙 |
✅ Design 확정 — architecture ED-11(pwsh → powershell → %COMSPEC%) |
— |
FR-603 |
| U-14 |
비-git 워크스페이스용 .gitignore matcher 선택과 판정 캐시 무효화 시점 |
확정 — D-041(직접 구현 · 전량 무효화) |
Design |
FR-302 · EPIC-3 S2 |
11. 승인
12. 추적 매트릭스
| Epic |
FR |
Seed AC |
Story 수(추정) |
| EPIC-1 셸 통합 |
FR-101~106 |
AC-01 |
6 |
| EPIC-2 버퍼 · 탭 · 저장 |
FR-201~210 |
AC-03 · AC-04 · AC-13 · AC-23 |
6 |
| EPIC-3 탐색 · 검색 |
FR-301~306 |
AC-01 · AC-06 |
5 |
| EPIC-4 언어 지원 |
FR-401~408 |
AC-07 · AC-23 · AC-24(May) |
6 |
| EPIC-5 실행 구성 |
FR-501~508 |
AC-08 · AC-11 · AC-23 |
5 |
| EPIC-6 PTY 터미널 |
FR-601~610 |
AC-09 · AC-12 · AC-23 · C-04 |
7 |
| EPIC-7 교차 · 가드 |
FR-701~704 |
AC-10 · AC-22 · AC-11 · AC-12 · AC-13 · AC-23 |
4 |
AC 커버리지: Must 8/8 · Must-Not 5/5 (AC-11 · 12 · 13 · 23 은 가드 FR-704 로, AC-22 는 FR-703 으로). May 9 건은 이 PRD 의 Epic 에 없음(의도).
Generated by cc-product Planning stage (BMAD v6) · 2026-09-16 · Seed v1.1.1 LOCKED 기준