@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,224 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { DOCS_NAMES } from "./docs-setup.js";
|
|
4
|
+
|
|
5
|
+
const TASKFLOW_DIR = ".taskflow";
|
|
6
|
+
const CLAUDE_MD = "CLAUDE.md";
|
|
7
|
+
const MCP_JSON = ".mcp.json";
|
|
8
|
+
const IMPORT_LINE = "@./.taskflow/CLAUDE.md";
|
|
9
|
+
const DOCS_SECTION_MARKER = "<!-- docs-reference -->";
|
|
10
|
+
|
|
11
|
+
export interface ClaudeMdContext {
|
|
12
|
+
projectName: string;
|
|
13
|
+
summary?: string;
|
|
14
|
+
stack?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function generateClaudeMd(
|
|
18
|
+
projectRoot: string,
|
|
19
|
+
ctx: ClaudeMdContext,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
const date = new Date().toISOString().split("T")[0];
|
|
22
|
+
const stackLine = ctx.stack?.length ? ctx.stack.join(", ") : "N/A";
|
|
23
|
+
|
|
24
|
+
const content = `# TaskFlow — ${ctx.projectName} 개발 가이드
|
|
25
|
+
|
|
26
|
+
## 프로젝트 정보
|
|
27
|
+
- 이름: ${ctx.projectName}
|
|
28
|
+
- 설명: ${ctx.summary ?? "N/A"}
|
|
29
|
+
- 기술 스택: ${stackLine}
|
|
30
|
+
- 생성일: ${date}
|
|
31
|
+
|
|
32
|
+
## TaskFlow MCP 도구
|
|
33
|
+
|
|
34
|
+
이 프로젝트는 TaskFlow로 태스크를 관리합니다.
|
|
35
|
+
아래 MCP 도구를 사용하여 태스크를 조회하고 관리하세요.
|
|
36
|
+
|
|
37
|
+
### 태스크 관리
|
|
38
|
+
- \`list_tasks\` — 태스크 목록 조회 (필터: status, priority)
|
|
39
|
+
- \`read_task\` — 태스크 상세 조회
|
|
40
|
+
- \`create_task\` — 새 태스크 생성
|
|
41
|
+
- \`update_task\` — 태스크 수정
|
|
42
|
+
- \`delete_task\` — 태스크 삭제
|
|
43
|
+
- \`set_task_status\` — 상태 변경
|
|
44
|
+
- \`get_next_task\` — 의존성/우선순위 기반 다음 태스크 추천
|
|
45
|
+
- \`expand_subtasks\` — 서브태스크 파일 생성
|
|
46
|
+
|
|
47
|
+
### PRD & 코드베이스
|
|
48
|
+
- \`scan_codebase\` — 코드베이스 파일 목록/시그니처 스캔
|
|
49
|
+
- \`save_prd\` — PRD 마크다운 저장
|
|
50
|
+
- \`read_prd\` — PRD 읽기
|
|
51
|
+
|
|
52
|
+
### 프로젝트
|
|
53
|
+
- \`initialize_project\` — 프로젝트 초기화
|
|
54
|
+
- \`generate_claude_md\` — CLAUDE.md 재생성
|
|
55
|
+
|
|
56
|
+
## Claude Code 스킬
|
|
57
|
+
|
|
58
|
+
다음 스킬 커맨드를 사용하여 워크플로우를 실행할 수 있습니다:
|
|
59
|
+
- \`/prd\` — PRD 대화형 생성
|
|
60
|
+
- \`/trd\` — PRD 기반 TRD 생성
|
|
61
|
+
- \`/parse-prd\` — PRD → 태스크 분해
|
|
62
|
+
- \`/brainstorm\` — 태스크 서브태스크 분해
|
|
63
|
+
- \`/refine\` — 요구사항 변경 영향 분석
|
|
64
|
+
- \`/next\` — 다음 작업할 태스크 추천
|
|
65
|
+
- \`/task-status\` — 진행 상황 요약
|
|
66
|
+
|
|
67
|
+
## 워크플로우
|
|
68
|
+
|
|
69
|
+
### 새 기능 구현 시
|
|
70
|
+
1. \`/next\`로 다음 태스크 확인
|
|
71
|
+
2. \`set_task_status\` → in-progress
|
|
72
|
+
3. 구현 완료 후 \`set_task_status\` → done
|
|
73
|
+
|
|
74
|
+
### PRD → 태스크 생성
|
|
75
|
+
1. \`/prd\`로 PRD 대화형 생성
|
|
76
|
+
2. \`/trd\`로 구현 계획 작성
|
|
77
|
+
3. \`/parse-prd\`로 태스크 분해
|
|
78
|
+
|
|
79
|
+
### 요구사항 변경 시
|
|
80
|
+
1. \`/refine\`으로 영향 받는 태스크 분석
|
|
81
|
+
2. 영향 받는 태스크 확인 및 수정
|
|
82
|
+
|
|
83
|
+
## 파일 구조
|
|
84
|
+
- \`.taskflow/config.json\` — 프로젝트 설정
|
|
85
|
+
- \`.taskflow/prd.md\` — PRD 문서
|
|
86
|
+
- \`.taskflow/trd.md\` — TRD 문서
|
|
87
|
+
- \`.taskflow/tasks/task-{NNN}.md\` — 개별 태스크 파일
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
const filePath = path.join(projectRoot, TASKFLOW_DIR, CLAUDE_MD);
|
|
91
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function generateMcpJson(projectRoot: string): Promise<void> {
|
|
95
|
+
const filePath = path.join(projectRoot, MCP_JSON);
|
|
96
|
+
|
|
97
|
+
let existing: Record<string, unknown> = {};
|
|
98
|
+
try {
|
|
99
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
100
|
+
existing = JSON.parse(raw);
|
|
101
|
+
} catch {
|
|
102
|
+
// file does not exist or invalid JSON
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const mcpServers = (existing.mcpServers as Record<string, unknown>) ?? {};
|
|
106
|
+
mcpServers.taskflow = {
|
|
107
|
+
type: "stdio",
|
|
108
|
+
command: "node",
|
|
109
|
+
args: ["./bin/task-mcp.mjs"],
|
|
110
|
+
env: {},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const merged = { ...existing, mcpServers };
|
|
114
|
+
await fs.writeFile(filePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function appendClaudeImport(projectRoot: string): Promise<void> {
|
|
118
|
+
const filePath = path.join(projectRoot, CLAUDE_MD);
|
|
119
|
+
|
|
120
|
+
let existing = "";
|
|
121
|
+
try {
|
|
122
|
+
existing = await fs.readFile(filePath, "utf-8");
|
|
123
|
+
} catch {
|
|
124
|
+
// file does not exist
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (existing.includes(IMPORT_LINE)) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const importBlock = `\n## TaskFlow\n${IMPORT_LINE}\n`;
|
|
132
|
+
|
|
133
|
+
if (existing.trim()) {
|
|
134
|
+
const content = existing.endsWith("\n") ? existing + importBlock : existing + "\n" + importBlock;
|
|
135
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
136
|
+
} else {
|
|
137
|
+
const content = `# Claude Code Instructions\n${importBlock}`;
|
|
138
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const DEFAULT_PLUGINS: Record<string, boolean> = {
|
|
143
|
+
"superpowers@claude-plugins-official": true,
|
|
144
|
+
"ralph-loop@claude-plugins-official": true,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* .claude/settings.local.json에 enabledPlugins를 추가한다.
|
|
149
|
+
* 이미 설정된 플러그인은 덮어쓰지 않는다.
|
|
150
|
+
*/
|
|
151
|
+
export async function setupPlugins(projectRoot: string): Promise<void> {
|
|
152
|
+
const settingsPath = path.join(projectRoot, ".claude", "settings.local.json");
|
|
153
|
+
|
|
154
|
+
let existing: Record<string, unknown> = {};
|
|
155
|
+
try {
|
|
156
|
+
const raw = await fs.readFile(settingsPath, "utf-8");
|
|
157
|
+
existing = JSON.parse(raw);
|
|
158
|
+
} catch {
|
|
159
|
+
// file does not exist or invalid JSON
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const enabledPlugins = (existing.enabledPlugins as Record<string, boolean>) ?? {};
|
|
163
|
+
|
|
164
|
+
for (const [plugin, enabled] of Object.entries(DEFAULT_PLUGINS)) {
|
|
165
|
+
if (!(plugin in enabledPlugins)) {
|
|
166
|
+
enabledPlugins[plugin] = enabled;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const merged = { ...existing, enabledPlugins };
|
|
171
|
+
|
|
172
|
+
await fs.mkdir(path.dirname(settingsPath), { recursive: true });
|
|
173
|
+
await fs.writeFile(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function appendDocsReference(projectRoot: string): Promise<void> {
|
|
177
|
+
const filePath = path.join(projectRoot, CLAUDE_MD);
|
|
178
|
+
|
|
179
|
+
let existing = "";
|
|
180
|
+
try {
|
|
181
|
+
existing = await fs.readFile(filePath, "utf-8");
|
|
182
|
+
} catch {
|
|
183
|
+
// file does not exist
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (existing.includes(DOCS_SECTION_MARKER)) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const docsList = DOCS_NAMES.map((name) => `- ${name}.md: ./docs/${name}.md`).join("\n");
|
|
191
|
+
|
|
192
|
+
const docsBlock = `
|
|
193
|
+
${DOCS_SECTION_MARKER}
|
|
194
|
+
For every request:
|
|
195
|
+
|
|
196
|
+
1. Always refer to the documentation files stored under \`/docs\`:
|
|
197
|
+
<docs>
|
|
198
|
+
${docsList}
|
|
199
|
+
</docs>
|
|
200
|
+
|
|
201
|
+
2. Use the relevant file(s) depending on the context of the request.
|
|
202
|
+
3. Never ignore these files. Consider them for:
|
|
203
|
+
- Providing accurate information
|
|
204
|
+
- Ensuring consistency
|
|
205
|
+
- Following documented guidelines
|
|
206
|
+
- Making decisions or generating content
|
|
207
|
+
`;
|
|
208
|
+
|
|
209
|
+
if (existing.trim()) {
|
|
210
|
+
// Insert docs reference at the top, after the first heading line
|
|
211
|
+
const lines = existing.split("\n");
|
|
212
|
+
const headingIndex = lines.findIndex((l) => l.startsWith("# "));
|
|
213
|
+
if (headingIndex >= 0) {
|
|
214
|
+
lines.splice(headingIndex + 1, 0, docsBlock);
|
|
215
|
+
await fs.writeFile(filePath, lines.join("\n"), "utf-8");
|
|
216
|
+
} else {
|
|
217
|
+
const content = docsBlock + "\n" + existing;
|
|
218
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
const content = `# Claude Code Instructions\n${docsBlock}`;
|
|
222
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { TaskFlowConfig } from "../types.js";
|
|
4
|
+
|
|
5
|
+
const CONFIG_FILE = "config.json";
|
|
6
|
+
const TASKFLOW_DIR = ".taskflow";
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_CONFIG: TaskFlowConfig = {
|
|
9
|
+
version: "1.0",
|
|
10
|
+
project: {
|
|
11
|
+
name: "",
|
|
12
|
+
},
|
|
13
|
+
tasks: {
|
|
14
|
+
statusFlow: ["pending", "in-progress", "blocked", "done"],
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export async function readConfig(projectRoot: string): Promise<TaskFlowConfig> {
|
|
19
|
+
const configPath = path.join(projectRoot, TASKFLOW_DIR, CONFIG_FILE);
|
|
20
|
+
try {
|
|
21
|
+
const raw = await fs.readFile(configPath, "utf-8");
|
|
22
|
+
return JSON.parse(raw) as TaskFlowConfig;
|
|
23
|
+
} catch {
|
|
24
|
+
return { ...DEFAULT_CONFIG };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function writeConfig(
|
|
29
|
+
projectRoot: string,
|
|
30
|
+
config: TaskFlowConfig,
|
|
31
|
+
): Promise<void> {
|
|
32
|
+
const configPath = path.join(projectRoot, TASKFLOW_DIR, CONFIG_FILE);
|
|
33
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
34
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { DOCS_TEMPLATES } from "./docs-templates.js";
|
|
4
|
+
|
|
5
|
+
const DOCS_DIR = "docs";
|
|
6
|
+
|
|
7
|
+
export const DOCS_NAMES = Object.keys(DOCS_TEMPLATES);
|
|
8
|
+
|
|
9
|
+
export async function installDocs(projectRoot: string): Promise<void> {
|
|
10
|
+
const docsDir = path.join(projectRoot, DOCS_DIR);
|
|
11
|
+
await fs.mkdir(docsDir, { recursive: true });
|
|
12
|
+
|
|
13
|
+
for (const [name, content] of Object.entries(DOCS_TEMPLATES)) {
|
|
14
|
+
const filePath = path.join(docsDir, `${name}.md`);
|
|
15
|
+
|
|
16
|
+
// Don't overwrite existing docs (user may have customized them)
|
|
17
|
+
try {
|
|
18
|
+
await fs.access(filePath);
|
|
19
|
+
continue;
|
|
20
|
+
} catch {
|
|
21
|
+
// File doesn't exist — create it
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
export const DOCS_TEMPLATES: Record<string, string> = {
|
|
2
|
+
guideline: `# Project Guideline
|
|
3
|
+
|
|
4
|
+
## Naming Conventions
|
|
5
|
+
- **Files**: kebab-case (\`parse-prd-flow.ts\`)
|
|
6
|
+
- **Components**: PascalCase (\`TaskCard.tsx\`)
|
|
7
|
+
- **Functions/Variables**: camelCase
|
|
8
|
+
- **Types/Interfaces**: PascalCase
|
|
9
|
+
- **Constants**: UPPER_SNAKE_CASE
|
|
10
|
+
- **Test files**: \`*.test.ts\` or \`__tests__/*.test.ts\` (co-located)
|
|
11
|
+
|
|
12
|
+
## Import Conventions
|
|
13
|
+
- Use path alias (\`@/*\` etc.) when available — avoid deep relative paths
|
|
14
|
+
- Group imports: external → internal → relative
|
|
15
|
+
- No circular imports between modules/features
|
|
16
|
+
|
|
17
|
+
## Module Structure
|
|
18
|
+
- Keep modules self-contained: co-locate related code (logic, types, tests)
|
|
19
|
+
- Separate concerns by layer (API, UI, business logic)
|
|
20
|
+
- Shared code belongs in \`lib/\` or \`utils/\` — not inside a feature module
|
|
21
|
+
|
|
22
|
+
## General Principles
|
|
23
|
+
- Prefer composition over inheritance
|
|
24
|
+
- Keep public API surface small — export only what's needed
|
|
25
|
+
- Avoid god files (>300 lines) — split by responsibility
|
|
26
|
+
- Configuration and constants belong in dedicated files, not scattered inline
|
|
27
|
+
`,
|
|
28
|
+
|
|
29
|
+
"clean-code": `# Clean Code Guidelines
|
|
30
|
+
|
|
31
|
+
## Principles
|
|
32
|
+
- **Single Responsibility**: One function/module = one job
|
|
33
|
+
- **DRY**: Extract only when duplicated 3+ times. Premature abstraction is worse than duplication
|
|
34
|
+
- **KISS**: Simplest solution that works. No over-engineering
|
|
35
|
+
- **YAGNI**: Don't build what you don't need yet
|
|
36
|
+
|
|
37
|
+
## Functions
|
|
38
|
+
- Max 20 lines per function (guideline, not hard rule)
|
|
39
|
+
- Max 3 parameters. Use an options object for more
|
|
40
|
+
- Name functions with verbs: \`createTask\`, \`parseMarkdown\`, \`validateInput\`
|
|
41
|
+
- Early return over nested conditionals
|
|
42
|
+
|
|
43
|
+
## Error Handling
|
|
44
|
+
- Use custom error classes per domain
|
|
45
|
+
- Let errors bubble up — catch only at boundaries (API routes, CLI commands)
|
|
46
|
+
- Never swallow errors silently
|
|
47
|
+
|
|
48
|
+
## Types
|
|
49
|
+
- Prefer \`type\` over \`interface\` for consistency (unless extending)
|
|
50
|
+
- Use schema-first approach (Zod, etc.); derive types from schemas
|
|
51
|
+
- Avoid \`any\` — use \`unknown\` and narrow
|
|
52
|
+
|
|
53
|
+
## Code Smells to Avoid
|
|
54
|
+
- God files (>300 lines) — split by responsibility
|
|
55
|
+
- Boolean parameters — use named options or separate functions
|
|
56
|
+
- Magic strings/numbers — use constants or enums
|
|
57
|
+
- Commented-out code — delete it, git has history
|
|
58
|
+
`,
|
|
59
|
+
|
|
60
|
+
git: `# Git Conventions
|
|
61
|
+
|
|
62
|
+
## Branch Naming
|
|
63
|
+
\`\`\`
|
|
64
|
+
feat/{description} # New feature
|
|
65
|
+
fix/{description} # Bug fix
|
|
66
|
+
refactor/{description} # Refactoring
|
|
67
|
+
docs/{description} # Documentation
|
|
68
|
+
chore/{description} # Maintenance
|
|
69
|
+
\`\`\`
|
|
70
|
+
|
|
71
|
+
## Commit Messages
|
|
72
|
+
Follow Conventional Commits:
|
|
73
|
+
\`\`\`
|
|
74
|
+
type(scope): description
|
|
75
|
+
|
|
76
|
+
feat(kanban): add drag-and-drop task reordering
|
|
77
|
+
fix(cli): handle missing config file gracefully
|
|
78
|
+
refactor(core): extract task validation logic
|
|
79
|
+
test(prd): add parser edge case coverage
|
|
80
|
+
\`\`\`
|
|
81
|
+
|
|
82
|
+
### Types
|
|
83
|
+
- \`feat\` — new feature
|
|
84
|
+
- \`fix\` — bug fix
|
|
85
|
+
- \`refactor\` — code restructuring (no behavior change)
|
|
86
|
+
- \`test\` — adding/updating tests
|
|
87
|
+
- \`docs\` — documentation only
|
|
88
|
+
- \`chore\` — build, deps, config changes
|
|
89
|
+
- \`style\` — formatting (no logic change)
|
|
90
|
+
|
|
91
|
+
## Rules
|
|
92
|
+
- Commit small, atomic changes
|
|
93
|
+
- Never commit secrets, \`.env\` files, or credentials
|
|
94
|
+
- Keep commits buildable — don't break the build mid-branch
|
|
95
|
+
- Squash WIP commits before PR
|
|
96
|
+
`,
|
|
97
|
+
|
|
98
|
+
tdd: `# Test-Driven Development
|
|
99
|
+
|
|
100
|
+
## Philosophy
|
|
101
|
+
- Write tests first for new features and bug fixes
|
|
102
|
+
- Tests document behavior — they are living specifications
|
|
103
|
+
- Fast feedback: unit tests should run in <1s per file
|
|
104
|
+
|
|
105
|
+
## Test Pyramid
|
|
106
|
+
1. **Unit tests** — core logic, utils, schemas (most coverage)
|
|
107
|
+
2. **Integration tests** — API routes, database queries (moderate)
|
|
108
|
+
3. **E2E tests** — critical user flows only (fewest)
|
|
109
|
+
|
|
110
|
+
## Conventions
|
|
111
|
+
- Co-locate tests: \`*.test.ts\` next to source or in \`__tests__/\`
|
|
112
|
+
- Use \`describe\` blocks to group by function/scenario
|
|
113
|
+
- Test names: \`it('should {expected behavior} when {condition}')\`
|
|
114
|
+
- One assertion concept per test
|
|
115
|
+
|
|
116
|
+
## Patterns
|
|
117
|
+
\`\`\`typescript
|
|
118
|
+
// Arrange
|
|
119
|
+
const input = createMockTask({ status: 'todo' });
|
|
120
|
+
|
|
121
|
+
// Act
|
|
122
|
+
const result = updateTaskStatus(input, 'in-progress');
|
|
123
|
+
|
|
124
|
+
// Assert
|
|
125
|
+
expect(result.status).toBe('in-progress');
|
|
126
|
+
\`\`\`
|
|
127
|
+
|
|
128
|
+
## What to Test
|
|
129
|
+
- Business logic and transformations
|
|
130
|
+
- Edge cases and error paths
|
|
131
|
+
- Schema validation
|
|
132
|
+
- API response shapes
|
|
133
|
+
|
|
134
|
+
## What NOT to Test
|
|
135
|
+
- Framework internals (routing, rendering)
|
|
136
|
+
- Third-party library behavior
|
|
137
|
+
- Implementation details (private functions, internal state)
|
|
138
|
+
- Trivial getters/setters
|
|
139
|
+
`,
|
|
140
|
+
|
|
141
|
+
"step-by-step": `# Step-by-Step Development Workflow
|
|
142
|
+
|
|
143
|
+
## Before Writing Code
|
|
144
|
+
1. **Understand the requirement** — Read the task/PRD thoroughly
|
|
145
|
+
2. **Check existing code** — Search for related implementations before creating new ones
|
|
146
|
+
3. **Plan the approach** — Identify which files to modify and what tests to write
|
|
147
|
+
|
|
148
|
+
## Implementation Flow
|
|
149
|
+
1. **Write the test** — Define expected behavior first
|
|
150
|
+
2. **Write minimal code** — Just enough to pass the test
|
|
151
|
+
3. **Refactor** — Clean up while tests stay green
|
|
152
|
+
4. **Verify** — Run full test suite
|
|
153
|
+
5. **Type check** — Run type checker
|
|
154
|
+
6. **Lint** — Run linter
|
|
155
|
+
|
|
156
|
+
## PR Checklist
|
|
157
|
+
- [ ] Tests pass
|
|
158
|
+
- [ ] Type check passes
|
|
159
|
+
- [ ] No lint errors
|
|
160
|
+
- [ ] New code has test coverage
|
|
161
|
+
- [ ] Commit messages follow conventions
|
|
162
|
+
- [ ] No secrets or .env files committed
|
|
163
|
+
|
|
164
|
+
## Debugging
|
|
165
|
+
1. Read the error message carefully
|
|
166
|
+
2. Reproduce the issue with a test
|
|
167
|
+
3. Identify root cause (don't just patch symptoms)
|
|
168
|
+
4. Fix and verify the test passes
|
|
169
|
+
5. Check for similar issues elsewhere
|
|
170
|
+
`,
|
|
171
|
+
|
|
172
|
+
security: `# Security Guidelines
|
|
173
|
+
|
|
174
|
+
## Authentication & Authorization
|
|
175
|
+
- All API routes must verify authentication
|
|
176
|
+
- Validate user session on every request — never trust client-side auth state
|
|
177
|
+
- Use row-level or resource-level access control for data isolation
|
|
178
|
+
|
|
179
|
+
## Input Validation
|
|
180
|
+
- Validate ALL external input with schemas at the boundary
|
|
181
|
+
- Never trust query params, request bodies, or URL params directly
|
|
182
|
+
- Sanitize user-generated content before rendering (XSS prevention)
|
|
183
|
+
|
|
184
|
+
## Secrets Management
|
|
185
|
+
- Store secrets in environment variables only
|
|
186
|
+
- Never commit \`.env\`, API keys, or credentials
|
|
187
|
+
- Use \`.env.local\` for local development (ensure it's in \`.gitignore\`)
|
|
188
|
+
|
|
189
|
+
## API Security
|
|
190
|
+
- Use parameterized queries — never concatenate user input into queries
|
|
191
|
+
- Rate limit public endpoints
|
|
192
|
+
- Return generic error messages to clients — log details server-side
|
|
193
|
+
|
|
194
|
+
## Dependencies
|
|
195
|
+
- Keep dependencies up to date
|
|
196
|
+
- Review new dependencies before adding (check maintenance, size, security)
|
|
197
|
+
- Run security audits periodically
|
|
198
|
+
|
|
199
|
+
## What to Never Do
|
|
200
|
+
- Expose internal error stacks to clients
|
|
201
|
+
- Store passwords in plain text
|
|
202
|
+
- Use \`eval()\` or dynamic code execution with user input
|
|
203
|
+
- Disable strict type checks for convenience
|
|
204
|
+
`,
|
|
205
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { writeConfig, DEFAULT_CONFIG } from "./config.js";
|
|
4
|
+
|
|
5
|
+
const TASKFLOW_DIR = ".taskflow";
|
|
6
|
+
|
|
7
|
+
export interface InitResult {
|
|
8
|
+
created: boolean;
|
|
9
|
+
alreadyExists: boolean;
|
|
10
|
+
projectRoot: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function initProject(projectRoot: string, projectName?: string): Promise<InitResult> {
|
|
14
|
+
const taskflowDir = path.join(projectRoot, TASKFLOW_DIR);
|
|
15
|
+
|
|
16
|
+
let alreadyExists = false;
|
|
17
|
+
try {
|
|
18
|
+
await fs.access(taskflowDir);
|
|
19
|
+
alreadyExists = true;
|
|
20
|
+
} catch {
|
|
21
|
+
// directory does not exist
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (alreadyExists) {
|
|
25
|
+
return { created: false, alreadyExists: true, projectRoot };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
await fs.mkdir(path.join(taskflowDir, "tasks"), { recursive: true });
|
|
29
|
+
await fs.mkdir(path.join(taskflowDir, "index"), { recursive: true });
|
|
30
|
+
await fs.mkdir(path.join(taskflowDir, "logs"), { recursive: true });
|
|
31
|
+
await fs.mkdir(path.join(taskflowDir, "cache"), { recursive: true });
|
|
32
|
+
|
|
33
|
+
const config = {
|
|
34
|
+
...DEFAULT_CONFIG,
|
|
35
|
+
project: { ...DEFAULT_CONFIG.project, name: projectName ?? "" },
|
|
36
|
+
};
|
|
37
|
+
await writeConfig(projectRoot, config);
|
|
38
|
+
|
|
39
|
+
return { created: true, alreadyExists: false, projectRoot };
|
|
40
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { SKILL_TEMPLATES } from "./skill-templates.js";
|
|
4
|
+
|
|
5
|
+
export const SKILL_NAMES = Object.keys(SKILL_TEMPLATES);
|
|
6
|
+
|
|
7
|
+
export async function installSkills(projectRoot: string): Promise<void> {
|
|
8
|
+
const srcDir = path.join(projectRoot, ".taskflow", ".claude", "commands");
|
|
9
|
+
const destDir = path.join(projectRoot, ".claude", "commands");
|
|
10
|
+
|
|
11
|
+
await fs.mkdir(srcDir, { recursive: true });
|
|
12
|
+
await fs.mkdir(destDir, { recursive: true });
|
|
13
|
+
|
|
14
|
+
for (const [name, content] of Object.entries(SKILL_TEMPLATES)) {
|
|
15
|
+
const srcFile = path.join(srcDir, `${name}.md`);
|
|
16
|
+
const destFile = path.join(destDir, `${name}.md`);
|
|
17
|
+
|
|
18
|
+
// 원본 스킬 파일 생성 (항상 최신 템플릿으로 덮어쓰기)
|
|
19
|
+
await fs.writeFile(srcFile, content, "utf-8");
|
|
20
|
+
|
|
21
|
+
// 기존 링크/파일이 있으면 심볼릭 링크 스킵
|
|
22
|
+
try {
|
|
23
|
+
await fs.lstat(destFile);
|
|
24
|
+
continue;
|
|
25
|
+
} catch {
|
|
26
|
+
// 없으면 생성
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const relativePath = path.relative(destDir, srcFile);
|
|
30
|
+
await fs.symlink(relativePath, destFile);
|
|
31
|
+
}
|
|
32
|
+
}
|