commit-ai-agent 1.0.3 → 1.0.5

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/.env.example CHANGED
@@ -1,3 +1,3 @@
1
1
  GEMINI_API_KEY=your_gemini_api_key_here
2
2
  PORT=3000
3
- DEV_ROOT=C:/dev
3
+ DEV_ROOT=C:/dev // 절대경로 입력
package/README.md CHANGED
@@ -14,15 +14,22 @@ AI가 git 커밋과 현재 변경사항을 한국어로 자동 분석해주는
14
14
 
15
15
  ### 방법 A — npx (설치 없이 바로 실행)
16
16
 
17
+ - 프로젝트가 모여있는 디렉토리에서 아래 명령어 실행
18
+ - 해당 디렉토리에서 .env 파일 생성(.env.example 참고)
19
+
17
20
  ```bash
18
21
  npx commit-ai-agent
19
22
  ```
20
23
 
21
24
  ### 방법 B — 전역 설치 후 명령어로 실행
22
25
 
26
+ - 전역으로 설치하면 어느 위치에서든 `commit-ai-agent` 명령어로 실행 가능
27
+ - 프로젝트가 모여있는 디렉토리에서 명령어 실행
28
+ - 해당 디렉토리에서 .env 파일 생성(.env.example 참고)
29
+
23
30
  ```bash
24
31
  npm install -g commit-ai-agent
25
- commit-analyzer
32
+ commit-ai-agent
26
33
  ```
27
34
 
28
35
  ### 방법 C — 직접 클론
@@ -34,7 +41,7 @@ npm install
34
41
  npm start
35
42
  ```
36
43
 
37
- npm start 대신 Windows 사용자는 `start.bat`를 더블클릭하여 바로 실행할 수 있습니다.
44
+ npm start 대신 Windows 사용자는 `start.bat`파일을 더블클릭하여 바로 실행할 수 있습니다.
38
45
 
39
46
  ---
40
47
 
@@ -44,7 +51,7 @@ npm start 대신 Windows 사용자는 `start.bat`를 더블클릭하여 바로
44
51
 
45
52
  ```env
46
53
  GEMINI_API_KEY=여기에_API_키_입력
47
- DEV_ROOT=C:/Users/이름/dev
54
+ DEV_ROOT=C:/Users/projects => 절대경로 입력
48
55
  PORT=3000
49
56
  ```
50
57
 
package/bin/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * commit-analyzer CLI 진입점
4
- * npx commit-analyzer 또는 npm install -g 후 commit-analyzer 명령으로 실행
3
+ * commit-ai-agent CLI 진입점
4
+ * npx commit-ai-agent 또는 npm install -g 후 commit-ai-agent 명령으로 실행
5
5
  */
6
6
  import dotenv from "dotenv";
7
7
  import path from "path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commit-ai-agent",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "AI-powered git commit & working status analyzer with web UI",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <rect width="32" height="32" rx="7" fill="#6366f1"/>
3
+ <path d="M20 2 L8 17 L15 17 L12 30 L24 15 L17 15 Z" fill="white"/>
4
+ </svg>
package/public/index.html CHANGED
@@ -9,6 +9,8 @@
9
9
  content="AI 기반 git 커밋 문서화 및 코드 리뷰 에이전트"
10
10
  />
11
11
  <meta name="theme-color" content="#6366f1" />
12
+ <link rel="icon" type="image/svg+xml" href="/icon.svg" />
13
+ <link rel="shortcut icon" href="/favicon.ico" />
12
14
  <link rel="preconnect" href="https://fonts.googleapis.com" />
13
15
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
14
16
  <link
@@ -118,7 +120,9 @@
118
120
  <div class="result-header">
119
121
  <h2 class="card-title">📝 AI 분석 결과</h2>
120
122
  <div class="result-actions">
121
- <button class="btn-ghost" id="copy-btn" style="display:none">📋 복사</button>
123
+ <button class="btn-ghost" id="copy-btn" style="display: none">
124
+ 📋 복사
125
+ </button>
122
126
  <span class="report-saved" id="report-saved"></span>
123
127
  </div>
124
128
  </div>
package/src/git.js CHANGED
@@ -1,9 +1,9 @@
1
- import simpleGit from 'simple-git';
2
- import path from 'path';
3
- import fs from 'fs';
1
+ import simpleGit from "simple-git";
2
+ import path from "path";
3
+ import fs from "fs";
4
4
 
5
5
  /**
6
- * c:\dev 하위의 git 프로젝트 목록을 반환합니다.
6
+ * DEV_ROOT 하위의 git 프로젝트 목록을 반환합니다.
7
7
  */
8
8
  export async function listGitProjects(devRoot) {
9
9
  const entries = fs.readdirSync(devRoot, { withFileTypes: true });
@@ -12,7 +12,7 @@ export async function listGitProjects(devRoot) {
12
12
  for (const entry of entries) {
13
13
  if (!entry.isDirectory()) continue;
14
14
  const fullPath = path.join(devRoot, entry.name);
15
- const gitDir = path.join(fullPath, '.git');
15
+ const gitDir = path.join(fullPath, ".git");
16
16
  if (fs.existsSync(gitDir)) {
17
17
  projects.push({ name: entry.name, path: fullPath });
18
18
  }
@@ -30,38 +30,38 @@ export async function getLatestCommit(projectPath) {
30
30
  // 최신 커밋 메타데이터
31
31
  const log = await git.log({ maxCount: 1 });
32
32
  if (!log.latest) {
33
- throw new Error('커밋 기록이 없습니다.');
33
+ throw new Error("커밋 기록이 없습니다.");
34
34
  }
35
35
 
36
36
  const { hash, message, author_name, author_email, date } = log.latest;
37
37
 
38
38
  // 이전 커밋과의 diff (파일 목록)
39
- let diffStat = '';
40
- let diffContent = '';
39
+ let diffStat = "";
40
+ let diffContent = "";
41
41
 
42
42
  try {
43
43
  // 부모 커밋이 있는지 확인
44
- const parentCount = await git.raw(['rev-list', '--count', 'HEAD']);
44
+ const parentCount = await git.raw(["rev-list", "--count", "HEAD"]);
45
45
  const count = parseInt(parentCount.trim(), 10);
46
46
 
47
47
  if (count > 1) {
48
- diffStat = await git.raw(['diff', '--stat', 'HEAD~1', 'HEAD']);
48
+ diffStat = await git.raw(["diff", "--stat", "HEAD~1", "HEAD"]);
49
49
  // diff 내용은 너무 클 수 있으므로 최대 300줄 제한
50
- const rawDiff = await git.raw(['diff', 'HEAD~1', 'HEAD']);
51
- const lines = rawDiff.split('\n');
52
- diffContent = lines.slice(0, 300).join('\n');
50
+ const rawDiff = await git.raw(["diff", "HEAD~1", "HEAD"]);
51
+ const lines = rawDiff.split("\n");
52
+ diffContent = lines.slice(0, 300).join("\n");
53
53
  if (lines.length > 300) {
54
- diffContent += '\n... (이하 생략, 너무 긴 diff)';
54
+ diffContent += "\n... (이하 생략, 너무 긴 diff)";
55
55
  }
56
56
  } else {
57
57
  // 첫 번째 커밋인 경우
58
- diffStat = await git.raw(['show', '--stat', 'HEAD']);
59
- const rawShow = await git.raw(['show', 'HEAD']);
60
- const lines = rawShow.split('\n');
61
- diffContent = lines.slice(0, 300).join('\n');
58
+ diffStat = await git.raw(["show", "--stat", "HEAD"]);
59
+ const rawShow = await git.raw(["show", "HEAD"]);
60
+ const lines = rawShow.split("\n");
61
+ diffContent = lines.slice(0, 300).join("\n");
62
62
  }
63
63
  } catch (e) {
64
- diffContent = '(diff를 가져올 수 없습니다)';
64
+ diffContent = "(diff를 가져올 수 없습니다)";
65
65
  }
66
66
 
67
67
  return {
@@ -70,7 +70,7 @@ export async function getLatestCommit(projectPath) {
70
70
  message,
71
71
  author: author_name,
72
72
  email: author_email,
73
- date: new Date(date).toLocaleString('ko-KR'),
73
+ date: new Date(date).toLocaleString("ko-KR"),
74
74
  diffStat,
75
75
  diffContent,
76
76
  };
@@ -85,7 +85,8 @@ export async function getWorkingStatus(projectPath) {
85
85
 
86
86
  // git status --short 로 파일 목록
87
87
  const statusSummary = await git.status();
88
- const { files, staged, modified, not_added, deleted, renamed } = statusSummary;
88
+ const { files, staged, modified, not_added, deleted, renamed } =
89
+ statusSummary;
89
90
 
90
91
  if (files.length === 0) {
91
92
  return null; // 변경사항 없음
@@ -96,44 +97,48 @@ export async function getWorkingStatus(projectPath) {
96
97
  for (const f of files) {
97
98
  statusLines.push(`${f.index}${f.working_dir} ${f.path}`);
98
99
  }
99
- const statusText = statusLines.join('\n');
100
+ const statusText = statusLines.join("\n");
100
101
 
101
102
  // staged diff (git diff --cached)
102
- let stagedDiff = '';
103
+ let stagedDiff = "";
103
104
  try {
104
- const raw = await git.raw(['diff', '--cached']);
105
- const lines = raw.split('\n');
106
- stagedDiff = lines.slice(0, 200).join('\n');
107
- if (lines.length > 200) stagedDiff += '\n... (이하 생략)';
105
+ const raw = await git.raw(["diff", "--cached"]);
106
+ const lines = raw.split("\n");
107
+ stagedDiff = lines.slice(0, 200).join("\n");
108
+ if (lines.length > 200) stagedDiff += "\n... (이하 생략)";
108
109
  } catch {}
109
110
 
110
111
  // unstaged diff (git diff)
111
- let unstagedDiff = '';
112
+ let unstagedDiff = "";
112
113
  try {
113
- const raw = await git.raw(['diff']);
114
- const lines = raw.split('\n');
115
- unstagedDiff = lines.slice(0, 200).join('\n');
116
- if (lines.length > 200) unstagedDiff += '\n... (이하 생략)';
114
+ const raw = await git.raw(["diff"]);
115
+ const lines = raw.split("\n");
116
+ unstagedDiff = lines.slice(0, 200).join("\n");
117
+ if (lines.length > 200) unstagedDiff += "\n... (이하 생략)";
117
118
  } catch {}
118
119
 
119
120
  // untracked 파일 내용 (최대 3개)
120
- let untrackedContent = '';
121
- const untrackedFiles = files.filter(f => f.index === '?' && f.working_dir === '?').slice(0, 3);
121
+ let untrackedContent = "";
122
+ const untrackedFiles = files
123
+ .filter((f) => f.index === "?" && f.working_dir === "?")
124
+ .slice(0, 3);
122
125
  for (const f of untrackedFiles) {
123
126
  try {
124
127
  const fullPath = path.join(projectPath, f.path);
125
- const content = fs.readFileSync(fullPath, 'utf-8');
126
- const lines = content.split('\n').slice(0, 80).join('\n');
128
+ const content = fs.readFileSync(fullPath, "utf-8");
129
+ const lines = content.split("\n").slice(0, 80).join("\n");
127
130
  untrackedContent += `\n--- ${f.path} (신규 파일) ---\n${lines}\n`;
128
131
  } catch {}
129
132
  }
130
133
 
131
134
  const hasStagedChanges = staged.length > 0 || renamed.length > 0;
132
135
  const diffContent = [
133
- stagedDiff ? `# Staged Changes (git diff --cached)\n${stagedDiff}` : '',
134
- unstagedDiff ? `# Unstaged Changes (git diff)\n${unstagedDiff}` : '',
135
- untrackedContent ? `# New Files\n${untrackedContent}` : '',
136
- ].filter(Boolean).join('\n\n');
136
+ stagedDiff ? `# Staged Changes (git diff --cached)\n${stagedDiff}` : "",
137
+ unstagedDiff ? `# Unstaged Changes (git diff)\n${unstagedDiff}` : "",
138
+ untrackedContent ? `# New Files\n${untrackedContent}` : "",
139
+ ]
140
+ .filter(Boolean)
141
+ .join("\n\n");
137
142
 
138
143
  return {
139
144
  statusText,
@@ -143,7 +148,7 @@ export async function getWorkingStatus(projectPath) {
143
148
  untrackedCount: not_added.length,
144
149
  totalFiles: files.length,
145
150
  hasStagedChanges,
146
- diffContent: diffContent || '(diff 내용 없음)',
151
+ diffContent: diffContent || "(diff 내용 없음)",
147
152
  diffStat: statusText,
148
153
  };
149
154
  }
package/src/server.js CHANGED
@@ -1,10 +1,10 @@
1
- import 'dotenv/config';
2
- import express from 'express';
3
- import path from 'path';
4
- import { fileURLToPath } from 'url';
5
- import fs from 'fs';
6
- import { listGitProjects, getLatestCommit, getWorkingStatus } from './git.js';
7
- import { analyzeCommit, analyzeWorkingStatus } from './analyzer.js';
1
+ import "dotenv/config";
2
+ import express from "express";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ import fs from "fs";
6
+ import { listGitProjects, getLatestCommit, getWorkingStatus } from "./git.js";
7
+ import { analyzeCommit, analyzeWorkingStatus } from "./analyzer.js";
8
8
 
9
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
10
  const app = express();
@@ -14,10 +14,11 @@ const DEV_ROOT = process.env.DEV_ROOT;
14
14
 
15
15
  // npx/global install 시: COMMIT_ANALYZER_ROOT = bin/cli.js가 설정한 패키지 루트
16
16
  // 로컬 dev 시: __dirname/../ 사용
17
- const PACKAGE_ROOT = process.env.COMMIT_ANALYZER_ROOT || path.join(__dirname, '..');
17
+ const PACKAGE_ROOT =
18
+ process.env.COMMIT_ANALYZER_ROOT || path.join(__dirname, "..");
18
19
 
19
20
  // reports는 항상 사용자 현재 디렉토리에 저장
20
- const REPORTS_DIR = path.join(process.cwd(), 'reports');
21
+ const REPORTS_DIR = path.join(process.cwd(), "reports");
21
22
 
22
23
  // 리포트 저장 디렉토리 생성
23
24
  if (!fs.existsSync(REPORTS_DIR)) {
@@ -25,35 +26,37 @@ if (!fs.existsSync(REPORTS_DIR)) {
25
26
  }
26
27
 
27
28
  app.use(express.json());
28
- app.use(express.static(path.join(PACKAGE_ROOT, 'public')));
29
+ app.use(express.static(path.join(PACKAGE_ROOT, "public")));
29
30
 
30
31
  // ──────────────────────────────────────────────
31
32
  // PWA 아이콘 (SVG를 PNG MIME으로 서빙)
32
33
  // ──────────────────────────────────────────────
33
- app.get('/api/icon/:size', (req, res) => {
34
- const svgPath = path.join(__dirname, '..', 'public', 'icon.svg');
35
- res.setHeader('Content-Type', 'image/svg+xml');
34
+ app.get("/api/icon/:size", (req, res) => {
35
+ const svgPath = path.join(__dirname, "..", "public", "icon.svg");
36
+ res.setHeader("Content-Type", "image/svg+xml");
36
37
  res.sendFile(svgPath);
37
38
  });
38
39
 
39
- app.get('/favicon.ico', (req, res) => {
40
- res.sendFile(path.join(__dirname, '..', 'public', 'icon.svg'));
40
+ app.get("/favicon.ico", (req, res) => {
41
+ res.sendFile(path.join(__dirname, "..", "public", "icon.svg"));
41
42
  });
42
43
 
43
-
44
44
  // ──────────────────────────────────────────────
45
45
  // API: 설정 확인
46
46
 
47
47
  // ──────────────────────────────────────────────
48
- app.get('/api/config', (req, res) => {
49
- const hasKey = !!(process.env.GEMINI_API_KEY && process.env.GEMINI_API_KEY !== 'your_gemini_api_key_here');
48
+ app.get("/api/config", (req, res) => {
49
+ const hasKey = !!(
50
+ process.env.GEMINI_API_KEY &&
51
+ process.env.GEMINI_API_KEY !== "your_gemini_api_key_here"
52
+ );
50
53
  res.json({ hasKey, devRoot: DEV_ROOT });
51
54
  });
52
55
 
53
56
  // ──────────────────────────────────────────────
54
57
  // API: 프로젝트 목록
55
58
  // ──────────────────────────────────────────────
56
- app.get('/api/projects', async (req, res) => {
59
+ app.get("/api/projects", async (req, res) => {
57
60
  try {
58
61
  const projects = await listGitProjects(DEV_ROOT);
59
62
  res.json({ projects });
@@ -65,7 +68,7 @@ app.get('/api/projects', async (req, res) => {
65
68
  // ──────────────────────────────────────────────
66
69
  // API: 최근 커밋 정보 조회
67
70
  // ──────────────────────────────────────────────
68
- app.get('/api/projects/:name/commit', async (req, res) => {
71
+ app.get("/api/projects/:name/commit", async (req, res) => {
69
72
  try {
70
73
  const projectPath = path.join(DEV_ROOT, req.params.name);
71
74
  const commit = await getLatestCommit(projectPath);
@@ -78,7 +81,7 @@ app.get('/api/projects/:name/commit', async (req, res) => {
78
81
  // ──────────────────────────────────────────────
79
82
  // API: 현재 git status 조회
80
83
  // ──────────────────────────────────────────────
81
- app.get('/api/projects/:name/status', async (req, res) => {
84
+ app.get("/api/projects/:name/status", async (req, res) => {
82
85
  try {
83
86
  const projectPath = path.join(DEV_ROOT, req.params.name);
84
87
  const status = await getWorkingStatus(projectPath);
@@ -91,22 +94,24 @@ app.get('/api/projects/:name/status', async (req, res) => {
91
94
  // ──────────────────────────────────────────────
92
95
  // API: AI 분석 실행 (SSE 스트리밍)
93
96
  // ──────────────────────────────────────────────
94
- app.post('/api/analyze', async (req, res) => {
97
+ app.post("/api/analyze", async (req, res) => {
95
98
  const { projectName } = req.body;
96
99
  const apiKey = process.env.GEMINI_API_KEY;
97
100
 
98
- if (!apiKey || apiKey === 'your_gemini_api_key_here') {
99
- return res.status(400).json({ error: 'GEMINI_API_KEY가 .env 파일에 설정되지 않았습니다.' });
101
+ if (!apiKey || apiKey === "your_gemini_api_key_here") {
102
+ return res
103
+ .status(400)
104
+ .json({ error: "GEMINI_API_KEY가 .env 파일에 설정되지 않았습니다." });
100
105
  }
101
106
 
102
107
  if (!projectName) {
103
- return res.status(400).json({ error: '프로젝트명이 필요합니다.' });
108
+ return res.status(400).json({ error: "프로젝트명이 필요합니다." });
104
109
  }
105
110
 
106
111
  // Server-Sent Events 설정
107
- res.setHeader('Content-Type', 'text/event-stream');
108
- res.setHeader('Cache-Control', 'no-cache');
109
- res.setHeader('Connection', 'keep-alive');
112
+ res.setHeader("Content-Type", "text/event-stream");
113
+ res.setHeader("Cache-Control", "no-cache");
114
+ res.setHeader("Connection", "keep-alive");
110
115
  res.flushHeaders();
111
116
 
112
117
  const send = (data) => {
@@ -114,27 +119,30 @@ app.post('/api/analyze', async (req, res) => {
114
119
  };
115
120
 
116
121
  try {
117
- send({ type: 'status', message: '커밋 정보를 가져오는 중...' });
122
+ send({ type: "status", message: "커밋 정보를 가져오는 중..." });
118
123
  const projectPath = path.join(DEV_ROOT, projectName);
119
124
  const commit = await getLatestCommit(projectPath);
120
125
 
121
- send({ type: 'commit', commit });
122
- send({ type: 'status', message: 'AI 분석 중... (30초~1분 소요)' });
126
+ send({ type: "commit", commit });
127
+ send({ type: "status", message: "AI 분석 중... (30초~1분 소요)" });
123
128
 
124
129
  const analysis = await analyzeCommit(commit, projectName, apiKey);
125
130
 
126
131
  // 리포트 저장
127
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
132
+ const timestamp = new Date()
133
+ .toISOString()
134
+ .replace(/[:.]/g, "-")
135
+ .slice(0, 19);
128
136
  const reportFilename = `${projectName}-${timestamp}.md`;
129
137
  const reportPath = path.join(REPORTS_DIR, reportFilename);
130
138
  const fullReport = buildMarkdownReport(projectName, commit, analysis);
131
- fs.writeFileSync(reportPath, fullReport, 'utf-8');
139
+ fs.writeFileSync(reportPath, fullReport, "utf-8");
132
140
 
133
- send({ type: 'analysis', analysis, reportFilename });
134
- send({ type: 'done' });
141
+ send({ type: "analysis", analysis, reportFilename });
142
+ send({ type: "done" });
135
143
  res.end();
136
144
  } catch (err) {
137
- send({ type: 'error', message: err.message });
145
+ send({ type: "error", message: err.message });
138
146
  res.end();
139
147
  }
140
148
  });
@@ -142,21 +150,23 @@ app.post('/api/analyze', async (req, res) => {
142
150
  // ──────────────────────────────────────────────
143
151
  // API: git status 변경사항 AI 분석 (SSE 스트리밍)
144
152
  // ──────────────────────────────────────────────
145
- app.post('/api/analyze-status', async (req, res) => {
153
+ app.post("/api/analyze-status", async (req, res) => {
146
154
  const { projectName } = req.body;
147
155
  const apiKey = process.env.GEMINI_API_KEY;
148
156
 
149
- if (!apiKey || apiKey === 'your_gemini_api_key_here') {
150
- return res.status(400).json({ error: 'GEMINI_API_KEY가 .env 파일에 설정되지 않았습니다.' });
157
+ if (!apiKey || apiKey === "your_gemini_api_key_here") {
158
+ return res
159
+ .status(400)
160
+ .json({ error: "GEMINI_API_KEY가 .env 파일에 설정되지 않았습니다." });
151
161
  }
152
162
 
153
163
  if (!projectName) {
154
- return res.status(400).json({ error: '프로젝트명이 필요합니다.' });
164
+ return res.status(400).json({ error: "프로젝트명이 필요합니다." });
155
165
  }
156
166
 
157
- res.setHeader('Content-Type', 'text/event-stream');
158
- res.setHeader('Cache-Control', 'no-cache');
159
- res.setHeader('Connection', 'keep-alive');
167
+ res.setHeader("Content-Type", "text/event-stream");
168
+ res.setHeader("Cache-Control", "no-cache");
169
+ res.setHeader("Connection", "keep-alive");
160
170
  res.flushHeaders();
161
171
 
162
172
  const send = (data) => {
@@ -164,32 +174,43 @@ app.post('/api/analyze-status', async (req, res) => {
164
174
  };
165
175
 
166
176
  try {
167
- send({ type: 'status', message: '변경사항을 가져오는 중...' });
177
+ send({ type: "status", message: "변경사항을 가져오는 중..." });
168
178
  const projectPath = path.join(DEV_ROOT, projectName);
169
179
  const workingStatus = await getWorkingStatus(projectPath);
170
180
 
171
181
  if (!workingStatus) {
172
- send({ type: 'error', message: '현재 변경사항이 없습니다. 코드를 수정한 뒤 다시 시도해 주세요.' });
182
+ send({
183
+ type: "error",
184
+ message:
185
+ "현재 변경사항이 없습니다. 코드를 수정한 뒤 다시 시도해 주세요.",
186
+ });
173
187
  return res.end();
174
188
  }
175
189
 
176
- send({ type: 'working-status', workingStatus });
177
- send({ type: 'status', message: 'AI 분석 중... (30초~1분 소요)' });
190
+ send({ type: "working-status", workingStatus });
191
+ send({ type: "status", message: "AI 분석 중... (30초~1분 소요)" });
178
192
 
179
- const analysis = await analyzeWorkingStatus(workingStatus, projectName, apiKey);
193
+ const analysis = await analyzeWorkingStatus(
194
+ workingStatus,
195
+ projectName,
196
+ apiKey,
197
+ );
180
198
 
181
199
  // 리포트 저장
182
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
200
+ const timestamp = new Date()
201
+ .toISOString()
202
+ .replace(/[:.]/g, "-")
203
+ .slice(0, 19);
183
204
  const reportFilename = `${projectName}-status-${timestamp}.md`;
184
205
  const reportPath = path.join(REPORTS_DIR, reportFilename);
185
206
  const fullReport = buildStatusReport(projectName, workingStatus, analysis);
186
- fs.writeFileSync(reportPath, fullReport, 'utf-8');
207
+ fs.writeFileSync(reportPath, fullReport, "utf-8");
187
208
 
188
- send({ type: 'analysis', analysis, reportFilename });
189
- send({ type: 'done' });
209
+ send({ type: "analysis", analysis, reportFilename });
210
+ send({ type: "done" });
190
211
  res.end();
191
212
  } catch (err) {
192
- send({ type: 'error', message: err.message });
213
+ send({ type: "error", message: err.message });
193
214
  res.end();
194
215
  }
195
216
  });
@@ -197,10 +218,11 @@ app.post('/api/analyze-status', async (req, res) => {
197
218
  // ──────────────────────────────────────────────
198
219
  // API: 저장된 리포트 목록
199
220
  // ──────────────────────────────────────────────
200
- app.get('/api/reports', (req, res) => {
221
+ app.get("/api/reports", (req, res) => {
201
222
  try {
202
- const files = fs.readdirSync(REPORTS_DIR)
203
- .filter(f => f.endsWith('.md'))
223
+ const files = fs
224
+ .readdirSync(REPORTS_DIR)
225
+ .filter((f) => f.endsWith(".md"))
204
226
  .sort()
205
227
  .reverse()
206
228
  .slice(0, 20); // 최근 20개
@@ -213,13 +235,13 @@ app.get('/api/reports', (req, res) => {
213
235
  // ──────────────────────────────────────────────
214
236
  // API: 특정 리포트 읽기
215
237
  // ──────────────────────────────────────────────
216
- app.get('/api/reports/:filename', (req, res) => {
238
+ app.get("/api/reports/:filename", (req, res) => {
217
239
  try {
218
240
  const filePath = path.join(REPORTS_DIR, req.params.filename);
219
241
  if (!fs.existsSync(filePath)) {
220
- return res.status(404).json({ error: '리포트를 찾을 수 없습니다.' });
242
+ return res.status(404).json({ error: "리포트를 찾을 수 없습니다." });
221
243
  }
222
- const content = fs.readFileSync(filePath, 'utf-8');
244
+ const content = fs.readFileSync(filePath, "utf-8");
223
245
  res.json({ content });
224
246
  } catch (err) {
225
247
  res.status(500).json({ error: err.message });
@@ -229,7 +251,7 @@ app.get('/api/reports/:filename', (req, res) => {
229
251
  function buildMarkdownReport(projectName, commit, analysis) {
230
252
  return `# 커밋 분석 리포트: ${projectName}
231
253
 
232
- > 생성 시각: ${new Date().toLocaleString('ko-KR')}
254
+ > 생성 시각: ${new Date().toLocaleString("ko-KR")}
233
255
 
234
256
  ## 커밋 정보
235
257
  | 항목 | 내용 |
@@ -248,7 +270,7 @@ ${analysis}
248
270
  function buildStatusReport(projectName, status, analysis) {
249
271
  return `# 작업 중 변경사항 분석: ${projectName}
250
272
 
251
- > 생성 시각: ${new Date().toLocaleString('ko-KR')}
273
+ > 생성 시각: ${new Date().toLocaleString("ko-KR")}
252
274
 
253
275
  ## 변경사항 요약
254
276
  | 항목 | 수량 |
@@ -269,7 +291,7 @@ ${analysis}
269
291
  }
270
292
 
271
293
  app.listen(PORT, () => {
272
- console.log(`\n🚀 Commit Analyzer 실행 중`);
294
+ console.log(`\n🚀 Commit Ai Agent 실행 중`);
273
295
  console.log(` 브라우저: http://localhost:${PORT}`);
274
296
  console.log(` 분석 대상: ${DEV_ROOT}\n`);
275
297
  });