@icode-js/icode 3.0.2 → 3.0.4
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 +17 -9
- package/package.json +1 -1
- package/src/core/git-service.js +34 -2
- package/src/workflows/ai-commit-workflow.js +24 -9
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# icode-js v3
|
|
1
|
+
# @icode-js/icode v3
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@icode-js/icode` 是一个面向 Git 日常操作的命令行工具,安装后可直接使用 `icode` 命令。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
目标:
|
|
6
6
|
|
|
7
7
|
- 更稳定地处理复杂 Git 场景
|
|
8
8
|
- 更好支持 `husky/git hooks` 校验链路
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
## 安装
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
npm i -g icode-js
|
|
15
|
+
npm i -g @icode-js/icode
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## 快速开始
|
|
@@ -127,7 +127,7 @@ icode push release test -m "feat: batch publish" -y # 默认本地 merge 推送
|
|
|
127
127
|
- `[targetBranch...]`:目标分支列表(可多个,空则默认当前分支)
|
|
128
128
|
- `-m, --message <msg>`:提交信息(未填会提示输入)
|
|
129
129
|
- `-y, --yes`:自动确认(跳过确认提示)
|
|
130
|
-
- `--local-merge
|
|
130
|
+
- `--local-merge`:显式声明使用本地 merge 模式(当前为默认行为,会切换分支并生成 merge commit)
|
|
131
131
|
- `--ai-commit`:push 前自动执行 AI commit
|
|
132
132
|
- `--ai-profile <name>`:指定 AI profile(用于 `--ai-commit`)
|
|
133
133
|
- `--pull-main`:提交前同步主分支到当前分支
|
|
@@ -339,8 +339,16 @@ node bin/icode.js help
|
|
|
339
339
|
node bin/icode.js undo
|
|
340
340
|
```
|
|
341
341
|
|
|
342
|
-
##
|
|
342
|
+
## 发布
|
|
343
343
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
344
|
+
当前 npm 包名:
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
@icode-js/icode
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
首次发布 scoped 包请显式指定公开访问权限:
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
npm publish --access public
|
|
354
|
+
```
|
package/package.json
CHANGED
package/src/core/git-service.js
CHANGED
|
@@ -162,20 +162,52 @@ export class GitService {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
async statusPorcelain() {
|
|
165
|
+
// 返回标准 porcelain 状态,供上层统一判断“是否存在未提交改动”。
|
|
165
166
|
const result = await this.exec(['status', '--porcelain'])
|
|
166
167
|
return result.stdout
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
async hasChanges() {
|
|
171
|
+
// 只要 porcelain 有输出,就说明暂存区、工作区或未跟踪文件存在改动。
|
|
170
172
|
const output = await this.statusPorcelain()
|
|
171
173
|
return cleanOutput(output).length > 0
|
|
172
174
|
}
|
|
173
175
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
// 列出未被忽略的未跟踪文件,供 AI 工作流补全新文件 diff。
|
|
177
|
+
async listUntrackedFiles() {
|
|
178
|
+
const result = await this.exec(['ls-files', '--others', '--exclude-standard'])
|
|
179
|
+
return result.stdout
|
|
180
|
+
.split('\n')
|
|
181
|
+
.map((item) => item.trim())
|
|
182
|
+
.filter(Boolean)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 通过 no-index diff 生成“/dev/null -> 新文件”的补丁,覆盖未跟踪文件内容。
|
|
186
|
+
async buildUntrackedFileDiff(filePath) {
|
|
187
|
+
const result = await this.exec(['diff', '--no-index', '--binary', '--', '/dev/null', filePath], {
|
|
188
|
+
allowFailure: true
|
|
189
|
+
})
|
|
176
190
|
return result.stdout
|
|
177
191
|
}
|
|
178
192
|
|
|
193
|
+
async diffWorkingTree() {
|
|
194
|
+
// git diff 默认不包含 untracked 文件,这里显式补齐,避免新仓库被误判成“无改动”。
|
|
195
|
+
const trackedResult = await this.exec(['diff'])
|
|
196
|
+
const untrackedFiles = await this.listUntrackedFiles()
|
|
197
|
+
const untrackedDiffs = []
|
|
198
|
+
|
|
199
|
+
for (const filePath of untrackedFiles) {
|
|
200
|
+
const diff = await this.buildUntrackedFileDiff(filePath)
|
|
201
|
+
if (cleanOutput(diff)) {
|
|
202
|
+
untrackedDiffs.push(diff.trimEnd())
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return [trackedResult.stdout.trimEnd(), ...untrackedDiffs]
|
|
207
|
+
.filter(Boolean)
|
|
208
|
+
.join('\n\n')
|
|
209
|
+
}
|
|
210
|
+
|
|
179
211
|
async diffStaged() {
|
|
180
212
|
const result = await this.exec(['diff', '--staged'])
|
|
181
213
|
return result.stdout
|
|
@@ -33,6 +33,15 @@ function buildCommitMessage(parsed) {
|
|
|
33
33
|
return `${header}\n\n${body}`
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// 将多个 diff 片段稳定拼接,避免空块污染 prompt。
|
|
37
|
+
function joinDiffSections(sections) {
|
|
38
|
+
return sections
|
|
39
|
+
.map((item) => (item || '').trim())
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.join('\n\n')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 基于“全部未提交改动”生成 AI commit,确保未暂存新文件也能被识别并提交。
|
|
36
45
|
export async function runAiCommitWorkflow(options) {
|
|
37
46
|
const context = await resolveGitContext({
|
|
38
47
|
cwd: options.cwd,
|
|
@@ -44,13 +53,19 @@ export async function runAiCommitWorkflow(options) {
|
|
|
44
53
|
logger.info(`仓库根目录: ${context.topLevelPath}`)
|
|
45
54
|
}
|
|
46
55
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
const stagedDiff = await git.diffStaged()
|
|
57
|
+
const workingDiff = await git.diffWorkingTree()
|
|
58
|
+
const hasStagedDiff = Boolean(stagedDiff.trim())
|
|
59
|
+
const hasWorkingDiff = Boolean(workingDiff.trim())
|
|
60
|
+
const diff = joinDiffSections([
|
|
61
|
+
hasStagedDiff ? `--- STAGED DIFF ---\n${stagedDiff}` : '',
|
|
62
|
+
hasWorkingDiff ? `--- WORKING TREE DIFF ---\n${workingDiff}` : ''
|
|
63
|
+
])
|
|
64
|
+
const diffSource = hasStagedDiff && hasWorkingDiff
|
|
65
|
+
? 'staged+working-tree'
|
|
66
|
+
: hasStagedDiff
|
|
67
|
+
? 'staged'
|
|
68
|
+
: 'working-tree'
|
|
54
69
|
|
|
55
70
|
if (!diff.trim()) {
|
|
56
71
|
throw new IcodeError('没有可用于生成提交信息的代码改动。', {
|
|
@@ -105,8 +120,8 @@ export async function runAiCommitWorkflow(options) {
|
|
|
105
120
|
}
|
|
106
121
|
}
|
|
107
122
|
|
|
108
|
-
if (
|
|
109
|
-
//
|
|
123
|
+
if (hasWorkingDiff) {
|
|
124
|
+
// 统一暂存工作区改动,确保未暂存修改和新文件与 AI 看到的 diff 保持一致。
|
|
110
125
|
await git.stageAll()
|
|
111
126
|
}
|
|
112
127
|
|