bitfab-cli 0.2.185 → 0.2.186

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 (2) hide show
  1. package/dist/index.js +625 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -30999,6 +30999,16 @@ var SETUP_COMMAND = "/bitfab:setup";
30999
30999
  var ANALYZE_REPO_COMMAND = "/bitfab:setup analyze-repo";
31000
31000
  var ASSISTANT_COMMAND = "/bitfab:assistant";
31001
31001
  var UPDATE_COMMAND = "/bitfab:update";
31002
+ var ANSI = {
31003
+ reset: "\x1B[0m",
31004
+ bold: "\x1B[1m",
31005
+ dim: "\x1B[2m",
31006
+ green: "\x1B[32m",
31007
+ yellow: "\x1B[33m",
31008
+ cyan: "\x1B[36m",
31009
+ magenta: "\x1B[35m",
31010
+ red: "\x1B[31m"
31011
+ };
31002
31012
  function isClaudeAuthStatusLoggedIn(output) {
31003
31013
  try {
31004
31014
  const status = JSON.parse(output);
@@ -31016,6 +31026,12 @@ var checkClaudeAuth = makeCliAuthCheck({
31016
31026
  });
31017
31027
  function runClaudeInstall() {
31018
31028
  const s = p3.spinner();
31029
+ const localPluginKey = activeLocalClaudeBitfabPlugin();
31030
+ if (localPluginKey !== null) {
31031
+ p3.log.step(`Local Bitfab plugin already enabled (${localPluginKey})`);
31032
+ p3.log.success("Bitfab plugin ready in Claude Code");
31033
+ return;
31034
+ }
31019
31035
  s.start("Adding bitfab marketplace");
31020
31036
  const marketplaceOut = runCli(CLI, ["plugin", "marketplace", "add", REPO]);
31021
31037
  if (marketplaceOut.includes("already")) {
@@ -31046,6 +31062,37 @@ function runClaudeInstall() {
31046
31062
  }
31047
31063
  p3.log.success("Bitfab plugin ready in Claude Code");
31048
31064
  }
31065
+ function activeLocalClaudeBitfabPlugin() {
31066
+ const settingsPath = path17.join(
31067
+ process.cwd(),
31068
+ ".claude",
31069
+ "settings.local.json"
31070
+ );
31071
+ if (!fs20.existsSync(settingsPath)) {
31072
+ return null;
31073
+ }
31074
+ let settings;
31075
+ try {
31076
+ settings = JSON.parse(fs20.readFileSync(settingsPath, "utf-8"));
31077
+ } catch {
31078
+ return null;
31079
+ }
31080
+ if (typeof settings !== "object" || settings === null) {
31081
+ return null;
31082
+ }
31083
+ const enabledPlugins = settings.enabledPlugins;
31084
+ if (typeof enabledPlugins !== "object" || enabledPlugins === null) {
31085
+ return null;
31086
+ }
31087
+ for (const [key, enabled] of Object.entries(
31088
+ enabledPlugins
31089
+ )) {
31090
+ if (key.startsWith("bitfab@") && key !== PLUGIN_KEY && enabled === true) {
31091
+ return key;
31092
+ }
31093
+ }
31094
+ return null;
31095
+ }
31049
31096
  function pullLatestClaudePlugin() {
31050
31097
  runCli(CLI, ["plugin", "marketplace", "update", MARKETPLACE]);
31051
31098
  return runCli(CLI, ["plugin", "update", PLUGIN_KEY, "--scope", "user"]);
@@ -31140,10 +31187,18 @@ async function runClaudeAnalyzeRepo(captureOverride, limit, prompt) {
31140
31187
  `analyze-repo exited with status ${exitCode}. See ${logPath}`
31141
31188
  );
31142
31189
  }
31143
- const uploaded = countUploadedTracePlans(stdoutChunks.join(""));
31190
+ const logText = stdoutChunks.join("");
31191
+ const uploaded = countUploadedTracePlans(logText);
31144
31192
  spinner6.stop(
31145
31193
  uploaded === 0 ? "No AI workflows found - nothing to upload" : `Uploaded ${uploaded} draft trace plan${uploaded === 1 ? "" : "s"} to Bitfab`
31146
31194
  );
31195
+ const report = extractAnalyzeRepoReport(logText);
31196
+ if (report !== null) {
31197
+ process.stdout.write(`
31198
+ ${formatAnalyzeRepoReport(report)}
31199
+
31200
+ `);
31201
+ }
31147
31202
  } catch (err) {
31148
31203
  spinner6.stop("analyze-repo failed");
31149
31204
  throw err;
@@ -31153,6 +31208,557 @@ async function runClaudeAnalyzeRepo(captureOverride, limit, prompt) {
31153
31208
  }
31154
31209
  p3.log.info(`Full run log: ${logPath}`);
31155
31210
  }
31211
+ function extractAnalyzeRepoReport(logText) {
31212
+ let lastAssistantText = null;
31213
+ let lastSubstantiveReport = null;
31214
+ let lastResultText = null;
31215
+ for (const line of logText.split("\n")) {
31216
+ if (line.trim() === "") {
31217
+ continue;
31218
+ }
31219
+ let event;
31220
+ try {
31221
+ event = JSON.parse(line);
31222
+ } catch {
31223
+ continue;
31224
+ }
31225
+ for (const assistantText of textCandidatesFromEvent(event)) {
31226
+ if (uploadedHeadingFromLine(assistantText) === null) {
31227
+ continue;
31228
+ }
31229
+ lastAssistantText = assistantText;
31230
+ lastSubstantiveReport = extractSubstantiveAnalyzeRepoReport(assistantText) ?? lastSubstantiveReport;
31231
+ }
31232
+ const resultText = resultTextFromEvent(event);
31233
+ if (resultText !== null && uploadedHeadingFromLine(resultText) !== null) {
31234
+ lastResultText = resultText;
31235
+ }
31236
+ }
31237
+ return lastSubstantiveReport ?? lastAssistantText ?? lastResultText;
31238
+ }
31239
+ function extractSubstantiveAnalyzeRepoReport(text) {
31240
+ const lines = text.trim().split(/\r?\n/);
31241
+ const tableStart = lines.findIndex((line, index) => {
31242
+ const next = lines[index + 1];
31243
+ return isMarkdownTableRow(line) && next !== void 0 && isTableDivider(next);
31244
+ });
31245
+ if (tableStart === -1) {
31246
+ return null;
31247
+ }
31248
+ const headingIndex = lines.findIndex((line, index) => {
31249
+ return index <= tableStart && uploadedHeadingFromLine(line) !== null;
31250
+ });
31251
+ if (headingIndex === -1) {
31252
+ return null;
31253
+ }
31254
+ let tableEnd = tableStart + 2;
31255
+ while (tableEnd < lines.length && isMarkdownTableRow(lines[tableEnd])) {
31256
+ tableEnd++;
31257
+ }
31258
+ const heading = uploadedHeadingFromLine(lines[headingIndex]);
31259
+ if (heading === null) {
31260
+ return null;
31261
+ }
31262
+ const followUp = usefulAnalyzeRepoFollowUp(lines.slice(tableEnd));
31263
+ return [heading, "", ...lines.slice(tableStart, tableEnd), ...followUp].join("\n").trim();
31264
+ }
31265
+ function formatAnalyzeRepoReport(report, options = {}) {
31266
+ const style = createAnalyzeRepoReportStyle(options);
31267
+ const lines = report.trim().split(/\r?\n/);
31268
+ const tableStart = lines.findIndex((line, index) => {
31269
+ const next = lines[index + 1];
31270
+ return isMarkdownTableRow(line) && next !== void 0 && isTableDivider(next);
31271
+ });
31272
+ if (tableStart === -1) {
31273
+ return styleNonTableReport(hideTracePlanLinks(report.trim()), style);
31274
+ }
31275
+ const headers = splitMarkdownTableRow(lines[tableStart]);
31276
+ const fieldValuePlan = formatFieldValuePlanTable(lines, tableStart, style);
31277
+ if (fieldValuePlan !== null) {
31278
+ return fieldValuePlan;
31279
+ }
31280
+ const indexes = {
31281
+ key: findColumnIndex(headers, ["trace function key", "function", "key"]),
31282
+ boundary: findColumnIndex(headers, ["boundary"]),
31283
+ description: findColumnIndex(headers, [
31284
+ "description of workflow",
31285
+ "workflow description",
31286
+ "description"
31287
+ ]),
31288
+ frameworksUsed: findColumnIndex(headers, ["frameworks used", "frameworks"]),
31289
+ refactorComplexity: findColumnIndex(headers, [
31290
+ "refactor complexity to instrument",
31291
+ "refactor complexity",
31292
+ "complexity"
31293
+ ]),
31294
+ methodsToCapture: findColumnIndex(headers, [
31295
+ "suggested methods to capture",
31296
+ "suggested methods to captured",
31297
+ "methods to capture",
31298
+ "methods captured"
31299
+ ]),
31300
+ methodsToMock: findColumnIndex(headers, [
31301
+ "number of methods that need to mocked",
31302
+ "number of methods that need to be mocked",
31303
+ "methods that need to be mocked",
31304
+ "methods to mock",
31305
+ "mocked methods"
31306
+ ]),
31307
+ realDataImprovement: findColumnIndex(headers, [
31308
+ "real data",
31309
+ "feature would improve",
31310
+ "improvement",
31311
+ "why",
31312
+ "worth tracing",
31313
+ "rationale"
31314
+ ])
31315
+ };
31316
+ if (indexes.key === -1) {
31317
+ return styleNonTableReport(hideTracePlanLinks(report.trim()), style);
31318
+ }
31319
+ let tableEnd = tableStart + 2;
31320
+ while (tableEnd < lines.length && isMarkdownTableRow(lines[tableEnd])) {
31321
+ tableEnd++;
31322
+ }
31323
+ const rows = lines.slice(tableStart + 2, tableEnd).map(splitMarkdownTableRow).filter((columns) => columns.length > indexes.key);
31324
+ if (rows.length === 0) {
31325
+ return styleNonTableReport(hideTracePlanLinks(report.trim()), style);
31326
+ }
31327
+ const before = lines.slice(0, tableStart).join("\n").trim();
31328
+ const after = formatTrailingReportBlock(lines.slice(tableEnd), style);
31329
+ const formattedRows = rows.map(
31330
+ (columns, index) => formatPlanRow(style, index + 1, {
31331
+ key: columns[indexes.key] ?? "",
31332
+ boundary: indexes.boundary === -1 ? "" : columns[indexes.boundary] ?? "",
31333
+ description: indexes.description === -1 ? "" : columns[indexes.description] ?? "",
31334
+ frameworksUsed: indexes.frameworksUsed === -1 ? "" : columns[indexes.frameworksUsed] ?? "",
31335
+ refactorComplexity: indexes.refactorComplexity === -1 ? "" : columns[indexes.refactorComplexity] ?? "",
31336
+ methodsToCapture: indexes.methodsToCapture === -1 ? "" : columns[indexes.methodsToCapture] ?? "",
31337
+ methodsToMock: indexes.methodsToMock === -1 ? "" : columns[indexes.methodsToMock] ?? "",
31338
+ realDataImprovement: indexes.realDataImprovement === -1 ? "" : columns[indexes.realDataImprovement] ?? ""
31339
+ })
31340
+ ).join("\n\n");
31341
+ return hideTracePlanLinks(
31342
+ [styleReportBlock(before, style), formattedRows, after].filter((part) => part !== "").join("\n\n")
31343
+ );
31344
+ }
31345
+ function formatPlanRow(style, number4, fields) {
31346
+ const lines = [
31347
+ `${number4}. ${style.bold(style.cyan(cleanInline(fields.key)))}`
31348
+ ];
31349
+ if (fields.description !== "") {
31350
+ lines.push(formatLabeledLine(style, "Workflow", fields.description));
31351
+ }
31352
+ if (fields.boundary !== "") {
31353
+ lines.push(formatLabeledLine(style, "Boundary", fields.boundary));
31354
+ }
31355
+ if (fields.frameworksUsed !== "") {
31356
+ lines.push(formatLabeledLine(style, "Uses", fields.frameworksUsed));
31357
+ }
31358
+ const captureParts = [
31359
+ fields.refactorComplexity === "" ? "" : instrumentationComplexityLabel(style, fields.refactorComplexity),
31360
+ fields.methodsToCapture === "" ? "" : `Methods: ${fields.methodsToCapture}`
31361
+ ].filter((part) => part !== "");
31362
+ if (captureParts.length > 0) {
31363
+ lines.push(
31364
+ formatLabeledLine(style, "Instrumentation", captureParts.join(". "))
31365
+ );
31366
+ }
31367
+ const replayParts = [
31368
+ fields.methodsToMock === "" ? "" : mockReplayLabel(fields.methodsToMock)
31369
+ ].filter((part) => part !== "");
31370
+ if (replayParts.length > 0) {
31371
+ lines.push(formatLabeledLine(style, "Mocks", replayParts.join(". ")));
31372
+ }
31373
+ if (fields.realDataImprovement !== "") {
31374
+ lines.push(formatLabeledLine(style, "Value", fields.realDataImprovement));
31375
+ }
31376
+ return lines.join("\n");
31377
+ }
31378
+ function mockReplayLabel(value) {
31379
+ const trimmed = value.trim();
31380
+ const countWithNames = trimmed.match(/^(\d+)\s*\((.+)\)$/);
31381
+ if (countWithNames) {
31382
+ return `${mockCountLabel(countWithNames[1])}: ${countWithNames[2]}`;
31383
+ }
31384
+ const countWithDetails = trimmed.match(/^(\d+)\s*[:—-]\s*(.+)$/);
31385
+ if (countWithDetails) {
31386
+ return `${mockCountLabel(countWithDetails[1])}: ${countWithDetails[2]}`;
31387
+ }
31388
+ return /^\d+$/.test(trimmed) ? mockCountLabel(trimmed) : trimmed;
31389
+ }
31390
+ function mockCountLabel(count) {
31391
+ return `${count} method${count === "1" ? "" : "s"} mocked on replay`;
31392
+ }
31393
+ function styleInlineValue(value, style) {
31394
+ const parts = value.split(/(`[^`]+`)/g);
31395
+ return parts.map((part) => {
31396
+ if (part.startsWith("`") && part.endsWith("`")) {
31397
+ return style.cyan(part.slice(1, -1));
31398
+ }
31399
+ return cleanInline(part);
31400
+ }).join("");
31401
+ }
31402
+ function cleanInline(value) {
31403
+ return value.replace(/[`*_]/g, "");
31404
+ }
31405
+ function formatFieldValuePlanTable(lines, tableStart, style) {
31406
+ const headers = splitMarkdownTableRow(lines[tableStart]).map(
31407
+ (header) => normalizeMarkdownText(header)
31408
+ );
31409
+ if (headers.length !== 2 || headers[0] !== "field" || headers[1] !== "value" && headers[1] !== "detail") {
31410
+ return null;
31411
+ }
31412
+ let tableEnd = tableStart + 2;
31413
+ while (tableEnd < lines.length && isMarkdownTableRow(lines[tableEnd])) {
31414
+ tableEnd++;
31415
+ }
31416
+ const values = /* @__PURE__ */ new Map();
31417
+ for (const row of lines.slice(tableStart + 2, tableEnd)) {
31418
+ const [field, value] = splitMarkdownTableRow(row);
31419
+ if (field === void 0 || value === void 0) {
31420
+ continue;
31421
+ }
31422
+ values.set(normalizeMarkdownText(field), value);
31423
+ }
31424
+ const key = firstFieldValue(values, [
31425
+ "trace function key",
31426
+ "trace function",
31427
+ "function",
31428
+ "key"
31429
+ ]);
31430
+ if (key === "") {
31431
+ return null;
31432
+ }
31433
+ return hideTracePlanLinks(
31434
+ [
31435
+ styleReportBlock(lines.slice(0, tableStart).join("\n").trim(), style),
31436
+ formatPlanRow(style, 1, {
31437
+ key,
31438
+ boundary: firstFieldValue(values, ["boundary"]),
31439
+ description: firstFieldValue(values, [
31440
+ "workflow",
31441
+ "description of workflow",
31442
+ "workflow description",
31443
+ "description"
31444
+ ]),
31445
+ frameworksUsed: firstFieldValue(values, [
31446
+ "frameworks used",
31447
+ "frameworks"
31448
+ ]),
31449
+ refactorComplexity: firstFieldValue(values, [
31450
+ "refactor complexity to instrument",
31451
+ "refactor complexity",
31452
+ "complexity"
31453
+ ]),
31454
+ methodsToCapture: firstFieldValue(values, [
31455
+ "suggested methods to capture",
31456
+ "suggested methods to captured",
31457
+ "methods to capture",
31458
+ "methods captured"
31459
+ ]),
31460
+ methodsToMock: firstFieldValue(values, [
31461
+ "methods to mock on replay",
31462
+ "number of methods that need to mocked",
31463
+ "number of methods that need to be mocked",
31464
+ "methods that need to be mocked",
31465
+ "methods to mock",
31466
+ "mocked methods"
31467
+ ]),
31468
+ realDataImprovement: firstFieldValue(values, [
31469
+ "how real data improves this",
31470
+ "value of running real data through it",
31471
+ "real data",
31472
+ "feature would improve",
31473
+ "improvement",
31474
+ "why",
31475
+ "worth tracing",
31476
+ "rationale"
31477
+ ])
31478
+ }),
31479
+ formatTrailingReportBlock(lines.slice(tableEnd), style)
31480
+ ].filter((part) => part !== "").join("\n\n")
31481
+ );
31482
+ }
31483
+ function firstFieldValue(values, fields) {
31484
+ for (const field of fields) {
31485
+ const value = values.get(field);
31486
+ if (value !== void 0) {
31487
+ return value;
31488
+ }
31489
+ }
31490
+ return "";
31491
+ }
31492
+ function createAnalyzeRepoReportStyle(options) {
31493
+ const enabled = options.color ?? (process.stdout.isTTY === true && process.env.NO_COLOR === void 0 && process.env.TERM !== "dumb");
31494
+ const wrap = (code, text) => enabled ? `${code}${text}${ANSI.reset}` : text;
31495
+ return {
31496
+ enabled,
31497
+ bold: (text) => wrap(ANSI.bold, text),
31498
+ dim: (text) => wrap(ANSI.dim, text),
31499
+ green: (text) => wrap(ANSI.green, text),
31500
+ yellow: (text) => wrap(ANSI.yellow, text),
31501
+ cyan: (text) => wrap(ANSI.cyan, text),
31502
+ magenta: (text) => wrap(ANSI.magenta, text),
31503
+ red: (text) => wrap(ANSI.red, text),
31504
+ width: normalizeReportWidth(
31505
+ options.width ?? (process.stdout.isTTY === true ? process.stdout.columns : void 0)
31506
+ )
31507
+ };
31508
+ }
31509
+ function formatLabeledLine(style, label, value) {
31510
+ const prefix = ` - ${style.dim(`${label}:`)} `;
31511
+ const continuationPrefix = " ";
31512
+ return wrapStyledLine(
31513
+ `${prefix}${styleInlineValue(value, style)}`,
31514
+ prefixVisibleLength(label),
31515
+ continuationPrefix,
31516
+ style.width
31517
+ );
31518
+ }
31519
+ function prefixVisibleLength(label) {
31520
+ return ` - ${label}: `.length;
31521
+ }
31522
+ function normalizeReportWidth(width) {
31523
+ if (width === void 0 || !Number.isFinite(width) || width <= 0) {
31524
+ return 1e4;
31525
+ }
31526
+ return Math.max(48, Math.min(110, Math.floor(width)));
31527
+ }
31528
+ function wrapStyledLine(line, firstPrefixLength, continuationPrefix, width) {
31529
+ if (visibleLength(line) <= width) {
31530
+ return line;
31531
+ }
31532
+ const segments = ansiSegments(line);
31533
+ const out = [];
31534
+ let current = "";
31535
+ let currentVisible = 0;
31536
+ let limit = width;
31537
+ for (const segment of segments) {
31538
+ if (segment.visible === 0) {
31539
+ current += segment.text;
31540
+ continue;
31541
+ }
31542
+ const words = segment.text.split(/(\s+)/);
31543
+ for (const word of words) {
31544
+ if (word === "") {
31545
+ continue;
31546
+ }
31547
+ const wordVisible = visibleLength(word);
31548
+ const isSpace = /^\s+$/.test(word);
31549
+ if (currentVisible > firstPrefixLength && currentVisible + wordVisible > limit && !isSpace) {
31550
+ out.push(current.trimEnd());
31551
+ current = continuationPrefix;
31552
+ currentVisible = continuationPrefix.length;
31553
+ limit = width;
31554
+ }
31555
+ if (!(currentVisible === continuationPrefix.length && isSpace)) {
31556
+ current += word;
31557
+ currentVisible += wordVisible;
31558
+ }
31559
+ }
31560
+ }
31561
+ if (current.trim() !== "") {
31562
+ out.push(current.trimEnd());
31563
+ }
31564
+ return out.join("\n");
31565
+ }
31566
+ function visibleLength(text) {
31567
+ return ansiSegments(text).reduce((sum, segment) => sum + segment.visible, 0);
31568
+ }
31569
+ function ansiSegments(text) {
31570
+ const segments = [];
31571
+ let index = 0;
31572
+ while (index < text.length) {
31573
+ const escapeIndex = text.indexOf("\x1B[", index);
31574
+ if (escapeIndex === -1) {
31575
+ const plain = text.slice(index);
31576
+ segments.push({ text: plain, visible: plain.length });
31577
+ break;
31578
+ }
31579
+ if (escapeIndex > index) {
31580
+ const plain = text.slice(index, escapeIndex);
31581
+ segments.push({ text: plain, visible: plain.length });
31582
+ }
31583
+ const endIndex = text.indexOf("m", escapeIndex);
31584
+ if (endIndex === -1) {
31585
+ const plain = text.slice(escapeIndex);
31586
+ segments.push({ text: plain, visible: plain.length });
31587
+ break;
31588
+ }
31589
+ segments.push({ text: text.slice(escapeIndex, endIndex + 1), visible: 0 });
31590
+ index = endIndex + 1;
31591
+ }
31592
+ return segments;
31593
+ }
31594
+ function usefulAnalyzeRepoFollowUp(lines) {
31595
+ const useful = [];
31596
+ let includeSkipped = false;
31597
+ for (const rawLine of lines) {
31598
+ const line = rawLine.trimEnd();
31599
+ const normalized = normalizeMarkdownText(line);
31600
+ if (line.trim() === "") {
31601
+ if (useful.length > 0 && useful[useful.length - 1] !== "") {
31602
+ useful.push("");
31603
+ }
31604
+ continue;
31605
+ }
31606
+ if (normalized === "skipped") {
31607
+ useful.push(line);
31608
+ includeSkipped = true;
31609
+ continue;
31610
+ }
31611
+ if (includeSkipped && line.trim().startsWith("- ")) {
31612
+ useful.push(line);
31613
+ continue;
31614
+ }
31615
+ includeSkipped = false;
31616
+ }
31617
+ while (useful[useful.length - 1] === "") {
31618
+ useful.pop();
31619
+ }
31620
+ return useful.length === 0 ? [] : ["", ...useful];
31621
+ }
31622
+ function formatTrailingReportBlock(lines, style) {
31623
+ return styleReportBlock(
31624
+ usefulAnalyzeRepoFollowUp(lines).join("\n").trim(),
31625
+ style
31626
+ );
31627
+ }
31628
+ function instrumentationComplexityLabel(style, raw) {
31629
+ const normalized = raw.trim().toLowerCase();
31630
+ if (normalized === "none" || normalized.startsWith("none ")) {
31631
+ return `${style.green("no refactor")}${raw.trim().slice(4)}`;
31632
+ }
31633
+ if (normalized === "low" || normalized.startsWith("low ")) {
31634
+ return `${style.green("low effort")}${raw.trim().slice(3)}`;
31635
+ }
31636
+ if (normalized === "med" || normalized === "medium") {
31637
+ return style.yellow("medium effort");
31638
+ }
31639
+ if (normalized.startsWith("med ") || normalized.startsWith("medium ")) {
31640
+ return `${style.yellow("medium effort")}${raw.trim().replace(/^medium|^med/i, "")}`;
31641
+ }
31642
+ if (normalized === "high" || normalized.startsWith("high ")) {
31643
+ return `${style.red("high effort")}${raw.trim().slice(4)}`;
31644
+ }
31645
+ return raw;
31646
+ }
31647
+ function styleReportBlock(block, style) {
31648
+ if (block === "") {
31649
+ return "";
31650
+ }
31651
+ return block.split(/\r?\n/).map((line) => styleReportLine(line, style)).join("\n");
31652
+ }
31653
+ function styleNonTableReport(report, style) {
31654
+ return report.split(/\r?\n/).map((line) => styleReportLine(line, style)).join("\n");
31655
+ }
31656
+ function styleReportLine(line, style) {
31657
+ const heading = uploadedHeadingFromLine(line);
31658
+ if (heading !== null) {
31659
+ return style.bold(style.green(heading));
31660
+ }
31661
+ if (line.trim() === "Skipped") {
31662
+ return style.bold(style.yellow(line));
31663
+ }
31664
+ if (line.startsWith("- ")) {
31665
+ return style.yellow(styleInlineValue(line, style));
31666
+ }
31667
+ if (line.startsWith("These are draft")) {
31668
+ return style.dim(styleInlineValue(line, style));
31669
+ }
31670
+ return styleInlineValue(line, style);
31671
+ }
31672
+ function uploadedHeadingFromLine(line) {
31673
+ const normalized = line.replace(/[`*_]/g, "").trim();
31674
+ const match = normalized.match(/Uploaded \d+ draft trace plans?/i);
31675
+ return match?.[0] ?? null;
31676
+ }
31677
+ function hideTracePlanLinks(report) {
31678
+ return report.split(/\r?\n/).map(
31679
+ (line) => line.replace(
31680
+ /\[([^\]]+)\]\(https:\/\/bitfab\.ai\/studio\/trace-plan\/[^\s)]+\)/g,
31681
+ "$1"
31682
+ ).replace(/https:\/\/bitfab\.ai\/studio\/trace-plan\/\S+/g, "").trimEnd()
31683
+ ).filter((line) => !/^\s*(plan|plan url):\s*$/i.test(line)).join("\n").trim();
31684
+ }
31685
+ function findColumnIndex(headers, needles) {
31686
+ return headers.findIndex((header) => {
31687
+ const normalized = normalizeMarkdownText(header);
31688
+ return needles.some((needle) => normalized.includes(needle));
31689
+ });
31690
+ }
31691
+ function normalizeMarkdownText(text) {
31692
+ return text.replace(/[`*_]/g, "").replace(/\s+/g, " ").trim().toLowerCase();
31693
+ }
31694
+ function isMarkdownTableRow(line) {
31695
+ const trimmed = line.trim();
31696
+ return trimmed.startsWith("|") && trimmed.endsWith("|");
31697
+ }
31698
+ function isTableDivider(line) {
31699
+ const cells = splitMarkdownTableRow(line);
31700
+ return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell.replace(/\s/g, "")));
31701
+ }
31702
+ function splitMarkdownTableRow(line) {
31703
+ return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
31704
+ }
31705
+ function assistantTextFromEvent(event) {
31706
+ const content = eventMessageContent(event);
31707
+ if (!content) {
31708
+ return null;
31709
+ }
31710
+ const blocks = [];
31711
+ for (const block of content) {
31712
+ if (typeof block !== "object" || block === null) {
31713
+ continue;
31714
+ }
31715
+ const b = block;
31716
+ if (b.type === "text" && typeof b.text === "string") {
31717
+ blocks.push(b.text);
31718
+ }
31719
+ }
31720
+ const text = blocks.join("\n").trim();
31721
+ return text === "" ? null : text;
31722
+ }
31723
+ function textCandidatesFromEvent(event) {
31724
+ const candidates = [];
31725
+ const assistantText = assistantTextFromEvent(event);
31726
+ if (assistantText !== null) {
31727
+ candidates.push(assistantText);
31728
+ }
31729
+ for (const text of stringValuesFromUnknown(event)) {
31730
+ const trimmed = text.trim();
31731
+ if (trimmed !== "" && !candidates.includes(trimmed) && !candidates.some((candidate) => candidate.includes(trimmed))) {
31732
+ candidates.push(trimmed);
31733
+ }
31734
+ }
31735
+ return candidates;
31736
+ }
31737
+ function stringValuesFromUnknown(value) {
31738
+ if (typeof value === "string") {
31739
+ return [value];
31740
+ }
31741
+ if (Array.isArray(value)) {
31742
+ return value.flatMap(stringValuesFromUnknown);
31743
+ }
31744
+ if (typeof value !== "object" || value === null) {
31745
+ return [];
31746
+ }
31747
+ return Object.values(value).flatMap(
31748
+ stringValuesFromUnknown
31749
+ );
31750
+ }
31751
+ function resultTextFromEvent(event) {
31752
+ if (typeof event !== "object" || event === null) {
31753
+ return null;
31754
+ }
31755
+ const e = event;
31756
+ if (e.type !== "result" || typeof e.result !== "string") {
31757
+ return null;
31758
+ }
31759
+ const text = e.result.trim();
31760
+ return text === "" ? null : text;
31761
+ }
31156
31762
  function latestActivityLabel(chunk2) {
31157
31763
  const lines = chunk2.split("\n").filter((l) => l.trim() !== "");
31158
31764
  for (let i = lines.length - 1; i >= 0; i--) {
@@ -31463,10 +32069,18 @@ async function runCodexAnalyzeRepo(captureOverride, limit, prompt) {
31463
32069
  `analyze-repo exited with status ${exitCode}. See ${logPath}`
31464
32070
  );
31465
32071
  }
31466
- const uploaded = countUploadedTracePlans2(stdoutChunks.join(""));
32072
+ const logText = stdoutChunks.join("");
32073
+ const uploaded = countUploadedTracePlans2(logText);
31467
32074
  spinner6.stop(
31468
32075
  uploaded === 0 ? "No AI workflows found - nothing to upload" : `Uploaded ${uploaded} draft trace plan${uploaded === 1 ? "" : "s"} to Bitfab`
31469
32076
  );
32077
+ const report = extractAnalyzeRepoReport(logText);
32078
+ if (report !== null) {
32079
+ process.stdout.write(`
32080
+ ${formatAnalyzeRepoReport(report)}
32081
+
32082
+ `);
32083
+ }
31470
32084
  } catch (err) {
31471
32085
  spinner6.stop("analyze-repo failed");
31472
32086
  throw err;
@@ -31834,10 +32448,18 @@ async function runCursorAnalyzeRepo(captureOverride, limit, prompt) {
31834
32448
  `analyze-repo exited with status ${exitCode}. See ${logPath}`
31835
32449
  );
31836
32450
  }
31837
- const uploaded = countUploadedTracePlans3(stdoutChunks.join(""));
32451
+ const logText = stdoutChunks.join("");
32452
+ const uploaded = countUploadedTracePlans3(logText);
31838
32453
  spinner6.stop(
31839
32454
  uploaded === 0 ? "No AI workflows found - nothing to upload" : `Uploaded ${uploaded} draft trace plan${uploaded === 1 ? "" : "s"} to Bitfab`
31840
32455
  );
32456
+ const report = extractAnalyzeRepoReport(logText);
32457
+ if (report !== null) {
32458
+ process.stdout.write(`
32459
+ ${formatAnalyzeRepoReport(report)}
32460
+
32461
+ `);
32462
+ }
31841
32463
  } catch (err) {
31842
32464
  spinner6.stop("analyze-repo failed");
31843
32465
  throw err;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitfab-cli",
3
- "version": "0.2.185",
3
+ "version": "0.2.186",
4
4
  "description": "Install and configure the Bitfab plugin in Claude Code, Codex, or Cursor.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",