Docs · Cocode ADE · 분석 · 설계 문서

풀스택 코딩 에디터 · E13

Scaffold — 골격 기록

설계를 디렉터리 골격과 시나리오 문서로 옮긴 기록

목차

파이프라인 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 실측, 둘 다 도너 프로젝트가 가져온 것):
      1. 코드 서명 — 개발 팀이 없으면 Runner.xcodeproj: error: Signing for "Runner" requires a development team. (xcodebuild exit 65). Patrol 의 테스트 번들은 앱과 같은 Runner 타깃을 서명하므로 patrol test 도 같은 자리에서 멈춘다.
      2. 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