Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ EntityConfig (설정 객체) 공통 UI Primitives 도메
src/
features/ ← 도메인별 feature 디렉토리 (타입, 서비스, 서버, 쿼리, 컴포넌트 co-locate)
hook/ ← Hook 도메인 (types, constants, utils, service, server, queries, config, components/)
skill/ ← Skill/Command 도메인 (types, utils, config, service, server, queries, components/)
components/
ui/ ← shadcn primitives (Button, Sheet, ListItem, Inspector 등)
skill/ ← SkillInspector, SkillListItem
memory/ ← MemoryInspector
agent/ ← AgentInspector
mcp/ ← McpInspector
Expand Down
4 changes: 2 additions & 2 deletions docs/CONVENTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@

## 서비스 파일 규칙

- 인프라(CLI spawn, 파일 I/O)와 비즈니스 로직을 같은 서비스 파일에 배치 (`claude-cli.ts`, `skills-service.ts` 참조)
- 인프라(CLI spawn, 파일 I/O)와 비즈니스 로직을 같은 서비스 파일에 배치 (`claude-cli.ts`, `features/skill/service.ts` 참조)
- 별도 repository 파일로 분리하지 말 것 — 프로젝트 규모에서 과도한 추상화
- 서버 함수(`createServerFn`)는 도메인별 파일에 배치 (예: 스킬 CRUD → `server/skills.ts`, 마켓플레이스 검색 → `server/marketplace.ts`)
- Feature directory 패턴: 도메인별 타입, 서비스, 서버 함수, 쿼리, 컴포넌트를 `src/features/{domain}/`에 co-locate (예: `features/hook/`, `features/skill/`)

## shared/ 디렉토리 규칙

Expand Down
8 changes: 4 additions & 4 deletions docs/DESIGN-SYSTEM.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export function XxxPanel({ scopeFilter, onSelectItem, onAction }: XxxPanelProps)
2. `src/components/board/entity-inspector.tsx` — Inspector 라우팅
3. `src/components/icons/entity-icons.tsx` — 아이콘 추가
4. `messages/en/common.json` + `messages/ko/common.json` — `board_no_*` 메시지
5. `src/config/entities/` — 엔티티 config 파일
5. `src/config/entities/` 또는 `src/features/{domain}/config.ts` — 엔티티 config 파일

## Inspector 패턴

Expand Down Expand Up @@ -235,10 +235,10 @@ export function XxxInspector({ itemKey }: { itemKey: string }) {
## 참조 구현

새 패널이나 디테일 뷰를 추가할 때는 기존 구현을 참조:
- 엔티티 config: `src/config/entities/command-config.tsx` (groupBy 포함 최신 패턴)
- 엔티티 config: `src/features/skill/config.ts` (groupBy 포함 최신 패턴)
- 범용 패널: `src/components/board/EntityListPanel.tsx` (flat + grouped 지원)
- Inspector: `src/components/skill/skill-inspector.tsx`
- Feature directory 패턴: `src/features/hook/` (타입, 서비스, 서버, 쿼리, 컴포넌트 co-locate)
- Inspector: `src/features/skill/components/skill-inspector.tsx`
- Feature directory 패턴: `src/features/hook/`, `src/features/skill/` (타입, 서비스, 서버, 쿼리, 컴포넌트 co-locate)
- 레이아웃: `src/components/board/BoardLayout.tsx`

## 금지 사항
Expand Down
5 changes: 5 additions & 0 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ Claude Code가 자체 설정 관리 GUI를 추가하면, 그걸 쓰면 된다. a
- [ ] 스크린샷 — `docs/screenshots/`에 대시보드 캡처 2-3장, README에 삽입
- [ ] .github/ issue template — `bug_report.md`, `feature_request.md`

### UI / UX
- [ ] Typography 컴포넌트 추가 — shadcn typography 참고하여 `H1`, `H2`, `H3`, `Muted` 등 자주 쓰는 타이포 컴포넌트를 `components/ui/`에 정의
- [ ] 사이드바 복원 — 추가한 프로젝트 리스트를 사이드바에 표시하여 빠른 전환 지원. `⌘+B`로 토글. 현재 스위치 방식보다 직관적

### 코드 정리
- [ ] PascalCase → kebab-case 일괄 리네이밍 — `src/components/` 내 기존 PascalCase 파일을 kebab-case로 통일
- [ ] 대시보드 ActionDropdown → 디테일 패널 액션 이동 — 리스트 아이템 → 디테일 패널 헤더로 통합
Expand All @@ -125,6 +129,7 @@ Claude Code가 자체 설정 관리 GUI를 추가하면, 그걸 쓰면 된다. a

## Shipped

- **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에서만).
- **Inspector 아키텍처 단순화** (2026-03-18) — `DashboardDetailTarget` union / `toDetailTarget` 제거. Inspector가 `itemKey`만 받아 자체 fetch 및 액션 처리. `use-entity-action-handler.ts` 제거.
- **Inspector 리디자인** (2026-03-18) — DetailPanel/EntityDetailPanel/DetailContent 시스템을 Inspector primitives + 도메인별 full Inspector로 전환. entity/ 해소, 도메인 디렉토리 이동.
Expand Down
4 changes: 2 additions & 2 deletions src/components/board/AddSkillDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
FieldLabel,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { useSkillMutations } from "@/hooks/use-skills"
import { addSkillSchema } from "@/lib/skill-constants"
import { useSkillMutations } from "@/features/skill/queries"
import { addSkillSchema } from "@/features/skill/types"
import { m } from "@/paraglide/messages"
import type { Scope } from "@/shared/types"

Expand Down
2 changes: 1 addition & 1 deletion src/components/board/PluginsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { ChevronDown } from "lucide-react"
import { type ElementType, useEffect, useMemo, useRef, useState } from "react"
import { ENTITY_ICONS } from "@/components/icons/entity-icons"
import { useProjectContext } from "@/components/ProjectContext"
import { SkillListItem } from "@/components/skill/skill-list-item"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible"
Expand All @@ -13,6 +12,7 @@ import {
EntityActionDropdown,
} from "@/components/ui/entity-action-menu"
import { ListItem, ListSubItem } from "@/components/ui/list-item"
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"
Expand Down
2 changes: 1 addition & 1 deletion src/components/board/entity-inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ 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 { SkillInspector } from "@/components/skill/skill-inspector"
import { HookInspector } from "@/features/hook/components/hook-inspector"
import { SkillInspector } from "@/features/skill/components/skill-inspector"

interface EntityInspectorProps {
type: string
Expand Down
10 changes: 5 additions & 5 deletions src/components/marketplace/skills-tab.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { PackageSearchIcon } from "lucide-react"
import { useCallback, useState } from "react"
import { SkillInspector } from "@/components/skill/skill-inspector"
import { SkillListItem } from "@/components/skill/skill-list-item"
import { Badge } from "@/components/ui/badge"
import { Empty, EmptyDescription, EmptyMedia } from "@/components/ui/empty"
import { ListItemSkeleton } from "@/components/ui/list-item"
import { SkillInspector } from "@/features/skill/components/skill-inspector"
import { SkillListItem } from "@/features/skill/components/skill-list-item"
import { useSkillActions, useSkillsQuery } from "@/features/skill/queries"
import type { MarketplaceSkill } from "@/features/skill/types"
import { isSameSkill } from "@/features/skill/utils"
import { useMarketplaceSearch } from "@/hooks/use-marketplace"
import { useSkillActions, useSkillsQuery } from "@/hooks/use-skills"
import { formatInstalls } from "@/lib/format"
import { isSameSkill } from "@/lib/skill-utils"
import { m } from "@/paraglide/messages"
import type { MarketplaceSkill } from "@/services/skills-service"
import type { AgentFile } from "@/shared/types"
import { MarketplaceSearchBar } from "./marketplace-search-bar"

Expand Down
7 changes: 2 additions & 5 deletions src/config/entities/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { registerEntity } from "@/config/entity-registry"
import { hookConfig } from "@/features/hook/config"
import { commandConfig, skillConfig } from "@/features/skill/config"
import { agentConfig } from "./agent-config"
import { commandConfig } from "./command-config"
import { fileConfig } from "./file-config"
import { mcpConfig } from "./mcp-config"
import { memoryConfig } from "./memory-config"
import { pluginConfig } from "./plugin-config"
import { skillConfig } from "./skill-config"

// 모듈 import 시 자동 등록
registerEntity(skillConfig)
registerEntity(commandConfig)
registerEntity(agentConfig)
Expand All @@ -20,11 +18,10 @@ registerEntity(fileConfig)

export type { HookItem } from "@/features/hook/config"
export { hookConfig } from "@/features/hook/config"
export { commandConfig, skillConfig } from "@/features/skill/config"
export { agentConfig } from "./agent-config"
export { commandConfig } from "./command-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"
export { skillConfig } from "./skill-config"
12 changes: 0 additions & 12 deletions src/config/entities/skill-config.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,16 @@ import { Separator } from "@/components/ui/separator"
import { Skeleton } from "@/components/ui/skeleton"
import { useAgentFiles } from "@/hooks/use-config"
import { usePluginsQuery } from "@/hooks/use-plugins"
import { useSkillDetailQuery } from "@/hooks/use-skill-detail"
import { useSkillActions } from "@/hooks/use-skills"
import { ENTITY_ACTIONS, type EntityActionId } from "@/lib/entity-actions"
import { extractBody, formatDate, formatInstalls } from "@/lib/format"
import { queryKeys } from "@/lib/query-keys"
import { resolveInstallStatus } from "@/lib/skill-install-status"
import { getSlashCommand } from "@/lib/slash-command"
import { m } from "@/paraglide/messages"
import { getLocale } from "@/paraglide/runtime"
import type { MarketplaceSkill } from "@/services/skills-service"
import type { AgentFile, AgentType, InstallStatus } from "@/shared/types"
import { agentTypeSchema } from "@/shared/types"

function isAgentFile(item: AgentFile | MarketplaceSkill): item is AgentFile {
return "path" in item
}
import { useSkillActions, useSkillDetailQuery } from "../queries"
import type { MarketplaceSkill } from "../types"
import { getSlashCommand, isAgentFile, resolveInstallStatus } from "../utils"

interface SkillInspectorProps {
itemKey: string
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ENTITY_ICONS } from "@/components/icons/entity-icons"
import { ListItem } from "@/components/ui/list-item"
import type { MarketplaceSkill } from "@/services/skills-service"
import type { AgentFile } from "@/shared/types"
import type { MarketplaceSkill } from "../types"

interface SkillListItemProps {
skill: AgentFile | MarketplaceSkill
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { describe, expect, it } from "vitest"
import type { AgentFile } from "@/shared/types"
import { commandConfig } from "./command-config"
import { skillConfig } from "./skill-config"
import { commandConfig, skillConfig } from "./config"

function makeAgentFile(overrides: Partial<AgentFile>): AgentFile {
function makeAgentFile(overrides: Partial<AgentFile> = {}): AgentFile {
return {
name: "test",
scope: "user",
Expand All @@ -20,7 +19,7 @@ describe("skillConfig", () => {
expect(skillConfig.type).toBe("skill")
})

it("groupBy가 없어야 한다 (skills는 namespace 없음)", () => {
it("groupBy가 없어야 한다", () => {
expect(skillConfig.groupBy).toBeUndefined()
})
})
Expand All @@ -36,7 +35,6 @@ describe("commandConfig", () => {
name: "greet",
namespace: "ys",
})
expect(commandConfig.groupBy).toBeDefined()
expect(commandConfig.groupBy?.(cmd)).toBe("ys")
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import type { EntityConfig } from "@/config/entity-registry"
import { ENTITY_ACTIONS } from "@/lib/entity-actions"
import type { AgentFile } from "@/shared/types"

export const skillConfig: EntityConfig<AgentFile> = {
type: "skill",
actions: ENTITY_ACTIONS.skill.map((a) => a.id),
getKey: (item) => item.path,
getLabel: (item) => item.frontmatter?.name ?? item.name,
getDescription: (item) => item.frontmatter?.description,
getScope: (item) => item.scope,
}

export const commandConfig: EntityConfig<AgentFile> = {
type: "command",
actions: ENTITY_ACTIONS.skill.map((a) => a.id),
Expand Down
24 changes: 21 additions & 3 deletions src/hooks/use-skills.ts → src/features/skill/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { useProjectContext } from "@/components/ProjectContext"
import { FREQUENT_REFETCH } from "@/hooks/use-config"
import { queryKeys } from "@/lib/query-keys"
import type { AgentFile, AgentType, InstallStatus, Scope } from "@/shared/types"
import {
createSkillFn,
getSkillDetailFn,
installSkillFn,
readSkillsLockFn,
readSupportingFileFn,
removeSkillFn,
saveFrontmatterFn,
} from "@/server/skills"
import type { MarketplaceSkill } from "@/services/skills-service"
import type { AgentType, InstallStatus, Scope } from "@/shared/types"
} from "./server"
import type { MarketplaceSkill } from "./types"
import { isAgentFile } from "./utils"

// ── Queries ──────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -58,6 +60,22 @@ export function useSkillsLock() {
})
}

export function useSkillDetailQuery(item: AgentFile | MarketplaceSkill) {
const isLocal = isAgentFile(item)

return useQuery({
queryKey: isLocal
? ["skillDetail", item.path]
: ["skillDetail", item.source, item.skillId],
queryFn: () =>
getSkillDetailFn({
data: isLocal
? { path: item.path }
: { source: item.source, skillId: item.skillId },
}),
})
}

// ── Mutations ────────────────────────────────────────────────────────────────

export function useSkillMutations() {
Expand Down
4 changes: 2 additions & 2 deletions src/server/skills.ts → src/features/skill/server.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { createServerFn } from "@tanstack/react-start"
import { z } from "zod"
import { agentTypeSchema, scopeSchema } from "@/shared/types"
import {
getSkillDetail,
installSkill,
readSkillsLock,
removeSkill,
} from "@/services/skills-service"
import { agentTypeSchema, scopeSchema } from "@/shared/types"
} from "./service"

export const readSupportingFileFn = createServerFn({ method: "GET" })
.inputValidator(
Expand Down
55 changes: 9 additions & 46 deletions src/services/skills-service.ts → src/features/skill/service.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,18 @@
// src/services/skills-service.ts
// src/features/skill/service.ts
// Skills domain logic: marketplace search, CLI spawn, lock file reading, skill detail
import { spawn } from "node:child_process"
import fs from "node:fs/promises"
import os from "node:os"
import path from "node:path"
import type { Skill } from "@/shared/types"

// ── Types ────────────────────────────────────────────────────────────────────

export interface SkillsCliResult {
success: boolean
output: string
}

export interface SkillsInstallOptions {
source: string
skill: string
scope: "user" | "project"
agent?: string
projectPath?: string
}

export interface SkillsRemoveOptions {
name: string
scope: "user" | "project"
agent?: string
projectPath?: string
}

export interface MarketplaceSkill {
id: string
skillId: string
name: string
source: string
installs: number
description?: string
}

export interface SkillsLockEntry {
source: string
sourceType: string
scope: "user" | "project"
}

export interface SkillsLockData {
skills: Record<string, SkillsLockEntry>
}

export type SkillDetailParams =
| { path: string }
| { source: string; skillId: string }
import type {
MarketplaceSkill,
SkillDetailParams,
SkillsCliResult,
SkillsInstallOptions,
SkillsLockData,
SkillsRemoveOptions,
} from "./types"

// ── Private: CLI infrastructure ──────────────────────────────────────────────

Expand Down
Loading
Loading