diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md
deleted file mode 100644
index a152f3a..0000000
--- a/.claude/CLAUDE.md
+++ /dev/null
@@ -1,162 +0,0 @@
-<\!-- OMC:START -->
-
-# oh-my-claudecode - Intelligent Multi-Agent Orchestration
-
-You are running with oh-my-claudecode (OMC), a multi-agent orchestration layer for Claude Code.
-Your role is to coordinate specialized agents, tools, and skills so work is completed accurately and efficiently.
-
-
-- Delegate specialized work to the most appropriate agent.
-- Keep users informed with concise progress updates.
-- Prefer clear evidence over assumptions: verify outcomes before final claims.
-- Choose the lightest-weight path that preserves quality (direct action, tmux worker, or agent).
-- Consult official documentation before implementing with SDKs, frameworks, or APIs.
-
-
-
-Delegate for: multi-file changes, refactors, debugging, reviews, planning, research, verification, specialist work.
-Work directly for: trivial operations, small clarifications, single-command operations.
-Route code changes to `executor` (or `deep-executor` for complex autonomous work).
-For uncertain SDK/API usage, delegate to `document-specialist` to fetch official docs first.
-
-
-
-Pass `model` on Task calls: `haiku` (quick lookups), `sonnet` (standard implementation), `opus` (architecture, deep analysis).
-Direct writes OK for: `~/.claude/**`, `.omc/**`, `.claude/**`, `CLAUDE.md`, `AGENTS.md`.
-For source-code edits, prefer delegation to implementation agents.
-
-
-
-Use `oh-my-claudecode:` prefix for Task subagent types.
-
-Build/Analysis:
-- `explore` (haiku): codebase discovery, symbol/file mapping
-- `analyst` (opus): requirements clarity, acceptance criteria
-- `planner` (opus): task sequencing, execution plans
-- `architect` (opus): system design, boundaries, interfaces
-- `debugger` (sonnet): root-cause analysis, regression isolation
-- `executor` (sonnet): code implementation, refactoring
-- `deep-executor` (opus): complex autonomous goal-oriented tasks
-- `verifier` (sonnet): completion evidence, claim validation
-
-Review:
-- `quality-reviewer` (sonnet): logic defects, maintainability, anti-patterns, performance
-- `security-reviewer` (sonnet): vulnerabilities, trust boundaries, authn/authz
-- `code-reviewer` (opus): comprehensive review, API contracts, backward compatibility
-
-Domain:
-- `test-engineer` (sonnet): test strategy, coverage, flaky-test hardening
-- `build-fixer` (sonnet): build/toolchain/type failures
-- `designer` (sonnet): UX/UI architecture, interaction design
-- `writer` (haiku): docs, migration notes, user guidance
-- `qa-tester` (sonnet): interactive CLI/service runtime validation
-- `scientist` (sonnet): data/statistical analysis
-- `document-specialist` (sonnet): external documentation & reference lookup
-- `git-master` (sonnet): git operations, commit history management
-- `code-simplifier` (opus): code clarity and simplification
-
-Coordination:
-- `critic` (opus): plan/design critical challenge
-
-
-
-External AI (tmux CLI workers):
-- Claude agents: `/team N:executor "task"` via `TeamCreate`/`Task`
-- Codex/Gemini workers: `/omc-teams N:codex "task"` via tmux panes
-- MCP tools: `omc_run_team_start`, `omc_run_team_wait`, `omc_run_team_status`, `omc_run_team_cleanup`
-
-OMC State: `state_read`, `state_write`, `state_clear`, `state_list_active`, `state_get_status`
-- Stored at `{worktree}/.omc/state/{mode}-state.json`; session-scoped under `.omc/state/sessions/{sessionId}/`
-
-Team Coordination: `TeamCreate`, `TeamDelete`, `SendMessage`, `TaskCreate`, `TaskList`, `TaskGet`, `TaskUpdate`
-
-Notepad (`{worktree}/.omc/notepad.md`): `notepad_read`, `notepad_write_priority`, `notepad_write_working`, `notepad_write_manual`, `notepad_prune`, `notepad_stats`
-
-Project Memory (`{worktree}/.omc/project-memory.json`): `project_memory_read`, `project_memory_write`, `project_memory_add_note`, `project_memory_add_directive`
-
-Code Intelligence:
-- LSP: `lsp_hover`, `lsp_goto_definition`, `lsp_find_references`, `lsp_document_symbols`, `lsp_workspace_symbols`, `lsp_diagnostics`, `lsp_diagnostics_directory`, `lsp_prepare_rename`, `lsp_rename`, `lsp_code_actions`, `lsp_code_action_resolve`, `lsp_servers`
-- AST: `ast_grep_search`, `ast_grep_replace`
-- `python_repl`: persistent Python REPL for data analysis
-
-
-
-Skills are user-invocable commands (`/oh-my-claudecode:`). When you detect trigger patterns, invoke the corresponding skill.
-
-Workflow:
-- `autopilot` ("autopilot", "build me", "I want a"): full autonomous execution from idea to working code
-- `ralph` ("ralph", "don't stop", "must complete"): self-referential loop with verifier verification; includes ultrawork
-- `ultrawork` ("ulw", "ultrawork"): maximum parallelism with parallel agent orchestration
-- `team` ("team", "coordinated team", "team ralph"): N coordinated Claude agents with stage-aware routing; `team ralph` for persistent team execution
-- `omc-teams` ("omc-teams", "codex", "gemini"): spawn CLI workers in tmux panes
-- `ccg` ("ccg", "tri-model", "claude codex gemini"): fan out to Codex + Gemini, Claude synthesizes
-- `ultraqa` (activated by autopilot): QA cycling -- test, verify, fix, repeat
-- `omc-plan` ("plan this", "plan the"): strategic planning; supports `--consensus` and `--review`
-- `ralplan` ("ralplan", "consensus plan"): alias for `/omc-plan --consensus` -- iterative planning with Planner, Architect, Critic until consensus; short deliberation by default, `--deliberate` for high-risk work (adds pre-mortem + expanded unit/integration/e2e/observability test planning)
-- `sciomc` ("sciomc"): parallel scientist agents for comprehensive analysis
-- `external-context`: parallel document-specialist agents for web searches
-- `deepinit` ("deepinit"): deep codebase init with hierarchical AGENTS.md
-
-Agent Shortcuts (thin wrappers):
-- `analyze` -> `debugger`: "analyze", "debug", "investigate"
-- `tdd` -> `test-engineer`: "tdd", "test first", "red green"
-- `build-fix` -> `build-fixer`: "fix build", "type errors"
-- `code-review` -> `code-reviewer`: "review code"
-- `security-review` -> `security-reviewer`: "security review"
-- `review` -> `omc-plan --review`: "review plan", "critique plan"
-
-Notifications: `configure-notifications` ("configure discord", "setup telegram", "configure slack")
-Utilities: `cancel`, `note`, `learner`, `omc-setup`, `mcp-setup`, `hud`, `omc-doctor`, `omc-help`, `trace`, `release`, `project-session-manager`, `skill`, `writer-memory`, `ralph-init`, `learn-about-omc`
-
-Disambiguation: bare "codex"/"gemini" -> omc-teams; "claude codex gemini" -> ccg. Ralph includes ultrawork.
-
-
-
-Team is the default multi-agent orchestrator: `team-plan -> team-prd -> team-exec -> team-verify -> team-fix (loop)`
-
-Stage routing:
-- `team-plan`: `explore` + `planner`, optionally `analyst`/`architect`
-- `team-prd`: `analyst`, optionally `critic`
-- `team-exec`: `executor` + specialists (`designer`, `build-fixer`, `writer`, `test-engineer`, `deep-executor`)
-- `team-verify`: `verifier` + reviewers as needed
-- `team-fix`: `executor`/`build-fixer`/`debugger` depending on defect type
-
-Fix loop bounded by max attempts. Terminal states: `complete`, `failed`, `cancelled`.
-`team ralph` links both modes; cancelling either cancels both.
-
-
-
-Verify before claiming completion. Sizing: small (<5 files) -> `verifier` haiku; standard -> sonnet; large/security -> opus.
-Loop: identify proof, run verification, read output, report with evidence. If verification fails, keep iterating.
-
-
-
-Broad requests (vague verbs, no file/function targets, 3+ areas): explore first, then use plan skill.
-Parallelization: 2+ independent tasks in parallel; Team mode preferred; `run_in_background` for builds/tests.
-Continuation: before concluding, confirm zero pending tasks, tests passing, zero errors, verifier evidence collected.
-
-
-
-Hooks inject context via `` tags:
-- `hook success: Success` -- proceed normally
-- `hook additional context: ...` -- read it; relevant to your task
-- `[MAGIC KEYWORD: ...]` -- invoke the indicated skill immediately
-- `The boulder never stops` -- ralph/ultrawork mode; keep working
-
-Persistence: `info` (7 days), `info` (permanent).
-Kill switches: `DISABLE_OMC` (all hooks), `OMC_SKIP_HOOKS` (comma-separated).
-
-
-
-Invoke `/oh-my-claudecode:cancel` to end execution modes (`--force` to clear all state).
-Cancel when: tasks done and verified, work blocked (explain first), user says "stop".
-Do not cancel when: stop hook fires but work is still incomplete.
-
-
-
-All OMC state lives under git worktree root: `.omc/state/` (mode state), `.omc/state/sessions/{sessionId}/` (session state), `.omc/notepad.md`, `.omc/project-memory.json`, `.omc/plans/`, `.omc/research/`, `.omc/logs/`.
-
-
-## Setup
-Say "setup omc" or run `/oh-my-claudecode:omc-setup`. Announce major behavior activations to keep users informed.
-<\!-- OMC:END -->
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index c7d8367..7f22181 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -69,14 +69,13 @@ src/
hook/ ← Hook 도메인 (types, constants, utils, service, server, queries, config, components/)
skill/ ← Skill/Command 도메인 (types, utils, config, service, server, queries, components/)
subagent/ ← Subagent 도메인 (config, server, queries, components/)
+ mcp/ ← MCP 도메인 (config, utils, service, server, queries, components/)
+ plugin/ ← Plugin 도메인 (config, constants, service, server, queries, components/)
+ file/ ← File 도메인 (config, constants, service, server, queries, components/)
+ memory/ ← Memory 도메인 (config, service, server, components/)
components/
ui/ ← shadcn primitives (Button, Sheet, ListItem, Inspector 등)
- memory/ ← MemoryInspector
- agent/ ← AgentInspector
- mcp/ ← McpInspector
- plugin/ ← PluginInspector
- file/ ← FileInspector
- board/ ← BoardLayout, EntityListPanel, EntityInspector, Add*Dialog
+ board/ ← BoardLayout, EntityListPanel, EntityInspector, FilesPanel, PluginsPanel
layout/ ← Sidebar, StatusBar, AppHeader
config-editor/ ← Settings 페이지 (ConfigPage, 카테고리별 설정)
files-editor/ ← 파일 트리 (FileTree, FileViewerPanel)
@@ -84,28 +83,10 @@ src/
icons/ ← entity-icons, agent-logos, editor-icons
config/
entity-registry.ts ← EntityConfig 타입 + 레지스트리
- entities/ ← 엔티티별 config (skill, command, agent, mcp, plugin, memory, file)
- hooks/ ← React Query 커스텀 훅 (use-mcp, use-plugins 등)
- server/ ← Server Functions (createServerFn 기반)
- skills.ts, marketplace.ts, items.ts, agents.ts
- mcp-fns.ts, plugins-fns.ts, files.ts, overview.ts
- claude-md.ts, config-settings.ts, memory.ts, editor.ts
- config.ts, validation.ts, projects.ts, middleware/auth.ts
- services/ ← 서버 사이드 서비스
- config-service.ts ← 설정 파일 파싱 (스킬, 커맨드, 에이전트)
- agent-file-service.ts ← AgentFile CRUD
- skills-service.ts ← 스킬 설치/삭제 (skills.sh CLI)
- mcp-service.ts ← MCP 서버 관리 (Claude CLI 위임)
- plugin-service.ts ← 플러그인 관리
- memory-service.ts ← 메모리 파일 조회
- overview-service.ts ← 대시보드 개요 데이터
- files-scanner.service.ts ← 파일 트리 스캔
- claude-cli.ts ← Claude CLI spawn 래퍼
- file-writer.ts ← 파일 쓰기
- project-store.ts ← 프로젝트 경로 관리
- config-settings.service.ts ← settings.json 관리
- scope-management.ts ← 스코프 이동/복사
- agentfiles-config.ts ← agentfiles 자체 설정
+ entities/ ← index.ts (등록 진입점, 모든 config를 features/에서 import)
+ hooks/ ← 공용 React Query 훅 (use-config 등)
+ server/ ← 공용 Server Functions (overview, projects, editor 등)
+ services/ ← 공용 서비스 (claude-cli, file-writer, project-store 등)
routes/ ← TanStack Router 라우트 (/, /marketplace, /settings)
__root.tsx ← 루트 레이아웃
index.tsx ← Dashboard (/)
@@ -114,8 +95,8 @@ src/
api/ ← API 라우트
lib/ ← 유틸리티, 상수, 엔티티 액션/아이콘 정의
shared/
- types.ts ← 공유 타입·스키마 (AgentType, AgentConfig, Skill 등)
- agents.ts ← agents Record (에이전트별 설정 데이터)
+ types.ts ← 공유 타입·스키마
+ agents.ts ← agents Record
messages/ ← i18n 메시지 (en/ko)
bin/cli.ts ← CLI 진입점
tests/ ← 통합/E2E 테스트
@@ -223,22 +204,28 @@ interface EntityConfig {
v1에서 서비스를 추가하고, v2에서 AI 서비스를 추가한다.
```text
-현재 Services v1+ 추가 v2 추가
-┌────────────────────┐ ┌──────────────────┐ ┌──────────────────┐
-│ ConfigService │ │ MarketplaceService│ │ AIService │
-│ AgentFileService │ └──────────────────┘ │ ReleaseService │
-│ SkillsService │ └──────────────────┘
-│ HooksService │
-│ McpService │
-│ PluginService │
-│ MemoryService │
-│ OverviewService │
-│ FilesScannerService│
-│ FileWriter │
-│ Claude CLI │
-│ ProjectStore │
-│ ScopeManagement │
+현재 Services → 위치
+┌────────────────────┐
+│ ConfigService │ services/
+│ AgentFileService │ services/
+│ SkillsService │ features/skill/
+│ HooksService │ features/hook/
+│ McpService │ features/mcp/
+│ PluginService │ features/plugin/
+│ MemoryService │ features/memory/
+│ FilesScannerService│ features/file/
+│ OverviewService │ services/
+│ FileWriter │ services/
+│ Claude CLI │ services/
+│ ProjectStore │ services/
+│ ScopeManagement │ services/
└────────────────────┘
+
+v1+ 추가 v2 추가
+┌──────────────────┐ ┌──────────────────┐
+│ MarketplaceService│ │ AIService │
+└──────────────────┘ │ ReleaseService │
+ └──────────────────┘
```
---
diff --git a/docs/CONVENTIONS.md b/docs/CONVENTIONS.md
index 1867a9b..113215a 100644
--- a/docs/CONVENTIONS.md
+++ b/docs/CONVENTIONS.md
@@ -24,7 +24,7 @@
- 인프라(CLI spawn, 파일 I/O)와 비즈니스 로직을 같은 서비스 파일에 배치 (`claude-cli.ts`, `features/skill/service.ts` 참조)
- 별도 repository 파일로 분리하지 말 것 — 프로젝트 규모에서 과도한 추상화
-- Feature directory 패턴: 도메인별 타입, 서비스, 서버 함수, 쿼리, 컴포넌트를 `src/features/{domain}/`에 co-locate (예: `features/hook/`, `features/skill/`)
+- Feature directory 패턴: 도메인별 타입, 서비스, 서버 함수, 쿼리, 컴포넌트를 `src/features/{domain}/`에 co-locate. 전체 7개 도메인: `hook`, `skill`, `subagent`, `mcp`, `plugin`, `file`, `memory`
## shared/ 디렉토리 규칙
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index eb9baca..22affa6 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -129,6 +129,7 @@ Claude Code가 자체 설정 관리 GUI를 추가하면, 그걸 쓰면 된다. a
## Shipped
+- **MCP/Plugin/File/Memory feature directory 이관** (2026-03-20) — 나머지 4개 도메인을 `src/features/`로 통합. 전체 7개 엔티티 도메인 co-located 구조 완성. 통합 테스트 16개 + 복원 테스트 19개 추가. ARCHITECTURE.md 동기화.
- **Agent→Subagent 리네이밍 + feature directory 이관** (2026-03-19) — 엔티티 타입 `"agent"`→`"subagent"` 리네이밍. `src/features/subagent/`로 이관. i18n 키, board column, entity icons/actions 전면 업데이트. CONVENTIONS.md에 Agent vs Subagent 용어 규칙 추가.
- **Skill feature directory 이관** (2026-03-19) — Skill/Command 도메인을 `src/features/skill/`로 통합. utils 4개 파일 병합, config 병합, 유닛 테스트 co-locate.
- **Hook feature directory 이관 + 리스트 액션 제거** (2026-03-19) — Hook 도메인 파일을 `src/features/hook/`로 통합. 서버 Zod 스키마 중복 제거, Inspector에 EntityActionDropdown 적용. EntityListPanel에서 더보기 버튼/컨텍스트 메뉴 제거 (조작은 Inspector에서만).
diff --git a/src/components/board/BoardLayout.tsx b/src/components/board/BoardLayout.tsx
index 360efba..553c4e0 100644
--- a/src/components/board/BoardLayout.tsx
+++ b/src/components/board/BoardLayout.tsx
@@ -41,27 +41,24 @@ import {
SheetTitle,
} from "@/components/ui/sheet"
import { Switch } from "@/components/ui/switch"
-import {
- commandConfig,
- type HookItem,
- hookConfig,
- mcpConfig,
- memoryConfig,
- skillConfig,
- subagentConfig,
-} from "@/config/entities"
import { getEntityConfig } from "@/config/entity-registry"
import { AddHookDialog } from "@/features/hook/components/add-hook-dialog"
+import type { HookItem } from "@/features/hook/config"
+import { hookConfig } from "@/features/hook/config"
import { useHooksQuery } from "@/features/hook/queries"
import type { HookScope, HooksSettings } from "@/features/hook/types"
import { getHookIcon } from "@/features/hook/utils"
+import { AddMcpDialog } from "@/features/mcp/components/add-mcp-dialog"
+import { mcpConfig } from "@/features/mcp/config"
+import { useMcpMutations, useMcpQuery } from "@/features/mcp/queries"
+import { memoryConfig } from "@/features/memory/config"
+import { usePluginsQuery } from "@/features/plugin/queries"
+import { commandConfig, skillConfig } from "@/features/skill/config"
import { AddSubagentDialog } from "@/features/subagent/components/add-subagent-dialog"
+import { subagentConfig } from "@/features/subagent/config"
import { useAgentFiles, useMemoryFiles } from "@/hooks/use-config"
-import { useMcpMutations, useMcpQuery } from "@/hooks/use-mcp"
-import { usePluginsQuery } from "@/hooks/use-plugins"
import { m } from "@/paraglide/messages"
import type { BoardColumnId, Scope } from "@/shared/types"
-import { AddMcpDialog } from "./AddMcpDialog"
import { AddSkillDialog } from "./AddSkillDialog"
import { BoardColumnSettings } from "./BoardColumnSettings"
import { EntityListPanel } from "./EntityListPanel"
diff --git a/src/components/board/FilesPanel.tsx b/src/components/board/FilesPanel.tsx
index 408d8c9..7869494 100644
--- a/src/components/board/FilesPanel.tsx
+++ b/src/components/board/FilesPanel.tsx
@@ -5,8 +5,8 @@ import { FileTreeNode } from "@/components/files-editor/components/FileTree"
import { useProjectContext } from "@/components/ProjectContext"
import { Empty, EmptyDescription, EmptyMedia } from "@/components/ui/empty"
import { Skeleton } from "@/components/ui/skeleton"
+import { getFileTreeFn } from "@/features/file/server"
import { m } from "@/paraglide/messages"
-import { getFileTreeFn } from "@/server/files"
interface FilesPanelProps {
scopeFilter?: string
diff --git a/src/components/board/LspServersPanel.tsx b/src/components/board/LspServersPanel.tsx
index 585443d..de9fd3b 100644
--- a/src/components/board/LspServersPanel.tsx
+++ b/src/components/board/LspServersPanel.tsx
@@ -8,7 +8,7 @@ import {
EntityActionDropdown,
} from "@/components/ui/entity-action-menu"
import { ListItem } from "@/components/ui/list-item"
-import { usePluginsQuery } from "@/hooks/use-plugins"
+import { usePluginsQuery } from "@/features/plugin/queries"
import type { EntityActionId } from "@/lib/entity-actions"
import { ENTITY_ACTIONS } from "@/lib/entity-actions"
import { m } from "@/paraglide/messages"
diff --git a/src/components/board/PluginsPanel.tsx b/src/components/board/PluginsPanel.tsx
index 356ce95..92d96a8 100644
--- a/src/components/board/PluginsPanel.tsx
+++ b/src/components/board/PluginsPanel.tsx
@@ -12,13 +12,13 @@ import {
EntityActionDropdown,
} from "@/components/ui/entity-action-menu"
import { ListItem, ListSubItem } from "@/components/ui/list-item"
+import { useMcpStatusQuery } from "@/features/mcp/queries"
+import { getMcpIconClass } from "@/features/mcp/utils"
+import { usePluginsQuery } from "@/features/plugin/queries"
import { SkillListItem } from "@/features/skill/components/skill-list-item"
-import { useMcpStatusQuery } from "@/hooks/use-mcp"
-import { usePluginsQuery } from "@/hooks/use-plugins"
import type { EntityActionId } from "@/lib/entity-actions"
import { ENTITY_ACTIONS } from "@/lib/entity-actions"
import { titleCase } from "@/lib/format"
-import { getMcpIconClass } from "@/lib/mcp-status"
import { cn } from "@/lib/utils"
import { m } from "@/paraglide/messages"
import type { McpConnectionStatus, Plugin } from "@/shared/types"
diff --git a/src/components/board/entity-inspector.tsx b/src/components/board/entity-inspector.tsx
index b16cbb1..db879d1 100644
--- a/src/components/board/entity-inspector.tsx
+++ b/src/components/board/entity-inspector.tsx
@@ -1,10 +1,10 @@
// src/components/board/entity-inspector.tsx
-import { FileInspector } from "@/components/file/file-inspector"
-import { McpInspector } from "@/components/mcp/mcp-inspector"
-import { MemoryInspector } from "@/components/memory/memory-inspector"
-import { PluginInspector } from "@/components/plugin/plugin-inspector"
+import { FileInspector } from "@/features/file/components/file-inspector"
import { HookInspector } from "@/features/hook/components/hook-inspector"
+import { McpInspector } from "@/features/mcp/components/mcp-inspector"
+import { MemoryInspector } from "@/features/memory/components/memory-inspector"
+import { PluginInspector } from "@/features/plugin/components/plugin-inspector"
import { SkillInspector } from "@/features/skill/components/skill-inspector"
import { SubagentInspector } from "@/features/subagent/components/subagent-inspector"
diff --git a/src/components/config-editor/api/config.functions.ts b/src/components/config-editor/api/config.functions.ts
deleted file mode 100644
index 800b15d..0000000
--- a/src/components/config-editor/api/config.functions.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// Re-export from canonical location
-export * from "@/server/config-settings"
diff --git a/src/components/config-editor/api/config.queries.ts b/src/components/config-editor/api/config.queries.ts
deleted file mode 100644
index 63c0450..0000000
--- a/src/components/config-editor/api/config.queries.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// Re-export from canonical location
-export * from "@/hooks/use-config-settings"
diff --git a/src/components/config-editor/components/ConfigCategoryNav.tsx b/src/components/config-editor/components/ConfigCategoryNav.tsx
index a1c5422..59f8a7a 100644
--- a/src/components/config-editor/components/ConfigCategoryNav.tsx
+++ b/src/components/config-editor/components/ConfigCategoryNav.tsx
@@ -9,7 +9,7 @@ import {
import type { ElementType } from "react"
import { memo } from "react"
import { ListItem } from "@/components/ui/list-item"
-import { type CategoryId, CONFIG_CATEGORIES } from "../constants"
+import { type CategoryId, CONFIG_CATEGORIES } from "@/lib/config-constants"
const CATEGORY_ICONS: Record = {
general: SettingsIcon,
diff --git a/src/components/config-editor/components/ConfigPageContent.tsx b/src/components/config-editor/components/ConfigPageContent.tsx
index f3e9d54..6eb72cc 100644
--- a/src/components/config-editor/components/ConfigPageContent.tsx
+++ b/src/components/config-editor/components/ConfigPageContent.tsx
@@ -3,8 +3,8 @@ import { ArrowLeftIcon, ExternalLink } from "lucide-react"
import { useProjectContext } from "@/components/ProjectContext"
import { Button } from "@/components/ui/button"
import { Skeleton } from "@/components/ui/skeleton"
+import { useConfigMutations, useConfigQuery } from "@/hooks/use-config-settings"
import { m } from "@/paraglide/messages"
-import { useConfigMutations, useConfigQuery } from "../api/config.queries"
import { useConfigSelection } from "../context/ConfigContext"
import { ConfigCategoryNav } from "./ConfigCategoryNav"
import { ConfigScopeTabs } from "./ConfigScopeTabs"
diff --git a/src/components/config-editor/components/ConfigScopeTabs.tsx b/src/components/config-editor/components/ConfigScopeTabs.tsx
index a5b8df1..a1006e9 100644
--- a/src/components/config-editor/components/ConfigScopeTabs.tsx
+++ b/src/components/config-editor/components/ConfigScopeTabs.tsx
@@ -1,6 +1,6 @@
import { memo } from "react"
+import type { ConfigScope } from "@/lib/config-constants"
import { m } from "@/paraglide/messages"
-import type { ConfigScope } from "../constants"
interface ConfigScopeTabsProps {
scope: ConfigScope
diff --git a/src/components/config-editor/components/ConfigSettingsPanel.tsx b/src/components/config-editor/components/ConfigSettingsPanel.tsx
index 929a629..b9f3eda 100644
--- a/src/components/config-editor/components/ConfigSettingsPanel.tsx
+++ b/src/components/config-editor/components/ConfigSettingsPanel.tsx
@@ -1,4 +1,4 @@
-import type { CategoryId } from "../constants"
+import type { CategoryId } from "@/lib/config-constants"
import { AuthSettings } from "./categories/AuthSettings"
import { DisplaySettings } from "./categories/DisplaySettings"
import { EnvironmentSettings } from "./categories/EnvironmentSettings"
diff --git a/src/components/config-editor/constants.test.ts b/src/components/config-editor/constants.test.ts
index d56e11b..476518f 100644
--- a/src/components/config-editor/constants.test.ts
+++ b/src/components/config-editor/constants.test.ts
@@ -3,7 +3,7 @@ import {
type CategoryId,
CONFIG_CATEGORIES,
getCategoryById,
-} from "./constants"
+} from "@/lib/config-constants"
describe("constants", () => {
it("defines exactly 6 categories", () => {
diff --git a/src/components/config-editor/constants.ts b/src/components/config-editor/constants.ts
deleted file mode 100644
index 9cf691b..0000000
--- a/src/components/config-editor/constants.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// Re-export from canonical location
-export * from "@/lib/config-constants"
diff --git a/src/components/config-editor/context/ConfigContext.tsx b/src/components/config-editor/context/ConfigContext.tsx
index b9b20a9..1219622 100644
--- a/src/components/config-editor/context/ConfigContext.tsx
+++ b/src/components/config-editor/context/ConfigContext.tsx
@@ -10,7 +10,7 @@ import {
type ConfigScope,
DEFAULT_CATEGORY,
DEFAULT_SCOPE,
-} from "../constants"
+} from "@/lib/config-constants"
export interface ConfigContextValue {
scope: ConfigScope
diff --git a/src/components/files-editor/api/files.functions.ts b/src/components/files-editor/api/files.functions.ts
deleted file mode 100644
index 3bce4c3..0000000
--- a/src/components/files-editor/api/files.functions.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// Re-export from canonical location
-export * from "@/server/files"
diff --git a/src/components/files-editor/api/files.queries.ts b/src/components/files-editor/api/files.queries.ts
deleted file mode 100644
index eb12865..0000000
--- a/src/components/files-editor/api/files.queries.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// Re-export from canonical location
-export * from "@/hooks/use-files"
diff --git a/src/components/files-editor/components/FileViewerPanel.tsx b/src/components/files-editor/components/FileViewerPanel.tsx
index 06966e5..cb141df 100644
--- a/src/components/files-editor/components/FileViewerPanel.tsx
+++ b/src/components/files-editor/components/FileViewerPanel.tsx
@@ -10,8 +10,8 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Skeleton } from "@/components/ui/skeleton"
+import { useFileContentQuery } from "@/features/file/queries"
import { m } from "@/paraglide/messages"
-import { useFileContentQuery } from "../api/files.queries"
interface FileViewerPanelProps {
filePath: string | null
diff --git a/src/components/files-editor/constants.ts b/src/components/files-editor/constants.ts
deleted file mode 100644
index 05d26e7..0000000
--- a/src/components/files-editor/constants.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// Re-export from canonical location
-export * from "@/lib/file-constants"
diff --git a/src/config/entities/index.ts b/src/config/entities/index.ts
index c50246f..87133f4 100644
--- a/src/config/entities/index.ts
+++ b/src/config/entities/index.ts
@@ -1,11 +1,11 @@
import { registerEntity } from "@/config/entity-registry"
+import { fileConfig } from "@/features/file/config"
import { hookConfig } from "@/features/hook/config"
+import { mcpConfig } from "@/features/mcp/config"
+import { memoryConfig } from "@/features/memory/config"
+import { pluginConfig } from "@/features/plugin/config"
import { commandConfig, skillConfig } from "@/features/skill/config"
import { subagentConfig } from "@/features/subagent/config"
-import { fileConfig } from "./file-config"
-import { mcpConfig } from "./mcp-config"
-import { memoryConfig } from "./memory-config"
-import { pluginConfig } from "./plugin-config"
registerEntity(skillConfig)
registerEntity(commandConfig)
@@ -15,13 +15,3 @@ registerEntity(mcpConfig)
registerEntity(pluginConfig)
registerEntity(memoryConfig)
registerEntity(fileConfig)
-
-export type { HookItem } from "@/features/hook/config"
-export { hookConfig } from "@/features/hook/config"
-export { commandConfig, skillConfig } from "@/features/skill/config"
-export { subagentConfig } from "@/features/subagent/config"
-export type { FileItem } from "./file-config"
-export { fileConfig } from "./file-config"
-export { mcpConfig } from "./mcp-config"
-export { memoryConfig } from "./memory-config"
-export { pluginConfig } from "./plugin-config"
diff --git a/src/components/file/file-inspector.tsx b/src/features/file/components/file-inspector.tsx
similarity index 97%
rename from src/components/file/file-inspector.tsx
rename to src/features/file/components/file-inspector.tsx
index bf795e4..d23017d 100644
--- a/src/components/file/file-inspector.tsx
+++ b/src/features/file/components/file-inspector.tsx
@@ -16,7 +16,7 @@ import {
InspectorTitle,
} from "@/components/ui/inspector"
import { Skeleton } from "@/components/ui/skeleton"
-import { useFileContentQuery } from "@/hooks/use-files"
+import { useFileContentQuery } from "@/features/file/queries"
import { m } from "@/paraglide/messages"
interface FileInspectorProps {
diff --git a/src/config/entities/file-config.tsx b/src/features/file/config.ts
similarity index 100%
rename from src/config/entities/file-config.tsx
rename to src/features/file/config.ts
diff --git a/src/lib/file-constants.ts b/src/features/file/constants.ts
similarity index 100%
rename from src/lib/file-constants.ts
rename to src/features/file/constants.ts
diff --git a/src/hooks/use-files.ts b/src/features/file/queries.ts
similarity index 63%
rename from src/hooks/use-files.ts
rename to src/features/file/queries.ts
index 4e513dd..f4aa879 100644
--- a/src/hooks/use-files.ts
+++ b/src/features/file/queries.ts
@@ -1,8 +1,9 @@
import { useQuery } from "@tanstack/react-query"
import { useProjectContext } from "@/components/ProjectContext"
+import type { FilesScope } from "@/features/file/constants"
+import { getFileContentFn, getFileTreeFn } from "@/features/file/server"
import { INFREQUENT_REFETCH } from "@/hooks/use-config"
-import type { FilesScope } from "@/lib/file-constants"
-import { getFileContentFn, getFileTreeFn } from "@/server/files"
+import { queryKeys } from "@/lib/query-keys"
// ── Feature-local query keys ──
@@ -43,3 +44,18 @@ export function useFileContentQuery(filePath: string | null) {
...INFREQUENT_REFETCH,
})
}
+
+export function useClaudeMdFiles() {
+ const { activeProjectPath } = useProjectContext()
+ return useQuery({
+ queryKey: queryKeys.claudeMdFiles.byProject(activeProjectPath),
+ queryFn: async () => {
+ if (!activeProjectPath) return []
+ const { scanClaudeMdFilesFn } = await import("@/server/projects")
+ return scanClaudeMdFilesFn({ data: { projectPath: activeProjectPath } })
+ },
+ enabled: !!activeProjectPath,
+ refetchOnWindowFocus: true,
+ refetchInterval: 30_000,
+ })
+}
diff --git a/src/server/files.ts b/src/features/file/server.ts
similarity index 79%
rename from src/server/files.ts
rename to src/features/file/server.ts
index 8af1756..b6cd872 100644
--- a/src/server/files.ts
+++ b/src/features/file/server.ts
@@ -11,7 +11,7 @@ export const getFileTreeFn = createServerFn({ method: "GET" })
}),
)
.handler(async ({ data }) => {
- const { scanClaudeDir } = await import("@/services/files-scanner.service")
+ const { scanClaudeDir } = await import("@/features/file/service")
return scanClaudeDir(data.scope, data.projectPath)
})
@@ -22,6 +22,6 @@ export const getFileContentFn = createServerFn({ method: "GET" })
}),
)
.handler(async ({ data }) => {
- const { readFileContent } = await import("@/services/files-scanner.service")
+ const { readFileContent } = await import("@/features/file/service")
return readFileContent(data.filePath)
})
diff --git a/src/services/files-scanner.service.ts b/src/features/file/service.ts
similarity index 100%
rename from src/services/files-scanner.service.ts
rename to src/features/file/service.ts
diff --git a/src/components/board/AddMcpDialog.tsx b/src/features/mcp/components/add-mcp-dialog.tsx
similarity index 99%
rename from src/components/board/AddMcpDialog.tsx
rename to src/features/mcp/components/add-mcp-dialog.tsx
index 278860d..6a1ee36 100644
--- a/src/components/board/AddMcpDialog.tsx
+++ b/src/features/mcp/components/add-mcp-dialog.tsx
@@ -17,7 +17,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
-import { useMcpMutations } from "@/hooks/use-mcp"
+import { useMcpMutations } from "@/features/mcp/queries"
import { m } from "@/paraglide/messages"
import type { McpServer, Scope } from "@/shared/types"
diff --git a/src/components/mcp/mcp-inspector.tsx b/src/features/mcp/components/mcp-inspector.tsx
similarity index 97%
rename from src/components/mcp/mcp-inspector.tsx
rename to src/features/mcp/components/mcp-inspector.tsx
index 462b151..04685cc 100644
--- a/src/components/mcp/mcp-inspector.tsx
+++ b/src/features/mcp/components/mcp-inspector.tsx
@@ -20,8 +20,8 @@ import {
useMcpMutations,
useMcpQuery,
useMcpStatusQuery,
-} from "@/hooks/use-mcp"
-import { usePluginsQuery } from "@/hooks/use-plugins"
+} from "@/features/mcp/queries"
+import { usePluginsQuery } from "@/features/plugin/queries"
import { ENTITY_ACTIONS, type EntityActionId } from "@/lib/entity-actions"
import { queryKeys } from "@/lib/query-keys"
import { m } from "@/paraglide/messages"
@@ -122,7 +122,7 @@ export function McpInspector({ itemKey }: McpInspectorProps) {
break
}
case "delete": {
- const { removeMcpServerFn } = await import("@/server/mcp-fns")
+ const { removeMcpServerFn } = await import("@/features/mcp/server")
await toast.promise(
removeMcpServerFn({
data: {
diff --git a/src/config/entities/mcp-config.tsx b/src/features/mcp/config.ts
similarity index 100%
rename from src/config/entities/mcp-config.tsx
rename to src/features/mcp/config.ts
diff --git a/src/hooks/use-mcp.ts b/src/features/mcp/queries.ts
similarity index 98%
rename from src/hooks/use-mcp.ts
rename to src/features/mcp/queries.ts
index e098490..0520d73 100644
--- a/src/hooks/use-mcp.ts
+++ b/src/features/mcp/queries.ts
@@ -1,14 +1,14 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { useProjectContext } from "@/components/ProjectContext"
-import { INFREQUENT_REFETCH } from "@/hooks/use-config"
-import { queryKeys } from "@/lib/query-keys"
import {
addMcpServerFn,
getMcpServersFn,
getMcpStatusFn,
removeMcpServerFn,
toggleMcpServerFn,
-} from "@/server/mcp-fns"
+} from "@/features/mcp/server"
+import { INFREQUENT_REFETCH } from "@/hooks/use-config"
+import { queryKeys } from "@/lib/query-keys"
import type { McpConnectionStatus, Scope } from "@/shared/types"
// Feature-local query keys
diff --git a/src/server/mcp-fns.ts b/src/features/mcp/server.ts
similarity index 93%
rename from src/server/mcp-fns.ts
rename to src/features/mcp/server.ts
index f9754e5..c4c24eb 100644
--- a/src/server/mcp-fns.ts
+++ b/src/features/mcp/server.ts
@@ -5,7 +5,7 @@ import { scopeSchema } from "@/shared/types"
export const getMcpServersFn = createServerFn({ method: "GET" })
.inputValidator(z.object({ projectPath: z.string().optional() }))
.handler(async ({ data }) => {
- const { getMcpServers } = await import("@/services/mcp-service")
+ const { getMcpServers } = await import("@/features/mcp/service")
return getMcpServers(data.projectPath)
})
@@ -54,7 +54,7 @@ export const toggleMcpServerFn = createServerFn({ method: "POST" })
}),
)
.handler(async ({ data }) => {
- const { toggleMcpServer } = await import("@/services/mcp-service")
+ const { toggleMcpServer } = await import("@/features/mcp/service")
await toggleMcpServer(data.name, data.enable, data.projectPath)
return { success: true }
})
@@ -63,7 +63,7 @@ export const getMcpStatusFn = createServerFn({ method: "GET" })
.inputValidator(z.object({ projectPath: z.string().optional() }))
.handler(async ({ data }) => {
const { mcpListStatus } = await import("@/services/claude-cli")
- const { parseMcpList } = await import("@/services/mcp-service")
+ const { parseMcpList } = await import("@/features/mcp/service")
try {
// Run `claude mcp list` from the active project dir so it picks up
// project-scoped servers. Falls back to home dir if no project selected.
diff --git a/tests/unit/mcp-service.test.ts b/src/features/mcp/service.test.ts
similarity index 80%
rename from tests/unit/mcp-service.test.ts
rename to src/features/mcp/service.test.ts
index d9358c3..e6e5cb5 100644
--- a/tests/unit/mcp-service.test.ts
+++ b/src/features/mcp/service.test.ts
@@ -1,7 +1,7 @@
/**
* 단위 테스트: getPluginMcpServers()
*
- * plugin-service의 getPlugins()를 mock하여
+ * plugin service의 getPlugins()를 mock하여
* 플러그인에서 MCP 서버를 읽는 로직을 검증한다.
*/
@@ -15,15 +15,15 @@ vi.mock("node:fs/promises", () => ({
},
}))
-// plugin-service mock
-vi.mock("@/services/plugin-service", () => ({
+// plugin service mock
+vi.mock("@/features/plugin/service", () => ({
getPlugins: vi.fn(),
}))
// mock 설정 후 import
import fs from "node:fs/promises"
-import { getPluginMcpServers } from "@/services/mcp-service"
-import { getPlugins } from "@/services/plugin-service"
+import { getPlugins } from "@/features/plugin/service"
+import { getPluginMcpServers } from "./service"
// fs.readFile 오버로드 중 string 반환 버전을 사용
const mockedReadFile = vi.mocked(
@@ -93,7 +93,7 @@ describe("getPluginMcpServers()", () => {
})
})
- it("user scope 플러그인은 scope: global로 변환", async () => {
+ it("user scope 플러그인은 scope: user로 변환", async () => {
const plugin = makePlugin({ scope: "user", enabled: true })
mockedGetPlugins.mockResolvedValue([plugin])
@@ -160,35 +160,6 @@ describe("getPluginMcpServers()", () => {
expect(result).toEqual([])
})
- it(".mcp.json이 mcpServers 래퍼 없이 flat 형식인 경우에도 서버 반환", async () => {
- // 실제 context7 플러그인처럼 { "serverName": { command, args } } 형식
- const plugin = makePlugin({
- name: "context7",
- installPath: "/home/user/.claude/plugins/context7",
- enabled: true,
- })
- mockedGetPlugins.mockResolvedValue([plugin])
-
- mockedReadFile.mockResolvedValue(
- JSON.stringify({
- context7: {
- command: "npx",
- args: ["-y", "@upstash/context7-mcp"],
- },
- }),
- )
-
- const result = await getPluginMcpServers()
-
- expect(result).toHaveLength(1)
- expect(result[0]).toMatchObject({
- name: "context7",
- fromPlugin: "context7",
- command: "npx",
- type: "stdio",
- })
- })
-
it("여러 플러그인에서 서버를 모두 수집", async () => {
const plugin1 = makePlugin({
name: "plugin-a",
@@ -202,9 +173,8 @@ describe("getPluginMcpServers()", () => {
})
mockedGetPlugins.mockResolvedValue([plugin1, plugin2])
- mockedReadFile.mockImplementation((filePath: unknown) => {
- const p = filePath as string
- if (p.includes("/plugins/a")) {
+ mockedReadFile.mockImplementation((filePath: string) => {
+ if (filePath.includes("/plugins/a")) {
return Promise.resolve(
JSON.stringify({
mcpServers: { "server-a": { command: "node", args: ["a.js"] } },
diff --git a/src/services/mcp-service.ts b/src/features/mcp/service.ts
similarity index 99%
rename from src/services/mcp-service.ts
rename to src/features/mcp/service.ts
index 4877949..e07f0b4 100644
--- a/src/services/mcp-service.ts
+++ b/src/features/mcp/service.ts
@@ -1,7 +1,7 @@
import fs from "node:fs/promises"
import os from "node:os"
import path from "node:path"
-import { getPlugins } from "@/services/plugin-service"
+import { getPlugins } from "@/features/plugin/service"
import type { McpConnectionStatus, McpServer, Scope } from "@/shared/types"
/**
diff --git a/src/lib/mcp-status.ts b/src/features/mcp/utils.ts
similarity index 100%
rename from src/lib/mcp-status.ts
rename to src/features/mcp/utils.ts
diff --git a/src/components/memory/memory-inspector.tsx b/src/features/memory/components/memory-inspector.tsx
similarity index 100%
rename from src/components/memory/memory-inspector.tsx
rename to src/features/memory/components/memory-inspector.tsx
diff --git a/src/config/entities/memory-config.tsx b/src/features/memory/config.ts
similarity index 100%
rename from src/config/entities/memory-config.tsx
rename to src/features/memory/config.ts
diff --git a/src/server/memory.ts b/src/features/memory/server.ts
similarity index 80%
rename from src/server/memory.ts
rename to src/features/memory/server.ts
index 8a59345..94cc1fa 100644
--- a/src/server/memory.ts
+++ b/src/features/memory/server.ts
@@ -3,6 +3,6 @@ import { createServerFn } from "@tanstack/react-start"
export const getMemoryFilesFn = createServerFn({ method: "GET" })
.inputValidator((data: { projectPath: string }) => data)
.handler(async ({ data }) => {
- const { getMemoryFiles } = await import("@/services/memory-service")
+ const { getMemoryFiles } = await import("@/features/memory/service")
return getMemoryFiles(data.projectPath)
})
diff --git a/src/services/memory-service.test.ts b/src/features/memory/service.test.ts
similarity index 98%
rename from src/services/memory-service.test.ts
rename to src/features/memory/service.test.ts
index 58dc79c..434bddf 100644
--- a/src/services/memory-service.test.ts
+++ b/src/features/memory/service.test.ts
@@ -9,7 +9,7 @@ import {
getMemoryDir,
getMemoryFiles,
projectPathToSlug,
-} from "@/services/memory-service"
+} from "@/features/memory/service"
describe("projectPathToSlug", () => {
it("converts absolute path to slug", () => {
diff --git a/src/services/memory-service.ts b/src/features/memory/service.ts
similarity index 100%
rename from src/services/memory-service.ts
rename to src/features/memory/service.ts
diff --git a/src/components/plugin/plugin-inspector.tsx b/src/features/plugin/components/plugin-inspector.tsx
similarity index 97%
rename from src/components/plugin/plugin-inspector.tsx
rename to src/features/plugin/components/plugin-inspector.tsx
index d293667..5b71599 100644
--- a/src/components/plugin/plugin-inspector.tsx
+++ b/src/features/plugin/components/plugin-inspector.tsx
@@ -13,7 +13,7 @@ import {
InspectorSkeleton,
InspectorTitle,
} from "@/components/ui/inspector"
-import { usePluginsQuery } from "@/hooks/use-plugins"
+import { usePluginsQuery } from "@/features/plugin/queries"
import { ENTITY_ACTIONS, type EntityActionId } from "@/lib/entity-actions"
import { queryKeys } from "@/lib/query-keys"
import { m } from "@/paraglide/messages"
@@ -49,7 +49,7 @@ export function PluginInspector({ itemKey }: PluginInspectorProps) {
break
}
case "delete": {
- const { uninstallPluginFn } = await import("@/server/plugins-fns")
+ const { uninstallPluginFn } = await import("@/features/plugin/server")
await toast.promise(
uninstallPluginFn({
data: {
diff --git a/src/config/entities/plugin-config.tsx b/src/features/plugin/config.ts
similarity index 100%
rename from src/config/entities/plugin-config.tsx
rename to src/features/plugin/config.ts
diff --git a/src/lib/plugin-constants.ts b/src/features/plugin/constants.ts
similarity index 100%
rename from src/lib/plugin-constants.ts
rename to src/features/plugin/constants.ts
diff --git a/src/hooks/use-plugins.ts b/src/features/plugin/queries.ts
similarity index 97%
rename from src/hooks/use-plugins.ts
rename to src/features/plugin/queries.ts
index 56fc77a..f6c3122 100644
--- a/src/hooks/use-plugins.ts
+++ b/src/features/plugin/queries.ts
@@ -1,12 +1,12 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
-import { FREQUENT_REFETCH } from "@/hooks/use-config"
-import { queryKeys } from "@/lib/query-keys"
import {
getPluginsFn,
togglePluginFn,
uninstallPluginFn,
updatePluginFn,
-} from "@/server/plugins-fns"
+} from "@/features/plugin/server"
+import { FREQUENT_REFETCH } from "@/hooks/use-config"
+import { queryKeys } from "@/lib/query-keys"
import type { Scope } from "@/shared/types"
const pluginKeys = {
diff --git a/src/server/plugins-fns.ts b/src/features/plugin/server.ts
similarity index 96%
rename from src/server/plugins-fns.ts
rename to src/features/plugin/server.ts
index 6a482ba..977a5fc 100644
--- a/src/server/plugins-fns.ts
+++ b/src/features/plugin/server.ts
@@ -7,7 +7,7 @@ export const getPluginsFn = createServerFn({ method: "GET" })
.inputValidator(z.object({ projectPath: z.string().optional() }))
.handler(async ({ data }) => {
const { validateProjectPath } = await import("@/server/validation")
- const { getPlugins } = await import("@/services/plugin-service")
+ const { getPlugins } = await import("@/features/plugin/service")
const projectPath = data.projectPath
? validateProjectPath(data.projectPath)
: undefined
diff --git a/src/services/plugin-service.ts b/src/features/plugin/service.ts
similarity index 100%
rename from src/services/plugin-service.ts
rename to src/features/plugin/service.ts
diff --git a/src/features/skill/components/skill-inspector.tsx b/src/features/skill/components/skill-inspector.tsx
index e87daee..ff72249 100644
--- a/src/features/skill/components/skill-inspector.tsx
+++ b/src/features/skill/components/skill-inspector.tsx
@@ -33,8 +33,8 @@ import {
} from "@/components/ui/inspector"
import { Separator } from "@/components/ui/separator"
import { Skeleton } from "@/components/ui/skeleton"
+import { usePluginsQuery } from "@/features/plugin/queries"
import { useAgentFiles } from "@/hooks/use-config"
-import { usePluginsQuery } from "@/hooks/use-plugins"
import { ENTITY_ACTIONS, type EntityActionId } from "@/lib/entity-actions"
import { extractBody, formatDate, formatInstalls } from "@/lib/format"
import { queryKeys } from "@/lib/query-keys"
diff --git a/src/features/subagent/components/subagent-inspector.tsx b/src/features/subagent/components/subagent-inspector.tsx
index b0caece..a878894 100644
--- a/src/features/subagent/components/subagent-inspector.tsx
+++ b/src/features/subagent/components/subagent-inspector.tsx
@@ -14,9 +14,9 @@ import {
InspectorTitle,
} from "@/components/ui/inspector"
import { Separator } from "@/components/ui/separator"
+import { usePluginsQuery } from "@/features/plugin/queries"
import { useAgentFileDetailQuery } from "@/hooks/use-agent-file-detail"
import { useAgentFiles } from "@/hooks/use-config"
-import { usePluginsQuery } from "@/hooks/use-plugins"
import { ENTITY_ACTIONS, type EntityActionId } from "@/lib/entity-actions"
import { extractBody, formatDate } from "@/lib/format"
import { queryKeys } from "@/lib/query-keys"
diff --git a/src/hooks/use-claude-md-files.ts b/src/hooks/use-claude-md-files.ts
deleted file mode 100644
index cd0afef..0000000
--- a/src/hooks/use-claude-md-files.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { useQuery } from "@tanstack/react-query"
-import { useProjectContext } from "@/components/ProjectContext"
-import { queryKeys } from "@/lib/query-keys"
-
-export function useClaudeMdFiles() {
- const { activeProjectPath } = useProjectContext()
-
- return useQuery({
- queryKey: queryKeys.claudeMdFiles.byProject(activeProjectPath),
- queryFn: async () => {
- if (!activeProjectPath) return []
- const { scanClaudeMdFilesFn } = await import("@/server/projects")
- return scanClaudeMdFilesFn({ data: { projectPath: activeProjectPath } })
- },
- enabled: !!activeProjectPath,
- refetchOnWindowFocus: true,
- refetchInterval: 30_000,
- })
-}
diff --git a/src/hooks/use-config.ts b/src/hooks/use-config.ts
index aea40d8..4339636 100644
--- a/src/hooks/use-config.ts
+++ b/src/hooks/use-config.ts
@@ -159,7 +159,7 @@ export function useMemoryFiles() {
queryKey: queryKeys.memory.byProject(activeProjectPath),
queryFn: async () => {
if (!activeProjectPath) return []
- const { getMemoryFilesFn } = await import("@/server/memory")
+ const { getMemoryFilesFn } = await import("@/features/memory/server")
return getMemoryFilesFn({ data: { projectPath: activeProjectPath } })
},
enabled: !!activeProjectPath,
diff --git a/src/services/overview-service.ts b/src/services/overview-service.ts
index 6f10642..9bdbfcb 100644
--- a/src/services/overview-service.ts
+++ b/src/services/overview-service.ts
@@ -1,13 +1,13 @@
import path from "node:path"
+import { getMcpServers } from "@/features/mcp/service"
+import { getMemoryFiles } from "@/features/memory/service"
+import { getPlugins } from "@/features/plugin/service"
import { scanMdDirWithScope } from "@/services/agent-file-service"
import {
getClaudeMd,
getGlobalConfigPath,
getProjectConfigPath,
} from "@/services/config-service"
-import { getMcpServers } from "@/services/mcp-service"
-import { getMemoryFiles } from "@/services/memory-service"
-import { getPlugins } from "@/services/plugin-service"
import type { AgentFile, Overview } from "@/shared/types"
export async function getOverview(projectPath?: string): Promise {
diff --git a/tests/integration/error-edge-cases.test.ts b/tests/integration/error-edge-cases.test.ts
index cc0ca5b..7783567 100644
--- a/tests/integration/error-edge-cases.test.ts
+++ b/tests/integration/error-edge-cases.test.ts
@@ -2,11 +2,11 @@ import fs from "node:fs/promises"
import os from "node:os"
import path from "node:path"
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
+import { getPlugins } from "@/features/plugin/service"
import { validateItemName } from "@/server/validation"
import { scanMdDir } from "@/services/agent-file-service"
import { getClaudeMd } from "@/services/config-service"
import { writeMarkdown } from "@/services/file-writer"
-import { getPlugins } from "@/services/plugin-service"
import { createTmpDir, removeTmpDir, writeFile } from "../helpers/test-utils"
// ── 모킹: process.cwd() + os.homedir() ──
diff --git a/src/services/mcp-service.test.ts b/tests/integration/mcp-service.test.ts
similarity index 97%
rename from src/services/mcp-service.test.ts
rename to tests/integration/mcp-service.test.ts
index 6c3456c..75e4ef1 100644
--- a/src/services/mcp-service.test.ts
+++ b/tests/integration/mcp-service.test.ts
@@ -1,12 +1,8 @@
import os from "node:os"
import path from "node:path"
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
-import {
- createTmpDir,
- removeTmpDir,
- writeFile,
-} from "../../tests/helpers/test-utils"
-import { getMcpServers, parseMcpList } from "./mcp-service"
+import { getMcpServers, parseMcpList } from "@/features/mcp/service"
+import { createTmpDir, removeTmpDir, writeFile } from "../helpers/test-utils"
async function writeJson(filePath: string, data: unknown): Promise {
await writeFile(filePath, JSON.stringify(data, null, 2))
diff --git a/src/services/plugin-service.test.ts b/tests/integration/plugin-service.test.ts
similarity index 99%
rename from src/services/plugin-service.test.ts
rename to tests/integration/plugin-service.test.ts
index 34a875e..7025cf4 100644
--- a/src/services/plugin-service.test.ts
+++ b/tests/integration/plugin-service.test.ts
@@ -7,12 +7,8 @@ import {
getPlugins,
readPluginManifest,
scanPluginComponents,
-} from "@/services/plugin-service"
-import {
- createTmpDir,
- removeTmpDir,
- writeFile,
-} from "../../tests/helpers/test-utils"
+} from "@/features/plugin/service"
+import { createTmpDir, removeTmpDir, writeFile } from "../helpers/test-utils"
async function writeJson(filePath: string, data: unknown): Promise {
await writeFile(filePath, JSON.stringify(data, null, 2))