pi-codex-marketplace 1.0.0 → 1.0.2

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.
@@ -1,3 +1,23 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { stripTypeScriptTypes } from "node:module";
3
+
4
+ const STRIP_TYPES_WARNING = "stripTypeScriptTypes is an experimental feature and might change at any time";
5
+
6
+ function stripTypes(source) {
7
+ const emitWarning = process.emitWarning;
8
+ process.emitWarning = (warning, ...args) => {
9
+ const message = warning instanceof Error ? warning.message : String(warning);
10
+ if (message === STRIP_TYPES_WARNING) return;
11
+ emitWarning.call(process, warning, ...args);
12
+ };
13
+
14
+ try {
15
+ return stripTypeScriptTypes(source, { mode: "strip" });
16
+ } finally {
17
+ process.emitWarning = emitWarning;
18
+ }
19
+ }
20
+
1
21
  export async function resolve(specifier, context, nextResolve) {
2
22
  try {
3
23
  return await nextResolve(specifier, context);
@@ -13,3 +33,16 @@ export async function resolve(specifier, context, nextResolve) {
13
33
  throw err;
14
34
  }
15
35
  }
36
+
37
+ export async function load(url, context, nextLoad) {
38
+ if (!url.endsWith(".ts")) {
39
+ return nextLoad(url, context);
40
+ }
41
+
42
+ const source = await readFile(new URL(url), "utf8");
43
+ return {
44
+ format: "module",
45
+ shortCircuit: true,
46
+ source: stripTypes(source),
47
+ };
48
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-codex-marketplace",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Bridge Package for Codex and Claude Marketplace compatibility in Pi — 極簡 /codex-marketplace 純文字指令與 Headless CLI(add/list/install/update/disable/enable/remove/forget)、單一 Global Bridge State、當下最新安裝與即時投影",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -45,6 +45,8 @@ import { buildGitSnapshot } from '../registration/snapshot.js';
45
45
  import { SourceCache } from '../cache/source-cache.js';
46
46
 
47
47
  export interface CommandOptions {
48
+ /** Optional presentation-only update progress; final result remains authoritative. */
49
+ onProgress?: (message: string) => void;
48
50
  statePath?: string;
49
51
  agentDir?: string;
50
52
  cwd?: string;
@@ -482,6 +484,20 @@ export async function runCommand(
482
484
  ): Promise<CommandResult> {
483
485
  const rawArgs = typeof argv === 'string' ? argv.trim().split(/\s+/).filter(Boolean) : [...argv];
484
486
 
487
+ // Strip the optional command token before normalizing the subcommand. Progress
488
+ // must follow the same parsing path as dispatch, including prefixed and mixed-case
489
+ // invocations.
490
+ if (rawArgs.length > 0 && (rawArgs[0] === '/codex-marketplace' || rawArgs[0] === 'codex-marketplace')) {
491
+ rawArgs.shift();
492
+ }
493
+ const subcmd = rawArgs[0]?.toLowerCase();
494
+
495
+ const progress = (message: string): void => {
496
+ // An observer must never affect acquisition or durable state.
497
+ try { opts.onProgress?.(message); } catch {}
498
+ };
499
+ if (subcmd === 'update') progress('開始更新 Marketplace…');
500
+
485
501
  // Credentialed Acquisition (#109,#117):逐次核准的 credential helper allowlist。
486
502
  // env 顯式設定 → 完全覆蓋;未設定/空白 → 自動偵測固定白名單(gh/keychain/store),
487
503
  // 開箱即用(私有 repo 不再要求先設 env)。解析結果只經既有 AcquisitionTrustOptions
@@ -491,10 +507,6 @@ export async function runCommand(
491
507
  const acquireTrust: { allowedCredentialHelpers: string[]; helperMode: 'detected' | 'approved' } | undefined =
492
508
  resolved.helpers.length > 0 ? { allowedCredentialHelpers: resolved.helpers, helperMode: resolved.mode as 'detected' | 'approved' } : undefined;
493
509
 
494
- // Strip leading command token if passed
495
- if (rawArgs.length > 0 && (rawArgs[0] === '/codex-marketplace' || rawArgs[0] === 'codex-marketplace')) {
496
- rawArgs.shift();
497
- }
498
510
 
499
511
  let state: MinimalBridgeState;
500
512
  let wasReset = false;
@@ -521,7 +533,6 @@ export async function runCommand(
521
533
  // Overview (no arguments)
522
534
  messages.push(...formatOverview(state));
523
535
  } else {
524
- const subcmd = rawArgs[0].toLowerCase();
525
536
  const subargs = rawArgs.slice(1);
526
537
 
527
538
  switch (subcmd) {
@@ -572,14 +583,13 @@ export async function runCommand(
572
583
  executor: opts.gitExecutor,
573
584
  trust: acquireTrust,
574
585
  });
575
- } catch (e) {
576
- const msg = e instanceof Error ? e.message : String(e);
577
- messages.push(`錯誤:git 取得失敗 — ${msg}`);
586
+ } catch {
587
+ messages.push('錯誤:git 取得失敗');
578
588
  break;
579
589
  }
580
590
  if (!acquireResult.ok) {
581
- const outcome = acquireResult.findings[0]?.outcome ?? acquireResult.stderr ?? 'git 取得失敗';
582
- messages.push(`錯誤:git 取得失敗 — ${outcome}`);
591
+ const outcome = acquireResult.findings[0]?.outcome;
592
+ messages.push(outcome ? `錯誤:git 取得失敗 — ${outcome}` : '錯誤:git 取得失敗');
583
593
  if (acquireResult.findings.length > 1) {
584
594
  const extra = acquireResult.findings.slice(1, 3).map((f) => f.outcome).join(';');
585
595
  if (extra) messages.push(`詳細:${extra}`);
@@ -959,11 +969,16 @@ export async function runCommand(
959
969
  // 整包 deep backup:任一 marketplace 失敗不影響其他;最後一次寫入,寫失敗即回滾全部。
960
970
  const stateBackup = JSON.parse(JSON.stringify(state)) as MinimalBridgeState;
961
971
  const updateLines: string[] = [];
972
+ const report = (line: string): void => {
973
+ updateLines.push(line);
974
+ progress(line);
975
+ };
962
976
  let anyChanged = false; // 有 plugin 實際升到最新 → reload+結尾「已重新載入生效」
963
977
  let gitAdvanced = false; // git registration 已推進到新 fingerprint → 需持久化(即使無已安裝 plugin)
964
978
 
965
979
  for (const reg of state.registrations) {
966
980
  const display = reg.marketplaceName || reg.alias || reg.id;
981
+ progress(`${display} ${reg.sourceKind === 'git' ? '重新抓取' : '檢查本機來源'}…`);
967
982
  const format = (reg.format ?? 'codex') as 'codex' | 'claude';
968
983
  const insts = state.installations.filter((i) => i.registrationId === reg.id);
969
984
  const upgraded: string[] = [];
@@ -972,12 +987,12 @@ export async function runCommand(
972
987
  if (reg.sourceKind === 'git') {
973
988
  // ---- git 重抓(當下最新):ls-remote → clone → checkout → snapshot fingerprint ----
974
989
  if (!reg.snapshot || !/^[0-9a-f]{64}$/.test(reg.snapshot)) {
975
- updateLines.push(`⚠ marketplace [${display}] git cache 指紋缺失,無法重抓(請先重新 add)`);
990
+ report(`⚠ marketplace [${display}] git cache 指紋缺失,無法重抓(請先重新 add)`);
976
991
  continue;
977
992
  }
978
993
  const locRes = normalizeGitLocator(reg.source);
979
994
  if (!locRes.ok) {
980
- updateLines.push(`⚠ marketplace [${display}] Git 網址不合法:${locRes.findings[0]?.outcome ?? '未知錯誤'}`);
995
+ report(`⚠ marketplace [${display}] Git 網址不合法:${locRes.findings[0]?.outcome ?? '未知錯誤'}`);
981
996
  continue;
982
997
  }
983
998
  let acquireResult;
@@ -987,17 +1002,16 @@ export async function runCommand(
987
1002
  executor: opts.gitExecutor,
988
1003
  trust: acquireTrust,
989
1004
  });
990
- } catch (e) {
991
- const msg = e instanceof Error ? e.message : String(e);
992
- updateLines.push(`錯誤:git 重抓失敗 — ${msg}`);
1005
+ } catch {
1006
+ report('錯誤:git 重抓失敗');
993
1007
  continue;
994
1008
  }
995
1009
  if (!acquireResult.ok) {
996
- const outcome = acquireResult.findings[0]?.outcome ?? acquireResult.stderr ?? 'git 重抓失敗';
997
- updateLines.push(`錯誤:git 重抓失敗 — ${outcome}`);
1010
+ const outcome = acquireResult.findings[0]?.outcome;
1011
+ report(outcome ? `錯誤:git 重抓失敗 — ${outcome}` : '錯誤:git 重抓失敗');
998
1012
  if (acquireResult.findings.length > 1) {
999
1013
  const extra = acquireResult.findings.slice(1, 3).map((f) => f.outcome).join(';');
1000
- if (extra) updateLines.push(`詳細:${extra}`);
1014
+ if (extra) report(`詳細:${extra}`);
1001
1015
  }
1002
1016
  if (acquireResult.acquiredPath && acquireResult.createdTemp) {
1003
1017
  try { cleanupAcquisition(acquireResult.acquiredPath); } catch {}
@@ -1014,6 +1028,7 @@ export async function runCommand(
1014
1028
  }
1015
1029
  };
1016
1030
 
1031
+ progress(`${display} 檢查來源…`);
1017
1032
  const sourceKey = gitSourceKey(locRes.locator!);
1018
1033
  const snapRes = buildGitSnapshot(acquiredPath, sourceKey, {
1019
1034
  canonicalLocator: reg.source,
@@ -1022,7 +1037,7 @@ export async function runCommand(
1022
1037
  });
1023
1038
  if (!snapRes.ok || !snapRes.snapshot) {
1024
1039
  cleanupAcquired();
1025
- updateLines.push(`⚠ marketplace [${display}] snapshot 建立失敗 — ${snapRes.findings[0]?.outcome ?? '未知錯誤'}`);
1040
+ report(`⚠ marketplace [${display}] snapshot 建立失敗 — ${snapRes.findings[0]?.outcome ?? '未知錯誤'}`);
1026
1041
  continue;
1027
1042
  }
1028
1043
  const fingerprint = snapRes.snapshot.fingerprint;
@@ -1030,7 +1045,7 @@ export async function runCommand(
1030
1045
  if (fingerprint === reg.snapshot) {
1031
1046
  // 當下最新與上次相同 → 無變化
1032
1047
  cleanupAcquired();
1033
- updateLines.push(`${display} 重新抓取… 無變化`);
1048
+ report(`${display} 重新抓取… 無變化`);
1034
1049
  continue;
1035
1050
  }
1036
1051
 
@@ -1047,7 +1062,7 @@ export async function runCommand(
1047
1062
  } catch (e) {
1048
1063
  cleanupAcquired();
1049
1064
  const msg = e instanceof Error ? e.message : String(e);
1050
- updateLines.push(`錯誤:cache 寫入失敗(fingerprint ${fingerprint.slice(0, 12)}…):${msg}`);
1065
+ report(`錯誤:cache 寫入失敗(fingerprint ${fingerprint.slice(0, 12)}…):${msg}`);
1051
1066
  continue;
1052
1067
  }
1053
1068
  cleanupAcquired();
@@ -1069,35 +1084,35 @@ export async function runCommand(
1069
1084
  inst.snapshot = fingerprint;
1070
1085
  upgraded.push(outcome.manifestName);
1071
1086
  for (const c of outcome.colliding) {
1072
- updateLines.push(`⚠ skill "${c}" 與既有同名,未投影(名稱衝突)`);
1087
+ report(`⚠ skill "${c}" 與既有同名,未投影(名稱衝突)`);
1073
1088
  }
1074
1089
  }
1075
1090
  } else {
1076
1091
  for (const inst of insts) failures.push(`${inst.manifestName} 更新失敗:cache 材料無法解析`);
1077
1092
  }
1078
1093
 
1079
- for (const f of failures) updateLines.push(`⚠ marketplace [${display}] ${f}`);
1094
+ for (const f of failures) report(`⚠ marketplace [${display}] ${f}`);
1080
1095
  if (upgraded.length > 0) {
1081
- updateLines.push(`${display} 重新抓取… ${upgraded.join(', ')} 有新版本`);
1096
+ report(`${display} 重新抓取… ${upgraded.join(', ')} 有新版本`);
1082
1097
  anyChanged = true;
1083
1098
  } else if (insts.length === 0) {
1084
1099
  // upstream 移動但沒有已安裝 plugin:registration 已指向最新,下次 install 即用最新
1085
- updateLines.push(`${display} 重新抓取… 有新版本`);
1100
+ report(`${display} 重新抓取… 有新版本`);
1086
1101
  }
1087
1102
  } else {
1088
1103
  // ---- 本機重讀(live 路徑)----
1089
1104
  if (!reg.source || !existsSync(reg.source)) {
1090
- updateLines.push(`⚠ marketplace [${display}] 本機路徑不存在(${reg.source ?? '未記錄'})`);
1105
+ report(`⚠ marketplace [${display}] 本機路徑不存在(${reg.source ?? '未記錄'})`);
1091
1106
  continue;
1092
1107
  }
1093
1108
  // 先 probe catalog:不可讀時不能聲稱「無變化」,必須明示(不靜默略過)
1094
1109
  const probe = readMarketplaceCatalog(reg.source, format);
1095
1110
  if (probe.error) {
1096
- updateLines.push(`⚠ marketplace [${display}] ${probe.error}`);
1111
+ report(`⚠ marketplace [${display}] ${probe.error}`);
1097
1112
  continue;
1098
1113
  }
1099
1114
  if (insts.length === 0) {
1100
- updateLines.push(`${display} 重新抓取… 無變化`);
1115
+ report(`${display} 重新抓取… 無變化`);
1101
1116
  continue;
1102
1117
  }
1103
1118
  let changed = false;
@@ -1113,21 +1128,22 @@ export async function runCommand(
1113
1128
  changed = changed || outcome.changed;
1114
1129
  upgraded.push(outcome.manifestName);
1115
1130
  for (const c of outcome.colliding) {
1116
- updateLines.push(`⚠ skill "${c}" 與既有同名,未投影(名稱衝突)`);
1131
+ report(`⚠ skill "${c}" 與既有同名,未投影(名稱衝突)`);
1117
1132
  }
1118
1133
  }
1119
- for (const f of failures) updateLines.push(`⚠ marketplace [${display}] ${f}`);
1134
+ for (const f of failures) report(`⚠ marketplace [${display}] ${f}`);
1120
1135
  if (upgraded.length === 0) continue; // 全部失敗,⚠ 已明示
1121
1136
  if (changed) {
1122
- updateLines.push(`${display} 重新抓取… ${upgraded.join(', ')} 有新版本`);
1137
+ report(`${display} 重新抓取… ${upgraded.join(', ')} 有新版本`);
1123
1138
  anyChanged = true;
1124
1139
  } else {
1125
- updateLines.push(`${display} 重新抓取… 無變化`);
1140
+ report(`${display} 重新抓取… 無變化`);
1126
1141
  }
1127
1142
  }
1128
1143
  }
1129
1144
 
1130
1145
  if (gitAdvanced || anyChanged) {
1146
+ progress('寫入 Bridge State…');
1131
1147
  try {
1132
1148
  writeMinimalBridgeState(state, opts);
1133
1149
  } catch (e) {
@@ -1135,7 +1151,8 @@ export async function runCommand(
1135
1151
  state.registrations = stateBackup.registrations;
1136
1152
  state.installations = stateBackup.installations;
1137
1153
  const msg = e instanceof Error ? e.message : String(e);
1138
- messages.push(`錯誤:寫入 Bridge State 失敗:${msg}`);
1154
+ // The final result remains a complete summary even though persistence failed.
1155
+ messages.push(...updateLines, `錯誤:寫入 Bridge State 失敗:${msg}`);
1139
1156
  break;
1140
1157
  }
1141
1158
  if (anyChanged) reload = true;
package/src/cli/index.ts CHANGED
@@ -13,7 +13,7 @@ export { getPackageVersion };
13
13
 
14
14
  export interface CliIO {
15
15
  stdout?: { write: (chunk: string) => unknown } | ((chunk: string) => unknown);
16
- stderr?: { write: (chunk: string) => unknown } | ((chunk: string) => unknown);
16
+ stderr?: { write: (chunk: string) => unknown; isTTY?: boolean } | ((chunk: string) => unknown);
17
17
  exit?: (code: number) => unknown;
18
18
  }
19
19
 
@@ -63,7 +63,38 @@ export async function runCli(
63
63
  return exitWith(0);
64
64
  }
65
65
 
66
- const result = await runCommand(argv, opts);
66
+ const terminal = typeof io.stderr === 'object' && io.stderr.isTTY ? io.stderr : undefined;
67
+ let timer: ReturnType<typeof setInterval> | undefined;
68
+ let frame = 0;
69
+ const clearActivity = (): void => {
70
+ if (timer !== undefined) {
71
+ clearInterval(timer);
72
+ timer = undefined;
73
+ try { terminal?.write('\r\x1b[2K'); } catch { /* Progress is best-effort. */ }
74
+ }
75
+ };
76
+ let result: CommandResult;
77
+ try {
78
+ result = await runCommand(argv, {
79
+ ...opts,
80
+ onProgress(message) {
81
+ clearActivity();
82
+ writeStream(io.stderr, message);
83
+ if (terminal) {
84
+ timer = setInterval(() => {
85
+ try { terminal.write(`\r${['|', '/', '-', '\\'][frame++ % 4]} 處理中…`); } catch {
86
+ clearInterval(timer);
87
+ timer = undefined;
88
+ }
89
+ }, 100);
90
+ timer.unref();
91
+ }
92
+ opts.onProgress?.(message);
93
+ },
94
+ });
95
+ } finally {
96
+ clearActivity();
97
+ }
67
98
  const output = formatCliOutput(result);
68
99
 
69
100
  if (result.ok) {
@@ -186,25 +186,25 @@ function classifyFailure(stderr: string, mode: CredentialHelperMode): FailureKin
186
186
  return null;
187
187
  }
188
188
 
189
- function failureFinding(kind: FailureKind, locator: CanonicalGitLocator, stderr: string): ValidationFinding {
189
+ function failureFinding(kind: FailureKind, locator: CanonicalGitLocator): ValidationFinding {
190
190
  switch (kind.kind) {
191
191
  case 'host-key':
192
192
  return trustFinding(
193
193
  kind.isChanged ? CODE.GIT_TRUST_HOST_KEY_CHANGED : CODE.GIT_TRUST_HOST_KEY_UNKNOWN,
194
194
  RULE.GIT_TRUST_HOST_KEY,
195
- `Acquisition Trust Base violation: SSH host key ${kind.isChanged ? 'changed' : 'unknown'} for ${locator.host} — ${stderr.trim()} (only pre-established known-host keys are trusted)`,
195
+ `Acquisition Trust Base violation: SSH host key ${kind.isChanged ? 'changed' : 'unknown'} for ${locator.host} (only pre-established known-host keys are trusted)`,
196
196
  );
197
197
  case 'redirect':
198
198
  return trustFinding(
199
199
  CODE.GIT_TRUST_REDIRECT,
200
200
  RULE.GIT_TRUST_REDIRECT,
201
- `Acquisition Trust Base violation: redirect that would change canonical locator (followRedirects disabled) — ${stderr.trim()}`,
201
+ 'Acquisition Trust Base violation: redirect that would change canonical locator (followRedirects disabled)',
202
202
  );
203
203
  case 'not-found':
204
204
  return trustFinding(
205
205
  CODE.GIT_REPO_NOT_FOUND,
206
206
  RULE.GIT_TRUST_AUTH_REQUIRED,
207
- `Acquisition Trust Base violation: repository not found — '${locator.canonicalUrl}' does not exist (check the URL or owner/repo name) — ${stderr.trim()}`,
207
+ `Acquisition Trust Base violation: repository not found — '${locator.canonicalUrl}' does not exist (check the URL or owner/repo name)`,
208
208
  );
209
209
  case 'invalid-helper': {
210
210
  // GIT-35:核准的 helper 名稱無效(git 找不到 `git-credential-<name>` 執行檔)。
@@ -213,7 +213,7 @@ function failureFinding(kind: FailureKind, locator: CanonicalGitLocator, stderr:
213
213
  return trustFinding(
214
214
  CODE.GIT_TRUST_CREDENTIAL_HELPER_INVALID,
215
215
  RULE.GIT_TRUST_CREDENTIAL_HELPER_INVALID,
216
- `Acquisition Trust Base violation: '${kind.name}' is not a valid git credential helper — use a native helper name (osxkeychain / store) or a shell form like '!gh auth git-credential' — ${stderr.trim()}`,
216
+ "Acquisition Trust Base violation: a configured credential helper is not valid — use a native helper name (osxkeychain / store) or a shell form like '!gh auth git-credential'",
217
217
  );
218
218
  }
219
219
  case 'auth': {
@@ -230,7 +230,7 @@ function failureFinding(kind: FailureKind, locator: CanonicalGitLocator, stderr:
230
230
  return trustFinding(
231
231
  CODE.GIT_TRUST_AUTH_REQUIRED,
232
232
  RULE.GIT_TRUST_AUTH_REQUIRED,
233
- `Acquisition Trust Base violation: ${why} — ${stderr.trim()}`,
233
+ `Acquisition Trust Base violation: ${why}`,
234
234
  );
235
235
  }
236
236
  case 'helper': {
@@ -243,7 +243,7 @@ function failureFinding(kind: FailureKind, locator: CanonicalGitLocator, stderr:
243
243
  return trustFinding(
244
244
  CODE.GIT_TRUST_CREDENTIAL_HELPER,
245
245
  RULE.GIT_TRUST_CREDENTIAL_HELPER,
246
- `Acquisition Trust Base: ${why} — ${stderr.trim()}`,
246
+ `Acquisition Trust Base: ${why}`,
247
247
  );
248
248
  }
249
249
  }
@@ -263,13 +263,13 @@ async function resolveHead(
263
263
  if (kind) {
264
264
  return {
265
265
  ok: false,
266
- findings: [failureFinding(kind, locator, res.stderr)],
266
+ findings: [failureFinding(kind, locator)],
267
267
  stderr: res.stderr,
268
268
  };
269
269
  }
270
270
  return {
271
271
  ok: false,
272
- findings: [acquireFinding(`failed to resolve HEAD via ls-remote: ${res.stderr.trim() || `exit ${res.exitCode}`}`)],
272
+ findings: [acquireFinding(`failed to resolve HEAD via ls-remote (exit ${res.exitCode})`)],
273
273
  stderr: res.stderr,
274
274
  };
275
275
  }
@@ -341,8 +341,8 @@ export async function acquireGitSource(opts: AcquireOptions): Promise<AcquireRes
341
341
  const stderr = cloneRes.stderr || '';
342
342
  const kind = classifyFailure(stderr, mode);
343
343
  const finding = kind
344
- ? failureFinding(kind, locator, stderr)
345
- : acquireFinding(`git clone failed: ${stderr.trim() || `exit ${cloneRes.exitCode}`}`);
344
+ ? failureFinding(kind, locator)
345
+ : acquireFinding(`git clone failed (exit ${cloneRes.exitCode})`);
346
346
  if (createdTemp) try { rmSync(dest, { recursive: true, force: true }); } catch {}
347
347
  return { ok: false, findings: [finding], stderr };
348
348
  }
@@ -389,7 +389,7 @@ export async function acquireGitSource(opts: AcquireOptions): Promise<AcquireRes
389
389
  if (createdTemp) try { rmSync(dest, { recursive: true, force: true }); } catch {}
390
390
  return {
391
391
  ok: false,
392
- findings: [acquireFinding(`resolved revision ${sha} not fetchable: ${fetchRes.stderr.trim()}`)],
392
+ findings: [acquireFinding(`resolved revision ${sha} not fetchable (git fetch exit ${fetchRes.exitCode})`)],
393
393
  stderr: fetchRes.stderr,
394
394
  };
395
395
  }
@@ -402,7 +402,7 @@ export async function acquireGitSource(opts: AcquireOptions): Promise<AcquireRes
402
402
  if (createdTemp) try { rmSync(dest, { recursive: true, force: true }); } catch {}
403
403
  return {
404
404
  ok: false,
405
- findings: [acquireFinding(`failed to checkout resolved revision ${sha}: ${checkoutRes.stderr.trim() || checkout2.stderr.trim()}`)],
405
+ findings: [acquireFinding(`failed to checkout resolved revision ${sha} (git checkout exit ${checkout2.exitCode})`)],
406
406
  stderr: checkoutRes.stderr,
407
407
  };
408
408
  }