@icode-js/icode 3.1.0 → 3.2.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.
Files changed (39) hide show
  1. package/README.md +105 -5
  2. package/package.json +1 -1
  3. package/src/cli.js +6 -0
  4. package/src/commands/clean.js +4 -1
  5. package/src/commands/codereview.js +4 -1
  6. package/src/commands/completion.js +9 -6
  7. package/src/commands/config.js +9 -6
  8. package/src/commands/doctor.js +80 -0
  9. package/src/commands/explain.js +4 -1
  10. package/src/commands/help.js +10 -0
  11. package/src/commands/info.js +4 -1
  12. package/src/commands/migrate.js +38 -1
  13. package/src/commands/push.js +45 -18
  14. package/src/commands/ready.js +110 -0
  15. package/src/commands/save.js +98 -0
  16. package/src/commands/undo.js +13 -1
  17. package/src/core/ai/diff-prompt.js +204 -0
  18. package/src/core/cli/args.js +23 -2
  19. package/src/core/cli/completion.js +33 -0
  20. package/src/core/config/store.js +2 -4
  21. package/src/core/git/context.js +14 -15
  22. package/src/core/git/preconditions.js +88 -0
  23. package/src/core/git/service.js +151 -1
  24. package/src/core/remote-merge/providers/gitlab.js +149 -90
  25. package/src/core/tools/interactive.js +209 -34
  26. package/src/core/tools/loading.js +5 -1
  27. package/src/core/tools/logger.js +5 -0
  28. package/src/workflows/ai-codereview-workflow.js +14 -2
  29. package/src/workflows/ai-commit-workflow.js +14 -6
  30. package/src/workflows/checkout-workflow.js +14 -2
  31. package/src/workflows/clean-workflow.js +87 -70
  32. package/src/workflows/doctor-workflow.js +131 -0
  33. package/src/workflows/migrate-workflow.js +265 -58
  34. package/src/workflows/push-workflow.js +232 -50
  35. package/src/workflows/ready-workflow.js +278 -0
  36. package/src/workflows/rollback-workflow.js +20 -5
  37. package/src/workflows/save-workflow.js +152 -0
  38. package/src/workflows/sync-workflow.js +18 -2
  39. package/src/workflows/undo-workflow.js +25 -7
@@ -1,4 +1,5 @@
1
1
  import { askAi } from '../core/ai/client.js'
2
+ import { buildLimitedDiffPrompt, formatDiffCoverage, summarizeDiffCoverage } from '../core/ai/diff-prompt.js'
2
3
  import { resolveAiDiffRange } from '../core/ai/diff-range.js'
3
4
  import { IcodeError } from '../core/errors.js'
4
5
  import { resolveGitContext } from '../core/git/context.js'
@@ -19,6 +20,14 @@ function joinSections(sections) {
19
20
  .join('\n\n')
20
21
  }
21
22
 
23
+ // 构建 AI code review prompt 中的 diff 片段,输入完整 diff,输出受限 diff 与覆盖范围。
24
+ function buildReviewDiffPrompt(diff) {
25
+ return buildLimitedDiffPrompt(diff, {
26
+ limit: 18000
27
+ })
28
+ }
29
+
30
+ // 执行 AI 代码审查工作流,输入 diff 范围和 AI 选项,输出审查文本与 diff 覆盖范围。
22
31
  export async function runAiCodeReviewWorkflow(options) {
23
32
  const context = await resolveGitContext({
24
33
  cwd: options.cwd,
@@ -101,10 +110,12 @@ export async function runAiCodeReviewWorkflow(options) {
101
110
  ])
102
111
  }
103
112
 
113
+ const limitedDiff = buildReviewDiffPrompt(diff)
114
+
104
115
  const review = await askAi(
105
116
  {
106
117
  systemPrompt: '你是严格的软件代码审查工程师,请优先关注 bug、安全风险、行为回归、缺失测试。输出中文 Markdown。',
107
- userPrompt: `请按如下结构输出:\n1. Findings(按严重度从高到低)\n2. Open Questions\n3. Summary\n\nFocus: ${options.focus || 'general'}\nRange: ${rangeSpec}\nDiff Source: ${diffSource}\n\nDiff Stat:\n${truncate(stat, 3000)}\n\nName Status:\n${truncate(nameStatus, 3000)}\n\nUnified Diff:\n${truncate(diff, 18000)}`
118
+ userPrompt: `请按如下结构输出:\n1. Findings(按严重度从高到低)\n2. Open Questions\n3. Summary\n\nFocus: ${options.focus || 'general'}\nRange: ${rangeSpec}\nDiff Source: ${diffSource}\n\n${formatDiffCoverage(limitedDiff.coverage)}\n\nDiff Stat:\n${truncate(stat, 3000)}\n\nName Status:\n${truncate(nameStatus, 3000)}\n\nUnified Diff:\n${limitedDiff.diff}`
108
119
  },
109
120
  {
110
121
  profile: options.profile,
@@ -121,6 +132,7 @@ export async function runAiCodeReviewWorkflow(options) {
121
132
 
122
133
  return {
123
134
  rangeSpec,
124
- review
135
+ review,
136
+ diffCoverage: summarizeDiffCoverage(limitedDiff.coverage)
125
137
  }
126
138
  }
@@ -1,5 +1,6 @@
1
1
  import { IcodeError } from '../core/errors.js'
2
2
  import { askAiJson } from '../core/ai/client.js'
3
+ import { buildLimitedDiffPrompt, formatDiffCoverage, summarizeDiffCoverage } from '../core/ai/diff-prompt.js'
3
4
  import { resolveGitContext } from '../core/git/context.js'
4
5
  import { scanCommitConventions } from '../core/git/commit-conventions.js'
5
6
  import { GitService } from '../core/git/service.js'
@@ -74,7 +75,9 @@ export async function runAiCommitWorkflow(options) {
74
75
  })
75
76
  }
76
77
 
77
- const limitedDiff = diff.length > 12000 ? `${diff.slice(0, 12000)}\n\n...<truncated>` : diff
78
+ const limitedDiff = buildLimitedDiffPrompt(diff, {
79
+ limit: 12000
80
+ })
78
81
  const conventionContext = scanCommitConventions(context)
79
82
  const conventionPrompt = conventionContext.hasConventions
80
83
  ? `Local commit conventions were detected from repository hooks/config files. Follow these local rules first when generating the commit message.\n\n${conventionContext.summary}\n\n`
@@ -89,7 +92,7 @@ export async function runAiCommitWorkflow(options) {
89
92
  const { parsed, text } = await askAiJson(
90
93
  {
91
94
  systemPrompt: `You are a senior software engineer. Generate a concise Conventional Commit message. Output JSON only. Language: ${language}.`,
92
- userPrompt: `${conventionPrompt}Based on the following git diff, return JSON with fields:\n{\"type\":\"feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert\",\"scope\":\"optional\",\"subject\":\"required one-line summary\",\"body\":\"optional details\"}\n\nDiff Source: ${diffSource}\n\nDiff:\n${limitedDiff}`
95
+ userPrompt: `${conventionPrompt}Based on the following git diff, return JSON with fields:\n{\"type\":\"feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert\",\"scope\":\"optional\",\"subject\":\"required one-line summary\",\"body\":\"optional details\"}\n\nDiff Source: ${diffSource}\n\n${formatDiffCoverage(limitedDiff.coverage)}\n\nDiff:\n${limitedDiff.diff}`
93
96
  },
94
97
  {
95
98
  profile: options.profile
@@ -98,13 +101,16 @@ export async function runAiCommitWorkflow(options) {
98
101
 
99
102
  const commitMessage = buildCommitMessage(parsed)
100
103
 
101
- logger.success(`AI 建议提交信息:\n${commitMessage}`)
104
+ if (!options.silentResultLog) {
105
+ logger.success(`AI 建议提交信息:\n${commitMessage}`)
106
+ }
102
107
 
103
108
  if (!options.apply) {
104
109
  return {
105
110
  applied: false,
106
111
  commitMessage,
107
- raw: text
112
+ raw: text,
113
+ diffCoverage: summarizeDiffCoverage(limitedDiff.coverage)
108
114
  }
109
115
  }
110
116
 
@@ -115,6 +121,7 @@ export async function runAiCommitWorkflow(options) {
115
121
  applied: false,
116
122
  commitMessage,
117
123
  raw: text,
124
+ diffCoverage: summarizeDiffCoverage(limitedDiff.coverage),
118
125
  canceled: true
119
126
  }
120
127
  }
@@ -130,7 +137,7 @@ export async function runAiCommitWorkflow(options) {
130
137
  })
131
138
 
132
139
  const commitId = await git.revParseShort('HEAD')
133
- if (commitId) {
140
+ if (commitId && !options.silentResultLog) {
134
141
  logger.success(`AI commit 已创建: ${commitId}`)
135
142
  }
136
143
 
@@ -138,6 +145,7 @@ export async function runAiCommitWorkflow(options) {
138
145
  applied: true,
139
146
  commitId,
140
147
  commitMessage,
141
- raw: text
148
+ raw: text,
149
+ diffCoverage: summarizeDiffCoverage(limitedDiff.coverage)
142
150
  }
143
151
  }
@@ -1,8 +1,18 @@
1
1
  import { IcodeError } from '../core/errors.js'
2
2
  import { resolveGitContext } from '../core/git/context.js'
3
+ import {
4
+ assertCleanWorktree,
5
+ assertNoConflictedFiles,
6
+ assertNoPendingOperation,
7
+ readGitSafetyState
8
+ } from '../core/git/preconditions.js'
3
9
  import { GitService } from '../core/git/service.js'
4
10
  import { logger } from '../core/tools/logger.js'
5
11
 
12
+ /**
13
+ * 执行 checkout 工作流。
14
+ * 输入为目标分支、基线分支和执行选项,输出分支切换结果;实际切换前要求工作区干净且没有未完成 Git 操作。
15
+ */
6
16
  export async function runCheckoutWorkflow(input) {
7
17
  if (!input.branchName) {
8
18
  throw new IcodeError('缺少分支名: icode checkout <branch> [base]', {
@@ -19,6 +29,10 @@ export async function runCheckoutWorkflow(input) {
19
29
  const git = new GitService(context)
20
30
  const branchName = input.branchName.trim()
21
31
  const baseBranchName = input.baseBranchName?.trim() || context.defaultBranch
32
+ const safetyState = await readGitSafetyState(git)
33
+ assertNoPendingOperation(safetyState.operation, 'checkout', 'CHECKOUT_GIT_OPERATION_IN_PROGRESS')
34
+ assertNoConflictedFiles(safetyState.changes, 'checkout', 'CHECKOUT_CONFLICTED_FILES')
35
+ assertCleanWorktree(safetyState.changes, 'checkout', 'CHECKOUT_DIRTY_WORKTREE')
22
36
 
23
37
  logger.info(`仓库根目录: ${context.topLevelPath}`)
24
38
  if (context.inheritedFromParent) {
@@ -55,7 +69,6 @@ export async function runCheckoutWorkflow(input) {
55
69
  if (input.pullMain && context.defaultBranch !== branchName) {
56
70
  logger.info(`同步主分支到当前分支: ${context.defaultBranch}`)
57
71
  await git.pull(context.defaultBranch, {
58
- allowUnrelatedHistories: true,
59
72
  noRebase: true
60
73
  })
61
74
  }
@@ -63,7 +76,6 @@ export async function runCheckoutWorkflow(input) {
63
76
  if (remoteExists) {
64
77
  logger.info(`拉取远程分支: ${branchName}`)
65
78
  await git.pull(branchName, {
66
- allowUnrelatedHistories: true,
67
79
  noRebase: true
68
80
  })
69
81
  }
@@ -1,6 +1,13 @@
1
1
  import { getRepoPolicy } from '../core/config/store.js'
2
2
  import { IcodeError } from '../core/errors.js'
3
3
  import { resolveGitContext } from '../core/git/context.js'
4
+ import {
5
+ assertCleanWorktree,
6
+ assertNoConflictedFiles,
7
+ assertNoPendingOperation,
8
+ assertOnBranch,
9
+ readGitSafetyState
10
+ } from '../core/git/preconditions.js'
4
11
  import { GitService } from '../core/git/service.js'
5
12
  import { confirm } from '../core/tools/interactive.js'
6
13
  import { logger } from '../core/tools/logger.js'
@@ -14,6 +21,10 @@ function normalizeKeepList(values = []) {
14
21
  )
15
22
  }
16
23
 
24
+ /**
25
+ * 执行 clean 工作流。
26
+ * 输入为清理基线、保留分支和删除选项,输出已删除分支;执行前要求工作区干净并在普通分支上。
27
+ */
17
28
  export async function runCleanWorkflow(options) {
18
29
  const context = await resolveGitContext({
19
30
  cwd: options.cwd,
@@ -22,8 +33,13 @@ export async function runCleanWorkflow(options) {
22
33
 
23
34
  const git = new GitService(context)
24
35
  const mergedTarget = (options.mergedTarget || context.defaultBranch).trim()
25
- const currentBranch = (await git.getCurrentBranch()) || context.currentBranch
36
+ const safetyState = await readGitSafetyState(git)
37
+ const currentBranch = safetyState.currentBranch || context.currentBranch
26
38
  const policy = getRepoPolicy(context.topLevelPath)
39
+ assertOnBranch(currentBranch, 'clean', 'CLEAN_DETACHED_HEAD')
40
+ assertNoPendingOperation(safetyState.operation, 'clean', 'CLEAN_GIT_OPERATION_IN_PROGRESS')
41
+ assertNoConflictedFiles(safetyState.changes, 'clean', 'CLEAN_CONFLICTED_FILES')
42
+ assertCleanWorktree(safetyState.changes, 'clean', 'CLEAN_DIRTY_WORKTREE')
27
43
 
28
44
  logger.info(`仓库根目录: ${context.topLevelPath}`)
29
45
  if (context.inheritedFromParent) {
@@ -41,91 +57,92 @@ export async function runCleanWorkflow(options) {
41
57
  })
42
58
  }
43
59
 
44
- if (targetLocalExists) {
45
- await git.checkout(mergedTarget)
46
- } else {
47
- await git.checkoutTracking(mergedTarget)
48
- }
49
-
50
- if (targetRemoteExists) {
51
- await git.pull(mergedTarget, {
52
- allowUnrelatedHistories: true,
53
- noRebase: true
54
- })
55
- }
56
-
57
- const mergedBranches = await git.listMergedLocalBranches(mergedTarget)
58
- const protectedBranches = new Set((policy.protectedBranches || []).map((item) => item.trim()).filter(Boolean))
59
- const keepSet = normalizeKeepList(options.keep || [])
60
+ try {
61
+ if (targetLocalExists) {
62
+ await git.checkout(mergedTarget)
63
+ } else {
64
+ await git.checkoutTracking(mergedTarget)
65
+ }
60
66
 
61
- // 这些分支永远不进入清理列表,避免误删核心分支。
62
- keepSet.add(mergedTarget)
63
- if (context.defaultBranch) {
64
- keepSet.add(context.defaultBranch)
65
- }
66
- if (currentBranch) {
67
- keepSet.add(currentBranch)
68
- }
69
- protectedBranches.forEach((branch) => keepSet.add(branch))
67
+ if (targetRemoteExists) {
68
+ await git.pull(mergedTarget, {
69
+ noRebase: true
70
+ })
71
+ }
70
72
 
71
- const candidates = mergedBranches.filter((branch) => !keepSet.has(branch))
73
+ const mergedBranches = await git.listMergedLocalBranches(mergedTarget)
74
+ const protectedBranches = new Set((policy.protectedBranches || []).map((item) => item.trim()).filter(Boolean))
75
+ const keepSet = normalizeKeepList(options.keep || [])
72
76
 
73
- if (!candidates.length) {
74
- logger.info('没有可清理的本地分支。')
75
- return {
76
- repoRoot: context.topLevelPath,
77
- mergedTarget,
78
- deletedLocal: [],
79
- deletedRemote: []
77
+ // 这些分支永远不进入清理列表,避免误删核心分支。
78
+ keepSet.add(mergedTarget)
79
+ if (context.defaultBranch) {
80
+ keepSet.add(context.defaultBranch)
80
81
  }
81
- }
82
+ if (currentBranch) {
83
+ keepSet.add(currentBranch)
84
+ }
85
+ protectedBranches.forEach((branch) => keepSet.add(branch))
82
86
 
83
- if (!options.yes) {
84
- const accepted = await confirm(
85
- `确认清理以下分支吗: ${candidates.join(', ')} ?`,
86
- false
87
- )
88
- if (!accepted) {
89
- logger.warn('已取消清理。')
87
+ const candidates = mergedBranches.filter((branch) => !keepSet.has(branch))
88
+
89
+ if (!candidates.length) {
90
+ logger.info('没有可清理的本地分支。')
90
91
  return {
91
- canceled: true,
92
92
  repoRoot: context.topLevelPath,
93
93
  mergedTarget,
94
- candidates
94
+ deletedLocal: [],
95
+ deletedRemote: []
95
96
  }
96
97
  }
97
- }
98
98
 
99
- const deletedLocal = []
100
- const deletedRemote = []
101
-
102
- for (const branch of candidates) {
103
- await git.deleteLocalBranch(branch, {
104
- force: options.force
105
- })
106
- deletedLocal.push(branch)
99
+ if (!options.yes) {
100
+ const accepted = await confirm(
101
+ `确认清理以下分支吗: ${candidates.join(', ')} ?`,
102
+ false
103
+ )
104
+ if (!accepted) {
105
+ logger.warn('已取消清理。')
106
+ return {
107
+ canceled: true,
108
+ repoRoot: context.topLevelPath,
109
+ mergedTarget,
110
+ candidates
111
+ }
112
+ }
113
+ }
107
114
 
108
- if (options.remote) {
109
- const existsRemote = await git.branchExistsRemote(branch)
110
- if (existsRemote) {
111
- await git.deleteRemoteBranch(branch)
112
- deletedRemote.push(branch)
115
+ const deletedLocal = []
116
+ const deletedRemote = []
117
+
118
+ for (const branch of candidates) {
119
+ await git.deleteLocalBranch(branch, {
120
+ force: options.force
121
+ })
122
+ deletedLocal.push(branch)
123
+
124
+ if (options.remote) {
125
+ const existsRemote = await git.branchExistsRemote(branch)
126
+ if (existsRemote) {
127
+ await git.deleteRemoteBranch(branch)
128
+ deletedRemote.push(branch)
129
+ }
113
130
  }
114
131
  }
115
- }
116
132
 
117
- try {
133
+ return {
134
+ repoRoot: context.topLevelPath,
135
+ mergedTarget,
136
+ deletedLocal,
137
+ deletedRemote
138
+ }
139
+ } finally {
118
140
  if (currentBranch && currentBranch !== mergedTarget) {
119
- await git.checkout(currentBranch)
141
+ try {
142
+ await git.checkout(currentBranch)
143
+ } catch (error) {
144
+ logger.warn(`未能切回原分支 ${currentBranch}: ${error.message}`)
145
+ }
120
146
  }
121
- } catch (error) {
122
- logger.warn(`未能切回原分支 ${currentBranch}: ${error.message}`)
123
- }
124
-
125
- return {
126
- repoRoot: context.topLevelPath,
127
- mergedTarget,
128
- deletedLocal,
129
- deletedRemote
130
147
  }
131
148
  }
@@ -0,0 +1,131 @@
1
+ import { getAiConfig } from '../core/ai/config.js'
2
+ import { getConfigFilePath, getPlatformConfig, getRepoPolicy } from '../core/config/store.js'
3
+ import { resolveGitContext } from '../core/git/context.js'
4
+ import { GitService } from '../core/git/service.js'
5
+ import { runCommand } from '../core/commands/shell.js'
6
+
7
+ // 构建单条健康检查结果,输入检查项字段,输出统一结构。
8
+ function buildCheck(id, status, title, detail, suggestion = '') {
9
+ return {
10
+ id,
11
+ status,
12
+ title,
13
+ detail,
14
+ suggestion
15
+ }
16
+ }
17
+
18
+ // 汇总检查结果状态,输入 checks 数组,输出整体状态与计数。
19
+ function summarizeChecks(checks) {
20
+ const summary = {
21
+ status: 'pass',
22
+ pass: 0,
23
+ warn: 0,
24
+ fail: 0
25
+ }
26
+
27
+ checks.forEach((check) => {
28
+ if (check.status === 'fail') {
29
+ summary.fail += 1
30
+ summary.status = 'fail'
31
+ return
32
+ }
33
+ if (check.status === 'warn') {
34
+ summary.warn += 1
35
+ if (summary.status !== 'fail') {
36
+ summary.status = 'warn'
37
+ }
38
+ return
39
+ }
40
+ summary.pass += 1
41
+ })
42
+
43
+ return summary
44
+ }
45
+
46
+ // 检查 git 命令可用性,输入仓库根目录,输出健康检查项。
47
+ async function checkGitVersion(repoRoot) {
48
+ const result = await runCommand('git', ['--version'], {
49
+ cwd: repoRoot,
50
+ allowFailure: true
51
+ })
52
+
53
+ if (result.exitCode !== 0) {
54
+ return buildCheck('git.version', 'fail', 'Git 命令不可用', '无法执行 git --version。', '请先安装 Git 并确认 PATH 配置正确。')
55
+ }
56
+
57
+ return buildCheck('git.version', 'pass', 'Git 命令可用', result.stdout.trim())
58
+ }
59
+
60
+ // 检查 origin remote 是否存在,输入 GitService,输出健康检查项。
61
+ async function checkOriginRemote(git) {
62
+ const remoteUrl = await git.getRemoteUrl('origin')
63
+ if (!remoteUrl) {
64
+ return buildCheck('git.origin', 'warn', 'origin remote 未配置', '当前仓库没有 origin remote。', '需要 push 或远程合并前,请先配置 git remote add origin <url>。')
65
+ }
66
+
67
+ return buildCheck('git.origin', 'pass', 'origin remote 已配置', remoteUrl)
68
+ }
69
+
70
+ // 检查 hook 配置,输入 git context,输出健康检查项。
71
+ function checkHooks(context) {
72
+ if (context.hasHuskyFolder || context.hasHookPath) {
73
+ return buildCheck('git.hooks', 'pass', 'Git hooks 已检测到', `hookPath=${context.hookPath}`)
74
+ }
75
+
76
+ return buildCheck('git.hooks', 'warn', '未检测到 Git hooks', '当前仓库没有 .husky 目录或 hooksPath。', '如团队依赖 commitlint/lint-staged,建议补齐 hooks 配置。')
77
+ }
78
+
79
+ // 检查 AI profile 配置,输入 AI 配置对象,输出健康检查项。
80
+ function checkAiProfile(aiConfig) {
81
+ const profileCount = Object.keys(aiConfig.profiles || {}).length
82
+ if (!profileCount || !aiConfig.activeProfile) {
83
+ return buildCheck('ai.profile', 'warn', 'AI profile 未配置', '当前没有可用 AI profile。', '如需使用 ai commit/codereview/explain,请执行 icode config ai set ... --activate。')
84
+ }
85
+
86
+ return buildCheck('ai.profile', 'pass', 'AI profile 已配置', `activeProfile=${aiConfig.activeProfile}, profileCount=${profileCount}`)
87
+ }
88
+
89
+ // 检查远程合并平台配置,输入平台配置与仓库策略,输出健康检查项。
90
+ function checkRemoteMerge(remoteMergePlatform, repoPolicy) {
91
+ const remoteMergePolicy = repoPolicy.remoteMerge && typeof repoPolicy.remoteMerge === 'object'
92
+ ? repoPolicy.remoteMerge
93
+ : {}
94
+
95
+ if (remoteMergePolicy.enabled === false) {
96
+ return buildCheck('remoteMerge.config', 'pass', '远程合并已在仓库禁用', 'repositories.<repo>.remoteMerge.enabled=false')
97
+ }
98
+
99
+ if (!remoteMergePlatform.provider || !remoteMergePlatform.apiKey) {
100
+ return buildCheck('remoteMerge.config', 'warn', '远程合并平台未完整配置', '缺少 provider 或 apiKey。', '如需 icode push -r,请执行 icode config platform remote-merge set --provider gitlab --api-key <key>。')
101
+ }
102
+
103
+ return buildCheck('remoteMerge.config', 'pass', '远程合并平台已配置', `provider=${remoteMergePlatform.provider}`)
104
+ }
105
+
106
+ // 执行项目健康检查,输入仓库选项,输出 context、checks 和 summary。
107
+ export async function runDoctorWorkflow(options = {}) {
108
+ const context = await resolveGitContext({
109
+ cwd: options.cwd,
110
+ repoMode: options.repoMode
111
+ })
112
+ const git = new GitService(context)
113
+ const repoPolicy = getRepoPolicy(context.topLevelPath)
114
+ const aiConfig = getAiConfig()
115
+ const remoteMergePlatform = getPlatformConfig('remoteMerge')
116
+
117
+ const checks = [
118
+ await checkGitVersion(context.topLevelPath),
119
+ await checkOriginRemote(git),
120
+ checkHooks(context),
121
+ checkAiProfile(aiConfig),
122
+ checkRemoteMerge(remoteMergePlatform, repoPolicy)
123
+ ]
124
+
125
+ return {
126
+ configPath: getConfigFilePath(),
127
+ context,
128
+ checks,
129
+ summary: summarizeChecks(checks)
130
+ }
131
+ }