nairon-bench 0.0.19 → 0.0.20

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 +474 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -16954,6 +16954,476 @@ function generateCSV(data) {
16954
16954
  `);
16955
16955
  }
16956
16956
 
16957
+ // src/commands/pr.ts
16958
+ import { execSync as execSync2 } from "node:child_process";
16959
+ var prCommand = defineCommand2({
16960
+ meta: {
16961
+ name: "pr",
16962
+ description: "Analyze AI usage in a pull request"
16963
+ },
16964
+ args: {
16965
+ pr: {
16966
+ type: "positional",
16967
+ description: "PR number or URL (defaults to current branch's PR)",
16968
+ required: false
16969
+ },
16970
+ json: {
16971
+ type: "boolean",
16972
+ description: "Output as JSON",
16973
+ default: false
16974
+ }
16975
+ },
16976
+ async run({ args }) {
16977
+ const jsonOutput = args.json;
16978
+ if (!jsonOutput) {
16979
+ console.log();
16980
+ console.log(colors2.bold(colors2.primary(" PR AI Analysis")));
16981
+ console.log(colors2.dim(" " + "─".repeat(40)));
16982
+ console.log();
16983
+ }
16984
+ const spinner = jsonOutput ? null : createSpinner("Analyzing PR...");
16985
+ if (spinner)
16986
+ spinner.start();
16987
+ try {
16988
+ const prInfo = await getPRInfo(args.pr);
16989
+ if (!prInfo) {
16990
+ if (spinner)
16991
+ spinner.fail("Could not find PR");
16992
+ if (!jsonOutput) {
16993
+ console.log(` ${icons.error} No PR found. Provide a PR number or run from a branch with an open PR.`);
16994
+ }
16995
+ return;
16996
+ }
16997
+ if (spinner)
16998
+ spinner.succeed(`Found PR #${prInfo.number}`);
16999
+ const commits = await getPRCommits(prInfo.number);
17000
+ const prStart = new Date(prInfo.createdAt);
17001
+ const prEnd = prInfo.mergedAt ? new Date(prInfo.mergedAt) : new Date;
17002
+ const sessions = await collectAgentSessions(prStart);
17003
+ const prSessions = sessions?.sessions.filter((s2) => {
17004
+ const sessionStart = new Date(s2.startedAt);
17005
+ return sessionStart >= prStart && sessionStart <= prEnd;
17006
+ }) || [];
17007
+ const totalTokens = prSessions.reduce((sum, s2) => sum + s2.totalTokens, 0);
17008
+ const totalCost = prSessions.reduce((sum, s2) => sum + s2.costUsd, 0);
17009
+ const totalDuration = prSessions.reduce((sum, s2) => sum + s2.durationMinutes, 0);
17010
+ const wastedTokens = prSessions.reduce((sum, s2) => sum + s2.patterns.reduce((ps, p) => ps + p.estimatedWastedTokens, 0), 0);
17011
+ const modelUsage = {};
17012
+ for (const session of prSessions) {
17013
+ if (session.model) {
17014
+ modelUsage[session.model] = (modelUsage[session.model] || 0) + 1;
17015
+ }
17016
+ }
17017
+ const frustrationCount = prSessions.reduce((sum, s2) => sum + s2.patterns.filter((p) => p.type !== "smooth_flow").reduce((ps, p) => ps + p.count, 0), 0);
17018
+ const result = {
17019
+ pr: {
17020
+ number: prInfo.number,
17021
+ title: prInfo.title,
17022
+ author: prInfo.author,
17023
+ createdAt: prInfo.createdAt,
17024
+ mergedAt: prInfo.mergedAt,
17025
+ commits: commits.length,
17026
+ additions: prInfo.additions,
17027
+ deletions: prInfo.deletions
17028
+ },
17029
+ aiUsage: {
17030
+ sessions: prSessions.length,
17031
+ totalTokens,
17032
+ totalCost: Math.round(totalCost * 100) / 100,
17033
+ durationMinutes: totalDuration,
17034
+ wastedTokens,
17035
+ wastePercentage: totalTokens > 0 ? Math.round(wastedTokens / totalTokens * 100) : 0,
17036
+ frustrationEvents: frustrationCount,
17037
+ modelUsage
17038
+ },
17039
+ efficiency: {
17040
+ tokensPerCommit: commits.length > 0 ? Math.round(totalTokens / commits.length) : 0,
17041
+ tokensPerLine: prInfo.additions + prInfo.deletions > 0 ? Math.round(totalTokens / (prInfo.additions + prInfo.deletions)) : 0,
17042
+ costPerCommit: commits.length > 0 ? Math.round(totalCost / commits.length * 100) / 100 : 0
17043
+ }
17044
+ };
17045
+ if (jsonOutput) {
17046
+ console.log(JSON.stringify(result, null, 2));
17047
+ return;
17048
+ }
17049
+ console.log();
17050
+ console.log(` ${colors2.bold("PR Details")}`);
17051
+ console.log(colors2.dim(" " + "─".repeat(40)));
17052
+ console.log(` ${colors2.dim("Number:")} #${prInfo.number}`);
17053
+ console.log(` ${colors2.dim("Title:")} ${prInfo.title.slice(0, 50)}${prInfo.title.length > 50 ? "..." : ""}`);
17054
+ console.log(` ${colors2.dim("Author:")} @${prInfo.author}`);
17055
+ console.log(` ${colors2.dim("Commits:")} ${commits.length}`);
17056
+ console.log(` ${colors2.dim("Changes:")} ${colors2.success(`+${prInfo.additions}`)} ${colors2.error(`-${prInfo.deletions}`)}`);
17057
+ console.log();
17058
+ console.log(` ${colors2.bold("AI Usage During PR")}`);
17059
+ console.log(colors2.dim(" " + "─".repeat(40)));
17060
+ console.log(` ${colors2.dim("Sessions:")} ${prSessions.length}`);
17061
+ console.log(` ${colors2.dim("Tokens:")} ${formatNumber(totalTokens)}`);
17062
+ console.log(` ${colors2.dim("Cost:")} ${colors2.success(`$${totalCost.toFixed(2)}`)}`);
17063
+ console.log(` ${colors2.dim("Duration:")} ${totalDuration} minutes`);
17064
+ if (wastedTokens > 0) {
17065
+ const wastePercent = Math.round(wastedTokens / totalTokens * 100);
17066
+ console.log(` ${colors2.dim("Wasted:")} ${colors2.error(`${formatNumber(wastedTokens)} tokens (${wastePercent}%)`)}`);
17067
+ }
17068
+ if (frustrationCount > 0) {
17069
+ console.log(` ${colors2.dim("Frustration events:")} ${colors2.warning(frustrationCount.toString())}`);
17070
+ }
17071
+ console.log();
17072
+ console.log(` ${colors2.bold("Efficiency")}`);
17073
+ console.log(colors2.dim(" " + "─".repeat(40)));
17074
+ console.log(` ${colors2.dim("Tokens/commit:")} ${formatNumber(result.efficiency.tokensPerCommit)}`);
17075
+ console.log(` ${colors2.dim("Tokens/line:")} ${result.efficiency.tokensPerLine}`);
17076
+ console.log(` ${colors2.dim("Cost/commit:")} $${result.efficiency.costPerCommit.toFixed(2)}`);
17077
+ console.log();
17078
+ if (Object.keys(modelUsage).length > 0) {
17079
+ console.log(` ${colors2.bold("Models Used")}`);
17080
+ console.log(colors2.dim(" " + "─".repeat(40)));
17081
+ for (const [model, count] of Object.entries(modelUsage).sort((a2, b2) => b2[1] - a2[1])) {
17082
+ const shortModel = model.length > 25 ? model.slice(0, 22) + "..." : model;
17083
+ console.log(` ${colors2.dim(shortModel.padEnd(25))} ${count} sessions`);
17084
+ }
17085
+ console.log();
17086
+ }
17087
+ } catch (error2) {
17088
+ if (spinner)
17089
+ spinner.fail("Analysis failed");
17090
+ if (!jsonOutput) {
17091
+ console.log(` ${icons.error} ${error2 instanceof Error ? error2.message : "Unknown error"}`);
17092
+ }
17093
+ }
17094
+ }
17095
+ });
17096
+ async function getPRInfo(prArg) {
17097
+ try {
17098
+ let prNumber;
17099
+ if (prArg) {
17100
+ const match = prArg.match(/\/pull\/(\d+)/);
17101
+ prNumber = match ? match[1] : prArg;
17102
+ } else {
17103
+ const branch = execSync2("git branch --show-current", { encoding: "utf-8" }).trim();
17104
+ const result2 = execSync2(`gh pr list --head "${branch}" --json number --limit 1`, { encoding: "utf-8" });
17105
+ const prs = JSON.parse(result2);
17106
+ if (prs.length === 0)
17107
+ return null;
17108
+ prNumber = prs[0].number.toString();
17109
+ }
17110
+ const result = execSync2(`gh pr view ${prNumber} --json number,title,author,createdAt,mergedAt,additions,deletions`, { encoding: "utf-8" });
17111
+ const pr = JSON.parse(result);
17112
+ return {
17113
+ number: pr.number,
17114
+ title: pr.title,
17115
+ author: pr.author.login,
17116
+ createdAt: pr.createdAt,
17117
+ mergedAt: pr.mergedAt,
17118
+ additions: pr.additions,
17119
+ deletions: pr.deletions
17120
+ };
17121
+ } catch {
17122
+ return null;
17123
+ }
17124
+ }
17125
+ async function getPRCommits(prNumber) {
17126
+ try {
17127
+ const result = execSync2(`gh pr view ${prNumber} --json commits --jq '.commits[].oid'`, { encoding: "utf-8" });
17128
+ return result.trim().split(`
17129
+ `).filter(Boolean);
17130
+ } catch {
17131
+ return [];
17132
+ }
17133
+ }
17134
+ function formatNumber(num) {
17135
+ if (num >= 1e6)
17136
+ return `${(num / 1e6).toFixed(1)}M`;
17137
+ if (num >= 1000)
17138
+ return `${Math.round(num / 1000)}K`;
17139
+ return num.toString();
17140
+ }
17141
+
17142
+ // src/lib/badges.ts
17143
+ function computeBadges(score, agents, git) {
17144
+ const badges = [];
17145
+ badges.push({
17146
+ id: "score-50",
17147
+ name: "Getting Started",
17148
+ description: "Reach a score of 50",
17149
+ icon: "\uD83C\uDFAF",
17150
+ tier: "bronze",
17151
+ category: "score",
17152
+ earned: score.overall >= 50,
17153
+ progress: Math.min(100, score.overall / 50 * 100)
17154
+ });
17155
+ badges.push({
17156
+ id: "score-70",
17157
+ name: "Proficient",
17158
+ description: "Reach a score of 70",
17159
+ icon: "⭐",
17160
+ tier: "silver",
17161
+ category: "score",
17162
+ earned: score.overall >= 70,
17163
+ progress: Math.min(100, score.overall / 70 * 100)
17164
+ });
17165
+ badges.push({
17166
+ id: "score-85",
17167
+ name: "Expert",
17168
+ description: "Reach a score of 85",
17169
+ icon: "\uD83C\uDFC6",
17170
+ tier: "gold",
17171
+ category: "score",
17172
+ earned: score.overall >= 85,
17173
+ progress: Math.min(100, score.overall / 85 * 100)
17174
+ });
17175
+ badges.push({
17176
+ id: "score-95",
17177
+ name: "Elite",
17178
+ description: "Reach a score of 95+",
17179
+ icon: "\uD83D\uDC8E",
17180
+ tier: "diamond",
17181
+ category: "score",
17182
+ earned: score.overall >= 95,
17183
+ progress: Math.min(100, score.overall / 95 * 100)
17184
+ });
17185
+ const wastePercent = score.tokenEfficiency.wastePercentage;
17186
+ badges.push({
17187
+ id: "low-waste",
17188
+ name: "Token Saver",
17189
+ description: "Keep token waste below 10%",
17190
+ icon: "\uD83D\uDCB0",
17191
+ tier: "silver",
17192
+ category: "efficiency",
17193
+ earned: wastePercent < 10,
17194
+ progress: wastePercent < 10 ? 100 : Math.max(0, 100 - (wastePercent - 10) * 5)
17195
+ });
17196
+ badges.push({
17197
+ id: "ultra-efficient",
17198
+ name: "Ultra Efficient",
17199
+ description: "Keep token waste below 5%",
17200
+ icon: "\uD83D\uDD25",
17201
+ tier: "gold",
17202
+ category: "efficiency",
17203
+ earned: wastePercent < 5,
17204
+ progress: wastePercent < 5 ? 100 : Math.max(0, 100 - wastePercent * 10)
17205
+ });
17206
+ if (agents) {
17207
+ const sessionCount = agents.totalSessions;
17208
+ badges.push({
17209
+ id: "first-session",
17210
+ name: "First Steps",
17211
+ description: "Complete your first AI session",
17212
+ icon: "\uD83D\uDC63",
17213
+ tier: "bronze",
17214
+ category: "consistency",
17215
+ earned: sessionCount >= 1,
17216
+ progress: sessionCount >= 1 ? 100 : 0
17217
+ });
17218
+ badges.push({
17219
+ id: "power-user",
17220
+ name: "Power User",
17221
+ description: "Complete 100 AI sessions",
17222
+ icon: "⚡",
17223
+ tier: "silver",
17224
+ category: "consistency",
17225
+ earned: sessionCount >= 100,
17226
+ progress: Math.min(100, sessionCount / 100 * 100)
17227
+ });
17228
+ badges.push({
17229
+ id: "ai-native",
17230
+ name: "AI Native",
17231
+ description: "Complete 500 AI sessions",
17232
+ icon: "\uD83E\uDD16",
17233
+ tier: "gold",
17234
+ category: "consistency",
17235
+ earned: sessionCount >= 500,
17236
+ progress: Math.min(100, sessionCount / 500 * 100)
17237
+ });
17238
+ const frustrationCount = agents.sessions.reduce((acc, s2) => acc + s2.patterns.filter((p) => p.type !== "smooth_flow").reduce((a2, p) => a2 + p.count, 0), 0);
17239
+ const smoothRatio = sessionCount > 0 ? 1 - frustrationCount / sessionCount / 5 : 0;
17240
+ badges.push({
17241
+ id: "smooth-operator",
17242
+ name: "Smooth Operator",
17243
+ description: "Maintain low frustration across sessions",
17244
+ icon: "\uD83D\uDE0E",
17245
+ tier: "gold",
17246
+ category: "efficiency",
17247
+ earned: smoothRatio > 0.8 && sessionCount >= 10,
17248
+ progress: Math.min(100, smoothRatio * 100)
17249
+ });
17250
+ const modelCount = Object.keys(agents.modelBreakdown).length;
17251
+ badges.push({
17252
+ id: "model-explorer",
17253
+ name: "Model Explorer",
17254
+ description: "Use 3+ different AI models",
17255
+ icon: "\uD83D\uDD2C",
17256
+ tier: "bronze",
17257
+ category: "tools",
17258
+ earned: modelCount >= 3,
17259
+ progress: Math.min(100, modelCount / 3 * 100)
17260
+ });
17261
+ }
17262
+ if (git) {
17263
+ badges.push({
17264
+ id: "commit-streak",
17265
+ name: "Commit Machine",
17266
+ description: "Average 5+ commits per day",
17267
+ icon: "\uD83D\uDCDD",
17268
+ tier: "silver",
17269
+ category: "consistency",
17270
+ earned: git.commitsPerDay >= 5,
17271
+ progress: Math.min(100, git.commitsPerDay / 5 * 100)
17272
+ });
17273
+ badges.push({
17274
+ id: "small-commits",
17275
+ name: "Atomic Commits",
17276
+ description: "Keep average commit size under 50 lines",
17277
+ icon: "\uD83C\uDFAF",
17278
+ tier: "silver",
17279
+ category: "efficiency",
17280
+ earned: git.avgCommitSize < 50,
17281
+ progress: git.avgCommitSize < 50 ? 100 : Math.max(0, 100 - (git.avgCommitSize - 50))
17282
+ });
17283
+ }
17284
+ const allPhasesAbove60 = score.phases.every((p) => p.score >= 60);
17285
+ badges.push({
17286
+ id: "full-coverage",
17287
+ name: "Full Coverage",
17288
+ description: "Score 60+ in all SDLC phases",
17289
+ icon: "\uD83C\uDF1F",
17290
+ tier: "gold",
17291
+ category: "score",
17292
+ earned: allPhasesAbove60,
17293
+ progress: Math.min(100, score.phases.filter((p) => p.score >= 60).length / score.phases.length * 100)
17294
+ });
17295
+ const implementationPhase = score.phases.find((p) => p.phase === "implementation");
17296
+ if (implementationPhase && implementationPhase.score >= 80) {
17297
+ badges.push({
17298
+ id: "code-wizard",
17299
+ name: "Code Wizard",
17300
+ description: "Score 80+ in implementation phase",
17301
+ icon: "\uD83E\uDDD9",
17302
+ tier: "platinum",
17303
+ category: "score",
17304
+ earned: true
17305
+ });
17306
+ }
17307
+ const testingPhase = score.phases.find((p) => p.phase === "testing");
17308
+ if (testingPhase && testingPhase.score >= 80) {
17309
+ badges.push({
17310
+ id: "test-champion",
17311
+ name: "Test Champion",
17312
+ description: "Score 80+ in testing phase",
17313
+ icon: "✅",
17314
+ tier: "platinum",
17315
+ category: "score",
17316
+ earned: true
17317
+ });
17318
+ }
17319
+ return badges;
17320
+ }
17321
+ function formatBadge(badge) {
17322
+ const tierColors = {
17323
+ bronze: "\x1B[33m",
17324
+ silver: "\x1B[37m",
17325
+ gold: "\x1B[93m",
17326
+ platinum: "\x1B[96m",
17327
+ diamond: "\x1B[95m"
17328
+ };
17329
+ const reset2 = "\x1B[0m";
17330
+ const dim2 = "\x1B[2m";
17331
+ const color = tierColors[badge.tier] || "";
17332
+ const checkmark = badge.earned ? "✓" : "○";
17333
+ const progressStr = badge.earned ? "" : ` ${dim2}(${Math.round(badge.progress || 0)}%)${reset2}`;
17334
+ return `${checkmark} ${badge.icon} ${color}${badge.name}${reset2}${progressStr}`;
17335
+ }
17336
+ function getEarnedBadges(badges) {
17337
+ return badges.filter((b2) => b2.earned);
17338
+ }
17339
+ function getBadgesByCategory(badges, category) {
17340
+ return badges.filter((b2) => b2.category === category);
17341
+ }
17342
+
17343
+ // src/commands/badges.ts
17344
+ var badgesCommand = defineCommand2({
17345
+ meta: {
17346
+ name: "badges",
17347
+ description: "View your earned badges and achievements"
17348
+ },
17349
+ args: {
17350
+ all: {
17351
+ type: "boolean",
17352
+ description: "Show all badges including unearned",
17353
+ default: false
17354
+ },
17355
+ json: {
17356
+ type: "boolean",
17357
+ description: "Output as JSON",
17358
+ default: false
17359
+ }
17360
+ },
17361
+ async run({ args }) {
17362
+ const jsonOutput = args.json;
17363
+ const showAll = args.all;
17364
+ if (!jsonOutput) {
17365
+ console.log();
17366
+ console.log(colors2.bold(colors2.primary(" Badges & Achievements")));
17367
+ console.log(colors2.dim(" " + "─".repeat(40)));
17368
+ console.log();
17369
+ }
17370
+ const spinner = jsonOutput ? null : createSpinner("Computing badges...");
17371
+ if (spinner)
17372
+ spinner.start();
17373
+ const since = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
17374
+ const projectDir = process.cwd();
17375
+ const git = await collectGit(projectDir, since);
17376
+ const agents = await collectAgentSessions(since);
17377
+ const tests = await collectTestResults(projectDir);
17378
+ const score = computeNaironScore(git ?? undefined, agents ?? undefined, tests ?? undefined);
17379
+ const allBadges = computeBadges(score, agents, git);
17380
+ const earnedBadges = getEarnedBadges(allBadges);
17381
+ if (spinner)
17382
+ spinner.succeed(`${earnedBadges.length}/${allBadges.length} badges earned`);
17383
+ if (jsonOutput) {
17384
+ console.log(JSON.stringify({
17385
+ earned: earnedBadges.length,
17386
+ total: allBadges.length,
17387
+ badges: showAll ? allBadges : earnedBadges
17388
+ }, null, 2));
17389
+ return;
17390
+ }
17391
+ console.log();
17392
+ const categories = ["score", "efficiency", "consistency", "tools", "special"];
17393
+ const categoryNames = {
17394
+ score: "Score Milestones",
17395
+ efficiency: "Efficiency",
17396
+ consistency: "Consistency",
17397
+ tools: "Tools & Models",
17398
+ special: "Special"
17399
+ };
17400
+ for (const category of categories) {
17401
+ const categoryBadges = getBadgesByCategory(allBadges, category);
17402
+ const badgesToShow = showAll ? categoryBadges : categoryBadges.filter((b2) => b2.earned);
17403
+ if (badgesToShow.length === 0)
17404
+ continue;
17405
+ console.log(` ${colors2.bold(categoryNames[category])}`);
17406
+ console.log();
17407
+ for (const badge of badgesToShow) {
17408
+ console.log(` ${formatBadge(badge)}`);
17409
+ if (showAll && !badge.earned) {
17410
+ console.log(` ${colors2.dim(badge.description)}`);
17411
+ }
17412
+ }
17413
+ console.log();
17414
+ }
17415
+ const earnedCount = earnedBadges.length;
17416
+ const totalCount = allBadges.length;
17417
+ const percentage = Math.round(earnedCount / totalCount * 100);
17418
+ console.log(colors2.dim(" " + "─".repeat(40)));
17419
+ console.log(` ${icons.success} ${colors2.bold(`${earnedCount}/${totalCount}`)} badges earned ${colors2.dim(`(${percentage}%)`)}`);
17420
+ if (!showAll && earnedCount < totalCount) {
17421
+ console.log(` ${colors2.dim(`Run ${colors2.primary("nb badges --all")} to see all available badges`)}`);
17422
+ }
17423
+ console.log();
17424
+ }
17425
+ });
17426
+
16957
17427
  // src/index.ts
16958
17428
  var VERSION = "0.0.13";
16959
17429
  var CYAN = "\x1B[36m";
@@ -16984,7 +17454,7 @@ function showBanner() {
16984
17454
  log("");
16985
17455
  }
16986
17456
  var args = process.argv.slice(2);
16987
- var subcommands = ["init", "scan", "report", "dashboard", "insights", "tools", "doctor", "publish", "history", "cost", "session", "export"];
17457
+ var subcommands = ["init", "scan", "report", "dashboard", "insights", "tools", "doctor", "publish", "history", "cost", "session", "export", "pr", "badges"];
16988
17458
  var hasSubcommand = args.some((arg) => subcommands.includes(arg));
16989
17459
  var hasHelp = args.includes("--help") || args.includes("-h");
16990
17460
  var hasVersion = args.includes("--version");
@@ -17009,7 +17479,9 @@ if (!hasSubcommand && !hasHelp && !hasVersion && args.length === 0) {
17009
17479
  history: historyCommand,
17010
17480
  cost: costCommand,
17011
17481
  session: sessionCommand,
17012
- export: exportCommand
17482
+ export: exportCommand,
17483
+ pr: prCommand,
17484
+ badges: badgesCommand
17013
17485
  }
17014
17486
  });
17015
17487
  runMain(main);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nairon-bench",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "description": "AI workflow benchmarking CLI",
5
5
  "type": "module",
6
6
  "bin": {