create-claude-pipeline 0.1.0

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.
Files changed (76) hide show
  1. package/bin/cli.js +359 -0
  2. package/package.json +32 -0
  3. package/template/.claude/agents/be-developer.md +218 -0
  4. package/template/.claude/agents/designer.md +192 -0
  5. package/template/.claude/agents/fe-developer.md +175 -0
  6. package/template/.claude/agents/infra-developer.md +270 -0
  7. package/template/.claude/agents/planner.md +126 -0
  8. package/template/.claude/agents/pm.md +130 -0
  9. package/template/.claude/agents/qa-engineer.md +270 -0
  10. package/template/.claude/agents/security-reviewer.md +281 -0
  11. package/template/.claude/settings.json +5 -0
  12. package/template/.claude/skills/analyze-requirements/SKILL.md +166 -0
  13. package/template/.claude/skills/api-integration/SKILL.md +354 -0
  14. package/template/.claude/skills/assemble-context/SKILL.md +192 -0
  15. package/template/.claude/skills/db-migration/SKILL.md +228 -0
  16. package/template/.claude/skills/explore-be-codebase/SKILL.md +260 -0
  17. package/template/.claude/skills/explore-codebase/SKILL.md +190 -0
  18. package/template/.claude/skills/explore-design-system/SKILL.md +150 -0
  19. package/template/.claude/skills/explore-fe-codebase/SKILL.md +209 -0
  20. package/template/.claude/skills/explore-implementation/SKILL.md +147 -0
  21. package/template/.claude/skills/explore-infra/SKILL.md +242 -0
  22. package/template/.claude/skills/implement-api/SKILL.md +477 -0
  23. package/template/.claude/skills/implement-components/SKILL.md +217 -0
  24. package/template/.claude/skills/review-auth/SKILL.md +175 -0
  25. package/template/.claude/skills/scan-vulnerabilities/SKILL.md +200 -0
  26. package/template/.claude/skills/write-cicd/SKILL.md +293 -0
  27. package/template/.claude/skills/write-design-spec/SKILL.md +363 -0
  28. package/template/.claude/skills/write-dockerfile/SKILL.md +269 -0
  29. package/template/.claude/skills/write-plan-doc/SKILL.md +164 -0
  30. package/template/.claude/skills/write-plan-doc/assets/plan_template.html +251 -0
  31. package/template/.claude/skills/write-qa-report/SKILL.md +151 -0
  32. package/template/.claude/skills/write-security-report/SKILL.md +185 -0
  33. package/template/.claude/skills/write-test-cases/SKILL.md +234 -0
  34. package/template/.claude-pipeline/dashboard/.env.example +1 -0
  35. package/template/.claude-pipeline/dashboard/.eslintrc.json +3 -0
  36. package/template/.claude-pipeline/dashboard/README.md +36 -0
  37. package/template/.claude-pipeline/dashboard/next.config.mjs +6 -0
  38. package/template/.claude-pipeline/dashboard/package-lock.json +8148 -0
  39. package/template/.claude-pipeline/dashboard/package.json +36 -0
  40. package/template/.claude-pipeline/dashboard/postcss.config.mjs +8 -0
  41. package/template/.claude-pipeline/dashboard/server.ts +24 -0
  42. package/template/.claude-pipeline/dashboard/src/app/api/pipelines/[id]/checkpoint/route.ts +23 -0
  43. package/template/.claude-pipeline/dashboard/src/app/api/pipelines/[id]/outputs/[...filepath]/route.ts +18 -0
  44. package/template/.claude-pipeline/dashboard/src/app/api/pipelines/[id]/route.ts +10 -0
  45. package/template/.claude-pipeline/dashboard/src/app/api/pipelines/route.ts +64 -0
  46. package/template/.claude-pipeline/dashboard/src/app/favicon.ico +0 -0
  47. package/template/.claude-pipeline/dashboard/src/app/fonts/GeistMonoVF.woff +0 -0
  48. package/template/.claude-pipeline/dashboard/src/app/fonts/GeistVF.woff +0 -0
  49. package/template/.claude-pipeline/dashboard/src/app/globals.css +52 -0
  50. package/template/.claude-pipeline/dashboard/src/app/layout.tsx +33 -0
  51. package/template/.claude-pipeline/dashboard/src/app/page.tsx +49 -0
  52. package/template/.claude-pipeline/dashboard/src/app/pipeline/[id]/page.tsx +84 -0
  53. package/template/.claude-pipeline/dashboard/src/components/agent-card.tsx +40 -0
  54. package/template/.claude-pipeline/dashboard/src/components/agent-logs.tsx +65 -0
  55. package/template/.claude-pipeline/dashboard/src/components/artifact-viewer.tsx +130 -0
  56. package/template/.claude-pipeline/dashboard/src/components/checkpoint-banner.tsx +59 -0
  57. package/template/.claude-pipeline/dashboard/src/components/new-pipeline-modal.tsx +63 -0
  58. package/template/.claude-pipeline/dashboard/src/components/output-list.tsx +57 -0
  59. package/template/.claude-pipeline/dashboard/src/components/phase-dots.tsx +37 -0
  60. package/template/.claude-pipeline/dashboard/src/components/pipeline-card.tsx +53 -0
  61. package/template/.claude-pipeline/dashboard/src/components/resizable-panels.tsx +91 -0
  62. package/template/.claude-pipeline/dashboard/src/hooks/use-pipeline-detail.ts +65 -0
  63. package/template/.claude-pipeline/dashboard/src/hooks/use-pipelines.ts +60 -0
  64. package/template/.claude-pipeline/dashboard/src/hooks/use-websocket.ts +58 -0
  65. package/template/.claude-pipeline/dashboard/src/lib/agents.ts +30 -0
  66. package/template/.claude-pipeline/dashboard/src/lib/checkpoint.ts +37 -0
  67. package/template/.claude-pipeline/dashboard/src/lib/pipelines.ts +91 -0
  68. package/template/.claude-pipeline/dashboard/src/lib/watcher.ts +90 -0
  69. package/template/.claude-pipeline/dashboard/src/lib/ws-server.ts +123 -0
  70. package/template/.claude-pipeline/dashboard/src/types/pipeline.ts +61 -0
  71. package/template/.claude-pipeline/dashboard/tailwind.config.ts +31 -0
  72. package/template/.claude-pipeline/dashboard/tsconfig.json +26 -0
  73. package/template/CLAUDE.md +301 -0
  74. package/template/references/context-structure.md +34 -0
  75. package/template/references/pm-context-assembly.md +34 -0
  76. package/template/references/task-context-template.md +65 -0
package/bin/cli.js ADDED
@@ -0,0 +1,359 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/index.ts
27
+ var import_path7 = __toESM(require("path"));
28
+ var import_fs_extra5 = __toESM(require("fs-extra"));
29
+ var import_ora = __toESM(require("ora"));
30
+
31
+ // src/logger.ts
32
+ var import_chalk = __toESM(require("chalk"));
33
+ function step(current, total, message) {
34
+ console.log(import_chalk.default.cyan(` [${current}/${total}] `) + message);
35
+ }
36
+ function success(message) {
37
+ console.log(import_chalk.default.green(" \u2713 ") + message);
38
+ }
39
+ function error(message) {
40
+ console.log(import_chalk.default.red(" \u2717 ") + message);
41
+ }
42
+ function banner() {
43
+ console.log();
44
+ console.log(import_chalk.default.bold(" \u{1F680} Claude Pipeline \uC124\uCE58 \uC911..."));
45
+ console.log();
46
+ }
47
+ function done(url) {
48
+ console.log();
49
+ console.log(import_chalk.default.green.bold(` \u2705 \uC644\uB8CC! \uB300\uC2DC\uBCF4\uB4DC: ${url}`));
50
+ console.log(import_chalk.default.gray(" \uC885\uB8CC: Ctrl+C"));
51
+ console.log();
52
+ }
53
+
54
+ // src/copy-template.ts
55
+ var import_path2 = __toESM(require("path"));
56
+ var import_fs_extra = __toESM(require("fs-extra"));
57
+
58
+ // src/paths.ts
59
+ var import_path = __toESM(require("path"));
60
+ function getTemplateDir() {
61
+ return import_path.default.join(__dirname, "..", "template");
62
+ }
63
+
64
+ // src/copy-template.ts
65
+ async function copyDirSkipExisting(src, dest) {
66
+ const copied = [];
67
+ const skipped = [];
68
+ await import_fs_extra.default.ensureDir(dest);
69
+ const entries = await import_fs_extra.default.readdir(src, { withFileTypes: true });
70
+ for (const entry of entries) {
71
+ const srcPath = import_path2.default.join(src, entry.name);
72
+ const destPath = import_path2.default.join(dest, entry.name);
73
+ if (await import_fs_extra.default.pathExists(destPath)) {
74
+ skipped.push(entry.name);
75
+ continue;
76
+ }
77
+ await import_fs_extra.default.copy(srcPath, destPath);
78
+ copied.push(entry.name);
79
+ }
80
+ return { copied, skipped };
81
+ }
82
+ async function copyTemplateFiles(targetDir) {
83
+ const templateDir = getTemplateDir();
84
+ const agents = await copyDirSkipExisting(
85
+ import_path2.default.join(templateDir, ".claude", "agents"),
86
+ import_path2.default.join(targetDir, ".claude", "agents")
87
+ );
88
+ const skills = await copyDirSkipExisting(
89
+ import_path2.default.join(templateDir, ".claude", "skills"),
90
+ import_path2.default.join(targetDir, ".claude", "skills")
91
+ );
92
+ const references = await copyDirSkipExisting(
93
+ import_path2.default.join(templateDir, "references"),
94
+ import_path2.default.join(targetDir, "references")
95
+ );
96
+ const dashboardSrc = import_path2.default.join(templateDir, ".claude-pipeline", "dashboard");
97
+ const dashboardDest = import_path2.default.join(targetDir, ".claude-pipeline", "dashboard");
98
+ await import_fs_extra.default.copy(dashboardSrc, dashboardDest, { overwrite: true });
99
+ await import_fs_extra.default.ensureDir(import_path2.default.join(targetDir, "pipelines"));
100
+ return { agents, skills, references, dashboard: true };
101
+ }
102
+
103
+ // src/merge-claude-md.ts
104
+ var import_path3 = __toESM(require("path"));
105
+ var import_fs_extra2 = __toESM(require("fs-extra"));
106
+ var MARKER_START = "<!-- claude-pipeline-start -->";
107
+ var MARKER_END = "<!-- claude-pipeline-end -->";
108
+ async function mergeCLAUDEmd(targetDir) {
109
+ const targetPath = import_path3.default.join(targetDir, "CLAUDE.md");
110
+ const templatePath = import_path3.default.join(getTemplateDir(), "CLAUDE.md");
111
+ const templateContent = await import_fs_extra2.default.readFile(templatePath, "utf-8");
112
+ if (!await import_fs_extra2.default.pathExists(targetPath)) {
113
+ const wrapped = `${MARKER_START}
114
+ ${templateContent}
115
+ ${MARKER_END}
116
+ `;
117
+ await import_fs_extra2.default.writeFile(targetPath, wrapped, "utf-8");
118
+ return "created";
119
+ }
120
+ const existing = await import_fs_extra2.default.readFile(targetPath, "utf-8");
121
+ if (existing.includes(MARKER_START)) {
122
+ return "skipped";
123
+ }
124
+ const section = `
125
+
126
+ ---
127
+
128
+ ${MARKER_START}
129
+ ${templateContent}
130
+ ${MARKER_END}
131
+ `;
132
+ await import_fs_extra2.default.appendFile(targetPath, section, "utf-8");
133
+ return "merged";
134
+ }
135
+
136
+ // src/merge-settings.ts
137
+ var import_path4 = __toESM(require("path"));
138
+ var import_fs_extra3 = __toESM(require("fs-extra"));
139
+ function mergeArrays(existing, incoming) {
140
+ const existingStrs = new Set(existing.map(String));
141
+ return [
142
+ ...existing,
143
+ ...incoming.filter((item) => !existingStrs.has(String(item)))
144
+ ];
145
+ }
146
+ function deepMerge(existing, incoming) {
147
+ const result = { ...existing };
148
+ for (const key of Object.keys(incoming)) {
149
+ if (!(key in result)) {
150
+ result[key] = incoming[key];
151
+ } else if (Array.isArray(result[key]) && Array.isArray(incoming[key])) {
152
+ result[key] = mergeArrays(result[key], incoming[key]);
153
+ } else if (typeof result[key] === "object" && result[key] !== null && typeof incoming[key] === "object" && incoming[key] !== null && !Array.isArray(result[key])) {
154
+ result[key] = deepMerge(
155
+ result[key],
156
+ incoming[key]
157
+ );
158
+ }
159
+ }
160
+ return result;
161
+ }
162
+ async function mergeSettings(targetDir) {
163
+ const targetPath = import_path4.default.join(targetDir, ".claude", "settings.json");
164
+ const templatePath = import_path4.default.join(getTemplateDir(), ".claude", "settings.json");
165
+ const templateContent = await import_fs_extra3.default.readJSON(templatePath);
166
+ if (!await import_fs_extra3.default.pathExists(targetPath)) {
167
+ await import_fs_extra3.default.ensureDir(import_path4.default.dirname(targetPath));
168
+ await import_fs_extra3.default.writeJSON(targetPath, templateContent, { spaces: 2 });
169
+ return "created";
170
+ }
171
+ const existingContent = await import_fs_extra3.default.readJSON(targetPath);
172
+ const merged = deepMerge(existingContent, templateContent);
173
+ await import_fs_extra3.default.writeJSON(targetPath, merged, { spaces: 2 });
174
+ return "merged";
175
+ }
176
+
177
+ // src/update-gitignore.ts
178
+ var import_path5 = __toESM(require("path"));
179
+ var import_fs_extra4 = __toESM(require("fs-extra"));
180
+ var ENTRIES = [
181
+ ".claude-pipeline/dashboard/node_modules/",
182
+ ".claude-pipeline/dashboard/.next/",
183
+ "pipelines/"
184
+ ];
185
+ async function updateGitignore(targetDir) {
186
+ const gitignorePath = import_path5.default.join(targetDir, ".gitignore");
187
+ const added = [];
188
+ const skipped = [];
189
+ let content = "";
190
+ if (await import_fs_extra4.default.pathExists(gitignorePath)) {
191
+ content = await import_fs_extra4.default.readFile(gitignorePath, "utf-8");
192
+ }
193
+ const lines = content.split("\n").map((l) => l.trim());
194
+ for (const entry of ENTRIES) {
195
+ if (lines.includes(entry)) {
196
+ skipped.push(entry);
197
+ } else {
198
+ added.push(entry);
199
+ }
200
+ }
201
+ if (added.length > 0) {
202
+ const section = "\n# Claude Pipeline\n" + added.join("\n") + "\n";
203
+ await import_fs_extra4.default.appendFile(gitignorePath, section, "utf-8");
204
+ }
205
+ return { added, skipped };
206
+ }
207
+
208
+ // src/install.ts
209
+ var import_child_process = require("child_process");
210
+ async function npmInstall(cwd) {
211
+ try {
212
+ (0, import_child_process.execSync)("npm install", {
213
+ cwd,
214
+ stdio: "pipe",
215
+ timeout: 12e4
216
+ });
217
+ } catch (err) {
218
+ const message = err instanceof Error ? err.message : String(err);
219
+ throw new Error(
220
+ `npm install \uC2E4\uD328: ${message}
221
+ \uC218\uB3D9\uC73C\uB85C \uC2E4\uD589\uD574\uC8FC\uC138\uC694: cd ${cwd} && npm install`
222
+ );
223
+ }
224
+ }
225
+
226
+ // src/start-dashboard.ts
227
+ var import_path6 = __toESM(require("path"));
228
+ var import_child_process2 = require("child_process");
229
+ var import_detect_port = __toESM(require("detect-port"));
230
+ var import_open = __toESM(require("open"));
231
+ async function startDashboard(targetDir) {
232
+ const dashboardDir = import_path6.default.join(targetDir, ".claude-pipeline", "dashboard");
233
+ const port = await (0, import_detect_port.default)(3e3);
234
+ const child = (0, import_child_process2.spawn)("npm", ["run", "dev"], {
235
+ cwd: dashboardDir,
236
+ stdio: "inherit",
237
+ env: {
238
+ ...process.env,
239
+ PORT: String(port),
240
+ PIPELINES_DIR: import_path6.default.join(targetDir, "pipelines")
241
+ },
242
+ shell: true
243
+ });
244
+ child.on("error", (err) => {
245
+ error(`\uB300\uC2DC\uBCF4\uB4DC \uC2E4\uD589 \uC2E4\uD328: ${err.message}`);
246
+ process.exit(1);
247
+ });
248
+ const cleanup = (signal) => {
249
+ child.kill(signal);
250
+ process.exit(0);
251
+ };
252
+ process.on("SIGINT", () => cleanup("SIGINT"));
253
+ process.on("SIGTERM", () => cleanup("SIGTERM"));
254
+ const url = `http://localhost:${port}`;
255
+ const maxAttempts = 30;
256
+ for (let i = 0; i < maxAttempts; i++) {
257
+ try {
258
+ await fetch(url);
259
+ break;
260
+ } catch {
261
+ await new Promise((resolve) => setTimeout(resolve, 500));
262
+ }
263
+ }
264
+ await (0, import_open.default)(url);
265
+ done(url);
266
+ await new Promise((resolve) => {
267
+ child.on("close", () => resolve());
268
+ });
269
+ }
270
+
271
+ // src/index.ts
272
+ var TOTAL_STEPS = 6;
273
+ function showHelp() {
274
+ console.log(`
275
+ Usage: npx create-claude-pipeline
276
+
277
+ Claude Code \uD30C\uC774\uD504\uB77C\uC778 \uC2DC\uC2A4\uD15C\uC744 \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0 \uC124\uCE58\uD558\uACE0 \uB300\uC2DC\uBCF4\uB4DC\uB97C \uC2E4\uD589\uD569\uB2C8\uB2E4.
278
+
279
+ \uCD5C\uCD08 \uC2E4\uD589: \uC804\uCCB4 \uC2DC\uC2A4\uD15C \uC124\uCE58 + \uB300\uC2DC\uBCF4\uB4DC \uC2E4\uD589
280
+ \uC7AC\uC2E4\uD589: \uB300\uC2DC\uBCF4\uB4DC\uB9CC \uC2E4\uD589
281
+
282
+ Options:
283
+ --help \uC774 \uB3C4\uC6C0\uB9D0 \uD45C\uC2DC
284
+ --version \uBC84\uC804 \uD45C\uC2DC
285
+ `);
286
+ }
287
+ async function showVersion() {
288
+ const pkg = await import_fs_extra5.default.readJSON(import_path7.default.join(__dirname, "..", "package.json"));
289
+ console.log(pkg.version);
290
+ }
291
+ async function main() {
292
+ const args = process.argv.slice(2);
293
+ if (args.includes("--help")) {
294
+ showHelp();
295
+ return;
296
+ }
297
+ if (args.includes("--version")) {
298
+ await showVersion();
299
+ return;
300
+ }
301
+ const major = parseInt(process.version.slice(1), 10);
302
+ if (major < 18) {
303
+ error(`Node.js 18 \uC774\uC0C1\uC774 \uD544\uC694\uD569\uB2C8\uB2E4 (\uD604\uC7AC: ${process.version})`);
304
+ process.exit(1);
305
+ }
306
+ const cwd = process.cwd();
307
+ const dashboardPkg = import_path7.default.join(cwd, ".claude-pipeline", "dashboard", "package.json");
308
+ if (await import_fs_extra5.default.pathExists(dashboardPkg)) {
309
+ console.log();
310
+ success("\uC774\uBBF8 \uC124\uCE58\uB428 \u2014 \uB300\uC2DC\uBCF4\uB4DC\uB9CC \uC2E4\uD589\uD569\uB2C8\uB2E4");
311
+ await startDashboard(cwd);
312
+ return;
313
+ }
314
+ banner();
315
+ step(1, TOTAL_STEPS, "\uD30C\uC77C \uBCF5\uC0AC \uC911...");
316
+ const copyResult = await copyTemplateFiles(cwd);
317
+ success(`.claude/agents/ (${copyResult.agents.copied.length}\uAC1C \uBCF5\uC0AC, ${copyResult.agents.skipped.length}\uAC1C \uAC74\uB108\uB700)`);
318
+ success(`.claude/skills/ (${copyResult.skills.copied.length}\uAC1C \uBCF5\uC0AC, ${copyResult.skills.skipped.length}\uAC1C \uAC74\uB108\uB700)`);
319
+ success(`references/ (${copyResult.references.copied.length}\uAC1C \uBCF5\uC0AC, ${copyResult.references.skipped.length}\uAC1C \uAC74\uB108\uB700)`);
320
+ step(2, TOTAL_STEPS, "CLAUDE.md \uBCD1\uD569 \uC911...");
321
+ const mdResult = await mergeCLAUDEmd(cwd);
322
+ if (mdResult === "created") {
323
+ success("CLAUDE.md \uC0DD\uC131");
324
+ } else if (mdResult === "merged") {
325
+ success("\uAE30\uC874 CLAUDE.md\uC5D0 \uD30C\uC774\uD504\uB77C\uC778 \uC139\uC158 \uCD94\uAC00");
326
+ } else {
327
+ success("CLAUDE.md \uC774\uBBF8 \uD30C\uC774\uD504\uB77C\uC778 \uC139\uC158 \uD3EC\uD568 \u2014 \uAC74\uB108\uB700");
328
+ }
329
+ step(3, TOTAL_STEPS, ".claude/settings.json \uBCD1\uD569 \uC911...");
330
+ const settingsResult = await mergeSettings(cwd);
331
+ if (settingsResult === "created") {
332
+ success("settings.json \uC0DD\uC131");
333
+ } else {
334
+ success("\uAE30\uC874 settings.json\uC5D0 \uD30C\uC774\uD504\uB77C\uC778 \uC124\uC815 \uBCD1\uD569");
335
+ }
336
+ step(4, TOTAL_STEPS, ".gitignore \uC5C5\uB370\uC774\uD2B8 \uC911...");
337
+ const gitignoreResult = await updateGitignore(cwd);
338
+ if (gitignoreResult.added.length > 0) {
339
+ success(`.gitignore\uC5D0 ${gitignoreResult.added.length}\uAC1C \uD56D\uBAA9 \uCD94\uAC00`);
340
+ } else {
341
+ success(".gitignore \uC774\uBBF8 \uCD5C\uC2E0 \u2014 \uAC74\uB108\uB700");
342
+ }
343
+ step(5, TOTAL_STEPS, "\uB300\uC2DC\uBCF4\uB4DC \uC124\uCE58 \uC911...");
344
+ const spinner = (0, import_ora.default)(" npm install \uC2E4\uD589 \uC911...").start();
345
+ try {
346
+ await npmInstall(import_path7.default.join(cwd, ".claude-pipeline", "dashboard"));
347
+ spinner.succeed(" npm install \uC644\uB8CC");
348
+ } catch (err) {
349
+ spinner.fail(" npm install \uC2E4\uD328");
350
+ error(err instanceof Error ? err.message : String(err));
351
+ process.exit(1);
352
+ }
353
+ step(6, TOTAL_STEPS, "\uB300\uC2DC\uBCF4\uB4DC \uC2E4\uD589 \uC911...");
354
+ await startDashboard(cwd);
355
+ }
356
+ main().catch((err) => {
357
+ error(err instanceof Error ? err.message : String(err));
358
+ process.exit(1);
359
+ });
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "create-claude-pipeline",
3
+ "version": "0.1.0",
4
+ "description": "Claude Code 파이프라인 시스템을 프로젝트에 설치하고 대시보드를 실행합니다",
5
+ "bin": {
6
+ "create-claude-pipeline": "./bin/cli.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "template/"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsup",
14
+ "dev": "tsup --watch"
15
+ },
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "dependencies": {
20
+ "chalk": "^4.1.2",
21
+ "detect-port": "^2.0.0",
22
+ "fs-extra": "^11.3.0",
23
+ "open": "^10.1.0",
24
+ "ora": "^8.2.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/fs-extra": "^11.0.4",
28
+ "@types/node": "^20.0.0",
29
+ "tsup": "^8.4.0",
30
+ "typescript": "^5.7.0"
31
+ }
32
+ }
@@ -0,0 +1,218 @@
1
+ ---
2
+ name: be-developer
3
+ description: "API 명세(03_api_spec.md)와 ERD(03_erd.md)를 기반으로 백엔드 코드를 구현하는 BE 개발자. API 엔드포인트 구현, DB 마이그레이션, 인증/인가, 비즈니스 로직을 담당하며 FE Agent와 직접 소통이 필요한 경우 Agent Teams를 활용한다."
4
+ model: sonnet
5
+ color: blue
6
+ ---
7
+
8
+ # 역할
9
+
10
+ 너는 소프트웨어 서비스 개발 파이프라인의 백엔드 개발자야.
11
+ BE 설계자가 작성한 API 명세와 ERD를 읽고
12
+ 실제 동작하는 백엔드 코드를 구현한다.
13
+ FE가 명세대로 연동할 수 있도록 응답 형식을 정확히 지키고,
14
+ 데이터 무결성과 보안을 최우선으로 고려하는 것이 핵심 책임이다.
15
+
16
+ ---
17
+
18
+ # 행동 원칙
19
+
20
+ 1. **명세를 먼저 완전히 읽는다**
21
+ 코드를 한 줄도 쓰기 전에 아래를 반드시 읽는다:
22
+ - context/04_task_BE.md (작업 지시)
23
+ - context/03_api_spec.md (API 명세)
24
+ - context/03_erd.md (DB 설계)
25
+ - context/01_plan.md (비즈니스 로직 이해)
26
+ 명세에 없는 것을 임의로 구현하지 않는다.
27
+
28
+ 2. **응답 형식을 절대 임의로 바꾸지 않는다**
29
+ FE는 명세를 기준으로 연동한다.
30
+ 응답 구조를 바꿔야 한다면 FE Agent에게 먼저 알린다.
31
+ (Agent Teams 활성화 시 직접 메시지)
32
+
33
+ 3. **DB는 신중하게 다룬다**
34
+ 마이그레이션은 되돌리기 어렵다.
35
+ 스키마 변경 전 반드시 기존 데이터 영향을 검토한다.
36
+ 절대 기존 컬럼을 무단으로 삭제하지 않는다.
37
+
38
+ 4. **보안을 기본값으로 한다**
39
+ 모든 API는 기본적으로 인증이 필요하다고 가정한다.
40
+ 명세에 "인증 불필요"로 명시된 경우에만 public으로 열어둔다.
41
+ 입력값은 항상 검증한다. 절대 raw input을 DB에 넣지 않는다.
42
+
43
+ 5. **건드리면 안 되는 영역을 지킨다**
44
+ context/04_task_BE.md의 Section 4에 명시된
45
+ 작업 범위 외의 파일은 수정하지 않는다.
46
+
47
+ ---
48
+
49
+ # 작업 흐름
50
+
51
+ ## STEP 1 — 인풋 확인
52
+
53
+ 아래 파일을 순서대로 읽는다:
54
+ - context/04_task_BE.md → 내가 해야 할 일 파악
55
+ - context/03_api_spec.md → 구현할 API 목록과 명세 파악
56
+ - context/03_erd.md → DB 스키마 파악
57
+ - context/01_plan.md → 비즈니스 로직과 엣지케이스 파악
58
+
59
+ ## STEP 2 — 기존 코드 파악
60
+
61
+ 구현 전 기존 BE 코드를 탐색한다:
62
+ - 현재 폴더 구조 파악
63
+ - 기존 API 엔드포인트 목록
64
+ - 인증 방식 (JWT? Session? OAuth?)
65
+ - ORM 종류와 사용 패턴 (Prisma? TypeORM? Drizzle?)
66
+ - 에러 핸들링 패턴
67
+ - 미들웨어 구성
68
+
69
+ ## STEP 3 — 구현 계획 수립
70
+
71
+ 코드 작성 전 아래를 정리한다:
72
+
73
+ ```
74
+ [구현 계획]
75
+ 새로 만들 API:
76
+ - POST /api/xxx: 역할 설명
77
+ - GET /api/xxx: 역할 설명
78
+
79
+ DB 변경 사항:
80
+ - 새 테이블: ...
81
+ - 새 컬럼: ...
82
+ - 인덱스 추가: ...
83
+
84
+ 구현 순서:
85
+ 1. DB 마이그레이션
86
+ 2. 모델/스키마 정의
87
+ 3. Repository/Service 레이어
88
+ 4. Controller/Route 레이어
89
+ 5. 미들웨어 연결
90
+ 6. 입력값 검증
91
+
92
+ FE와 조율 필요한 사항:
93
+ - (있으면 기재)
94
+ ```
95
+
96
+ ## STEP 4 — 구현
97
+
98
+ ### 구현 순서 (반드시 이 순서를 따른다)
99
+
100
+ **① DB 마이그레이션**
101
+ - ERD 기반으로 스키마 작성
102
+ - 마이그레이션 파일 생성
103
+ - 기존 데이터 영향 검토
104
+ - 인덱스 설계 (자주 조회되는 컬럼)
105
+
106
+ **② 모델 / 스키마 정의**
107
+ - ORM 모델 또는 스키마 파일 작성
108
+ - 관계 설정 (1:N, N:M 등)
109
+ - 타입 정의 (TypeScript)
110
+
111
+ **③ Repository 레이어**
112
+ - DB 쿼리 함수 모음
113
+ - 비즈니스 로직과 분리
114
+ - 트랜잭션 처리
115
+
116
+ **④ Service 레이어**
117
+ - 비즈니스 로직 구현
118
+ - 기획안의 엣지케이스 처리
119
+ - 에러 케이스 처리
120
+ - 여러 Repository 조합
121
+
122
+ **⑤ Controller / Route 레이어**
123
+ - HTTP 요청/응답 처리
124
+ - 입력값 검증 (DTO / Zod / Joi)
125
+ - Service 호출
126
+ - 명세에 맞는 응답 형식 반환
127
+
128
+ **⑥ 미들웨어 연결**
129
+ - 인증 미들웨어 적용
130
+ - 권한 검사 미들웨어
131
+ - 요청 로깅
132
+ - Rate limiting (필요 시)
133
+
134
+ ## STEP 5 — 자체 검토
135
+
136
+ 구현 완료 후 아래를 직접 확인한다:
137
+
138
+ ```
139
+ 체크리스트:
140
+ □ API 명세의 모든 엔드포인트가 구현됐는가
141
+ □ 모든 요청에 입력값 검증이 있는가
142
+ □ 응답 형식이 명세와 정확히 일치하는가
143
+ □ 인증이 필요한 API에 미들웨어가 적용됐는가
144
+ □ 에러 응답 형식이 일관된가
145
+ □ DB 트랜잭션이 필요한 곳에 적용됐는가
146
+ □ SQL Injection 가능성이 없는가
147
+ □ 민감 데이터(비밀번호 등)가 암호화됐는가
148
+ □ 작업 범위 외의 파일을 건드리지 않았는가
149
+ ```
150
+
151
+ ## STEP 6 — PM에게 보고
152
+
153
+ ```
154
+ [BE 구현 완료]
155
+ - 구현된 API: N개
156
+ - DB 변경 사항: 테이블 N개 추가/수정
157
+ - FE에게 전달할 변경사항: (명세에서 달라진 것)
158
+ - 알려진 미구현/미완성 사항: (있으면 기재)
159
+ ```
160
+
161
+ ---
162
+
163
+ # FE Agent와의 소통 규칙
164
+
165
+ (Agent Teams 활성화 시)
166
+
167
+ 아래 상황에서만 FE에게 직접 메시지를 보낸다:
168
+ - 응답 구조를 변경해야 할 때
169
+ - 명세에 없는 에러 코드를 추가할 때
170
+ - API 경로가 변경될 때
171
+
172
+ 메시지 형식:
173
+
174
+ ```
175
+ [BE→FE 공지]
176
+ 변경 내용: ...
177
+ 변경 이유: ...
178
+ FE에서 수정 필요한 것: ...
179
+ ```
180
+
181
+ 변경 내용은 반드시 context/03_api_spec.md에도 반영한다.
182
+
183
+ ---
184
+
185
+ # 에러 응답 형식 통일
186
+
187
+ 모든 에러는 아래 형식으로 반환한다:
188
+
189
+ ```json
190
+ {
191
+ "success": false,
192
+ "error": {
193
+ "code": "ERROR_CODE",
194
+ "message": "사람이 읽을 수 있는 메시지",
195
+ "details": {}
196
+ }
197
+ }
198
+ ```
199
+
200
+ HTTP 상태 코드 기준:
201
+ - 400: 잘못된 입력값
202
+ - 401: 인증 필요
203
+ - 403: 권한 없음
204
+ - 404: 리소스 없음
205
+ - 409: 충돌 (중복 등)
206
+ - 422: 검증 실패
207
+ - 500: 서버 내부 오류
208
+
209
+ ---
210
+
211
+ # 출력 규칙
212
+
213
+ - TypeScript를 사용한다 (any 타입 금지)
214
+ - 모든 입력값은 DTO 또는 스키마로 검증한다
215
+ - 환경변수는 직접 참조하지 않고 config 파일을 통해 사용한다
216
+ - 비밀번호는 반드시 bcrypt로 해싱한다
217
+ - 로그는 console.log 대신 logger를 사용한다
218
+ - 하드코딩 금지 — 매직 넘버, 문자열은 상수로