파이프라인 4.5단계(Scaffold) 산출물 · 2026-09-16 · 네이밍 정렬 반영(development #293 · #295 · #297: cocode_* 패키지 · Cocode* 심볼 · .cocode/ · Key cocode.<ns>.<key>)
입력: docs/architecture-fullstack-code-editor.md rev.4 · docs/ux-spec-fullstack-code-editor.md rev.1a · docs/prd-fullstack-code-editor.md v1.1 · docs/bdd-fullstack-code-editor.md v1.1(83 시나리오). 규약: cc-flutter patrol-bdd-conventions · bdd-canonical-steps · cc-e2e feature-file-conventions.
원칙: 코드 생성기 없음(.feature 는 문서, Patrol 테스트는 손으로 쓴다) · Mason brick 은 쓰지 않는다(ED-01 — 신설 패키지 0, cc-bricks 의 feature-* brick 은 GoRouter · Serverpod 전제라 데스크톱 셸 모듈에 맞지 않는다 → 최소 디렉터리 골격) · 프로덕션 코드 · 계측(Key 부여) · 테스트 본문은 Development 의 몫.
1. 기능 매니페스트 (Phase 1)
기능(app/cocode/lib/feature/) |
Epic · FR |
주 엔티티 |
표면(ux-spec) |
BDD Feature → .feature |
shell |
EPIC-1 · FR-101~106 |
Workspace(상위) |
셸 5슬롯 · 상태바 · 메뉴 |
F1 → cocode_shell_tree.feature |
editor |
EPIC-2 · FR-201~210 |
DocumentBuffer |
S-E01 · S-E02 · S-E07 배너 |
F2 → cocode_editor_buffers.feature |
search |
EPIC-3 · FR-301~306 |
(SearchHit) |
S-E03 · 탐색 트리 |
F3 → cocode_workspace_search.feature |
language |
EPIC-4 · FR-401~408 |
(Diagnostic) |
S-E04 |
F4 → cocode_language_support.feature |
run |
EPIC-5 · FR-501~508 |
RunConfiguration |
S-E06 |
F5 → cocode_run_configurations.feature |
terminal |
EPIC-6 · FR-601~610 |
TerminalSession |
S-E05 |
F6 → cocode_terminal_sessions.feature |
crossing |
EPIC-7 · FR-701~704 |
(DelegatedTask · EditSnapshot, 상위) |
S-E07 · 트리 배지 |
F7 → cocode_human_agent_crossing.feature · F8 → cocode_scope_guards.feature |
- brick 매칭: 전부 해당 없음(fallback = 최소 골격). 도메인 층(엔티티 · 유즈케이스 · 포트)은
core, 데이터 층(writer · scanner · store · search)은 workspace, 프레젠테이션만 앱 — 아키텍처 §2 배치표를 따르므로 brick 의 domain/data/presentation 3층 디렉터리를 앱 안에 만들지 않는다.
- 백엔드 스캐폴딩: 없음(Serverpod 서버는 이 모듈의 실행 대상이지 구성 요소가 아니다, Seed C-03).
2. 코드 골격 (Phase 2)
| 경로 |
내용 |
채우는 Story |
app/cocode/lib/feature/{shell,editor,search,language,run,terminal,crossing}/<name>.dart |
자리표시 라이브러리(library; + 놓일 파일 · 결정 ID 목록) — 컴파일 대상이지만 심볼 0 |
EPIC-1~7 각 S1 |
app/cocode/integration_test/keys/cocode_keys.dart |
Widget Key 문자열 사전 8군(S-E01~07 + 셸) — 계측 대상 목록, 계측은 Development |
각 표면 Story |
app/cocode/integration_test/helpers/cocode_shell_initializer.dart |
initializeCocodeShell($) · createFixtureWorkspace() · disposeFixtureWorkspace() · cocodePatrolConfig — 도너 app_initializer.dart 와 분리 |
EPIC-1 S1(CocodeAdeApp(workspaceRoot:) seam, D-018 후) |
app/cocode/integration_test/step/the_cocode_workbench_is_reset.dart |
배치 리셋 step 템플릿(3항 계약) |
EPIC-1 S1 |
공용 cocode step 9종(the_cocode_workspace_is_open · i_open_the_file_from_the_tree · the_file_is_open_in_a_tab · i_edit_the_active_file · i_save_the_active_file · the_buffer_is_dirty/clean · the_file_on_disk_contains/does_not_contain) |
전 Feature 공유 — 디스크 단정 2종은 구현 완료, 나머지 템플릿 |
EPIC-2 |
- 단위 테스트 자리(아키텍처 §8)는 만들지 않았다 —
skip 만 있는 테스트는 초록으로 보이는 빈 껍데기라 check_bdd_layout.py 가 경계하는 형태와 같다. 각 Story 의 DoD 로 넘긴다: test/src/feature/shell/workspace_scope_test.dart(ED-13 순서) · test/src/feature/editor/editor_session_bloc_test.dart(ED-02 필드 열거) · workspace parity 테스트(ED-18) · 가드 회귀 케이스 2건(ED-06 4번).
3. BDD 산출물 (Phase 3) — app/cocode/integration_test/
.feature(영문 Gherkin + # 한글) |
시나리오 |
배치 대상 |
@isolated |
@guard(Patrol 밖) |
시나리오 파일 |
cocode_editor_buffers.feature |
17 |
8 (배치 1파일) |
9 |
0 |
10 |
cocode_human_agent_crossing.feature |
6 |
4 (배치 1파일) |
2 |
0 |
3 |
cocode_language_support.feature |
11 |
6 (배치 1파일) |
4 |
1 |
5 |
cocode_run_configurations.feature |
14 |
3 (배치 1파일) |
11 |
0 |
12 |
cocode_scope_guards.feature |
3 |
2 (배치 0파일) |
0 |
1 |
2 |
cocode_shell_tree.feature |
10 |
5 (배치 1파일) |
5 |
0 |
6 |
cocode_terminal_sessions.feature |
12 |
7 (배치 1파일) |
4 |
1 |
5 |
cocode_workspace_search.feature |
10 |
8 (배치 1파일) |
2 |
0 |
3 |
| 합계 |
83 (= BDD v1.1 83, Outline 2건 포함) |
|
37 |
3 |
46 (배치 7 · 단독 39) |
- 매핑 규약: 각
Scenario: 의 꼬리 주석이 docs/bdd-fullstack-code-editor.md 원문 제목([AC-nn] …)을 그대로 담는다 — 문서 ↔ .feature ↔ Patrol 파일(runScreenBatch 맵 키 = Scenario 영문 제목)이 문자열로 맞물린다. Scenario Outline 은 Examples 행마다 '<제목> (<첫 열 값>)' 키.
- 배치 규칙(cc-e2e §5.1): 같은 Background(「워크스페이스 W 가 열려 있다」)를 공유하는 시나리오는 Feature 당 배치 1파일. 다른 픽스처(git 유무 · 권한 · 손상 JSON · 실행 파일 부재) · 프로세스 수준 상태(앱 종료 · 워크스페이스 닫기 · PTY 실패) · 리셋 불가는
@isolated 1파일 1시나리오.
@guard 3건은 Patrol 이 아니다: F4 「진단은 자가 검증 ④ 의 입력이 아니다」(toolchain 의존 단위 테스트 + check_package_layers.py) · F6 「한글 IME 판정」(상위 probe.ac16-ime-replay 워크플로) · F8 「stdin 쓰기 경로 부재」(check_process_launch_allowlist.py 확장 + terminal 단위 테스트). .feature 머리말에 실행 주체를 적었고 step 템플릿은 만들었다.
- 판정 보류: F7
[AC-22] 2건은 @isolated @pending-d013 — 템플릿이 skipScenario 로 명시 skip 한다(U-9, 상위 D-013 확정 후 해제).
- step 템플릿: 신규 207파일(공용 cocode step 10 포함). 그중 25개는 순수
dart:io 픽스처 조작 · 디스크 단정으로 구현 완료, 나머지 182개는 // TODO(…) + throw UnimplementedError — Development 의 Story DoD 다. canonical 공유 step(package:test_driver/shared_steps.dart export 25종)은 파일을 만들지 않고 직접 호출한다.
- Key 사전:
keys/cocode_keys.dart 8군(에디터 · 처분 다이얼로그 · 트리 · 검색 · 문제 · 터미널 · 실행 구성 · 셸), 문자열은 상위 ui 규약대로 cocode.<ns>.<key>(#297 의 Key 네임스페이스 정렬 반영). 계측(위젯에 Key 부여)은 각 Story 에서 — Offstage 오버레이 금지.
4. Scaffold Gate
| 검사 |
기준 |
결과 |
| Brick output |
기능 디렉터리 존재(brick 매칭 없음 → 최소 골격) |
PASS — lib/feature/ 7종, 자리표시 라이브러리 컴파일(dart analyze 오류 0) |
| Feature files |
Feature 마다 .feature 가 integration_test/features/ 에 있고 영문 + 한글 주석 |
PASS — 8/8, 한글 전용 문장 0, 시나리오 83/83 |
| Patrol scenario templates |
배치/단독 템플릿이 scenarios/ 에 손으로 작성 |
PASS — 46파일, @guard 제외 전 시나리오 대응(스크립트 검증: 제목 문자열 일치) |
| Step templates |
모든 Gherkin step 이 canonical export 또는 step/ 파일에 대응 |
PASS — 미대응 0 (파일명 = 영문 문장 snake_case, 파라미터 제외 · CamelCase 분리 · 아포스트로피 제거) |
| 정적 분석 |
fvm dart analyze --no-fatal-warnings 신규 파일 전수 |
PASS — error 0 · warning 0 · info 는 lines_longer_than_80_chars(영문 문장 + 한글 주석의 Usage 행 · 시나리오 제목 맵 키) 367건뿐 |
| 포맷 |
fvm dart format |
적용. dcm format(리포 정본)은 로컬 라이선스 미활성으로 미적용 — CI 포맷 게이트가 trailing comma 차이를 지적하면 그 커밋에서 dcm format 을 돌린다 |
검증 스크립트: scratchpad/scaffold_gate.py(세션 임시) — 규칙은 위 표의 문장으로 충분히 재현된다.
5. Development 로 넘기는 seam · 결정 목록
fork 4개가 .feature 문장을 Key 로 표현할 수 없어 step TODO 에 남긴 것 — 각 Story 가 seam 을 만들거나 시나리오를 ScenarioSkipped 로 명시 skip 한다.
| 영역 |
seam / 미결 |
담당 Epic |
| 셸 |
CocodeAdeApp(workspaceRoot:) 주입(D-018 · ED-13) — 현재 초기화는 셸만 띄운다 |
EPIC-1 S1 |
| 셸 |
앱 기동 환경 주입(git/dart/serverpod 실행 파일 부재 · PATH · HOME) — 실행 중 PATH 변경 불가 |
EPIC-1 · EPIC-4 · EPIC-5 |
| 에디터 |
버퍼 내용 · 해시 · 커서 위치 노출(recordedContentHashes · 활성 CodeLineEditingController selection) · 파일 감시 결과 신호(U-3 시간 조건 없음) |
EPIC-2 |
| 에디터 |
저장 실패 픽스처는 부모 디렉터리 555 — HumanEditWriter 의 임시 파일 → rename 은 444 파일에서 성공한다 |
EPIC-2 |
| 검색 |
결과 행 → 에디터 커서 이동 단정 seam |
EPIC-3 |
| 언어 |
시맨틱 토큰 색 · 구문 강조 언어 노출 · 서버 pid/세대 seam · 상태 텍스트는 «자리표시자»라 Status.semanticLabel 로 판정 |
EPIC-4 |
| 실행 · 터미널 |
런처 저널(프로파일 · PTY 경로) · backend_kind Semantics(U-8) · PtySpawner/셸 경로/scrollback 주입 · 고아 프로세스 pid seam |
EPIC-5 · EPIC-6 |
| 터미널 |
canonical should be disabled/enabled 는 Patrol 에서 조기 반환 — running 탭 × 비활성(E-C3) 단정은 Semantics 기반 검사로 교체 전까지 공허 |
EPIC-6 |
| 교차 |
DelegatedTask · EditSnapshot 시드(상위 SnapshotChainStore) · 롤백 충돌 화면 Key(상위 CocodeRollbackConflictView, D-013) |
EPIC-7 · 상위 Story |
| 공통 |
공용 step 파일명 = 영문 문장 snake_case(i_edit_the_active_file_with.dart 로 정정) · F2 의 a_terminal_session_and_a_run_session_are_running 은 F5/F6 step 으로 재조합 |
— |
- Patrol 러너 — 세워짐 (#326, 2026-09-16).
run_all_scenarios.sh 가 스위트를 가른다: PATROL_SUITE(명시) 또는 디바이스(-d macos → cocode)로 정해지며, cocode 스위트는 scenarios/cocode_*_test.dart 만 돌리고 배치 7종을 먼저 세운다. 도너 목록에서는 cocode_* 를 뺀다 — 두 앱은 진입점 · 전제 · 디바이스가 전부 달라 한 목록으로 돌리면 어느 쪽이든 절반이 전제 미충족으로 죽는다.
- ⚠️ cocode 스위트도
--flavor 를 넘긴다. 「cocode 셸에는 플레이버가 없다」는 처음 가정이 실측에 뒤집혔다 — flutter build macos 가 "The Xcode project defines schemes: development, Flutter Assemble, production, staging / You must specify a --flavor option" 로 끝난다. macOS Runner 에 기본 스킴이 없다(도너가 플레이버 4종을 만들어 뒀다). 여기서 --flavor 는 서버 baseUrl 이 아니라 Xcode 스킴 이름이고 값은 development(PATROL_COCODE_SCHEME)다. E2E_FLAVOR dart-define 은 도너 서버 설정이라 넘기지 않는다.
- nightly 는
.github/workflows/patrol-macos-nightly.yml(04:00 KST + workflow_dispatch). 도너 patrol-test.yml 에 잡을 더하지 않은 이유: 그쪽은 FTL 업로드 · 플레이버별 서버 · 로그인 픽스처 계정 위에 서 있고 cocode 에는 하나도 해당하지 않는다.
- 그 워크플로가 green 이라는 뜻은 시나리오가 전부 통과한다가 아니라 실행 경로가 살아 있다는 것 하나다 — 빌드가 서고, Patrol 이 붙고, 배치가
📋 BATCH SUMMARY 를 낸다. 요약이 없으면 그때만 실패시킨다.
- ⛔ 아직 실제로 돌지 않는다 — macOS Runner 전제 2건(2026-09-16 실측, 둘 다 도너 프로젝트가 가져온 것):
- 코드 서명 — 개발 팀이 없으면
Runner.xcodeproj: error: Signing for "Runner" requires a development team. (xcodebuild exit 65). Patrol 의 테스트 번들은 앱과 같은 Runner 타깃을 서명하므로 patrol test 도 같은 자리에서 멈춘다.
- FlutterFire Crashlytics 심볼 업로드 단계 — 서명을
XCODE_XCCONFIG_FILE 로 우회하면 그 다음에 이것이 걸린다: Exception: Could not find the Crashlytics upload symbols script at ".../firebase-ios-sdk/Crashlytics/run" → Command PhaseScriptExecution failed with a nonzero exit code. cocode ADE 셸은 Crashlytics 를 쓰지 않는다 — Runner 타깃이 도너의 빌드 단계를 들고 있을 뿐이다.
- 그 앞 단계는 전부 살아 있음을 확인했다: patrol_cli 가 붙고(버전 조회 통과), 테스트 번들 엔트리포인트가 생성되고, Dart 쪽은 컴파일된다(
flutter test 로 별도 확인 — 남는 실패는 patrolAppService 부재, 즉 네이티브 자동화가 없다는 사실 하나다).
- 두 전제를 떼는 일은 도너 표면 전환(D-031)의 macOS 네이티브 판이라 이 Story 범위 밖이다 — #396 으로 세웠다.
- 공용 step 10종 구현 완료 (#326).
the_cocode_workspace_is_open · the_app_shell_is_rendered · no_file_is_open · the_cocode_workbench_is_reset(3항 계약) · i_open_the_file_from_the_tree · the_file_is_open_in_a_tab · i_edit_the_active_file_with · i_save_the_active_file · the_buffer_is_dirty · the_buffer_is_clean 에 UnimplementedError 가 남아 있지 않다.
i_edit_the_active_file_with 는 본문을 치환한다 — re_editor 의 입력부가 EditableText 가 아니라 DeltaTextInputClient 라(_code_input.dart:3) 플랫폼 텍스트 채널로 「지금 값」을 통째로 보낸다. 덧붙이기를 흉내 내면 기존 내용을 모르는 step 이 추측한 값을 쓰게 된다.
i_save_the_active_file 는 메뉴 항목을 라벨 텍스트로 찾는다 — CoUI CoreMenubarEntry.action 이 key 를 받지 않는다(실측). 라벨은 아직 «자리표시자» 표기이고 그 사실은 #315 의 래칫이 지킨다.
i_open_the_file_from_the_tree 는 워크스페이스 스캐너(#334)가 들어오기 전까지 「노드를 찾지 못했다」로 실패한다. 찾은 척하고 다음 단정에서 엉뚱하게 죽는 쪽보다 낫다.
- 위젯 단위 · 순수 Dart 단위 테스트 자리는 §2 대로 Story DoD.
Generated by cc-product Scaffold stage · 2026-09-16