@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 CHANGED
@@ -1,8 +1,8 @@
1
- # icode-js v3
1
+ # @icode-js/icode v3
2
2
 
3
- `icode-js` 是一个面向 Git 日常操作的命令行工具。
3
+ `@icode-js/icode` 是一个面向 Git 日常操作的命令行工具,安装后可直接使用 `icode` 命令。
4
4
 
5
- 这次版本是重写级重构,不复用旧的 `lerna + packages/*` 逻辑,目标是:
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`:使用本地 merge 模式(默认,会切换分支并生成 merge commit)
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
- ## 迁移说明(v2 -> v3)
342
+ ## 发布
343
343
 
344
- - 移除了旧版远程平台 API 合并流程(GitHub/GitLab/Gitee Token 管理)
345
- - 统一聚焦本地 Git 工作流自动化
346
- - 部分旧参数仍做兼容映射(如 `-pm`)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icode-js/icode",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
4
4
  "description": "Modern git workflow CLI with robust parent-repo and hook handling",
5
5
  "type": "module",
6
6
  "author": "C",
@@ -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
- async diffWorkingTree() {
175
- const result = await this.exec(['diff'])
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
- let diff = await git.diffStaged()
48
- let diffSource = 'staged'
49
-
50
- if (!diff.trim()) {
51
- diff = await git.diffWorkingTree()
52
- diffSource = 'working-tree'
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 (diffSource === 'working-tree') {
109
- // 从 working-tree 生成信息时,提交前统一暂存,避免 commit 为空。
123
+ if (hasWorkingDiff) {
124
+ // 统一暂存工作区改动,确保未暂存修改和新文件与 AI 看到的 diff 保持一致。
110
125
  await git.stageAll()
111
126
  }
112
127