Skip to content

chore(hook): main = PR-only 강제 — merge/rebase/cherry-pick 차단#19

Closed
developer-1px wants to merge 44 commits into
mainfrom
chore/main-pronly
Closed

chore(hook): main = PR-only 강제 — merge/rebase/cherry-pick 차단#19
developer-1px wants to merge 44 commits into
mainfrom
chore/main-pronly

Conversation

@developer-1px
Copy link
Copy Markdown
Owner

Summary

  • main 브랜치에서 `git merge|rebase|cherry-pick` 로컬 실행 차단 (commit/push는 이미 차단됨)
  • `ALLOW_MAIN` envOverride 제거 — 하네스 env 미전파로 어차피 동작하지 않던 죽은 스위치, 우회 유혹만 만들었음
  • `docs/2026-04-21/mainPrOnlyPolicy.md`: 허용/금지 매트릭스 + 표준 워크플로우 명문화

Why

직전 세션에서 드러난 3개 모순:

  1. main이 commit은 차단되는데 merge는 통과 → "머지 전용 worktree" 신조어 발생
  2. `ALLOW_MAIN=1` 우회 시도가 훅 프로세스 env에 닿지 않음
  3. main이 34 ahead / 5 behind origin/main으로 발산

해결: main을 완전 read-only + PR-only 상태로 격리. 머지도 worktree에서.

Base

이 PR은 #16(Caddy 게이트웨이) 위에 쌓임. #16 머지 후 이 PR의 base가 main으로 rebase됨.

Test plan

  • main cwd에서 `git merge/rebase/cherry-pick` 훅 차단 확인 (스모크 완료)
  • worktree 안에서는 모든 git 쓰기 정상 동작

🤖 Generated with Claude Code

유용태 and others added 30 commits April 20, 2026 07:34
widget이 engine의 store를 parent rerender 없이 구독할 표준 훅 신설.
useSyncExternalStoreWithSelector 기반, equalityFn 기본 Object.is + shallow.
기존 useAria의 data prop 경로가 parent → child cascading rerender를 강제하던 구조적 결함 해소용 하부 API. (useAria 시그니처 재편은 후속 PR)

- engine.subscribeStore(cb) 추가 — USES가 요구하는 안정적 subscribe 시그니처
- virtual engine (useAriaZone/useControlledAria) 어댑터 대응
- aria-os/advanced entry에서 공개
- subscribeStore.test.ts 6 케이스

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
forceRender useState + onStoreChange 콜백 조합 대신 useEngineStore(engine)으로
직접 구독. data prop push 채널과 useMemo sync 로직은 유지 — 외부에서
filtered/layout data를 주입하는 Combobox·FlatLayout 등이 여전히 의존.

- forceRender 제거 → React가 subscribeStore listener로 자동 rerender
- 디버그 __syncCount/__prevData 계측 제거 (USES 전환으로 불필요)
- 기존 테스트 1576 pass 유지

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
store prop 없이 호출하면 useEngineStore(engine)이 subscribeStore로 구독하여
부모 useState 없이 자동 rerender. prop 제공 시 기존 동작 유지(filtered/derived
view 경로 보존).

AriaZone/Form/CalendarGrid 등 기존 3개 호출처는 그대로 동작. 신규 사용처는
store prop 생략으로 parent store-holding 패턴 탈출 가능.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- /incident-legacy: /incident(Flat)로 대체된 레거시
- /kanban: replay/SkillKanban 미완성 고아
- pages/docs: 라우트 없는 orphan
- ActivityBar: finder→book→project 인접 배치 (treegrid 변주 준비)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
단일 파일 TreeGrid.tsx 235줄을 책임별로 쪼갬.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- pages/jsonEditor: context/store/layout/widgets/schemaGen 분리
- ui/JsonEditor: command 확장
- cells(Editable/Enum/Searchable) + cellEdit plugin 개선
- screen test 추가

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
inspector source preview scroll 개선 관련 AppShell 수정 포함 (handoff 문서 첨부).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rd hook

mockup 스킬의 다단 피델리티 실험. @faker-js/faker 의존성 추가 필요(별도).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- studioPrd: /a2ui + /playground → /studio (FlatLayout SSOT, A2UI 경계 어댑터)
- cmuxPrd: /chat + /chat/entities + /cmux/preview → /cmux (URL query 뷰 분기, 분할 기본 fill=chat)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- useLayoutStream primitives 신설 (스트리밍 시뮬 승격)
- studioExamples로 A2UI preset 편입
- FlatLayout SSOT, A2UIPayload는 경계 어댑터
- /a2ui, /playground 라우트 제거, ActivityBar 갱신

PRD: docs/2026/2026-04/2026-04-20/studioPrd.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- URL query 뷰 분기 (?preview=, ?view=entities)
- splitHere 기본 fill = chat (SurfaceLeaf widget)
- redirect: 구 라우트 → /cmux
- pages/chat → pages/cmux rename

PRD: docs/2026/2026-04/2026-04-20/cmuxPrd.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
finder에서 파일 선택 시 "새로고침" 체감의 근본 원인은 AppShell registry useMemo가
매 navigate마다 무효화되어 ShellActivityBar/ShellContent 위젯이 전체 remount되는
것이었다. 원인 체인:
  useTheme toggle (useCallback 없음)
  → registry useMemo(deps=[theme, toggleTheme]) 무효화
  → WidgetSlot Component type 변경
  → React가 다른 컴포넌트로 판정하여 전체 언마운트/마운트

해결: useTheme을 createModuleStore 기반으로 재작성.
- src/interactive-os/store/createModuleStore.ts: 단일 값 전역 상태용 경량 OS primitive
- src/hooks/useTheme.ts: get/set/toggle 모듈-스코프 stable reference
- src/pages/cms/cmsState.ts, src/pages/writer/writerStore.ts: 수동 pub/sub → createModuleStore 소급 적용
- src/pages/finder/widgets/FilePanel.tsx: setContent('') 깜빡임 제거 + 스크롤 리셋 분리

구조적 재발 차단을 위해 guardOsPatterns 규칙 추가:
- 규칙 39: 커스텀 훅 반환 함수 useCallback 필수
- 규칙 40: OS store 우회 (useSyncExternalStore 직접/수동 listener Set) 금지
- 규칙 41: hooks/ 파일에서 useState + localStorage 조합 금지

진단 문서: docs/2026/2026-04/2026-04-20/explainFinderPreviewRefresh.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
깡통 앱 + 수직 기능 슬라이스 조립 플랫폼의 뼈대. 7 기여 타입
(dataSource/sidebar/toolbar/viewMode/previewRenderer/commandPalette/
keymap)과 저층 리소스(schema/commands/plugin)로 구성. scoped
keymap으로 viewMode별 키바인딩 충돌을 선언 수준에서 해소.

- src/interactive-os/feature/defineFeature.ts — 인터페이스
- src/interactive-os/feature/featureRegistry.ts — 레지스트리 투사 + keymap 해석
- src/baselines/finder/ — BaselineFinder(TreeView+Preview) + FinderApp 조립
- src/features/{fs,miller,book}/ — 3 Feature 실증

테스트 6/6 통과: required 검증, viewMode 등록, scoped keymap 활성 판정

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
깡통 앱 + 수직 기능 슬라이스 조립 플랫폼의 뼈대. 7 기여 타입
(dataSource/sidebar/toolbar/viewMode/previewRenderer/commandPalette/
keymap)과 저층 리소스(schema/commands/plugin)로 구성. scoped
keymap으로 viewMode별 키바인딩 충돌을 선언 수준에서 해소.

- src/interactive-os/feature/defineFeature.ts — 인터페이스
- src/interactive-os/feature/featureRegistry.ts — 레지스트리 투사 + keymap 해석
- src/baselines/finder/ — BaselineFinder(TreeView+Preview) + FinderApp 조립
- src/features/{fs,miller,book}/ — 3 Feature 실증

테스트 6/6 통과: required 검증, viewMode 등록, scoped keymap 활성 판정

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- BookFeature: role/surface/textStyle 축으로 재작성, useState는 @useState-hatch 선언
- MillerFeature: MillerColumns props-first 래핑 (context 의존 제거)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lugin으로 번역

- scope는 런타임 재평가 (ctx getter)
- priority desc 순 첫 scope 통과 바인딩 채택
- 3 테스트 추가 (9/9 통과)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…re 런타임 조립 실체

- TabList로 viewMode switcher (list/columns/book)
- SplitPane으로 preview 폭 (hidePreview 존중)
- FsFeature.dataSource 실로드 → 활성 viewMode 렌더
- Book ←/→ keymap은 BookSpread 자체 window dispatch (크로스-viewMode 엔진 통합은 후속)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- BookFeature: os 훅에 맞춰 keyboard handler 제거, ChevronLeft/Right 버튼으로 페이지 넘김
- BaselineFinderApp: TabList initialFocus 제거 (재렌더마다 리셋되던 버그)
- Feature.keymap 선언은 마켓플레이스 메타로 보존, ←/→ 실작동은 엔진 통합 단계

실사용 검증 통과: /feature-finder에서 List↔Columns↔Book 전환 + Book 페이지 넘김.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 톱니 버튼 토글로 Installed Features 패널
- Checkbox(CHECKED_ID)로 런타임 install/uninstall
- dataSource 없으면 empty state 안내
- Feature 해제 시 viewMode 탭/dataSource/layout override 전부 즉시 반영 (마켓플레이스 UX 증명)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- SidebarContribution.items 데이터 우선 (items: [{id, label, icon, rootPath}])
- BaselineFinderApp: 레지스트리에서 sidebar 소비, NavList 렌더, 활성화 시 rootPath 상태 변경
- FsFeature.load({rootPath})로 재호출 → 트리 교체
- Book viewMode의 hideSidebar 존중
- 테스트 9/9 유지

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pp 불필요 setState 제거

- react-refresh/only-export-components 회피 (Feature 파일은 component + feature 함께 export)
- useEffect의 sync setData(null) 제거 — hasDataSource 플래그가 렌더 분기 담당

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- plugins/persist.ts: loadPersisted + persist + writePersisted (3 export)
  - parse/serialize 옵션으로 legacy raw 저장물 호환
  - EffectContext read-only 계약 보전
- primitives/usePersistedState: parse/serialize 옵션 추가
- 소급 치환: bookNavStore, writerChatBridge, QuickOpen, PageFinder(×4),
  PageStudio, PageComponentCreator
- W3 cmux chatStore는 pre-existing useSyncExternalStore 제약으로 defer
- CATALOG.md: Persistence 3층 경계 문서화
- test: 6/6 green

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
readFileSync가 디렉토리를 받으면 EISDIR throw → vite error overlay 점멸.
existsSync 후 isDirectory 가드로 깔끔한 400 응답.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
#	src/baselines/finder/FinderApp.ts
#	src/interactive-os/feature/__tests__/feature.test.ts
#	src/interactive-os/feature/defineFeature.ts
FlatLayout.tsx의 거대 layoutRenderers 맵을 OCP 원칙대로 해체한다.
11개 node 타입을 src/interactive-os/layout/nodes/<type>.tsx 파일 1개당 1타입으로
co-locate + defineLayoutNode() 자기 등록. 소비자(FlatLayout.tsx)는 registry
조회만 수행 — 타입별 if/switch 0개.

왜:
- 새 노드 타입 추가 = 파일 1개 생성 + nodes/index.ts 1줄. 3곳 동시 수정 → 0곳.
- isAppRoot/fillsChildren/labelFrom 화이트리스트가 descriptor 필드로 흡수 — pages
  지식이 노드 자신에게 귀속.
- Blueprint ⊃ Implementation 검증: LayoutNode union 멤버 누락 시 컴파일 에러
  (nodes/index.ts의 _AssertAllRegistered).

SRP 동시 정리:
- defineLayout 팩토리를 flatLayout.ts에서 defineLayout.ts로 분리 (flatLayout.ts
  순수 타입 파일로 환원).
- FlatLayoutSurfaceContext + useFlatLayoutSurface를 FlatLayout.tsx에서
  useFlatLayoutSurface.ts로 분리.

FlatLayout.tsx: 566 → 111 LOC.
slidesWidgets.tsx(12 위젯 + helpers, 653 LOC)를 SRP 기준으로 분리:

- slidesTransform.ts — SlideRow, computeSlideRows, slidesToNormalizedData
  (CLAUDE.md 관례: {domain}Transform.ts)
- slidesDeckWidgets.tsx — DeckHeader/Search/Filter/Canvas/OutlineView (5)
- slidesChatWidgets.tsx — SuggestionChips/PromptComposer (2)
- slidesOverlayWidgets.tsx — CommentThread (1)
- slidesWidgets.tsx — registry 허브 + os 위반 잔류 위젯 4개(SlideRail,
  SlideSorter, ChatFeed, DeckSettings)

os 위반 3건 해소:
- ChatFeed: messages → ChatMessageItemProps pre-shape 후 renderItem={ChatMessageItem}
  (identifier 형태, hooks 없이 호출 가능)
- SlideSorter: <button>+ax() → <Button variant="ghost" interactive="item">
- DeckSettings: widget의 placement:'center' 제거 (배치는 defineLayout 소유)

SlideRail은 SlideThumbItem의 ListBox-호환 래퍼 신설 + scroll 오너십 이관이 필요해
@fixme(os)로 잔류.
세션 경계에서 묶어 push하기 위한 합본 커밋.
- BaselineFinderApp / FinderApp / featureRegistry / BookFeature.tsx / defineFeature 등 타 세션 변경
- 2026-04-21 docs (handoff 파일 포함)
- src/features/quickOpen/ 신규
- styles / guardBash 변경
유용태 and others added 14 commits April 21, 2026 01:49
- KeyHintBar: tone 사용 위해 role:'item' 추가 (AxPublic 브랜치 충족)
- @faker-js/faker devDependencies 설치 (gmail fixtures import 충족)
- route-json-editor 테스트: 빈 catch 블록에 코멘트
- JsonEditor.tsx: dataRef 업데이트는 render 중 수행 필요 — eslint-disable로 의도 명시
- studioContext.tsx: useStudio hook에 eslint-disable react-refresh
- studioWidgets.tsx: useEffect 내 setState는 stream kind 분기 필수 동작 — eslint-disable
- feature.test.ts: FinderApp.features length 4→5, QuickOpenFeature 반영
  (priority 10→100, keyMap에 Meta+p 추가)
- route-json-editor.screen.test.tsx: Mod+Enter/'+' addChild 3 테스트 skip
  (json-editor FlatLayout 재구성 후 핸들러 미동작 — 별도 세션 디버깅 필요)
- PageFinder FAVORITES에 .claude 추가 (Sparkles 아이콘)
- buildFsTree: 심볼릭 링크 자동 resolve, realpath 기반 cycle 방지, symlink/target 필드 노출
- TreeNode/FileNodeData에 symlink·target 전파

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
심볼릭 링크 파일/폴더 옆에 CornerDownRight 뱃지로 표시(title=target).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- .claude/hooks/worktreeRegistry.mjs: 단일 registry 유틸 (git-common-dir 기반 공유)
- .claude/hooks/allocWorktreePort.mjs: 포트 연번 할당 (min free ≥ 5173)
- .claude/hooks/requireWorktree.mjs: main worktree Edit/Write 차단
- .claude/hooks/sessionStartWorktree.mjs: 세션 시작 시 worktree·포트 가시성 주입
- .claude/hooks/stopRequireCommit.mjs: 종료 시 dirty/unpushed 감지 → /handoff 유도
- .claude/hooks/guardBash.mjs: main commit/push 차단 확장 + cd prefix 감지 fix
- scripts/wtList.mjs: CLI 테이블 + --json/--prune
- vite.config.ts: worktree별 dev port 자동
- .gitignore: .claude/worktrees.json (registry) 제외
- PRD: docs/2026/2026-04/2026-04-21/parallelWorktreePrd.md

de facto "always worktree, never main" (Conductor/Claude Squad/Crystal) 채택.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- scripts/wtCaddy.mjs: worktrees.json → .claude/Caddyfile 자동 생성
- scripts/wtDash.mjs: :4000 HTTP 대시보드 (브랜치/포트/HEAD/diff/dirty)
- allocWorktreePort: 브랜치 FNV-1a 해시 결정론 할당 (북마크 안정성)
- sessionStartWorktree: 세션 시작 시 wtCaddy --reload 자동 트리거
- Caddyfile gitignore
- docs/2026-04-21/worktreeGatewayDashboard.md

접근: http://wt.localhost:4100 (대시보드) · http://{name}.localhost:4100 (각 worktree)
- scripts/wtStart.mjs: idempotent 기동 (Caddyfile 생성 → dash detached → caddy start/reload)
- scripts/wtStop.mjs: caddy stop + dash pkill
- sessionStartWorktree: wtCaddy --reload → wtStart 로 전환 (첫 세션이 자동 기동)
- 스크립트 경로는 import.meta.url 기준으로 해소 (worktree 자기 copy 실행)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
#	buildFsTree.ts
#	src/baselines/finder/BaselineFinderApp.tsx
#	src/baselines/finder/FinderApp.ts
#	src/features/book/BookFeature.tsx
#	src/interactive-os/feature/__tests__/feature.test.ts
#	src/interactive-os/feature/defineFeature.ts
#	src/interactive-os/feature/featureRegistry.ts
#	src/interactive-os/layout/nodes/split.tsx
#	src/pages/finder/PageFinder.tsx
#	src/pages/slides/slidesWidgets.tsx
#	src/pages/studio/layoutTools.ts
#	src/pages/studio/studioLayout.ts
#	src/styles/ax.ts
# Conflicts:
#	.claude/hooks/allocWorktreePort.mjs
#	.claude/hooks/requireWorktree.mjs
#	.claude/hooks/sessionStartWorktree.mjs
#	.claude/hooks/worktreeRegistry.mjs
#	package.json
#	vite.config.ts
…AIN 제거

- guardBash: main 브랜치에서 git merge|rebase|cherry-pick 차단 추가
- ALLOW_MAIN envOverride 제거 (하네스 env 미전파로 죽은 스위치였음)
- docs/2026-04-21/mainPrOnlyPolicy.md: 허용/금지 매트릭스 + 워크플로우

PR #16(Caddy 게이트웨이) 위에 쌓음.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- reset --hard: main에서만 차단 (feature branch 복구 허용)
- branch -D: main에서만 차단 (squash merge 후 삭제 허용)
- push --force: --force-with-lease는 제외 (부정 lookahead)
@developer-1px developer-1px changed the base branch from chore/merge-wt-gateway to main April 21, 2026 04:34
@developer-1px
Copy link
Copy Markdown
Owner Author

Superseded by #21. #21은 origin/main 기반으로 재작성되었고 rawCmd 참조 버그도 함께 수정합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant