@raolin2025/claude-code-node 2.1.0 → 2.2.1

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.
@@ -0,0 +1,330 @@
1
+ /**
2
+ * Diff 解析器 - 解析 GitHub unified diff format
3
+ * 支持行级别定位,为 PR 评论提供 position
4
+ */
5
+
6
+ /**
7
+ * 解析 diff 字符串
8
+ * @param {string} diff - 原始 diff 内容
9
+ * @returns {Array<DiffHunk>} hunk 数组
10
+ */
11
+ export function parseDiff(diff) {
12
+ const hunks = []
13
+ const lines = diff.split('\n')
14
+ let currentHunk = null
15
+
16
+ for (let i = 0; i < lines.length; i++) {
17
+ const line = lines[i]
18
+
19
+ // Hunk header: @@ -oldStart,oldCount +newStart,newCount @@
20
+ const hunkMatch = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/)
21
+ if (hunkMatch) {
22
+ if (currentHunk) {
23
+ hunks.push(currentHunk)
24
+ }
25
+ currentHunk = {
26
+ oldStart: parseInt(hunkMatch[1], 10),
27
+ oldCount: parseInt(hunkMatch[2] || '1', 10),
28
+ newStart: parseInt(hunkMatch[3], 10),
29
+ newCount: parseInt(hunkMatch[4] || '1', 10),
30
+ lines: [],
31
+ headerLine: line
32
+ }
33
+ continue
34
+ }
35
+
36
+ if (currentHunk) {
37
+ const diffLine = parseDiffLine(line, currentHunk)
38
+ currentHunk.lines.push(diffLine)
39
+ }
40
+ }
41
+
42
+ if (currentHunk) {
43
+ hunks.push(currentHunk)
44
+ }
45
+
46
+ return hunks
47
+ }
48
+
49
+ /**
50
+ * 解析单行 diff
51
+ */
52
+ function parseDiffLine(line, hunk) {
53
+ const type = line[0]
54
+ const content = line.slice(1)
55
+
56
+ // 计算行号
57
+ let oldLineNum = null
58
+ let newLineNum = null
59
+
60
+ if (type === '+' && hunk.oldStart !== null) {
61
+ newLineNum = hunk.newStart + (hunk.newLineCount || 0)
62
+ hunk.newLineCount = (hunk.newLineCount || 0) + 1
63
+ } else if (type === '-' && hunk.oldStart !== null) {
64
+ oldLineNum = hunk.oldStart + (hunk.oldLineCount || 0)
65
+ hunk.oldLineCount = (hunk.oldLineCount || 0) + 1
66
+ } else if (type === ' ') {
67
+ // context line
68
+ const oldCount = hunk.oldLineCount || 0
69
+ const newCount = hunk.newLineCount || 0
70
+ oldLineNum = hunk.oldStart + oldCount
71
+ newLineNum = hunk.newStart + newCount
72
+ hunk.oldLineCount = oldCount + 1
73
+ hunk.newLineCount = newCount + 1
74
+ }
75
+
76
+ return {
77
+ type, // ' ' | '+' | '-'
78
+ content,
79
+ oldLineNum,
80
+ newLineNum,
81
+ raw: line
82
+ }
83
+ }
84
+
85
+ /**
86
+ * 在 diff 中查找文件对应的所有 hunk
87
+ * @param {Array<DiffHunk>} hunks
88
+ * @param {string} filePath - 文件路径
89
+ * @returns {Array<DiffHunk>} 该文件的所有 hunk
90
+ */
91
+ export function getHunksForFile(hunks, filePath) {
92
+ // 实际 diff 中,每个文件的开头会有:
93
+ // --- a/path/to/file
94
+ // +++ b/path/to/file
95
+ // 然后跟上多个 @@ headers
96
+ // 简化版:我们假设调用者已经根据文件筛选了 hunks
97
+ // 完整版需要扫描整个 diff 找到文件边界
98
+
99
+ // 这里我们使用简化的方法:传入的 hunks 已经是该文件的所有 hunk
100
+ return hunks
101
+ }
102
+
103
+ /**
104
+ * 根据新文件行号计算在 diff 中的 position
105
+ * GitHub API 的 position 是从文件 diff 开始处计数的行号(包含 hunk headers)
106
+ *
107
+ * @param {Array<DiffHunk>} hunks - 该文件的所有 hunk
108
+ * @param {number} lineNum - 新文件中的行号
109
+ * @returns {number} position (null if not found)
110
+ */
111
+ export function getPositionInDiff(hunks, lineNum) {
112
+ let position = 0
113
+
114
+ for (const hunk of hunks) {
115
+ // hunk header 占 1 行
116
+ position += 1
117
+
118
+ for (const line of hunk.lines) {
119
+ position += 1
120
+
121
+ const isLineMatch = (
122
+ (line.type === '+' || line.type === ' ') &&
123
+ line.newLineNum === lineNum
124
+ )
125
+
126
+ if (isLineMatch) {
127
+ return position
128
+ }
129
+ }
130
+ }
131
+
132
+ return null // 该行不在 diff 中(可能是未修改的行)
133
+ }
134
+
135
+ /**
136
+ * 构建 PR 评论的 payload
137
+ * @param {string} body - 评论内容
138
+ * @param {string} path - 文件路径
139
+ * @param {number} position - diff position
140
+ * @param {string} commitId - commit SHA(可选)
141
+ */
142
+ export function buildCommentPayload(body, path, position, commitId = null) {
143
+ const payload = {
144
+ body,
145
+ path,
146
+ position
147
+ }
148
+ if (commitId) {
149
+ payload.commit_id = commitId
150
+ }
151
+ return payload
152
+ }
153
+
154
+ /**
155
+ * Diff Hunk 数据结构
156
+ * @typedef {Object} DiffHunk
157
+ * @property {number} oldStart
158
+ * @property {number} oldCount
159
+ * @property {number} newStart
160
+ * @property {number} newCount
161
+ * @property {string} headerLine
162
+ * @property {Array<DiffLine>} lines
163
+ */
164
+
165
+ /**
166
+ * Diff Line 数据结构
167
+ * @typedef {Object} DiffLine
168
+ * @property {string} type - ' ', '+', '-'
169
+ * @property {string} content - 行内容
170
+ * @property {number|null} oldLineNum
171
+ * @property {number|null} newLineNum
172
+ * @property {string} raw - 原始行
173
+ */
174
+
175
+ /**
176
+ * 将 diff 按文件分割
177
+ * @param {string} diff
178
+ * @returns {Map<string, Array<DiffHunk>}} 文件路径 → hunk 数组
179
+ */
180
+ export function splitDiffByFile(diff) {
181
+ const fileMap = new Map()
182
+ const lines = diff.split('\n')
183
+
184
+ let currentFile = null
185
+ let currentHunk = null
186
+ let fileStartLine = 0
187
+
188
+ for (let i = 0; i < lines.length; i++) {
189
+ const line = lines[i]
190
+
191
+ // 检测文件边界
192
+ // --- a/path/to/file
193
+ // +++ b/path/to/file
194
+ const oldFileMatch = line.match(/^--- a\/(.+)/)
195
+ const newFileMatch = lines[i + 1]?.match(/^\+\+\+ b\/(.+)/)
196
+
197
+ if (oldFileMatch && newFileMatch) {
198
+ // 切换到新文件
199
+ if (currentFile && currentHunk) {
200
+ fileMap.get(currentFile).push(currentHunk)
201
+ }
202
+
203
+ // 新文件的路径(来自 b/ 侧)
204
+ currentFile = newFileMatch[1]
205
+ if (!fileMap.has(currentFile)) {
206
+ fileMap.set(currentFile, [])
207
+ }
208
+
209
+ fileStartLine = i + 2 // 跳过 ---/+++ 行
210
+ i++ // 跳过 +++ 行
211
+ currentHunk = { file: currentFile, hunks: [], startLine: fileStartLine }
212
+ continue
213
+ }
214
+
215
+ // Hunk header
216
+ const hunkMatch = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/)
217
+ if (hunkMatch && currentFile !== null) {
218
+ // 保存上一个 hunk
219
+ if (currentHunk && currentHunk.hunks.length > 0) {
220
+ fileMap.get(currentFile).push(currentHunk)
221
+ }
222
+
223
+ // 开始新 hunk
224
+ currentHunk = {
225
+ oldStart: parseInt(hunkMatch[1], 10),
226
+ oldCount: parseInt(hunkMatch[2] || '1', 10),
227
+ newStart: parseInt(hunkMatch[3], 10),
228
+ newCount: parseInt(hunkMatch[4] || '1', 10),
229
+ lines: [],
230
+ headerLine: line,
231
+ file: currentFile,
232
+ startLine: i
233
+ }
234
+ fileMap.get(currentFile).push(currentHunk)
235
+ continue
236
+ }
237
+
238
+ // diff 内容行
239
+ if (currentHunk) {
240
+ const diffLine = parseDiffLine(line, currentHunk)
241
+ currentHunk.lines.push(diffLine)
242
+ }
243
+ }
244
+
245
+ // 保存最后一个 hunk
246
+ if (currentHunk && currentHunk.lines.length > 0) {
247
+ fileMap.get(currentFile)?.push(currentHunk)
248
+ }
249
+
250
+ return fileMap
251
+ }
252
+
253
+ /**
254
+ * 验证文件路径是否存在
255
+ * @param {Map<string, Array>} fileMap
256
+ * @param {string} path
257
+ */
258
+ export function fileExistsInDiff(fileMap, path) {
259
+ return fileMap.has(path)
260
+ }
261
+
262
+ /**
263
+ * 获取文件的所有 hunks(包含 empty hunks)
264
+ */
265
+ export function getHunksForFileRaw(fileMap, filePath) {
266
+ return fileMap.get(filePath) || []
267
+ }
268
+
269
+ /**
270
+ * 统计文件修改情况
271
+ * @param {Array} hunks
272
+ * @returns {{ additions: number, deletions: number, changes: number }}
273
+ */
274
+ export function countFileChanges(hunks) {
275
+ let additions = 0
276
+ let deletions = 0
277
+
278
+ for (const hunk of hunks) {
279
+ for (const line of hunk.lines) {
280
+ if (line.type === '+') additions++
281
+ else if (line.type === '-') deletions++
282
+ }
283
+ }
284
+
285
+ return {
286
+ additions,
287
+ deletions,
288
+ changes: additions + deletions
289
+ }
290
+ }
291
+
292
+ // 测试辅助函数:打印解析结果
293
+ export function debugPrintHunks(hunks) {
294
+ for (const hunk of hunks) {
295
+ console.log(`Hunk: @@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@`)
296
+ for (const line of hunk.lines) {
297
+ const marker = line.type === ' ' ? ' ' : line.type
298
+ console.log(` ${marker} ${line.newLineNum || ''} | ${line.content}`)
299
+ }
300
+ }
301
+ }
302
+
303
+ /**
304
+ * 实用函数:将行号映射为 GitHub API 的 position
305
+ * 注意:position 是从 diff 文件开头计数的行号(包括 @@ headers)
306
+ */
307
+ export function mapLineToPosition(diffText, filePath, lineNum) {
308
+ const fileMap = splitDiffByFile(diffText)
309
+ const hunks = getHunksForFileRaw(fileMap, filePath)
310
+ return getPositionInDiff(hunks, lineNum)
311
+ }
312
+
313
+ /**
314
+ * 实用函数:找出 diff 中新增的代码块的位置范围
315
+ */
316
+ export function findAddedLines(hunks) {
317
+ const ranges = []
318
+ for (const hunk of hunks) {
319
+ for (const line of hunk.lines) {
320
+ if (line.type === '+' && line.newLineNum) {
321
+ ranges.push({
322
+ start: line.newLineNum,
323
+ end: line.newLineNum,
324
+ content: line.content
325
+ })
326
+ }
327
+ }
328
+ }
329
+ return ranges
330
+ }
@@ -0,0 +1,308 @@
1
+ /**
2
+ * GitTool - GitHub PR 自动化管理工具
3
+ * 符合 ToolDef 接口规范
4
+ */
5
+
6
+ import { GitHubAPI, createGitHubAPI } from '../git/github-api.js'
7
+ import { PRReviewer } from '../git/pr-reviewer.js'
8
+ import { PRMergePolicy } from '../git/pr-merge-policy.js'
9
+ import { ToolDef } from '../types/index.js'
10
+
11
+ const TOOL_NAME = 'GitTool'
12
+ const TOOL_DESCRIPTION = `
13
+ GitHub PR 自动化管理工具。
14
+
15
+ 前置条件:
16
+ - 设置 GITHUB_TOKEN 环境变量(有 repo 权限)
17
+ - 设置 GITHUB_OWNER 和 GITHUB_REPO(或在 ~/.claude-code/config.json 中配置)
18
+ - 可选:DEEPSEEK_API_KEY for LLM 智能分析
19
+
20
+ 主要功能:
21
+ - 列出 PR(list-prs)
22
+ - 获取 PR 详情(get-pr)
23
+ - 自动审查(review-pr):代码质量、安全、测试、文档
24
+ - 智能合并(merge-pr):策略检查
25
+ - 评论、approve、request changes
26
+ - 批量操作(auto-review-all, auto-merge-eligible)
27
+ `
28
+
29
+ const TOOL_PARAMETERS = {
30
+ type: 'object',
31
+ properties: {
32
+ action: {
33
+ type: 'string',
34
+ enum: [
35
+ 'list-prs',
36
+ 'get-pr',
37
+ 'review-pr',
38
+ 'merge-pr',
39
+ 'auto-review-all',
40
+ 'comment',
41
+ 'approve',
42
+ 'request-changes',
43
+ 'check-mergeable',
44
+ 'auto-merge-eligible'
45
+ ],
46
+ description: '要执行的操作'
47
+ },
48
+ owner: { type: 'string', description: 'GitHub repository owner' },
49
+ repo: { type: 'string', description: 'GitHub repository name' },
50
+ prNumber: { type: 'number', description: 'PR number' },
51
+ state: { type: 'string', enum: ['open', 'closed', 'all'], description: 'PR state filter' },
52
+ head: { type: 'string', description: 'Filter by head branch' },
53
+ base: { type: 'string', description: 'Filter by base branch' },
54
+ labels: { type: 'array', items: { type: 'string' }, description: 'Label filter' },
55
+ limit: { type: 'number', description: 'Max PRs to fetch' },
56
+ checks: {
57
+ type: 'array',
58
+ items: { type: 'string', enum: ['code-quality', 'security', 'tests', 'docs', 'complexity', 'duplication'] },
59
+ description: '检查项列表'
60
+ },
61
+ analyzeLLM: { type: 'boolean', description: '是否使用 LLM 智能分析' },
62
+ commentThreshold: { type: 'string', enum: ['INFO', 'WARNING', 'ERROR'], description: '评论阈值' },
63
+ autoComment: { type: 'boolean', description: '是否自动发表审查评论' },
64
+ method: { type: 'string', enum: ['merge', 'squash', 'rebase'], description: 'Merge method' },
65
+ body: { type: 'string', description: 'Comment body' },
66
+ label: { type: 'string', description: 'Label filter for batch operations' }
67
+ },
68
+ required: ['action']
69
+ }
70
+
71
+ /**
72
+ * 内部 GitTool 类(无状态)
73
+ */
74
+ class GitTool {
75
+ constructor(config = {}) {
76
+ this.config = config
77
+ this.github = null
78
+ this.reviewer = null
79
+ this.mergePolicy = null
80
+ }
81
+
82
+ ensureGitHubClient(options = {}) {
83
+ if (this.github) return
84
+
85
+ const owner = options.owner || this.config.owner || this._readConfig('github.owner')
86
+ const repo = options.repo || this.config.repo || this._readConfig('github.repo')
87
+
88
+ if (!owner || !repo) {
89
+ throw new Error('GitHub owner and repo required. Set in config or pass as parameters.')
90
+ }
91
+
92
+ this.github = createGitHubAPI({
93
+ token: process.env.GITHUB_TOKEN,
94
+ owner,
95
+ repo,
96
+ baseUrl: this.config.baseUrl
97
+ })
98
+
99
+ const policyConfig = this.config.mergePolicy || {}
100
+ this.reviewer = new PRReviewer(this.github, this.config.reviewRules, {
101
+ enableLLM: this.config.enableLLM,
102
+ apiConfig: this.config.llm
103
+ })
104
+ this.mergePolicy = new PRMergePolicy(this.github, policyConfig)
105
+ }
106
+
107
+ async execute(params) {
108
+ const { action } = params
109
+ switch (action) {
110
+ case 'list-prs': return this.listPRs(params)
111
+ case 'get-pr': return this.getPR(params)
112
+ case 'review-pr': return this.reviewPR(params)
113
+ case 'merge-pr': return this.mergePR(params)
114
+ case 'auto-review-all': return this.autoReviewAll(params)
115
+ case 'comment': return this.comment(params)
116
+ case 'approve': return this.approve(params)
117
+ case 'request-changes': return this.requestChanges(params)
118
+ case 'check-mergeable': return this.checkMergeable(params)
119
+ case 'auto-merge-eligible': return this.autoMergeEligible(params)
120
+ default: throw new Error(`Unknown action: ${action}`)
121
+ }
122
+ }
123
+
124
+ async listPRs({ state = 'open', head, base, labels, limit = 50 }) {
125
+ this.ensureGitHubClient()
126
+ if (labels && !Array.isArray(labels)) {
127
+ throw new Error('labels must be an array of strings')
128
+ }
129
+ const query = { state, per_page: Math.min(limit, 100) }
130
+ if (head) query.head = head
131
+ if (base) query.base = base
132
+ if (labels?.length) query.labels = labels.join(',')
133
+ const prs = await this.github.listPRs(query)
134
+ const prList = Array.isArray(prs) ? prs : []
135
+ return {
136
+ count: prList.length,
137
+ prs: prList.map(pr => ({
138
+ number: pr.number,
139
+ title: pr.title,
140
+ state: pr.state,
141
+ user: pr.user?.login,
142
+ head: pr.head?.ref,
143
+ base: pr.base?.ref,
144
+ createdAt: pr.created_at,
145
+ updatedAt: pr.updated_at,
146
+ comments: pr.comments,
147
+ labels: pr.labels?.map(l => l.name) || []
148
+ }))
149
+ }
150
+ }
151
+
152
+ async getPR({ prNumber }) {
153
+ this.ensureGitHubClient()
154
+ const pr = await this.github.getPR(prNumber)
155
+ const files = await this.github.getPRFiles(prNumber)
156
+ const reviews = await this.github.listReviews(prNumber)
157
+ return {
158
+ number: pr.number,
159
+ title: pr.title,
160
+ body: pr.body,
161
+ user: pr.user?.login,
162
+ head: pr.head?.ref,
163
+ base: pr.base?.ref,
164
+ mergeable: pr.mergeable,
165
+ changedFiles: pr.changed_files,
166
+ additions: pr.additions,
167
+ deletions: pr.deletions,
168
+ files: files.map(f => ({ filename: f.filename, additions: f.additions, deletions: f.deletions })),
169
+ reviews: reviews.map(r => ({ user: r.user?.login, state: r.state }))
170
+ }
171
+ }
172
+
173
+ async reviewPR({ prNumber, checks, analyzeLLM = true, commentThreshold = 'WARNING', autoComment = false }) {
174
+ this.ensureGitHubClient()
175
+ const reviewRules = {}
176
+ if (checks) {
177
+ reviewRules.checks = {}
178
+ checks.forEach(c => reviewRules.checks[c] = true)
179
+ }
180
+ const reviewer = new PRReviewer(this.github, reviewRules, {
181
+ enableLLM: this.config.enableLLM,
182
+ apiConfig: this.config.llm
183
+ })
184
+ const result = await reviewer.reviewPR(prNumber, { analyzeLLM, commentThreshold })
185
+ if (autoComment && result.comments.length > 0) {
186
+ const pr = await this.github.getPR(prNumber)
187
+ const summary = result.comments.find(c => !c.position)
188
+ if (summary) {
189
+ await this.github.createReview(prNumber, summary.body)
190
+ }
191
+ }
192
+ return result
193
+ }
194
+
195
+ async mergePR({ prNumber, method }) {
196
+ if (!prNumber) throw new Error('prNumber required')
197
+ this.ensureGitHubClient()
198
+ return await this.mergePolicy.merge(prNumber, { method })
199
+ }
200
+
201
+ async autoReviewAll({ labelFilter, limit = 50 }) {
202
+ this.ensureGitHubClient()
203
+ const prs = await this.github.listPRs({ state: 'open', per_page: limit })
204
+ const results = []
205
+ const prList = Array.isArray(prs) ? prs : []
206
+ for (const pr of prList) {
207
+ if (labelFilter && !pr.labels?.some(l => l.name === labelFilter)) continue
208
+ try {
209
+ const review = await this.reviewer.reviewPR(pr.number, { commentThreshold: 'WARNING' })
210
+ results.push({ prNumber: pr.number, title: pr.title, findings: review.findings.length, status: 'reviewed' })
211
+ } catch (e) {
212
+ results.push({ prNumber: pr.number, title: pr.title, status: 'error', error: e.message })
213
+ }
214
+ }
215
+ return { total: prList.length, reviewed: results.filter(r => r.status === 'reviewed').length, errors: results.filter(r => r.status === 'error'), details: results }
216
+ }
217
+
218
+ async comment({ prNumber, body, path, line }) {
219
+ if (!prNumber || !body) throw new Error('prNumber and body required')
220
+ this.ensureGitHubClient()
221
+
222
+ if (path && line) {
223
+ // Get PR diff, parse and find the position for the specific line
224
+ const pr = await this.github.getPR(prNumber)
225
+ const diffText = await this.github.getPRDiff(prNumber)
226
+
227
+ // Import diff parser dynamically (ESM)
228
+ const { splitDiffByFile, getPositionInDiff, getHunksForFileRaw } = await import('../git/utils/diff-parser.js')
229
+
230
+ const fileMap = splitDiffByFile(diffText)
231
+ const hunks = getHunksForFileRaw(fileMap, path)
232
+
233
+ if (!hunks || hunks.length === 0) {
234
+ throw new Error('File ' + path + ' not found in PR diff')
235
+ }
236
+
237
+ const position = getPositionInDiff(hunks, line)
238
+ if (position === null) {
239
+ throw new Error('Line ' + line + ' not found in diff for ' + path)
240
+ }
241
+
242
+ // Use latest commit SHA as commit_id
243
+ const commitId = pr.head?.sha || null
244
+
245
+ return await this.github.createComment(prNumber, body, { path, position, commitId })
246
+ }
247
+
248
+ return await this.github.createReview(prNumber, body)
249
+ }
250
+
251
+ async approve({ prNumber, body }) {
252
+ this.ensureGitHubClient()
253
+ if (!prNumber) throw new Error('prNumber required')
254
+ return await this.github.approvePR(prNumber, body || 'Approved')
255
+ }
256
+
257
+ async requestChanges({ prNumber, body }) {
258
+ this.ensureGitHubClient()
259
+ if (!prNumber) throw new Error('prNumber required')
260
+ return await this.github.requestChanges(prNumber, body || 'Changes requested')
261
+ }
262
+
263
+ async checkMergeable({ prNumber }) {
264
+ this.ensureGitHubClient()
265
+ return await this.mergePolicy.checkMergeable(prNumber)
266
+ }
267
+
268
+ async autoMergeEligible({ label = 'auto-merge', limit = 50 }) {
269
+ this.ensureGitHubClient()
270
+ const prs = await this.github.listPRs({ state: 'open', per_page: limit, labels: label })
271
+ const eligible = []
272
+ const prList = Array.isArray(prs) ? prs : []
273
+ for (const pr of prList) {
274
+ try {
275
+ const check = await this.mergePolicy.checkMergeable(pr.number)
276
+ if (check.mergeable) eligible.push({ pr, check })
277
+ } catch (e) {
278
+ console.error(`Check failed for PR ${pr.number}:`, e.message)
279
+ }
280
+ }
281
+ return { label, total: prList.length, eligible: eligible.length, items: eligible.map(e => ({ number: e.pr.number, title: e.pr.title })) }
282
+ }
283
+
284
+ _readConfig(path) {
285
+ try {
286
+ const configPath = `${process.env.HOME}/.claude-code/config.json`
287
+ const data = require('fs').readFileSync(configPath, 'utf-8')
288
+ const config = JSON.parse(data)
289
+ return path.split('.').reduce((obj, key) => obj?.[key], config)
290
+ } catch { return null }
291
+ }
292
+ }
293
+
294
+ /**
295
+ * ToolDef 工厂函数
296
+ */
297
+ function createToolDef(config = {}) {
298
+ const tool = new GitTool(config)
299
+ return new ToolDef(TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMETERS, (input, ctx) => tool.execute(input), 'high')
300
+ }
301
+
302
+ /**
303
+ * 导出 ToolDef 实例
304
+ */
305
+ export const gitTool = createToolDef()
306
+
307
+ export { GitTool, createToolDef }
308
+ export default gitTool
@@ -10,6 +10,7 @@ import { grepTool } from './grep.js'
10
10
  import { webFetchTool } from './web-fetch.js'
11
11
  import { webSearchTool } from './web-search.js'
12
12
  import { askUserTool } from './ask-user.js'
13
+ import { gitTool } from './git-tool.js'
13
14
 
14
15
  /**
15
16
  * 所有内置工具列表
@@ -24,6 +25,7 @@ export const builtinTools = [
24
25
  webFetchTool,
25
26
  webSearchTool,
26
27
  askUserTool,
28
+ gitTool,
27
29
  ]
28
30
 
29
31
  /**
@@ -31,9 +31,11 @@ export class UserMessage extends Message {
31
31
 
32
32
  export class AssistantMessage extends Message {
33
33
  toolCalls
34
- constructor(content, toolCalls = []) {
34
+ reasoningContent
35
+ constructor(content, toolCalls = [], reasoningContent = '') {
35
36
  super(Role.ASSISTANT, content)
36
37
  this.toolCalls = toolCalls
38
+ this.reasoningContent = reasoningContent
37
39
  }
38
40
  }
39
41