change-log-agent 1.0.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.
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/dist/index.mjs +171 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jesse Vincent
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# 🤖 log-agent
|
|
2
|
+
|
|
3
|
+
> **"讓 AI 成為你的專案文件官:一鍵同步 Git Commit 到高品質開發文件。"**
|
|
4
|
+
|
|
5
|
+
`log-agent` 是一款基於 **Agentic Workflow** 設計的 CLI 工具。它不只是簡單的文字搬運工,而是透過驅動你本地的 **Claude Code** 實例,將混亂的 Git Commit 歷史轉化為結構清晰、具備「人味」的專案文件(如 README 或 CHANGELOG)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ✨ 核心亮點
|
|
10
|
+
|
|
11
|
+
- **💰 零 API 成本**:直接利用你現有的 Claude Pro/Team 訂閱,無需支付額外 API Tokens 費用。
|
|
12
|
+
- **🧠 代理人架構 (Agentic Workflow)**:CLI 會動態生成任務規範(Mission Spec),指揮 AI 自主決定如何最佳化文件內容。
|
|
13
|
+
- **🎯 Surgical 級精準修改**:透過 HTML 註釋標記(`<!-- log-agent-start -->` / `<!-- log-agent-end -->`)定位,確保 AI 只修改授權區塊,絕不破壞你的原始文件排版。
|
|
14
|
+
- **⚙️ 語境感知 (Context-Aware)**:自動過濾碎屑 Commit(如 typo、formatting),並根據專案背景(如:官網重構)進行智能歸納。
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 🛠️ 運作原理
|
|
19
|
+
|
|
20
|
+
1. **萃取 (Extract)**:CLI 自動掃描 Git 歷史,鎖定自上次版本以來的變更。
|
|
21
|
+
2. **策劃 (Plan)**:根據專案現狀動態生成一份暫時的 `.claudemd.tmp` 任務規範。
|
|
22
|
+
3. **執行 (Execute)**:啟動 `claude-code` 擔任「執行官」,依照規範對目標檔案進行智能手術。
|
|
23
|
+
4. **清理 (Cleanup)**:任務完成後自動銷毀暫存檔,不留痕跡。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🚀 快速開始
|
|
28
|
+
|
|
29
|
+
### 1. 前置要求
|
|
30
|
+
|
|
31
|
+
- 已安裝 **Node.js** (v18+)
|
|
32
|
+
- 已安裝並登入 **Claude Code**:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g @anthropic-ai/claude-code
|
|
36
|
+
claude login
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. 安裝與開發
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# 克隆專案
|
|
43
|
+
git clone https://github.com/your-username/log-agent.git
|
|
44
|
+
cd log-agent
|
|
45
|
+
|
|
46
|
+
# 安裝依賴
|
|
47
|
+
npm install
|
|
48
|
+
|
|
49
|
+
# 編譯
|
|
50
|
+
npm run build
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 3. 初始化目標文件
|
|
54
|
+
|
|
55
|
+
在你的 `README.md` 或 `CHANGELOG.md` 中加入以下標記:
|
|
56
|
+
|
|
57
|
+
```markdown
|
|
58
|
+
## 📝 更新日誌
|
|
59
|
+
|
|
60
|
+
<!-- log-agent-start -->
|
|
61
|
+
<!-- log-agent-end -->
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 4. 執行同步
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# 使用本地開發版本測試
|
|
68
|
+
npx ts-node src/index.ts sync
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 📂 專案結構
|
|
74
|
+
|
|
75
|
+
- `src/core/git.ts`:負責處理 Git 指令與 Log 提取。
|
|
76
|
+
- `src/core/template.ts`:動態任務規範 (Mission Spec) 生成器。
|
|
77
|
+
- `src/index.ts`:CLI 入口與流程調度中心。
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 🤝 貢獻與開發
|
|
82
|
+
|
|
83
|
+
本專案目前專注於與 `claude-code` 的深度整合。如果你有任何關於 Prompt 優化或跨平台相容性的建議,歡迎提交 Issue 或 Pull Request!
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
License
|
|
88
|
+
|
|
89
|
+
MIT License - see LICENSE file for details
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { execa } from "execa";
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
|
|
7
|
+
//#region src/core/git.ts
|
|
8
|
+
/**
|
|
9
|
+
* 擷取 git log 並 parse 成結構化資料
|
|
10
|
+
* @param since - ISO date string, e.g. "2026-02-01"
|
|
11
|
+
*/
|
|
12
|
+
async function getCommits(since) {
|
|
13
|
+
const { stdout } = await execa("git", [
|
|
14
|
+
"log",
|
|
15
|
+
"--pretty=format:%h|%an|%ai|%s",
|
|
16
|
+
...since ? [`--since=${since}`] : []
|
|
17
|
+
]);
|
|
18
|
+
if (!stdout.trim()) return [];
|
|
19
|
+
return stdout.trim().split("\n").map(parseLine);
|
|
20
|
+
}
|
|
21
|
+
function parseLine(line) {
|
|
22
|
+
const parts = line.split("|");
|
|
23
|
+
const hash = parts[0] ?? "";
|
|
24
|
+
const author = parts[1] ?? "";
|
|
25
|
+
const dateRaw = parts[2] ?? "";
|
|
26
|
+
const message = parts[3] ?? "";
|
|
27
|
+
return {
|
|
28
|
+
hash,
|
|
29
|
+
author,
|
|
30
|
+
date: dateRaw.slice(0, 10),
|
|
31
|
+
message,
|
|
32
|
+
type: extractType(message)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function extractType(message) {
|
|
36
|
+
return message.match(/^(\w+)(?:\(.+?\))?:/)?.[1] ?? "other";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/core/template.ts
|
|
41
|
+
function buildMissionSpec(commits, targetFile) {
|
|
42
|
+
const commitBlock = commits.map((c) => `- \`${c.hash}\` ${c.message} (${c.author}, ${c.date})`).join("\n");
|
|
43
|
+
return {
|
|
44
|
+
prompt: [
|
|
45
|
+
`You are a documentation update assistant. Update the changelog section in ${targetFile} based on the following git commits.`,
|
|
46
|
+
"",
|
|
47
|
+
"## Rules",
|
|
48
|
+
"1. Only modify content between `<!-- log-agent-start -->` and `<!-- log-agent-end -->` markers",
|
|
49
|
+
"2. Group commits by date, most recent first",
|
|
50
|
+
"3. Each commit should be a single bullet point with a type badge",
|
|
51
|
+
"4. Do not modify anything outside the markers",
|
|
52
|
+
"5. Write in the same language as the existing document",
|
|
53
|
+
"",
|
|
54
|
+
"## Commits",
|
|
55
|
+
commitBlock,
|
|
56
|
+
"",
|
|
57
|
+
"## Output format example",
|
|
58
|
+
"```markdown",
|
|
59
|
+
"### 2026-02-05",
|
|
60
|
+
"- **feat** Implement FilePreview component with transition effects (`8a2f3b4`)",
|
|
61
|
+
"- **fix** Adjust MobileMenu padding for better responsiveness (`4d1e9a2`)",
|
|
62
|
+
"```",
|
|
63
|
+
"",
|
|
64
|
+
`Directly edit ${targetFile}. Do not output any explanation.`
|
|
65
|
+
].join("\n"),
|
|
66
|
+
targetFile
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/core/bridge.ts
|
|
72
|
+
async function executeMission(spec) {
|
|
73
|
+
try {
|
|
74
|
+
const { stdout } = await execa("claude", [
|
|
75
|
+
"-p",
|
|
76
|
+
"--allowedTools",
|
|
77
|
+
["Read", "Edit"].join(",")
|
|
78
|
+
], {
|
|
79
|
+
input: spec.prompt,
|
|
80
|
+
timeout: 6e4
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
output: stdout
|
|
85
|
+
};
|
|
86
|
+
} catch (error) {
|
|
87
|
+
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
88
|
+
throw new Error(`claude -p execution failed: ${message}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/core/marker.ts
|
|
94
|
+
const START_TAG = "<!-- log-agent-start -->";
|
|
95
|
+
const END_TAG = "<!-- log-agent-end -->";
|
|
96
|
+
async function checkMarkers(filePath) {
|
|
97
|
+
try {
|
|
98
|
+
const content = await readFile(filePath, "utf-8");
|
|
99
|
+
const hasStart = content.includes(START_TAG);
|
|
100
|
+
const hasEnd = content.includes(END_TAG);
|
|
101
|
+
const lastDate = hasStart && hasEnd ? extractLastDate(content) : void 0;
|
|
102
|
+
return {
|
|
103
|
+
found: hasStart && hasEnd,
|
|
104
|
+
lastDate,
|
|
105
|
+
startTag: START_TAG,
|
|
106
|
+
endTag: END_TAG
|
|
107
|
+
};
|
|
108
|
+
} catch {
|
|
109
|
+
return {
|
|
110
|
+
found: false,
|
|
111
|
+
lastDate: void 0,
|
|
112
|
+
startTag: START_TAG,
|
|
113
|
+
endTag: END_TAG
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function extractLastDate(content) {
|
|
118
|
+
const startIdx = content.indexOf(START_TAG);
|
|
119
|
+
const endIdx = content.indexOf(END_TAG);
|
|
120
|
+
if (startIdx === -1 || endIdx === -1) return void 0;
|
|
121
|
+
return [...content.slice(startIdx, endIdx).matchAll(/###\s+(\d{4}-\d{2}-\d{2})/g)].map((m) => m[1]).filter((d) => d !== void 0).sort().at(-1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/index.ts
|
|
126
|
+
const program = new Command();
|
|
127
|
+
program.name("log-agent").description("AI-Powered Agentic Documentation CLI").version("0.1.0");
|
|
128
|
+
program.command("sync").description("Sync git log to documentation").option("--since <date>", "Only include commits after this date (e.g. 2026-02-01)").option("--target <file>", "Target file to update", "README.md").option("--dry-run", "Preview the mission spec without executing", false).action(async (options) => {
|
|
129
|
+
const { target: targetFile, since, dryRun } = options;
|
|
130
|
+
try {
|
|
131
|
+
console.log(chalk.blue("Starting log-agent sync..."));
|
|
132
|
+
const markers = await checkMarkers(targetFile);
|
|
133
|
+
if (!dryRun && !markers.found) {
|
|
134
|
+
console.error(chalk.red(`Markers not found in ${targetFile}.`));
|
|
135
|
+
console.error(chalk.gray(`Add these lines to your file:\n ${markers.startTag}\n ${markers.endTag}`));
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const effectiveSince = since ?? markers.lastDate;
|
|
140
|
+
if (effectiveSince) console.log(chalk.gray(`Fetching commits since ${effectiveSince}`));
|
|
141
|
+
const commits = await getCommits(effectiveSince);
|
|
142
|
+
if (commits.length === 0) {
|
|
143
|
+
console.log(chalk.yellow("No commits found, skipping sync."));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
console.log(chalk.gray(`Found ${commits.length} commit(s)`));
|
|
147
|
+
const spec = buildMissionSpec(commits, targetFile);
|
|
148
|
+
if (dryRun) {
|
|
149
|
+
console.log(chalk.yellow("--- Dry Run: Mission Spec ---"));
|
|
150
|
+
console.log(spec.prompt);
|
|
151
|
+
console.log(chalk.yellow("--- End ---"));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
console.log(chalk.gray("Mission spec generated, invoking claude -p..."));
|
|
155
|
+
const result = await executeMission(spec);
|
|
156
|
+
console.log(chalk.green("Sync completed successfully."));
|
|
157
|
+
console.log(chalk.gray(result.output));
|
|
158
|
+
} catch (error) {
|
|
159
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
160
|
+
if (message.includes("ENOENT") && message.includes("claude")) {
|
|
161
|
+
console.error(chalk.red("Claude Code is not installed."));
|
|
162
|
+
console.error(chalk.gray("Run: npm install -g @anthropic-ai/claude-code"));
|
|
163
|
+
} else if (message.includes("not a git repository")) console.error(chalk.red("Not a git repository."));
|
|
164
|
+
else console.error(chalk.red(`Sync failed: ${message}`));
|
|
165
|
+
process.exitCode = 1;
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
program.parse();
|
|
169
|
+
|
|
170
|
+
//#endregion
|
|
171
|
+
export { };
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "change-log-agent",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-powered CLI that syncs git commit history to project documentation via Claude Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.mjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"log-agent": "dist/index.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsdown",
|
|
15
|
+
"prepublishOnly": "npm run build",
|
|
16
|
+
"test": "vitest run"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"cli",
|
|
20
|
+
"git",
|
|
21
|
+
"changelog",
|
|
22
|
+
"documentation",
|
|
23
|
+
"claude",
|
|
24
|
+
"ai"
|
|
25
|
+
],
|
|
26
|
+
"author": "Eric Cai",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/ericcai0814/log-agent.git"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"chalk": "^5.6.2",
|
|
34
|
+
"commander": "^14.0.3",
|
|
35
|
+
"execa": "^9.6.1"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^25.2.0",
|
|
39
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
40
|
+
"tsdown": "^0.20.3",
|
|
41
|
+
"typescript": "^5.9.3",
|
|
42
|
+
"vitest": "^4.0.18"
|
|
43
|
+
}
|
|
44
|
+
}
|