@muyichengshayu/promptx 0.2.2 → 0.2.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.3
4
+
5
+ - 重构代码变更审查的 `git diff worker` 为带队列的 worker 池,避免单个大 diff 或超时请求拖住所有项目;同时补充更细的 worker 诊断信息,便于排查卡顿、超时和异常退出。
6
+ - 优化代码变更弹窗首屏加载:工作区 / 任务范围优先返回轻量文件列表,整体增删统计与单文件 patch 改为按需加载,明显降低“刚打开代码变更就卡住”的概率。
7
+ - 收紧单文件 diff 的降级策略:为 patch 生成补充源文件大小阈值、patch 行数阈值和命令超时控制;超大或过慢的文件不再拖垮整个弹窗,而是稳定降级为提示或摘要预览。
8
+ - 为超长 diff 新增摘要预览:当完整 patch 过长时,PromptX 会保留文件头和前几个 hunk 的摘要内容,而不再只显示一行“太长无法展示”的空白提示。
9
+ - 优化代码变更弹窗切文件手感:前端新增单文件 patch 请求复用与前几个可见文件的后台预取,常见文件切换更接近秒开,同时避免点击和预取重复发起相同请求。
10
+
3
11
  ## 0.2.2
4
12
 
5
13
  - 修复 `WeChat` 主题下删除任务等危险确认弹窗的按钮文字不可见问题:补齐 `tool-button-danger` 与各类 `subtle` 按钮在微信主题中的语义色覆盖,避免通用按钮样式把危险操作刷成白底白字。
@@ -9,6 +9,11 @@ import { getTaskBySlug } from './repository.js'
9
9
 
10
10
  const MAX_SNAPSHOT_TEXT_BYTES = 220_000
11
11
  const MAX_PATCH_TEXT_BYTES = 260_000
12
+ const MAX_INLINE_PATCH_SOURCE_BYTES = Math.max(48_000, Number(process.env.PROMPTX_GIT_DIFF_MAX_INLINE_FILE_BYTES) || 140_000)
13
+ const MAX_PATCH_LINE_COUNT = Math.max(400, Number(process.env.PROMPTX_GIT_DIFF_MAX_PATCH_LINES) || 2200)
14
+ const MAX_PATCH_PREVIEW_LINES = Math.max(80, Number(process.env.PROMPTX_GIT_DIFF_MAX_PATCH_PREVIEW_LINES) || 220)
15
+ const MAX_PATCH_PREVIEW_HUNKS = Math.max(1, Number(process.env.PROMPTX_GIT_DIFF_MAX_PATCH_PREVIEW_HUNKS) || 5)
16
+ const PATCH_COMMAND_TIMEOUT_MS = Math.max(1_000, Number(process.env.PROMPTX_GIT_DIFF_PATCH_COMMAND_TIMEOUT_MS) || 8000)
12
17
  const DIFF_REVIEW_CACHE_TTL_MS = 4000
13
18
  const DIFF_REVIEW_CACHE_MAX_ENTRIES = 80
14
19
  const FILE_DIFF_CACHE_TTL_MS = 8000
@@ -66,6 +71,9 @@ function runGit(repoRoot = '', args = [], options = {}) {
66
71
  status: typeof result.status === 'number' ? result.status : 1,
67
72
  stdout: String(result.stdout || ''),
68
73
  stderr: String(result.stderr || ''),
74
+ signal: String(result.signal || ''),
75
+ errorCode: String(result.error?.code || ''),
76
+ timedOut: String(result.error?.code || '') === 'ETIMEDOUT',
69
77
  }
70
78
  }
71
79
 
@@ -81,6 +89,9 @@ function runGitBuffer(repoRoot = '', args = [], options = {}) {
81
89
  status: typeof result.status === 'number' ? result.status : 1,
82
90
  stdout: Buffer.isBuffer(result.stdout) ? result.stdout : Buffer.from(result.stdout || ''),
83
91
  stderr: Buffer.isBuffer(result.stderr) ? result.stderr : Buffer.from(result.stderr || ''),
92
+ signal: String(result.signal || ''),
93
+ errorCode: String(result.error?.code || ''),
94
+ timedOut: String(result.error?.code || '') === 'ETIMEDOUT',
84
95
  }
85
96
  }
86
97
 
@@ -92,6 +103,80 @@ function createHash(value) {
92
103
  return crypto.createHash('sha1').update(value).digest('hex')
93
104
  }
94
105
 
106
+ function countTextLines(value = '') {
107
+ const text = String(value || '')
108
+ if (!text) {
109
+ return 0
110
+ }
111
+
112
+ let lines = 1
113
+ for (let index = 0; index < text.length; index += 1) {
114
+ if (text.charCodeAt(index) === 10) {
115
+ lines += 1
116
+ }
117
+ }
118
+ return lines
119
+ }
120
+
121
+ function buildPatchPreviewSummary(patch = '') {
122
+ const text = String(patch || '').trim()
123
+ if (!text) {
124
+ return ''
125
+ }
126
+
127
+ const lines = text.split('\n')
128
+ if (lines.length <= MAX_PATCH_PREVIEW_LINES) {
129
+ return text
130
+ }
131
+
132
+ const previewLines = []
133
+ let index = 0
134
+ let hunkCount = 0
135
+
136
+ while (index < lines.length && !lines[index].startsWith('@@ ')) {
137
+ if (previewLines.length >= MAX_PATCH_PREVIEW_LINES) {
138
+ break
139
+ }
140
+ previewLines.push(lines[index])
141
+ index += 1
142
+ }
143
+
144
+ while (index < lines.length && previewLines.length < MAX_PATCH_PREVIEW_LINES) {
145
+ const line = lines[index]
146
+ if (line.startsWith('@@ ')) {
147
+ if (hunkCount >= MAX_PATCH_PREVIEW_HUNKS) {
148
+ break
149
+ }
150
+ hunkCount += 1
151
+ }
152
+
153
+ previewLines.push(line)
154
+ index += 1
155
+ }
156
+
157
+ while (previewLines.length && !previewLines[previewLines.length - 1].startsWith('@@ ') && previewLines.length > MAX_PATCH_PREVIEW_LINES - 12) {
158
+ previewLines.pop()
159
+ }
160
+
161
+ const omittedLineCount = Math.max(0, lines.length - previewLines.length)
162
+ previewLines.push(`... 已截断,剩余 ${omittedLineCount} 行未展示 ...`)
163
+ return previewLines.join('\n').trim()
164
+ }
165
+
166
+ function createLongPatchPreviewPayload(stats = {}, patch = '', includeStats = true) {
167
+ const previewPatch = buildPatchPreviewSummary(patch)
168
+ return {
169
+ binary: false,
170
+ tooLarge: true,
171
+ patch: previewPatch,
172
+ patchLoaded: true,
173
+ additions: includeStats ? stats.additions : null,
174
+ deletions: includeStats ? stats.deletions : null,
175
+ statsLoaded: includeStats,
176
+ message: previewPatch ? 'diff 内容较长,当前仅展示摘要预览。' : 'diff 内容较长,暂不在页面内完整展示。',
177
+ }
178
+ }
179
+
95
180
  function getCachedValue(cache, key, ttlMs, metricKey = '', options = {}) {
96
181
  const {
97
182
  channel = 'all',
@@ -857,7 +942,21 @@ function buildSubmoduleDiffPayload(repoRoot = '', filePath = '', options = {}) {
857
942
  }
858
943
  diffArgs.push('--', normalizedPath)
859
944
 
860
- const result = runGit(repoRoot, diffArgs)
945
+ const result = runGit(repoRoot, diffArgs, {
946
+ timeout: includePatch ? PATCH_COMMAND_TIMEOUT_MS : undefined,
947
+ })
948
+ if (result.timedOut) {
949
+ return {
950
+ binary: false,
951
+ tooLarge: true,
952
+ patch: '',
953
+ patchLoaded: true,
954
+ additions: null,
955
+ deletions: null,
956
+ statsLoaded: false,
957
+ message: '该文件 diff 计算超时,暂不在线展示详细内容。',
958
+ }
959
+ }
861
960
  const patch = String(result.stdout || '').trim()
862
961
  const stats = includeStats ? parsePatchStats(patch) : { additions: null, deletions: null }
863
962
 
@@ -875,16 +974,11 @@ function buildSubmoduleDiffPayload(repoRoot = '', filePath = '', options = {}) {
875
974
  }
876
975
 
877
976
  if (patch.length > MAX_PATCH_TEXT_BYTES) {
878
- return {
879
- binary: false,
880
- tooLarge: true,
881
- patch: '',
882
- patchLoaded: true,
883
- additions: stats.additions,
884
- deletions: stats.deletions,
885
- statsLoaded: includeStats,
886
- message: 'diff 内容较长,暂不在页面内完整展示。',
887
- }
977
+ return createLongPatchPreviewPayload(stats, patch, includeStats)
978
+ }
979
+
980
+ if (countTextLines(patch) > MAX_PATCH_LINE_COUNT) {
981
+ return createLongPatchPreviewPayload(stats, patch, includeStats)
888
982
  }
889
983
 
890
984
  return {
@@ -1042,6 +1136,32 @@ function buildDiffPayloadForFile(filePath = '', previousState = null, nextState
1042
1136
  return payload
1043
1137
  }
1044
1138
 
1139
+ if (includePatch && Math.max(
1140
+ Math.max(0, Number(previousState?.size) || 0),
1141
+ Math.max(0, Number(nextState?.size) || 0)
1142
+ ) > MAX_INLINE_PATCH_SOURCE_BYTES) {
1143
+ const payload = {
1144
+ binary: false,
1145
+ tooLarge: true,
1146
+ patch: '',
1147
+ patchLoaded: true,
1148
+ additions: null,
1149
+ deletions: null,
1150
+ statsLoaded: false,
1151
+ message: '文件内容较大,暂不展示具体 diff。',
1152
+ }
1153
+ setCachedValue(fileDiffCache, cacheKey, payload, FILE_DIFF_CACHE_MAX_ENTRIES, {
1154
+ channel: 'file',
1155
+ cacheName: 'file-diff',
1156
+ debugMeta: {
1157
+ path: String(filePath || '').trim(),
1158
+ includePatch,
1159
+ includeStats,
1160
+ },
1161
+ })
1162
+ return payload
1163
+ }
1164
+
1045
1165
  if (!includeStats) {
1046
1166
  const payload = {
1047
1167
  binary: false,
@@ -1084,16 +1204,15 @@ function buildDiffPayloadForFile(filePath = '', previousState = null, nextState
1084
1204
  fs.writeFileSync(nextPath, nextState.text || '', 'utf8')
1085
1205
  }
1086
1206
 
1087
- const statsResult = runGit(tempDir, [
1088
- 'diff',
1089
- '--no-index',
1090
- '--numstat',
1091
- previousState?.exists ? previousPath : nullPath,
1092
- nextState?.exists ? nextPath : nullPath,
1093
- ])
1094
- const numstat = parseNumstat(statsResult.stdout)
1095
-
1096
1207
  if (!includePatch) {
1208
+ const statsResult = runGit(tempDir, [
1209
+ 'diff',
1210
+ '--no-index',
1211
+ '--numstat',
1212
+ previousState?.exists ? previousPath : nullPath,
1213
+ nextState?.exists ? nextPath : nullPath,
1214
+ ])
1215
+ const numstat = parseNumstat(statsResult.stdout)
1097
1216
  const payload = {
1098
1217
  binary: false,
1099
1218
  tooLarge: false,
@@ -1123,7 +1242,31 @@ function buildDiffPayloadForFile(filePath = '', previousState = null, nextState
1123
1242
  '--unified=3',
1124
1243
  previousState?.exists ? previousPath : nullPath,
1125
1244
  nextState?.exists ? nextPath : nullPath,
1126
- ])
1245
+ ], {
1246
+ timeout: PATCH_COMMAND_TIMEOUT_MS,
1247
+ })
1248
+ if (result.timedOut) {
1249
+ const payload = {
1250
+ binary: false,
1251
+ tooLarge: true,
1252
+ patch: '',
1253
+ patchLoaded: true,
1254
+ additions: null,
1255
+ deletions: null,
1256
+ statsLoaded: false,
1257
+ message: '该文件 diff 计算超时,暂不在线展示详细内容。',
1258
+ }
1259
+ setCachedValue(fileDiffCache, cacheKey, payload, FILE_DIFF_CACHE_MAX_ENTRIES, {
1260
+ channel: 'file',
1261
+ cacheName: 'file-diff',
1262
+ debugMeta: {
1263
+ path: String(filePath || '').trim(),
1264
+ includePatch,
1265
+ includeStats,
1266
+ },
1267
+ })
1268
+ return payload
1269
+ }
1127
1270
 
1128
1271
  let patch = String(result.stdout || '').trim()
1129
1272
  if (patch) {
@@ -1158,16 +1301,21 @@ function buildDiffPayloadForFile(filePath = '', previousState = null, nextState
1158
1301
  }
1159
1302
 
1160
1303
  if (patch.length > MAX_PATCH_TEXT_BYTES) {
1161
- const payload = {
1162
- binary: false,
1163
- tooLarge: true,
1164
- patch: '',
1165
- patchLoaded: true,
1166
- additions: stats.additions,
1167
- deletions: stats.deletions,
1168
- statsLoaded: true,
1169
- message: 'diff 内容较长,暂不在页面内完整展示。',
1170
- }
1304
+ const payload = createLongPatchPreviewPayload(stats, patch, true)
1305
+ setCachedValue(fileDiffCache, cacheKey, payload, FILE_DIFF_CACHE_MAX_ENTRIES, {
1306
+ channel: 'file',
1307
+ cacheName: 'file-diff',
1308
+ debugMeta: {
1309
+ path: String(filePath || '').trim(),
1310
+ includePatch,
1311
+ includeStats,
1312
+ },
1313
+ })
1314
+ return payload
1315
+ }
1316
+
1317
+ if (countTextLines(patch) > MAX_PATCH_LINE_COUNT) {
1318
+ const payload = createLongPatchPreviewPayload(stats, patch, true)
1171
1319
  setCachedValue(fileDiffCache, cacheKey, payload, FILE_DIFF_CACHE_MAX_ENTRIES, {
1172
1320
  channel: 'file',
1173
1321
  cacheName: 'file-diff',
@@ -1340,6 +1488,21 @@ function createUnsupportedResult(reason = '', repoRoot = '', branch = '') {
1340
1488
  }
1341
1489
  }
1342
1490
 
1491
+ function createLightweightDiffFileEntry(filePath = '', status = 'M') {
1492
+ return {
1493
+ path: String(filePath || '').trim(),
1494
+ status: normalizeDiffStatus(status),
1495
+ additions: null,
1496
+ deletions: null,
1497
+ statsLoaded: false,
1498
+ binary: false,
1499
+ tooLarge: false,
1500
+ patch: '',
1501
+ patchLoaded: false,
1502
+ message: '',
1503
+ }
1504
+ }
1505
+
1343
1506
  export function getWorkspaceGitDiffReviewByCwd(cwd = '', options = {}) {
1344
1507
  const repoRoot = resolveGitRepoRoot(cwd)
1345
1508
  if (!repoRoot) {
@@ -1350,8 +1513,8 @@ export function getWorkspaceGitDiffReviewByCwd(cwd = '', options = {}) {
1350
1513
  const workspaceStatusSignature = resolveWorkspaceStatusSignature(repoRoot)
1351
1514
  const currentHeadOid = resolveGitHeadOid(repoRoot)
1352
1515
  const targetFilePath = String(options.filePath || '').trim()
1353
- const includeFiles = targetFilePath || options.includeFiles !== false
1354
- const includeStats = targetFilePath || options.includeStats !== false
1516
+ const includeFiles = Boolean(targetFilePath) || options.includeFiles !== false
1517
+ const includeStats = Boolean(targetFilePath) || options.includeStats !== false
1355
1518
  const cacheKey = JSON.stringify([
1356
1519
  'workspace',
1357
1520
  repoRoot,
@@ -1379,6 +1542,44 @@ export function getWorkspaceGitDiffReviewByCwd(cwd = '', options = {}) {
1379
1542
  }
1380
1543
 
1381
1544
  const { headOid, entries: workingTreeEntries } = listGitChangeEntries(repoRoot)
1545
+ if (!targetFilePath && !includeStats) {
1546
+ const files = includeFiles
1547
+ ? sortDiffFiles(
1548
+ [...workingTreeEntries.values()].map((entry) => createLightweightDiffFileEntry(entry.path, entry.status))
1549
+ )
1550
+ : []
1551
+
1552
+ const payload = {
1553
+ supported: true,
1554
+ scope: 'workspace',
1555
+ runId: '',
1556
+ repoRoot,
1557
+ branch,
1558
+ baseline: null,
1559
+ warnings: [],
1560
+ baselineCreatedAt: '',
1561
+ summary: {
1562
+ fileCount: workingTreeEntries.size,
1563
+ additions: null,
1564
+ deletions: null,
1565
+ statsComplete: false,
1566
+ },
1567
+ files,
1568
+ }
1569
+ setCachedValue(diffReviewCache, cacheKey, payload, DIFF_REVIEW_CACHE_MAX_ENTRIES, {
1570
+ channel: 'review',
1571
+ cacheName: 'diff-review',
1572
+ debugMeta: {
1573
+ scope: 'workspace',
1574
+ repo: path.basename(repoRoot),
1575
+ fileCount: workingTreeEntries.size,
1576
+ includeFiles,
1577
+ includeStats,
1578
+ },
1579
+ })
1580
+ return payload
1581
+ }
1582
+
1382
1583
  const baselineStateForPath = createBaselineStateResolver(repoRoot, {
1383
1584
  headOid,
1384
1585
  entries: new Map(),
@@ -1516,12 +1717,14 @@ export function getTaskGitDiffReview(taskSlug = '', options = {}) {
1516
1717
  : 'workspace'
1517
1718
  const runId = String(options.runId || '').trim()
1518
1719
  const targetFilePath = String(options.filePath || '').trim()
1519
- const includeFiles = targetFilePath || options.includeFiles !== false
1520
- const includeStats = targetFilePath || options.includeStats !== false
1720
+ const includeFiles = Boolean(targetFilePath) || options.includeFiles !== false
1721
+ const includeStats = Boolean(targetFilePath) || options.includeStats !== false
1521
1722
 
1522
1723
  if (scope === 'workspace') {
1523
1724
  return getWorkspaceGitDiffReviewByCwd(resolveTaskRepoRoot(normalizedTaskSlug), {
1524
1725
  filePath: targetFilePath,
1726
+ includeFiles,
1727
+ includeStats,
1525
1728
  })
1526
1729
  }
1527
1730