aws-runtime-bridge 1.7.35 → 1.7.37

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.
@@ -18,7 +18,21 @@ interface GitRepositoryContext {
18
18
  relativeWorkspacePath: string;
19
19
  }
20
20
  export declare function assertGitWorkspacePathAccessible(workspacePath: string): Promise<void>;
21
+ interface GitStashItem {
22
+ ref: string;
23
+ stashName?: string;
24
+ message: string;
25
+ branch: string;
26
+ createdAt: string | null;
27
+ }
21
28
  type GitDiffFileStatus = 'modified' | 'added' | 'deleted' | 'renamed';
29
+ interface GitDiffSummaryFile {
30
+ path: string;
31
+ status: GitDiffFileStatus;
32
+ additions: number;
33
+ deletions: number;
34
+ staged: boolean;
35
+ }
22
36
  /**
23
37
  * 判断 Git diff 汇总行是否应该展示在差异树中。
24
38
  * 普通 modified 且文本增删均为 0 通常是 filemode/元数据噪声,避免显示为“无差异内容”。
@@ -37,17 +51,32 @@ export declare function createMissingInitialCommitSnapshotError(): string;
37
51
  export declare function isGitStashNoChangesOutput(output: string): boolean;
38
52
  interface LatestGitStash {
39
53
  ref: string;
54
+ stashName: string;
40
55
  message: string;
41
56
  }
57
+ /**
58
+ * 解析 git stash list --format 输出行。
59
+ * 主流程:优先返回稳定 stash commit hash 作为 ref,同时保留 stash@{n} 供界面展示。
60
+ */
61
+ export declare function parseGitStashListLine(line: string): GitStashItem | null;
42
62
  /**
43
63
  * 从 git stash list -n 1 输出解析最近一次 stash。
44
- * 主流程:提取 stash 引用并保留后续描述,供无变更快照复用最近快照引用。
64
+ * 主流程:提取稳定 stash commit hash,供无变更快照复用最近快照引用。
45
65
  */
46
66
  export declare function parseLatestGitStash(output: string): LatestGitStash | null;
47
67
  /**
48
68
  * 解析 git diff 的 name-status 与 numstat 输出,生成前端文件树需要的汇总数据。
49
69
  */
50
70
  export declare function parseGitStatusStagedPaths(output: string): Set<string>;
71
+ /**
72
+ * 解析 git diff 的 name-status 与 numstat 输出,生成前端文件树需要的汇总数据。
73
+ */
74
+ export declare function parseGitDiffSummary(nameStatusOutput: string, numstatOutput: string, stagedPaths?: Set<string>, forceStaged?: boolean): GitDiffSummaryFile[];
75
+ /**
76
+ * 合并工作区与暂存区差异汇总。
77
+ * 主流程:以路径去重,保留工作区展示顺序;同一路径若也存在暂存变更则标记为 staged 并累加统计。
78
+ */
79
+ export declare function mergeGitDiffSummaryFiles(unstagedFiles: GitDiffSummaryFile[], stagedFiles: GitDiffSummaryFile[]): GitDiffSummaryFile[];
51
80
  export declare function normalizeGitCommitMessage(message: unknown): string;
52
81
  export declare function createScopedGitPathspecArgs(context: Pick<GitRepositoryContext, 'relativeWorkspacePath'>): string[];
53
82
  export declare function createScopedFilePathspecArgs(filePath: string, context: Pick<GitRepositoryContext, 'relativeWorkspacePath'>): string[];
@@ -1 +1 @@
1
- {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/routes/git.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,eAAO,MAAM,SAAS,4CAAW,CAAC;AAIlC;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CAOnD;AAED,UAAU,oBAAoB;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAsB,gCAAgC,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3F;AASD,KAAK,iBAAiB,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAkBtE;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,iBAAiB,EACzB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,OAAO,GACpB,OAAO,CAMT;AAED;;;GAGG;AACH,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAK3E;AAED,wBAAgB,uCAAuC,IAAI,MAAM,CAEhE;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAKjE;AAED,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAWzE;AA+KD;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAmBrE;AAkDD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAElE;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,IAAI,CAAC,oBAAoB,EAAE,uBAAuB,CAAC,GAAG,MAAM,EAAE,CAElH;AAED,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,oBAAoB,EAAE,uBAAuB,CAAC,GAAG,MAAM,EAAE,CAErI"}
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/routes/git.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,eAAO,MAAM,SAAS,4CAAW,CAAC;AAIlC;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CAOnD;AAED,UAAU,oBAAoB;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAsB,gCAAgC,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3F;AAED,UAAU,YAAY;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,KAAK,iBAAiB,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAEtE,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;CACjB;AAUD;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,iBAAiB,EACzB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,OAAO,GACpB,OAAO,CAMT;AAED;;;GAGG;AACH,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAK3E;AAED,wBAAgB,uCAAuC,IAAI,MAAM,CAEhE;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAKjE;AAED,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAmCvE;AAkBD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAYzE;AA+KD;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAmBrE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,gBAAgB,EAAE,MAAM,EACxB,aAAa,EAAE,MAAM,EACrB,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa,EACpC,WAAW,UAAQ,GAClB,kBAAkB,EAAE,CAuCtB;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,aAAa,EAAE,kBAAkB,EAAE,EACnC,WAAW,EAAE,kBAAkB,EAAE,GAChC,kBAAkB,EAAE,CAwBtB;AAMD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAElE;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,IAAI,CAAC,oBAAoB,EAAE,uBAAuB,CAAC,GAAG,MAAM,EAAE,CAElH;AAED,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,oBAAoB,EAAE,uBAAuB,CAAC,GAAG,MAAM,EAAE,CAErI"}
@@ -71,20 +71,73 @@ export function isGitStashNoChangesOutput(output) {
71
71
  || normalized.includes('no changes to save')
72
72
  || normalized.includes('没有本地变更需要保存');
73
73
  }
74
+ /**
75
+ * 解析 git stash list --format 输出行。
76
+ * 主流程:优先返回稳定 stash commit hash 作为 ref,同时保留 stash@{n} 供界面展示。
77
+ */
78
+ export function parseGitStashListLine(line) {
79
+ const rawLine = String(line || '').trim();
80
+ if (!rawLine)
81
+ return null;
82
+ const fields = rawLine.split('\0');
83
+ if (fields.length >= 3) {
84
+ const stableRef = fields[0]?.trim();
85
+ const stashName = fields[1]?.trim();
86
+ const subject = fields[2]?.trim() || stashName;
87
+ const createdAt = fields[3]?.trim() || null;
88
+ if (!stableRef || !stashName)
89
+ return null;
90
+ const parsedSubject = parseGitStashSubject(subject || '');
91
+ return {
92
+ ref: stableRef,
93
+ stashName,
94
+ message: parsedSubject.message,
95
+ branch: parsedSubject.branch,
96
+ createdAt,
97
+ };
98
+ }
99
+ const match = rawLine.match(/^(stash@\{\d+\})(?::\s*)?(.*)$/);
100
+ if (!match)
101
+ return null;
102
+ const stashName = match[1];
103
+ const parsedSubject = parseGitStashSubject(match[2]?.trim() || stashName);
104
+ return {
105
+ ref: stashName,
106
+ stashName,
107
+ message: parsedSubject.message,
108
+ branch: parsedSubject.branch,
109
+ createdAt: null,
110
+ };
111
+ }
112
+ /**
113
+ * 从 stash subject 中拆出分支和用户消息。
114
+ */
115
+ function parseGitStashSubject(subject) {
116
+ const normalized = String(subject || '').trim();
117
+ const branchMatch = normalized.match(/^(?:On|WIP on)\s+([^:]+):\s*(.*)$/);
118
+ if (branchMatch) {
119
+ return {
120
+ branch: branchMatch[1].trim(),
121
+ message: branchMatch[2].trim() || normalized,
122
+ };
123
+ }
124
+ return { branch: '', message: normalized };
125
+ }
74
126
  /**
75
127
  * 从 git stash list -n 1 输出解析最近一次 stash。
76
- * 主流程:提取 stash 引用并保留后续描述,供无变更快照复用最近快照引用。
128
+ * 主流程:提取稳定 stash commit hash,供无变更快照复用最近快照引用。
77
129
  */
78
130
  export function parseLatestGitStash(output) {
79
131
  const firstLine = String(output || '').split('\n').find(line => line.trim());
80
132
  if (!firstLine)
81
133
  return null;
82
- const match = firstLine.match(/^(stash@\{\d+\})(?::\s*)?(.*)$/);
83
- if (!match)
134
+ const parsed = parseGitStashListLine(firstLine);
135
+ if (!parsed)
84
136
  return null;
85
137
  return {
86
- ref: match[1],
87
- message: match[2]?.trim() || match[1],
138
+ ref: parsed.ref,
139
+ stashName: parsed.stashName || parsed.ref,
140
+ message: parsed.message || parsed.ref,
88
141
  };
89
142
  }
90
143
  /**
@@ -255,7 +308,7 @@ export function parseGitStatusStagedPaths(output) {
255
308
  /**
256
309
  * 解析 git diff 的 name-status 与 numstat 输出,生成前端文件树需要的汇总数据。
257
310
  */
258
- function parseGitDiffSummary(nameStatusOutput, numstatOutput, stagedPaths = new Set()) {
311
+ export function parseGitDiffSummary(nameStatusOutput, numstatOutput, stagedPaths = new Set(), forceStaged = false) {
259
312
  const statusMap = new Map();
260
313
  const nameStatusLines = nameStatusOutput.split('\n').filter(line => line.trim());
261
314
  for (const line of nameStatusLines) {
@@ -285,10 +338,35 @@ function parseGitDiffSummary(nameStatusOutput, numstatOutput, stagedPaths = new
285
338
  if (!filePath || !shouldIncludeGitDiffSummaryFile(status, additions, deletions, isBinaryDiff)) {
286
339
  continue;
287
340
  }
288
- files.push({ path: filePath, status, additions, deletions, staged: stagedPaths.has(filePath) });
341
+ files.push({ path: filePath, status, additions, deletions, staged: forceStaged || stagedPaths.has(filePath) });
289
342
  }
290
343
  return files;
291
344
  }
345
+ /**
346
+ * 合并工作区与暂存区差异汇总。
347
+ * 主流程:以路径去重,保留工作区展示顺序;同一路径若也存在暂存变更则标记为 staged 并累加统计。
348
+ */
349
+ export function mergeGitDiffSummaryFiles(unstagedFiles, stagedFiles) {
350
+ const fileMap = new Map();
351
+ for (const file of unstagedFiles) {
352
+ fileMap.set(file.path, { ...file });
353
+ }
354
+ for (const stagedFile of stagedFiles) {
355
+ const existingFile = fileMap.get(stagedFile.path);
356
+ if (!existingFile) {
357
+ fileMap.set(stagedFile.path, { ...stagedFile, staged: true });
358
+ continue;
359
+ }
360
+ fileMap.set(stagedFile.path, {
361
+ ...existingFile,
362
+ status: existingFile.status === 'modified' ? stagedFile.status : existingFile.status,
363
+ additions: existingFile.additions + stagedFile.additions,
364
+ deletions: existingFile.deletions + stagedFile.deletions,
365
+ staged: true,
366
+ });
367
+ }
368
+ return [...fileMap.values()];
369
+ }
292
370
  function normalizeCommitHash(commitHash) {
293
371
  return String(commitHash || '').trim();
294
372
  }
@@ -374,13 +452,14 @@ gitRouter.post('/git/stash/create', validateToken, async (req, res) => {
374
452
  : defaultMessage;
375
453
  const result = await execGitCommand(normalizedPath, ['stash', 'push', '-m', stashMessage]);
376
454
  if (isGitStashNoChangesOutput(`${result.stdout}\n${result.stderr}`)) {
377
- const latestResult = await execGitCommand(normalizedPath, ['stash', 'list', '-n', '1']);
455
+ const latestResult = await execGitCommand(normalizedPath, ['stash', 'list', '-n', '1', '--format=%H%x00%gd%x00%gs%x00%cr']);
378
456
  const latestStash = latestResult.exitCode === 0 ? parseLatestGitStash(latestResult.stdout) : null;
379
457
  res.json({
380
458
  ok: true,
381
459
  stashRef: latestStash?.ref ?? null,
460
+ stashName: latestStash?.stashName ?? null,
382
461
  message: latestStash
383
- ? `没有本地变更需要保存,已复用最近快照 ${latestStash.ref}`
462
+ ? `没有本地变更需要保存,已复用最近快照 ${latestStash.stashName}`
384
463
  : '没有本地变更需要保存',
385
464
  noChanges: true,
386
465
  reusedLatestStash: Boolean(latestStash),
@@ -396,13 +475,13 @@ gitRouter.post('/git/stash/create', validateToken, async (req, res) => {
396
475
  res.status(400).json({ error: result.stderr || 'git stash push failed' });
397
476
  return;
398
477
  }
399
- const listResult = await execGitCommand(normalizedPath, ['stash', 'list', '-n', '1']);
400
- const firstLine = listResult.stdout.split('\n')[0] || '';
401
- const match = firstLine.match(/^(stash@\{\d+\})/);
402
- const stashRef = match ? match[1] : 'stash@{0}';
478
+ const listResult = await execGitCommand(normalizedPath, ['stash', 'list', '-n', '1', '--format=%H%x00%gd%x00%gs%x00%cr']);
479
+ const latestStash = listResult.exitCode === 0 ? parseLatestGitStash(listResult.stdout) : null;
480
+ const stashRef = latestStash?.ref || 'stash@{0}';
403
481
  res.json({
404
482
  ok: true,
405
483
  stashRef,
484
+ stashName: latestStash?.stashName ?? null,
406
485
  message: stashMessage,
407
486
  workspacePath: normalizedPath
408
487
  });
@@ -425,33 +504,15 @@ gitRouter.post('/git/stash/list', validateToken, async (req, res) => {
425
504
  try {
426
505
  const normalizedPath = path.resolve(String(workspacePath).trim());
427
506
  const stashLimit = Math.min(Math.max(1, Number(limit) || 20), 100);
428
- const result = await execGitCommand(normalizedPath, ['stash', 'list', `-n`, String(stashLimit)]);
507
+ const result = await execGitCommand(normalizedPath, ['stash', 'list', '-n', String(stashLimit), '--format=%H%x00%gd%x00%gs%x00%cr']);
429
508
  if (result.exitCode !== 0) {
430
509
  res.status(400).json({ error: result.stderr || 'git stash list failed' });
431
510
  return;
432
511
  }
433
- const stashes = [];
434
- const lines = result.stdout.split('\n').filter(line => line.trim());
435
- for (const line of lines) {
436
- const refMatch = line.match(/^(stash@\{\d+\})/);
437
- if (!refMatch)
438
- continue;
439
- const ref = refMatch[1];
440
- const restOfLine = line.slice(ref.length + 2);
441
- let branch = '';
442
- let message = restOfLine;
443
- const branchMatch = restOfLine.match(/^(?:On|WIP on)\s+([^:]+):\s*(.*)$/);
444
- if (branchMatch) {
445
- branch = branchMatch[1].trim();
446
- message = branchMatch[2].trim();
447
- }
448
- stashes.push({
449
- ref,
450
- message,
451
- branch,
452
- createdAt: null,
453
- });
454
- }
512
+ const stashes = result.stdout
513
+ .split('\n')
514
+ .map(parseGitStashListLine)
515
+ .filter((stash) => stash !== null);
455
516
  res.json({
456
517
  ok: true,
457
518
  stashes,
@@ -594,12 +655,23 @@ gitRouter.post('/git/diff', validateToken, async (req, res) => {
594
655
  res.status(400).json({ error: result.stderr || 'git diff failed' });
595
656
  return;
596
657
  }
658
+ const stagedNameStatusResult = await execGitCommand(context.repositoryRootPath, createScopedGitArgs(['diff', '--cached', '--name-status'], context));
659
+ if (stagedNameStatusResult.exitCode !== 0) {
660
+ res.status(400).json({ error: stagedNameStatusResult.stderr || 'git staged diff failed' });
661
+ return;
662
+ }
663
+ const stagedNumstatResult = await execGitCommand(context.repositoryRootPath, createScopedGitArgs(['diff', '--cached', '--numstat'], context));
664
+ if (stagedNumstatResult.exitCode !== 0) {
665
+ res.status(400).json({ error: stagedNumstatResult.stderr || 'git staged diff failed' });
666
+ return;
667
+ }
597
668
  const statusResult = await execGitCommand(context.repositoryRootPath, createScopedGitArgs(['status', '--porcelain=v1'], context));
598
669
  if (statusResult.exitCode !== 0) {
599
670
  res.status(400).json({ error: statusResult.stderr || 'git status failed' });
600
671
  return;
601
672
  }
602
- const files = parseGitDiffSummary(nameStatusResult.stdout, result.stdout, parseGitStatusStagedPaths(statusResult.stdout));
673
+ const stagedPaths = parseGitStatusStagedPaths(statusResult.stdout);
674
+ const files = mergeGitDiffSummaryFiles(parseGitDiffSummary(nameStatusResult.stdout, result.stdout, stagedPaths), parseGitDiffSummary(stagedNameStatusResult.stdout, stagedNumstatResult.stdout, stagedPaths, true));
603
675
  res.json({
604
676
  ok: true,
605
677
  isGitRepo: context.isGitRepo,
@@ -832,12 +904,28 @@ gitRouter.post('/git/diff-file', validateToken, async (req, res) => {
832
904
  const normalizedCommitHash = normalizeCommitHash(commitHash);
833
905
  const gitArgs = normalizedCommitHash
834
906
  ? ['show', '--format=', '--no-ext-diff', normalizedCommitHash, '--', repositoryRelativeFilePath]
835
- : ['diff', '--', repositoryRelativeFilePath];
836
- const result = await execGitCommand(context.repositoryRootPath, gitArgs);
907
+ : ['diff', 'HEAD', '--', repositoryRelativeFilePath];
908
+ let result = await execGitCommand(context.repositoryRootPath, gitArgs);
909
+ if (!normalizedCommitHash && result.exitCode !== 0) {
910
+ result = await execGitCommand(context.repositoryRootPath, ['diff', '--', repositoryRelativeFilePath]);
911
+ }
837
912
  if (result.exitCode !== 0) {
838
913
  res.status(400).json({ error: result.stderr || 'git diff file failed' });
839
914
  return;
840
915
  }
916
+ if (!normalizedCommitHash && !result.stdout.trim()) {
917
+ const stagedResult = await execGitCommand(context.repositoryRootPath, [
918
+ 'diff',
919
+ '--cached',
920
+ '--',
921
+ repositoryRelativeFilePath,
922
+ ]);
923
+ if (stagedResult.exitCode !== 0) {
924
+ res.status(400).json({ error: stagedResult.stderr || 'git staged diff file failed' });
925
+ return;
926
+ }
927
+ result = stagedResult;
928
+ }
841
929
  res.json({
842
930
  ok: true,
843
931
  filePath: normalizedFilePath,
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { createScopedFilePathspecArgs, createScopedGitPathspecArgs, normalizeGitCommitMessage, parseGitStatusStagedPaths, } from './git.js';
2
+ import { createScopedFilePathspecArgs, createScopedGitPathspecArgs, mergeGitDiffSummaryFiles, normalizeGitCommitMessage, parseGitDiffSummary, parseGitStashListLine, parseGitStatusStagedPaths, parseLatestGitStash, } from './git.js';
3
3
  describe('git commit helpers', () => {
4
4
  it('normalizes commit messages before executing git commit', () => {
5
5
  expect(normalizeGitCommitMessage(' chore: update dashboard ')).toBe('chore: update dashboard');
@@ -20,4 +20,63 @@ describe('git commit helpers', () => {
20
20
  const stagedPaths = parseGitStatusStagedPaths('M staged.ts\n M unstaged.ts\nA added.ts\n?? untracked.ts\nR old.ts -> renamed.ts\n');
21
21
  expect([...stagedPaths].sort()).toEqual(['added.ts', 'renamed.ts', 'staged.ts']);
22
22
  });
23
+ it('marks cached diff files as staged when parsing staged summaries', () => {
24
+ const files = parseGitDiffSummary('M\tsrc/staged.ts\n', '2\t1\tsrc/staged.ts\n', new Set(), true);
25
+ expect(files).toEqual([
26
+ {
27
+ path: 'src/staged.ts',
28
+ status: 'modified',
29
+ additions: 2,
30
+ deletions: 1,
31
+ staged: true,
32
+ },
33
+ ]);
34
+ });
35
+ it('merges unstaged and staged summaries while preserving staged-only files', () => {
36
+ const unstagedFiles = parseGitDiffSummary('M\tsrc/mixed.ts\nM\tsrc/unstaged.ts\n', '1\t0\tsrc/mixed.ts\n3\t1\tsrc/unstaged.ts\n', new Set(['src/mixed.ts']));
37
+ const stagedFiles = parseGitDiffSummary('M\tsrc/mixed.ts\nA\tsrc/staged-only.ts\n', '2\t1\tsrc/mixed.ts\n5\t0\tsrc/staged-only.ts\n', new Set(['src/mixed.ts', 'src/staged-only.ts']), true);
38
+ expect(mergeGitDiffSummaryFiles(unstagedFiles, stagedFiles)).toEqual([
39
+ {
40
+ path: 'src/mixed.ts',
41
+ status: 'modified',
42
+ additions: 3,
43
+ deletions: 1,
44
+ staged: true,
45
+ },
46
+ {
47
+ path: 'src/unstaged.ts',
48
+ status: 'modified',
49
+ additions: 3,
50
+ deletions: 1,
51
+ staged: false,
52
+ },
53
+ {
54
+ path: 'src/staged-only.ts',
55
+ status: 'added',
56
+ additions: 5,
57
+ deletions: 0,
58
+ staged: true,
59
+ },
60
+ ]);
61
+ });
62
+ });
63
+ describe('git stash reference helpers', () => {
64
+ it('parses stable stash object hashes while preserving the display selector', () => {
65
+ const stash = parseGitStashListLine('0123456789abcdef0123456789abcdef01234567\u0000stash@{3}\u0000On main: 快照: 任务线-2026-06-02 10:00:00\u00002 hours ago');
66
+ expect(stash).toEqual({
67
+ ref: '0123456789abcdef0123456789abcdef01234567',
68
+ stashName: 'stash@{3}',
69
+ message: '快照: 任务线-2026-06-02 10:00:00',
70
+ branch: 'main',
71
+ createdAt: '2 hours ago',
72
+ });
73
+ });
74
+ it('uses the stable stash object hash for the latest no-change snapshot reuse', () => {
75
+ const latest = parseLatestGitStash('fedcba9876543210fedcba9876543210fedcba98\u0000stash@{0}\u0000On master: 快照: latest\u0000just now\n');
76
+ expect(latest).toEqual({
77
+ ref: 'fedcba9876543210fedcba9876543210fedcba98',
78
+ stashName: 'stash@{0}',
79
+ message: '快照: latest',
80
+ });
81
+ });
23
82
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-runtime-bridge",
3
- "version": "1.7.35",
3
+ "version": "1.7.37",
4
4
  "description": "AgentsWorkStudio runtime bridge service for machine-level agent runtime integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",