@kmgeon/taskflow 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +374 -0
- package/bin/task-mcp.mjs +19 -0
- package/bin/task.mjs +19 -0
- package/docs/clean-code.md +29 -0
- package/docs/git.md +36 -0
- package/docs/guideline.md +25 -0
- package/docs/security.md +32 -0
- package/docs/step-by-step.md +29 -0
- package/docs/superpowers/specs/2026-03-21-cli-advisor-design.md +383 -0
- package/docs/superpowers/specs/2026-03-21-init-redesign-design.md +429 -0
- package/docs/superpowers/specs/2026-03-21-skill-architecture-design.md +362 -0
- package/docs/superpowers/specs/2026-03-23-t-create-task-run-design.md +40 -0
- package/docs/superpowers/specs/2026-03-23-task-run-design.md +44 -0
- package/docs/tdd.md +41 -0
- package/package.json +114 -0
- package/src/app/(protected)/dashboard/page.tsx +7 -0
- package/src/app/(protected)/layout.tsx +10 -0
- package/src/app/api/[[...hono]]/route.ts +13 -0
- package/src/app/example/page.tsx +11 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +168 -0
- package/src/app/layout.tsx +35 -0
- package/src/app/page.tsx +5 -0
- package/src/app/providers.tsx +57 -0
- package/src/backend/config/index.ts +36 -0
- package/src/backend/hono/app.ts +32 -0
- package/src/backend/hono/context.ts +38 -0
- package/src/backend/http/response.ts +64 -0
- package/src/backend/middleware/context.ts +23 -0
- package/src/backend/middleware/error.ts +31 -0
- package/src/backend/middleware/supabase.ts +23 -0
- package/src/backend/supabase/client.ts +17 -0
- package/src/cli/commands/__tests__/task-commands.test.ts +170 -0
- package/src/cli/commands/advisor.ts +45 -0
- package/src/cli/commands/ask.ts +50 -0
- package/src/cli/commands/board.ts +72 -0
- package/src/cli/commands/init.ts +184 -0
- package/src/cli/commands/list.ts +138 -0
- package/src/cli/commands/run.ts +143 -0
- package/src/cli/commands/set-status.ts +50 -0
- package/src/cli/commands/show.ts +28 -0
- package/src/cli/commands/tree.ts +72 -0
- package/src/cli/index.ts +38 -0
- package/src/cli/lib/__tests__/formatter.test.ts +123 -0
- package/src/cli/lib/error-boundary.test.ts +135 -0
- package/src/cli/lib/error-boundary.ts +70 -0
- package/src/cli/lib/formatter.ts +764 -0
- package/src/cli/lib/trd.ts +33 -0
- package/src/cli/lib/validate.test.ts +89 -0
- package/src/cli/lib/validate.ts +43 -0
- package/src/cli/prompts/task-run.md +25 -0
- package/src/components/layout/AppLayout.tsx +15 -0
- package/src/components/layout/Sidebar.tsx +124 -0
- package/src/components/ui/accordion.tsx +58 -0
- package/src/components/ui/avatar.tsx +50 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/button.tsx +56 -0
- package/src/components/ui/card.tsx +79 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/dialog.tsx +122 -0
- package/src/components/ui/dropdown-menu.tsx +200 -0
- package/src/components/ui/file-upload.tsx +50 -0
- package/src/components/ui/form.tsx +179 -0
- package/src/components/ui/input.tsx +25 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/scroll-area.tsx +48 -0
- package/src/components/ui/select.tsx +160 -0
- package/src/components/ui/separator.tsx +31 -0
- package/src/components/ui/sheet.tsx +140 -0
- package/src/components/ui/textarea.tsx +22 -0
- package/src/components/ui/toast.tsx +129 -0
- package/src/components/ui/toaster.tsx +35 -0
- package/src/core/ai/claude-client.ts +79 -0
- package/src/core/claude-runner/flag-builder.ts +57 -0
- package/src/core/claude-runner/index.ts +2 -0
- package/src/core/claude-runner/spawner.ts +86 -0
- package/src/core/prd/__tests__/auto-analyzer.test.ts +35 -0
- package/src/core/prd/__tests__/generator.test.ts +26 -0
- package/src/core/prd/__tests__/scanner.test.ts +35 -0
- package/src/core/prd/auto-analyzer.ts +9 -0
- package/src/core/prd/generator.ts +8 -0
- package/src/core/prd/scanner.ts +117 -0
- package/src/core/project/__tests__/claude-setup.test.ts +133 -0
- package/src/core/project/__tests__/config.test.ts +30 -0
- package/src/core/project/__tests__/init.test.ts +37 -0
- package/src/core/project/__tests__/skill-setup.test.ts +62 -0
- package/src/core/project/claude-setup.ts +224 -0
- package/src/core/project/config.ts +34 -0
- package/src/core/project/docs-setup.ts +26 -0
- package/src/core/project/docs-templates.ts +205 -0
- package/src/core/project/init.ts +40 -0
- package/src/core/project/skill-setup.ts +32 -0
- package/src/core/project/skill-templates.ts +277 -0
- package/src/core/task/index.ts +16 -0
- package/src/core/types.ts +58 -0
- package/src/features/example/backend/error.ts +9 -0
- package/src/features/example/backend/route.ts +52 -0
- package/src/features/example/backend/schema.ts +25 -0
- package/src/features/example/backend/service.ts +73 -0
- package/src/features/example/components/example-status.test.tsx +97 -0
- package/src/features/example/components/example-status.tsx +160 -0
- package/src/features/example/hooks/useExampleQuery.ts +23 -0
- package/src/features/example/lib/dto.test.ts +57 -0
- package/src/features/example/lib/dto.ts +5 -0
- package/src/features/kanban/backend/__tests__/sse-broadcaster.test.ts +137 -0
- package/src/features/kanban/backend/__tests__/sse-event-format.test.ts +55 -0
- package/src/features/kanban/backend/route.ts +55 -0
- package/src/features/kanban/backend/sse-broadcaster.ts +142 -0
- package/src/features/kanban/backend/sse-route.ts +43 -0
- package/src/features/kanban/components/KanbanBoard.tsx +105 -0
- package/src/features/kanban/components/KanbanColumn.tsx +51 -0
- package/src/features/kanban/components/KanbanError.tsx +29 -0
- package/src/features/kanban/components/KanbanSkeleton.tsx +46 -0
- package/src/features/kanban/components/ProgressCard.tsx +42 -0
- package/src/features/kanban/components/TaskCard.tsx +76 -0
- package/src/features/kanban/components/__tests__/kanban-components.test.tsx +86 -0
- package/src/features/kanban/hooks/useTaskSse.ts +66 -0
- package/src/features/kanban/hooks/useTasksQuery.ts +52 -0
- package/src/features/kanban/lib/__tests__/kanban-utils.test.ts +97 -0
- package/src/features/kanban/lib/kanban-utils.ts +37 -0
- package/src/features/taskflow/constants.ts +54 -0
- package/src/features/taskflow/index.ts +27 -0
- package/src/features/taskflow/lib/__tests__/filter.test.ts +89 -0
- package/src/features/taskflow/lib/__tests__/graph.test.ts +247 -0
- package/src/features/taskflow/lib/__tests__/repository.test.ts +233 -0
- package/src/features/taskflow/lib/__tests__/serializer.test.ts +98 -0
- package/src/features/taskflow/lib/advisor/__tests__/advisor-integration.test.ts +98 -0
- package/src/features/taskflow/lib/advisor/ai-advisor.test.ts +40 -0
- package/src/features/taskflow/lib/advisor/ai-advisor.ts +20 -0
- package/src/features/taskflow/lib/advisor/context-builder.test.ts +73 -0
- package/src/features/taskflow/lib/advisor/context-builder.ts +151 -0
- package/src/features/taskflow/lib/advisor/db.test.ts +106 -0
- package/src/features/taskflow/lib/advisor/db.ts +185 -0
- package/src/features/taskflow/lib/advisor/local-summary.test.ts +53 -0
- package/src/features/taskflow/lib/advisor/local-summary.ts +72 -0
- package/src/features/taskflow/lib/advisor/prompts.ts +86 -0
- package/src/features/taskflow/lib/filter.ts +54 -0
- package/src/features/taskflow/lib/fs-utils.ts +50 -0
- package/src/features/taskflow/lib/graph.ts +148 -0
- package/src/features/taskflow/lib/index-builder.ts +42 -0
- package/src/features/taskflow/lib/repository.ts +168 -0
- package/src/features/taskflow/lib/serializer.ts +62 -0
- package/src/features/taskflow/lib/watcher.ts +40 -0
- package/src/features/taskflow/types.ts +71 -0
- package/src/hooks/use-toast.ts +194 -0
- package/src/lib/remote/api-client.ts +40 -0
- package/src/lib/supabase/client.ts +8 -0
- package/src/lib/supabase/server.ts +46 -0
- package/src/lib/supabase/types.ts +3 -0
- package/src/lib/utils.ts +6 -0
- package/src/mcp/index.ts +7 -0
- package/src/mcp/server.ts +21 -0
- package/src/mcp/tools/brainstorm.ts +48 -0
- package/src/mcp/tools/prd.ts +71 -0
- package/src/mcp/tools/project.ts +39 -0
- package/src/mcp/tools/task-status.ts +40 -0
- package/src/mcp/tools/task.ts +82 -0
- package/src/mcp/util.ts +6 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
export const SKILL_TEMPLATES: Record<string, string> = {
|
|
2
|
+
prd: `# PRD 생성
|
|
3
|
+
|
|
4
|
+
사용자와 대화하며 PRD(Product Requirements Document)를 작성합니다.
|
|
5
|
+
|
|
6
|
+
## 사용 가능한 MCP 도구
|
|
7
|
+
- \`mcp__taskflow__scan_codebase\` — 코드베이스 파일 목록/시그니처 스캔
|
|
8
|
+
- \`mcp__taskflow__save_prd\` — PRD를 .taskflow/prd.md에 저장
|
|
9
|
+
|
|
10
|
+
## 워크플로우
|
|
11
|
+
|
|
12
|
+
1. 먼저 \`mcp__taskflow__scan_codebase\`로 현재 프로젝트 상태를 파악합니다.
|
|
13
|
+
2. 사용자에게 **한 번에 하나씩** 질문하며 요구사항을 수집합니다:
|
|
14
|
+
- 프로젝트명, 한 줄 요약
|
|
15
|
+
- 타겟 사용자
|
|
16
|
+
- 해결하려는 문제 (Pain Points)와 해결 방안
|
|
17
|
+
- 목표 및 핵심 지표 (KPI)
|
|
18
|
+
- 주요 사용 시나리오
|
|
19
|
+
- 필수 기능 (Must-Have)과 선택 기능 (Nice-to-Have)
|
|
20
|
+
- 비기능 요구사항 (성능, 보안 등)
|
|
21
|
+
- 기술 스택
|
|
22
|
+
- 범위 (포함/제외)
|
|
23
|
+
- 마일스톤
|
|
24
|
+
- 리스크 및 완화 전략
|
|
25
|
+
3. 충분한 정보가 모이면 PRD 마크다운을 작성합니다.
|
|
26
|
+
4. 사용자에게 최종 확인을 받은 후 \`mcp__taskflow__save_prd\`로 저장합니다.
|
|
27
|
+
|
|
28
|
+
## PRD 형식
|
|
29
|
+
|
|
30
|
+
다음 11개 섹션을 포함하는 마크다운으로 작성합니다:
|
|
31
|
+
|
|
32
|
+
1. 제품 개요
|
|
33
|
+
2. 타겟 사용자
|
|
34
|
+
3. 해결하려는 문제 및 솔루션 (표)
|
|
35
|
+
4. 목표 및 핵심 지표
|
|
36
|
+
5. 주요 사용 시나리오
|
|
37
|
+
6. 기능 요구사항 (표: #, 기능, 우선순위)
|
|
38
|
+
7. 비기능 요구사항
|
|
39
|
+
8. 기술 스택
|
|
40
|
+
9. 범위 (포함/제외)
|
|
41
|
+
10. 마일스톤
|
|
42
|
+
11. 리스크 및 완화 전략
|
|
43
|
+
|
|
44
|
+
## 규칙
|
|
45
|
+
- 한국어로 작성합니다.
|
|
46
|
+
- 한 번에 하나의 질문만 합니다.
|
|
47
|
+
- 가능하면 객관식으로 제시합니다.
|
|
48
|
+
- 사용자의 답변이 모호하면 구체적으로 되물어봅니다.
|
|
49
|
+
`,
|
|
50
|
+
trd: `# TRD 생성 (Task Implementation Plan)
|
|
51
|
+
|
|
52
|
+
PRD를 기반으로 기술적 구현 계획(TRD)을 작성합니다.
|
|
53
|
+
|
|
54
|
+
## 사용 가능한 MCP 도구
|
|
55
|
+
- \`mcp__taskflow__read_prd\` — .taskflow/prd.md 읽기
|
|
56
|
+
- \`mcp__taskflow__list_tasks\` — 기존 태스크 목록 확인
|
|
57
|
+
|
|
58
|
+
## 워크플로우
|
|
59
|
+
|
|
60
|
+
1. \`mcp__taskflow__read_prd\`로 PRD를 읽습니다.
|
|
61
|
+
2. \`mcp__taskflow__list_tasks\`로 기존 태스크가 있는지 확인합니다.
|
|
62
|
+
3. PRD를 분석하여 구현 단계별 TRD를 작성합니다:
|
|
63
|
+
- 각 단계의 목적과 산출물
|
|
64
|
+
- 기술적 결정사항 (라이브러리, 패턴, 구조)
|
|
65
|
+
- 단계 간 의존성
|
|
66
|
+
- 리스크 및 대안
|
|
67
|
+
4. 사용자에게 섹션별로 확인을 받습니다.
|
|
68
|
+
5. 최종 확인 후 Write 도구로 \`.taskflow/trd.md\`에 저장합니다.
|
|
69
|
+
|
|
70
|
+
## TRD 형식
|
|
71
|
+
|
|
72
|
+
# {프로젝트명} — TRD (Task Implementation Plan)
|
|
73
|
+
|
|
74
|
+
## 아키텍처 개요
|
|
75
|
+
(시스템 구조, 주요 컴포넌트, 데이터 흐름)
|
|
76
|
+
|
|
77
|
+
## Phase 1: {단계명}
|
|
78
|
+
### 목적
|
|
79
|
+
### 산출물
|
|
80
|
+
### 기술 결정사항
|
|
81
|
+
### 태스크 목록
|
|
82
|
+
### 의존성
|
|
83
|
+
### 리스크
|
|
84
|
+
|
|
85
|
+
## Phase 2: ...
|
|
86
|
+
|
|
87
|
+
## 규칙
|
|
88
|
+
- 한국어로 작성합니다.
|
|
89
|
+
- PRD의 우선순위(Must-Have → Nice-to-Have)를 반영합니다.
|
|
90
|
+
- 각 Phase는 독립적으로 배포 가능한 단위로 나눕니다.
|
|
91
|
+
`,
|
|
92
|
+
"parse-prd": `# PRD → 태스크 분해
|
|
93
|
+
|
|
94
|
+
PRD를 분석하여 개별 태스크 파일을 자동 생성합니다.
|
|
95
|
+
|
|
96
|
+
## 사용 가능한 MCP 도구
|
|
97
|
+
- \`mcp__taskflow__read_prd\` — .taskflow/prd.md 읽기
|
|
98
|
+
- \`mcp__taskflow__list_tasks\` — 기존 태스크 확인 (중복 방지)
|
|
99
|
+
- \`mcp__taskflow__create_task\` — 태스크 생성
|
|
100
|
+
|
|
101
|
+
## 워크플로우
|
|
102
|
+
|
|
103
|
+
1. \`mcp__taskflow__read_prd\`로 PRD를 읽습니다.
|
|
104
|
+
2. \`mcp__taskflow__list_tasks\`로 기존 태스크를 확인합니다.
|
|
105
|
+
3. PRD의 기능 요구사항을 분석하여 태스크 목록을 도출합니다:
|
|
106
|
+
- 각 태스크의 제목, 설명, 우선순위
|
|
107
|
+
- 태스크 간 의존성
|
|
108
|
+
- 예상 복잡도
|
|
109
|
+
4. 도출된 태스크 목록을 사용자에게 보여주고 확인을 받습니다.
|
|
110
|
+
5. 승인 후 \`mcp__taskflow__create_task\`로 하나씩 생성합니다.
|
|
111
|
+
6. 생성 결과를 요약하여 보여줍니다.
|
|
112
|
+
|
|
113
|
+
## 태스크 분해 기준
|
|
114
|
+
- Must-Have 기능 → 높은 우선순위 태스크
|
|
115
|
+
- Nice-to-Have 기능 → 낮은 우선순위 태스크
|
|
116
|
+
- 하나의 기능이 크면 여러 태스크로 분할
|
|
117
|
+
- 의존성이 있으면 dependencies에 명시
|
|
118
|
+
- 기존 태스크와 중복되면 스킵
|
|
119
|
+
|
|
120
|
+
## 규칙
|
|
121
|
+
- 한국어로 작성합니다.
|
|
122
|
+
- 태스크 하나는 1~2일 이내에 완료할 수 있는 크기로 분할합니다.
|
|
123
|
+
- 분해 결과를 보여주고 반드시 사용자 승인을 받은 후 생성합니다.
|
|
124
|
+
`,
|
|
125
|
+
brainstorm: `# 태스크 브레인스토밍
|
|
126
|
+
|
|
127
|
+
특정 태스크를 서브태스크로 분해합니다.
|
|
128
|
+
|
|
129
|
+
## 사용 가능한 MCP 도구
|
|
130
|
+
- \`mcp__taskflow__list_tasks\` — 전체 태스크 목록 조회
|
|
131
|
+
- \`mcp__taskflow__read_task\` — 태스크 상세 읽기
|
|
132
|
+
- \`mcp__taskflow__expand_subtasks\` — 서브태스크 파일 생성
|
|
133
|
+
|
|
134
|
+
## 워크플로우
|
|
135
|
+
|
|
136
|
+
1. 사용자에게 분해할 태스크 ID를 확인합니다.
|
|
137
|
+
- ID를 모르면 \`mcp__taskflow__list_tasks\`로 목록을 보여줍니다.
|
|
138
|
+
2. \`mcp__taskflow__read_task\`로 해당 태스크의 상세 정보를 읽습니다.
|
|
139
|
+
3. 사용자와 대화하며 분해 방향을 논의합니다:
|
|
140
|
+
- 어떤 관점으로 나눌지 (기능별, 계층별, 단계별)
|
|
141
|
+
- 어느 정도 깊이로 나눌지
|
|
142
|
+
4. 서브태스크 목록을 제안합니다 (제목, 설명, 우선순위, 의존성).
|
|
143
|
+
5. 사용자 확인 후 \`mcp__taskflow__expand_subtasks\`로 생성합니다.
|
|
144
|
+
|
|
145
|
+
## 규칙
|
|
146
|
+
- 한국어로 작성합니다.
|
|
147
|
+
- 서브태스크는 4시간 이내에 완료 가능한 크기로 나눕니다.
|
|
148
|
+
- 반드시 사용자 승인 후 생성합니다.
|
|
149
|
+
`,
|
|
150
|
+
refine: `# 요구사항 변경 분석
|
|
151
|
+
|
|
152
|
+
요구사항 변경이 기존 태스크에 미치는 영향을 분석하고 업데이트합니다.
|
|
153
|
+
|
|
154
|
+
## 사용 가능한 MCP 도구
|
|
155
|
+
- \`mcp__taskflow__read_prd\` — 현재 PRD 읽기
|
|
156
|
+
- \`mcp__taskflow__list_tasks\` — 전체 태스크 조회
|
|
157
|
+
- \`mcp__taskflow__read_task\` — 개별 태스크 읽기
|
|
158
|
+
- \`mcp__taskflow__update_task\` — 태스크 수정
|
|
159
|
+
- \`mcp__taskflow__create_task\` — 새 태스크 생성
|
|
160
|
+
- \`mcp__taskflow__delete_task\` — 불필요한 태스크 삭제
|
|
161
|
+
- \`mcp__taskflow__save_prd\` — PRD 업데이트
|
|
162
|
+
|
|
163
|
+
## 워크플로우
|
|
164
|
+
|
|
165
|
+
1. 사용자에게 변경사항을 확인합니다:
|
|
166
|
+
- 어떤 요구사항이 바뀌었는지 설명을 듣거나
|
|
167
|
+
- 변경된 파일/diff를 확인합니다
|
|
168
|
+
2. \`mcp__taskflow__read_prd\`로 현재 PRD를 읽습니다.
|
|
169
|
+
3. \`mcp__taskflow__list_tasks\`로 전체 태스크를 조회합니다.
|
|
170
|
+
4. 변경사항이 영향을 미치는 태스크를 식별합니다:
|
|
171
|
+
- 수정이 필요한 태스크
|
|
172
|
+
- 새로 추가해야 할 태스크
|
|
173
|
+
- 더 이상 필요 없는 태스크
|
|
174
|
+
5. 영향 분석 결과를 사용자에게 표로 보여줍니다.
|
|
175
|
+
6. 사용자 승인 후:
|
|
176
|
+
- \`mcp__taskflow__update_task\`로 수정
|
|
177
|
+
- \`mcp__taskflow__create_task\`로 추가
|
|
178
|
+
- \`mcp__taskflow__delete_task\`로 삭제
|
|
179
|
+
7. 필요시 \`mcp__taskflow__save_prd\`로 PRD도 업데이트합니다.
|
|
180
|
+
|
|
181
|
+
## 규칙
|
|
182
|
+
- 한국어로 작성합니다.
|
|
183
|
+
- 변경 전/후를 명확히 대비하여 보여줍니다.
|
|
184
|
+
- 반드시 사용자 승인 후 수정합니다.
|
|
185
|
+
`,
|
|
186
|
+
next: `# 다음 태스크 추천
|
|
187
|
+
|
|
188
|
+
의존성과 우선순위를 기반으로 다음 작업할 태스크를 추천합니다.
|
|
189
|
+
|
|
190
|
+
## 사용 가능한 MCP 도구
|
|
191
|
+
- \`mcp__taskflow__get_next_task\` — 추천 태스크 조회
|
|
192
|
+
- \`mcp__taskflow__read_task\` — 태스크 상세 읽기
|
|
193
|
+
- \`mcp__taskflow__set_task_status\` — 태스크 상태 변경
|
|
194
|
+
|
|
195
|
+
## 워크플로우
|
|
196
|
+
|
|
197
|
+
1. \`mcp__taskflow__get_next_task\`로 추천 태스크 목록을 조회합니다.
|
|
198
|
+
2. 각 추천 태스크에 대해:
|
|
199
|
+
- \`mcp__taskflow__read_task\`로 상세 정보를 읽습니다.
|
|
200
|
+
- 추천 이유를 설명합니다 (의존성 해소됨, 높은 우선순위 등).
|
|
201
|
+
3. 사용자가 태스크를 선택하면 \`mcp__taskflow__set_task_status\`로 상태를 \`in-progress\`로 변경합니다.
|
|
202
|
+
|
|
203
|
+
## 규칙
|
|
204
|
+
- 한국어로 응답합니다.
|
|
205
|
+
- 추천 목록은 최대 3개까지 보여줍니다.
|
|
206
|
+
- 각 추천에 이유를 명확히 설명합니다.
|
|
207
|
+
`,
|
|
208
|
+
"task-status": `# 프로젝트 진행 상황
|
|
209
|
+
|
|
210
|
+
현재 프로젝트의 태스크 진행 상황을 요약합니다.
|
|
211
|
+
|
|
212
|
+
## 사용 가능한 MCP 도구
|
|
213
|
+
- \`mcp__taskflow__list_tasks\` — 전체 태스크 조회
|
|
214
|
+
|
|
215
|
+
## 워크플로우
|
|
216
|
+
|
|
217
|
+
1. \`mcp__taskflow__list_tasks\`로 전체 태스크를 조회합니다.
|
|
218
|
+
2. 다음을 요약하여 보여줍니다:
|
|
219
|
+
- 상태별 태스크 수 (Todo / In-Progress / Blocked / Done)
|
|
220
|
+
- 전체 완료율 (%)
|
|
221
|
+
- 현재 진행 중인 태스크
|
|
222
|
+
- 블로커가 있는 태스크
|
|
223
|
+
- 의존성이 해소되어 시작 가능한 태스크
|
|
224
|
+
3. 주요 이슈나 지연된 태스크가 있으면 하이라이트합니다.
|
|
225
|
+
|
|
226
|
+
## 규칙
|
|
227
|
+
- 한국어로 응답합니다.
|
|
228
|
+
- 간결한 표 형식으로 요약합니다.
|
|
229
|
+
`,
|
|
230
|
+
"t-create": `# 기능 요구사항 정의 (브레인스토밍 + TRD)
|
|
231
|
+
|
|
232
|
+
브레인스토밍을 통해 명확한 TRD(Task Requirements Document)를 작성합니다.
|
|
233
|
+
태스크 분해는 하지 않습니다 — task run이 담당합니다.
|
|
234
|
+
|
|
235
|
+
## 중요
|
|
236
|
+
- 코드를 직접 수정하거나 구현하지 마세요
|
|
237
|
+
- 오직 요구사항을 수집하고 TRD를 작성하는 것만 합니다
|
|
238
|
+
|
|
239
|
+
## 워크플로우
|
|
240
|
+
|
|
241
|
+
### Phase 1: 요구사항 수집
|
|
242
|
+
사용자의 첫 메시지를 기능 요청으로 이해하고 추가 질문으로 구체화합니다.
|
|
243
|
+
- 한 번에 하나의 질문만
|
|
244
|
+
- 가능하면 객관식(A/B/C)으로 제시
|
|
245
|
+
- 목적, 사용자 시나리오, 기술 제약, 성공 기준에 집중
|
|
246
|
+
|
|
247
|
+
### Phase 2: 접근법 탐색 (브레인스토밍)
|
|
248
|
+
요구사항이 충분히 모이면 **2-3가지 접근법**을 제안합니다:
|
|
249
|
+
- 각 접근법의 장단점(트레이드오프) 설명
|
|
250
|
+
- 추천 접근법을 먼저 제시 + 이유
|
|
251
|
+
- 사용자 선택 대기
|
|
252
|
+
|
|
253
|
+
### Phase 3: TRD 작성
|
|
254
|
+
선택된 접근법 기반으로 TRD를 섹션별로 나눠서 제시하고, 각 섹션마다 사용자 확인을 받습니다.
|
|
255
|
+
|
|
256
|
+
TRD 형식:
|
|
257
|
+
# {기능명} — TRD
|
|
258
|
+
## 개요
|
|
259
|
+
## 사용자 시나리오
|
|
260
|
+
## 기술 설계 (아키텍처, 데이터 모델, API, UI/UX)
|
|
261
|
+
## 의존성
|
|
262
|
+
## 성공 기준
|
|
263
|
+
## 리스크
|
|
264
|
+
|
|
265
|
+
### Phase 4: 파일 저장
|
|
266
|
+
TRD 전체 승인 후 Write 도구로 저장:
|
|
267
|
+
- 파일명: .taskflow/trd-{기능명-kebab-case}.md
|
|
268
|
+
|
|
269
|
+
저장 후 안내: "task run으로 자동 구현을 시작할 수 있습니다"
|
|
270
|
+
|
|
271
|
+
## 규칙
|
|
272
|
+
- 한국어로 대화합니다
|
|
273
|
+
- 접근법 제안 없이 바로 TRD 작성하지 마세요
|
|
274
|
+
- YAGNI: 불필요한 기능 추가하지 마세요
|
|
275
|
+
- 태스크 분해는 하지 마세요
|
|
276
|
+
`,
|
|
277
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Phase 1: Re-export from existing feature module
|
|
2
|
+
// Phase 2: Move files here and update imports
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
ensureRepo,
|
|
6
|
+
readTask,
|
|
7
|
+
listTasks,
|
|
8
|
+
createTask,
|
|
9
|
+
updateTask,
|
|
10
|
+
deleteTask,
|
|
11
|
+
searchTasks,
|
|
12
|
+
} from "../../features/taskflow/lib/repository.js";
|
|
13
|
+
|
|
14
|
+
export { parseTask, serializeTask } from "../../features/taskflow/lib/serializer.js";
|
|
15
|
+
export { filterTasks, sortTasks } from "../../features/taskflow/lib/filter.js";
|
|
16
|
+
export { detectCycles, computeReadySet, recommend } from "../../features/taskflow/lib/graph.js";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Re-export existing task types for convenience
|
|
2
|
+
export type {
|
|
3
|
+
Task,
|
|
4
|
+
TaskStatus,
|
|
5
|
+
TaskCreateInput,
|
|
6
|
+
TaskUpdateInput,
|
|
7
|
+
TaskFilter,
|
|
8
|
+
TaskSortKey,
|
|
9
|
+
TaskSortOrder,
|
|
10
|
+
} from "../features/taskflow/types.js";
|
|
11
|
+
|
|
12
|
+
export { TASK_STATUSES } from "../features/taskflow/types.js";
|
|
13
|
+
|
|
14
|
+
// PRD types (consolidated from cli/flows/auto.ts and cli/flows/interactive.ts)
|
|
15
|
+
export interface PrdResult {
|
|
16
|
+
markdown: string;
|
|
17
|
+
meta: {
|
|
18
|
+
projectName: string;
|
|
19
|
+
generatedAt: string;
|
|
20
|
+
mode: "brainstorm" | "auto";
|
|
21
|
+
filesScanned?: number;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Feature PRD types
|
|
26
|
+
export interface FeaturePrdResult {
|
|
27
|
+
markdown: string;
|
|
28
|
+
meta: {
|
|
29
|
+
projectName: string;
|
|
30
|
+
featureName: string;
|
|
31
|
+
generatedAt: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Project config
|
|
36
|
+
export interface TaskFlowConfig {
|
|
37
|
+
version: string;
|
|
38
|
+
project: {
|
|
39
|
+
name: string;
|
|
40
|
+
summary?: string;
|
|
41
|
+
stack?: string[];
|
|
42
|
+
};
|
|
43
|
+
tasks: {
|
|
44
|
+
statusFlow: string[];
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Task brainstorm result
|
|
49
|
+
export interface TaskBrainstormResult {
|
|
50
|
+
subtasks: Array<{
|
|
51
|
+
title: string;
|
|
52
|
+
description: string;
|
|
53
|
+
priority: number;
|
|
54
|
+
dependencies?: string[];
|
|
55
|
+
estimate?: string;
|
|
56
|
+
}>;
|
|
57
|
+
rationale?: string;
|
|
58
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const exampleErrorCodes = {
|
|
2
|
+
notFound: 'EXAMPLE_NOT_FOUND',
|
|
3
|
+
fetchError: 'EXAMPLE_FETCH_ERROR',
|
|
4
|
+
validationError: 'EXAMPLE_VALIDATION_ERROR',
|
|
5
|
+
} as const;
|
|
6
|
+
|
|
7
|
+
type ExampleErrorValue = (typeof exampleErrorCodes)[keyof typeof exampleErrorCodes];
|
|
8
|
+
|
|
9
|
+
export type ExampleServiceError = ExampleErrorValue;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import {
|
|
3
|
+
failure,
|
|
4
|
+
respond,
|
|
5
|
+
type ErrorResult,
|
|
6
|
+
} from '@/backend/http/response';
|
|
7
|
+
import {
|
|
8
|
+
getLogger,
|
|
9
|
+
getSupabase,
|
|
10
|
+
type AppEnv,
|
|
11
|
+
} from '@/backend/hono/context';
|
|
12
|
+
import { ExampleParamsSchema } from '@/features/example/backend/schema';
|
|
13
|
+
import { getExampleById } from './service';
|
|
14
|
+
import {
|
|
15
|
+
exampleErrorCodes,
|
|
16
|
+
type ExampleServiceError,
|
|
17
|
+
} from './error';
|
|
18
|
+
|
|
19
|
+
export const registerExampleRoutes = (app: Hono<AppEnv>) => {
|
|
20
|
+
app.get('/example/:id', async (c) => {
|
|
21
|
+
const parsedParams = ExampleParamsSchema.safeParse({ id: c.req.param('id') });
|
|
22
|
+
|
|
23
|
+
if (!parsedParams.success) {
|
|
24
|
+
return respond(
|
|
25
|
+
c,
|
|
26
|
+
failure(
|
|
27
|
+
400,
|
|
28
|
+
'INVALID_EXAMPLE_PARAMS',
|
|
29
|
+
'The provided example id is invalid.',
|
|
30
|
+
parsedParams.error.format(),
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const supabase = getSupabase(c);
|
|
36
|
+
const logger = getLogger(c);
|
|
37
|
+
|
|
38
|
+
const result = await getExampleById(supabase, parsedParams.data.id);
|
|
39
|
+
|
|
40
|
+
if (!result.ok) {
|
|
41
|
+
const errorResult = result as ErrorResult<ExampleServiceError, unknown>;
|
|
42
|
+
|
|
43
|
+
if (errorResult.error.code === exampleErrorCodes.fetchError) {
|
|
44
|
+
logger.error('Failed to fetch example', errorResult.error.message);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return respond(c, result);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return respond(c, result);
|
|
51
|
+
});
|
|
52
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const ExampleParamsSchema = z.object({
|
|
4
|
+
id: z.string().uuid({ message: 'Example id must be a valid UUID.' }),
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
export const ExampleResponseSchema = z.object({
|
|
8
|
+
id: z.string().uuid(),
|
|
9
|
+
fullName: z.string(),
|
|
10
|
+
avatarUrl: z.string().url(),
|
|
11
|
+
bio: z.string().nullable(),
|
|
12
|
+
updatedAt: z.string(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export type ExampleResponse = z.infer<typeof ExampleResponseSchema>;
|
|
16
|
+
|
|
17
|
+
export const ExampleTableRowSchema = z.object({
|
|
18
|
+
id: z.string().uuid(),
|
|
19
|
+
full_name: z.string().nullable(),
|
|
20
|
+
avatar_url: z.string().nullable(),
|
|
21
|
+
bio: z.string().nullable(),
|
|
22
|
+
updated_at: z.string(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export type ExampleRow = z.infer<typeof ExampleTableRowSchema>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import {
|
|
3
|
+
failure,
|
|
4
|
+
success,
|
|
5
|
+
type HandlerResult,
|
|
6
|
+
} from '@/backend/http/response';
|
|
7
|
+
import {
|
|
8
|
+
ExampleResponseSchema,
|
|
9
|
+
ExampleTableRowSchema,
|
|
10
|
+
type ExampleResponse,
|
|
11
|
+
type ExampleRow,
|
|
12
|
+
} from '@/features/example/backend/schema';
|
|
13
|
+
import {
|
|
14
|
+
exampleErrorCodes,
|
|
15
|
+
type ExampleServiceError,
|
|
16
|
+
} from '@/features/example/backend/error';
|
|
17
|
+
|
|
18
|
+
const EXAMPLE_TABLE = 'example';
|
|
19
|
+
|
|
20
|
+
const fallbackAvatar = (id: string) =>
|
|
21
|
+
`https://picsum.photos/seed/${encodeURIComponent(id)}/200/200`;
|
|
22
|
+
|
|
23
|
+
export const getExampleById = async (
|
|
24
|
+
client: SupabaseClient,
|
|
25
|
+
id: string,
|
|
26
|
+
): Promise<HandlerResult<ExampleResponse, ExampleServiceError, unknown>> => {
|
|
27
|
+
const { data, error } = await client
|
|
28
|
+
.from(EXAMPLE_TABLE)
|
|
29
|
+
.select('id, full_name, avatar_url, bio, updated_at')
|
|
30
|
+
.eq('id', id)
|
|
31
|
+
.maybeSingle<ExampleRow>();
|
|
32
|
+
|
|
33
|
+
if (error) {
|
|
34
|
+
return failure(500, exampleErrorCodes.fetchError, error.message);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!data) {
|
|
38
|
+
return failure(404, exampleErrorCodes.notFound, 'Example not found');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const rowParse = ExampleTableRowSchema.safeParse(data);
|
|
42
|
+
|
|
43
|
+
if (!rowParse.success) {
|
|
44
|
+
return failure(
|
|
45
|
+
500,
|
|
46
|
+
exampleErrorCodes.validationError,
|
|
47
|
+
'Example row failed validation.',
|
|
48
|
+
rowParse.error.format(),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const mapped = {
|
|
53
|
+
id: rowParse.data.id,
|
|
54
|
+
fullName: rowParse.data.full_name ?? 'Anonymous User',
|
|
55
|
+
avatarUrl:
|
|
56
|
+
rowParse.data.avatar_url ?? fallbackAvatar(rowParse.data.id),
|
|
57
|
+
bio: rowParse.data.bio,
|
|
58
|
+
updatedAt: rowParse.data.updated_at,
|
|
59
|
+
} satisfies ExampleResponse;
|
|
60
|
+
|
|
61
|
+
const parsed = ExampleResponseSchema.safeParse(mapped);
|
|
62
|
+
|
|
63
|
+
if (!parsed.success) {
|
|
64
|
+
return failure(
|
|
65
|
+
500,
|
|
66
|
+
exampleErrorCodes.validationError,
|
|
67
|
+
'Example payload failed validation.',
|
|
68
|
+
parsed.error.format(),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return success(parsed.data);
|
|
73
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
5
|
+
import { ExampleStatus } from './example-status';
|
|
6
|
+
|
|
7
|
+
// Mock the API client
|
|
8
|
+
vi.mock('@/lib/remote/api-client', () => ({
|
|
9
|
+
apiClient: {
|
|
10
|
+
get: vi.fn(),
|
|
11
|
+
},
|
|
12
|
+
extractApiErrorMessage: vi.fn((error, defaultMsg) => defaultMsg),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
const createWrapper = () => {
|
|
16
|
+
const queryClient = new QueryClient({
|
|
17
|
+
defaultOptions: {
|
|
18
|
+
queries: {
|
|
19
|
+
retry: false,
|
|
20
|
+
gcTime: 0,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function Wrapper({ children }: { children: React.ReactNode }) {
|
|
26
|
+
return (
|
|
27
|
+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return Wrapper;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
describe('ExampleStatus', () => {
|
|
35
|
+
it('should render the component correctly', () => {
|
|
36
|
+
render(<ExampleStatus />, { wrapper: createWrapper() });
|
|
37
|
+
|
|
38
|
+
expect(screen.getByText(/Backend Health Check/i)).toBeInTheDocument();
|
|
39
|
+
expect(screen.getByPlaceholderText(/00000000-0000-0000-0000-000000000000/i)).toBeInTheDocument();
|
|
40
|
+
expect(screen.getByRole('button', { name: /조회하기/i })).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should display idle status initially', () => {
|
|
44
|
+
render(<ExampleStatus />, { wrapper: createWrapper() });
|
|
45
|
+
|
|
46
|
+
expect(screen.getByText(/Idle/i)).toBeInTheDocument();
|
|
47
|
+
expect(screen.getByText(/UUID를 입력하고 조회하기 버튼을 누르면/i)).toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should update input value when user types', async () => {
|
|
51
|
+
const user = userEvent.setup();
|
|
52
|
+
render(<ExampleStatus />, { wrapper: createWrapper() });
|
|
53
|
+
|
|
54
|
+
const input = screen.getByPlaceholderText(/00000000-0000-0000-0000-000000000000/i);
|
|
55
|
+
|
|
56
|
+
await user.type(input, 'test-id');
|
|
57
|
+
|
|
58
|
+
expect(input).toHaveValue('test-id');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should clear results when empty input is submitted', async () => {
|
|
62
|
+
const user = userEvent.setup();
|
|
63
|
+
render(<ExampleStatus />, { wrapper: createWrapper() });
|
|
64
|
+
|
|
65
|
+
const input = screen.getByPlaceholderText(/00000000-0000-0000-0000-000000000000/i);
|
|
66
|
+
const button = screen.getByRole('button', { name: /조회하기/i });
|
|
67
|
+
|
|
68
|
+
// Submit empty input
|
|
69
|
+
await user.type(input, ' ');
|
|
70
|
+
await user.click(button);
|
|
71
|
+
|
|
72
|
+
expect(screen.getByText(/Idle/i)).toBeInTheDocument();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should show current status header', () => {
|
|
76
|
+
render(<ExampleStatus />, { wrapper: createWrapper() });
|
|
77
|
+
|
|
78
|
+
expect(screen.getByText(/현재 상태/i)).toBeInTheDocument();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should have accessible form elements', () => {
|
|
82
|
+
render(<ExampleStatus />, { wrapper: createWrapper() });
|
|
83
|
+
|
|
84
|
+
const input = screen.getByPlaceholderText(/00000000-0000-0000-0000-000000000000/i);
|
|
85
|
+
const button = screen.getByRole('button', { name: /조회하기/i });
|
|
86
|
+
|
|
87
|
+
expect(input).toBeInTheDocument();
|
|
88
|
+
expect(button).toBeInTheDocument();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should display description text', () => {
|
|
92
|
+
render(<ExampleStatus />, { wrapper: createWrapper() });
|
|
93
|
+
|
|
94
|
+
expect(screen.getByText(/예시 API/i)).toBeInTheDocument();
|
|
95
|
+
expect(screen.getByText(/Supabase 예시 레코드의 UUID를 입력하면/i)).toBeInTheDocument();
|
|
96
|
+
});
|
|
97
|
+
});
|