nairon-bench 0.6.0 → 0.6.1

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 +269 -59
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8516,9 +8516,9 @@ async function collectGit(projectDir, since) {
8516
8516
  }
8517
8517
 
8518
8518
  // src/collectors/agents.ts
8519
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
8519
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
8520
8520
  import { homedir as homedir2 } from "node:os";
8521
- import { join as join2, basename, resolve } from "node:path";
8521
+ import { join as join2, basename, resolve, sep as sep2 } from "node:path";
8522
8522
  // ../shared/src/constants.ts
8523
8523
  var PHASE_WEIGHTS = {
8524
8524
  requirements: 0.2,
@@ -8935,18 +8935,34 @@ async function collectAgentSessions(since, useCache = true, projectDir) {
8935
8935
  const openCodeSessions = collectOpenCodeSessions(openCodeDir, since, targetProjectDir);
8936
8936
  sessions.push(...openCodeSessions);
8937
8937
  }
8938
- const cursorDirs = [
8939
- join2(homedir2(), ".cursor", "sessions"),
8940
- join2(homedir2(), "Library", "Application Support", "Cursor", "sessions"),
8941
- join2(homedir2(), ".config", "cursor", "sessions")
8938
+ const cursorProjectsDirs = [
8939
+ join2(homedir2(), ".cursor", "projects"),
8940
+ join2(homedir2(), "Library", "Application Support", "Cursor", "projects"),
8941
+ join2(homedir2(), ".config", "cursor", "projects")
8942
8942
  ];
8943
- for (const cursorDir of cursorDirs) {
8944
- if (existsSync2(cursorDir)) {
8945
- const cursorSessions = collectCursorSessions(cursorDir, since);
8946
- sessions.push(...cursorSessions);
8947
- break;
8943
+ let cursorSessions = [];
8944
+ for (const cursorProjectsDir of cursorProjectsDirs) {
8945
+ if (existsSync2(cursorProjectsDir)) {
8946
+ cursorSessions = collectCursorProjectSessions(cursorProjectsDir, since, targetProjectDir);
8947
+ if (cursorSessions.length > 0)
8948
+ break;
8949
+ }
8950
+ }
8951
+ if (cursorSessions.length === 0) {
8952
+ const cursorDirs = [
8953
+ join2(homedir2(), ".cursor", "sessions"),
8954
+ join2(homedir2(), "Library", "Application Support", "Cursor", "sessions"),
8955
+ join2(homedir2(), ".config", "cursor", "sessions")
8956
+ ];
8957
+ for (const cursorDir of cursorDirs) {
8958
+ if (existsSync2(cursorDir)) {
8959
+ cursorSessions = collectCursorSessions(cursorDir, since, targetProjectDir);
8960
+ if (cursorSessions.length > 0)
8961
+ break;
8962
+ }
8948
8963
  }
8949
8964
  }
8965
+ sessions.push(...cursorSessions);
8950
8966
  const windsurfDirs = [
8951
8967
  join2(homedir2(), ".codeium", "chat_history"),
8952
8968
  join2(homedir2(), ".windsurf", "sessions"),
@@ -9549,7 +9565,7 @@ function detectOpenCodePatterns(messages, partStorageDir) {
9549
9565
  }
9550
9566
  return patterns;
9551
9567
  }
9552
- function collectCursorSessions(sessionsDir, since) {
9568
+ function collectCursorSessions(sessionsDir, since, targetProjectDir) {
9553
9569
  const sessions = [];
9554
9570
  try {
9555
9571
  const files = readdirSync(sessionsDir).filter((f3) => f3.endsWith(".json") || f3.endsWith(".jsonl"));
@@ -9572,6 +9588,10 @@ function collectCursorSessions(sessionsDir, since) {
9572
9588
  }
9573
9589
  if (data.length === 0)
9574
9590
  continue;
9591
+ const sessionProjectDir = getCursorSessionProjectDir(data);
9592
+ if (!sessionProjectDir || !isPathMatch(sessionProjectDir, targetProjectDir)) {
9593
+ continue;
9594
+ }
9575
9595
  const firstEntry = data[0];
9576
9596
  const lastEntry = data[data.length - 1];
9577
9597
  const startTime = firstEntry?.timestamp ? new Date(firstEntry.timestamp) : new Date;
@@ -9613,6 +9633,152 @@ function collectCursorSessions(sessionsDir, since) {
9613
9633
  } catch {}
9614
9634
  return sessions;
9615
9635
  }
9636
+ function collectCursorProjectSessions(projectsDir, since, targetProjectDir) {
9637
+ const sessions = [];
9638
+ try {
9639
+ const entries = readdirSync(projectsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
9640
+ const targetKey = normalizeCursorProjectKey(encodeCursorProjectKey(targetProjectDir));
9641
+ const matchingProjects = entries.filter((name) => {
9642
+ const normalized = normalizeCursorProjectKey(name);
9643
+ return normalized === targetKey || normalized.startsWith(targetKey + "-") || targetKey.startsWith(normalized + "-");
9644
+ });
9645
+ for (const projectName of matchingProjects) {
9646
+ const transcriptsDir = join2(projectsDir, projectName, "agent-transcripts");
9647
+ if (!existsSync2(transcriptsDir))
9648
+ continue;
9649
+ const files = readdirSync(transcriptsDir).filter((f3) => f3.endsWith(".json")).map((f3) => join2(transcriptsDir, f3));
9650
+ for (const filePath of files) {
9651
+ const session = parseCursorTranscript(filePath, since);
9652
+ if (session)
9653
+ sessions.push(session);
9654
+ }
9655
+ }
9656
+ } catch {}
9657
+ return sessions;
9658
+ }
9659
+ function parseCursorTranscript(filePath, since) {
9660
+ try {
9661
+ const cached = sessionCache?.get(filePath);
9662
+ if (cached) {
9663
+ const cachedEndedAt = cached.endedAt ? new Date(cached.endedAt) : null;
9664
+ if (cachedEndedAt && cachedEndedAt >= since) {
9665
+ return cached;
9666
+ }
9667
+ return null;
9668
+ }
9669
+ const stats = statSync2(filePath);
9670
+ const endedAt = new Date(stats.mtimeMs);
9671
+ if (endedAt < since)
9672
+ return null;
9673
+ const startedAt = stats.birthtimeMs && stats.birthtimeMs > 0 ? new Date(stats.birthtimeMs) : endedAt;
9674
+ const content = readFileSync2(filePath, "utf-8");
9675
+ const data = JSON.parse(content);
9676
+ if (!Array.isArray(data) || data.length === 0)
9677
+ return null;
9678
+ let messageCount = 0;
9679
+ let toolCalls = 0;
9680
+ const toolNames = new Set;
9681
+ for (const entry of data) {
9682
+ if (entry?.role)
9683
+ messageCount += 1;
9684
+ if (Array.isArray(entry?.toolCalls)) {
9685
+ toolCalls += entry.toolCalls.length;
9686
+ for (const tool of entry.toolCalls) {
9687
+ if (tool?.toolName)
9688
+ toolNames.add(tool.toolName);
9689
+ }
9690
+ }
9691
+ if (entry?.toolResult?.toolName) {
9692
+ toolNames.add(entry.toolResult.toolName);
9693
+ }
9694
+ }
9695
+ const durationMinutes = Math.max(1, Math.round((endedAt.getTime() - startedAt.getTime()) / 60000));
9696
+ const session = {
9697
+ agent: "cursor",
9698
+ sessionId: basename(filePath, ".json").slice(0, 8),
9699
+ startedAt: startedAt.toISOString(),
9700
+ endedAt: endedAt.toISOString(),
9701
+ durationMinutes,
9702
+ inputTokens: 0,
9703
+ outputTokens: 0,
9704
+ cachedTokens: 0,
9705
+ totalTokens: 0,
9706
+ costUsd: 0,
9707
+ model: "cursor",
9708
+ toolCalls,
9709
+ messageCount,
9710
+ toolNames: Array.from(toolNames),
9711
+ patterns: [{ type: "smooth_flow", count: 1, estimatedWastedTokens: 0, messageIndices: [] }]
9712
+ };
9713
+ sessionCache?.set(filePath, session);
9714
+ return session;
9715
+ } catch {
9716
+ return null;
9717
+ }
9718
+ }
9719
+ function encodeCursorProjectKey(projectDir) {
9720
+ const normalized = resolve(projectDir);
9721
+ const trimmed2 = normalized.replace(/^[\\/]+/, "");
9722
+ return trimmed2.replace(/[:\\/]/g, "-");
9723
+ }
9724
+ function normalizeCursorProjectKey(projectKey) {
9725
+ return process.platform === "win32" ? projectKey.toLowerCase() : projectKey;
9726
+ }
9727
+ function isPathMatch(pathA, pathB) {
9728
+ const resolvedA = resolve(pathA);
9729
+ const resolvedB = resolve(pathB);
9730
+ return resolvedA === resolvedB || resolvedA.startsWith(resolvedB + sep2) || resolvedB.startsWith(resolvedA + sep2);
9731
+ }
9732
+ function getCursorSessionProjectDir(entries) {
9733
+ for (const entry of entries) {
9734
+ const pathCandidate = extractCursorProjectPath(entry);
9735
+ if (pathCandidate)
9736
+ return pathCandidate;
9737
+ }
9738
+ return null;
9739
+ }
9740
+ function extractCursorProjectPath(entry) {
9741
+ const candidates = [
9742
+ entry.cwd,
9743
+ entry.projectPath,
9744
+ entry.workspacePath,
9745
+ entry.workspaceRoot,
9746
+ entry.rootPath,
9747
+ entry.repositoryPath,
9748
+ entry.folder,
9749
+ entry.workspace
9750
+ ];
9751
+ for (const candidate of candidates) {
9752
+ if (typeof candidate === "string") {
9753
+ const normalized = normalizeFileUri(candidate);
9754
+ if (normalized)
9755
+ return normalized;
9756
+ }
9757
+ if (candidate && typeof candidate === "object") {
9758
+ const record = candidate;
9759
+ const nestedCandidates = [record.path, record.uri, record.folder, record.workspace, record.root];
9760
+ for (const nested of nestedCandidates) {
9761
+ if (typeof nested === "string") {
9762
+ const normalized = normalizeFileUri(nested);
9763
+ if (normalized)
9764
+ return normalized;
9765
+ }
9766
+ }
9767
+ }
9768
+ }
9769
+ return null;
9770
+ }
9771
+ function normalizeFileUri(value) {
9772
+ if (!value)
9773
+ return null;
9774
+ if (!value.startsWith("file://"))
9775
+ return value;
9776
+ let decoded = decodeURIComponent(value.replace(/^file:\/\//, ""));
9777
+ if (/^\/[A-Za-z]:\//.test(decoded)) {
9778
+ decoded = decoded.slice(1);
9779
+ }
9780
+ return decoded;
9781
+ }
9616
9782
  function collectWindsurfSessions(sessionsDir, since) {
9617
9783
  const sessions = [];
9618
9784
  try {
@@ -16641,54 +16807,88 @@ var scanCommand = defineCommand2({
16641
16807
  } else if (sdlc.overall.summary.includes("optimizations available")) {
16642
16808
  sdlc.overall.summary = sdlc.overall.summary.replace(/\d+ optimizations available\./, "All recommended tools installed.");
16643
16809
  }
16644
- const phaseChoice = await consola.prompt("Which SDLC phase do you want to optimize?", {
16645
- type: "select",
16646
- options: [
16647
- { value: "requirements", label: "Requirements & Context" },
16648
- { value: "planning", label: "Planning & Design" },
16649
- { value: "implementation", label: "Implementation" },
16650
- { value: "review", label: "Review & Testing" }
16651
- ]
16652
- });
16653
- const selectedPhase = phaseChoice === "planning" ? sdlc.planning : phaseChoice === "implementation" ? sdlc.implementation : phaseChoice === "review" ? sdlc.review : sdlc.requirements;
16654
- const sdlcLines = formatSDLCPhaseForTerminal(selectedPhase, sdlc.overall);
16655
- for (const line of sdlcLines) {
16656
- console.log(line);
16657
- }
16658
- let phaseRecs = [];
16659
- for (const rec of selectedPhase.recommendations) {
16660
- if (rec.installCommand) {
16661
- phaseRecs.push(phaseRecToInstallable(rec));
16810
+ const phaseOptions = [
16811
+ { value: "requirements", label: "Requirements & Context" },
16812
+ { value: "planning", label: "Planning & Design" },
16813
+ { value: "implementation", label: "Implementation" },
16814
+ { value: "review", label: "Review & Testing" },
16815
+ { value: "exit", label: "Exit" }
16816
+ ];
16817
+ while (true) {
16818
+ const phaseChoice = await consola.prompt("Which SDLC phase do you want to optimize?", {
16819
+ type: "select",
16820
+ options: phaseOptions
16821
+ });
16822
+ if (typeof phaseChoice === "symbol" || phaseChoice === "exit") {
16823
+ break;
16662
16824
  }
16663
- }
16664
- phaseRecs = filterAlreadyInstalled(phaseRecs, installedStatus);
16665
- if (phaseRecs.length > 0) {
16666
- console.log();
16667
- const optionLabels = phaseRecs.map((rec) => `${rec.name} - ${rec.description}`);
16668
- const selected = await consola.prompt(`${phaseRecs.length} optimizations available for ${selectedPhase.name}. Select to install (space to select, enter to confirm):`, {
16669
- type: "multiselect",
16670
- options: optionLabels,
16671
- required: false
16825
+ const selectedPhase = phaseChoice === "planning" ? sdlc.planning : phaseChoice === "implementation" ? sdlc.implementation : phaseChoice === "review" ? sdlc.review : sdlc.requirements;
16826
+ const sdlcLines = formatSDLCPhaseForTerminal(selectedPhase, sdlc.overall);
16827
+ for (const line of sdlcLines) {
16828
+ console.log(line);
16829
+ }
16830
+ let phaseRecs = [];
16831
+ for (const rec of selectedPhase.recommendations) {
16832
+ if (rec.installCommand) {
16833
+ phaseRecs.push(phaseRecToInstallable(rec));
16834
+ }
16835
+ }
16836
+ phaseRecs = filterAlreadyInstalled(phaseRecs, installedStatus);
16837
+ const nextAction = await consola.prompt("What would you like to do next?", {
16838
+ type: "select",
16839
+ options: phaseRecs.length > 0 ? [
16840
+ { value: "install", label: "Install optimizations" },
16841
+ { value: "change", label: "Choose another phase" },
16842
+ { value: "exit", label: "Exit" }
16843
+ ] : [
16844
+ { value: "change", label: "Choose another phase" },
16845
+ { value: "exit", label: "Exit" }
16846
+ ]
16672
16847
  });
16673
- if (selected && typeof selected !== "symbol" && Array.isArray(selected) && selected.length > 0) {
16674
- const toInstall = phaseRecs.filter((rec) => selected.some((s2) => s2.startsWith(rec.name))).map((r3) => ({ ...r3, selected: true }));
16675
- if (toInstall.length > 0) {
16676
- console.log();
16677
- console.log(` ${colors2.bold("Installing...")}${colors2.dim("")}`);
16678
- console.log();
16679
- const results = await installOptimizations(toInstall, projectDir, {});
16680
- for (const result of results) {
16681
- if (result.success) {
16682
- console.log(` ${colors2.success("✓")} ${result.name}: ${result.message}`);
16683
- } else {
16684
- console.log(` ${colors2.error("✗")} ${result.name}: ${result.error || result.message}`);
16848
+ if (typeof nextAction === "symbol" || nextAction === "exit") {
16849
+ break;
16850
+ }
16851
+ if (nextAction === "change") {
16852
+ continue;
16853
+ }
16854
+ if (phaseRecs.length > 0) {
16855
+ console.log();
16856
+ const optionLabels = phaseRecs.map((rec) => `${rec.name} - ${rec.description}`);
16857
+ const selected = await consola.prompt(`${phaseRecs.length} optimizations available for ${selectedPhase.name}. Select to install (space to select, enter to confirm):`, {
16858
+ type: "multiselect",
16859
+ options: optionLabels,
16860
+ required: false
16861
+ });
16862
+ if (selected && typeof selected !== "symbol" && Array.isArray(selected) && selected.length > 0) {
16863
+ const toInstall = phaseRecs.filter((rec) => selected.some((s2) => s2.startsWith(rec.name))).map((r3) => ({ ...r3, selected: true }));
16864
+ if (toInstall.length > 0) {
16865
+ console.log();
16866
+ console.log(` ${colors2.bold("Installing...")}${colors2.dim("")}`);
16867
+ console.log();
16868
+ const results = await installOptimizations(toInstall, projectDir, {});
16869
+ for (const result of results) {
16870
+ if (result.success) {
16871
+ console.log(` ${colors2.success("✓")} ${result.name}: ${result.message}`);
16872
+ } else {
16873
+ console.log(` ${colors2.error("✗")} ${result.name}: ${result.error || result.message}`);
16874
+ }
16685
16875
  }
16876
+ console.log();
16686
16877
  }
16878
+ } else {
16879
+ console.log(` ${colors2.dim("Skipped.")}`);
16687
16880
  console.log();
16688
16881
  }
16689
- } else {
16690
- console.log(` ${colors2.dim("Skipped.")}`);
16691
- console.log();
16882
+ }
16883
+ const continueChoice = await consola.prompt("Continue with another phase?", {
16884
+ type: "select",
16885
+ options: [
16886
+ { value: "change", label: "Choose another phase" },
16887
+ { value: "exit", label: "Exit" }
16888
+ ]
16889
+ });
16890
+ if (typeof continueChoice === "symbol" || continueChoice === "exit") {
16891
+ break;
16692
16892
  }
16693
16893
  }
16694
16894
  }
@@ -17085,7 +17285,7 @@ init_dist();
17085
17285
  init_client();
17086
17286
 
17087
17287
  // src/collectors/report-data.ts
17088
- import { existsSync as existsSync11, readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync2 } from "node:fs";
17288
+ import { existsSync as existsSync11, readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync3 } from "node:fs";
17089
17289
  import { homedir as homedir9 } from "node:os";
17090
17290
  import { join as join11, basename as basename3 } from "node:path";
17091
17291
  async function collectReportData(projectDir, since, until = new Date, harness) {
@@ -17192,7 +17392,7 @@ function collectClaudeCodeSessions(claudeDir, since, until, projectDir) {
17192
17392
  const transcriptFiles = readdirSync5(transcriptsDir).filter((f3) => f3.endsWith(".jsonl"));
17193
17393
  for (const file of transcriptFiles) {
17194
17394
  const filePath = join11(transcriptsDir, file);
17195
- const stat = statSync2(filePath);
17395
+ const stat = statSync3(filePath);
17196
17396
  if (stat.mtime < since || stat.mtime > until)
17197
17397
  continue;
17198
17398
  try {
@@ -17208,7 +17408,7 @@ function collectClaudeCodeSessions(claudeDir, since, until, projectDir) {
17208
17408
  const projectFiles = readdirSync5(projectSessionDir).filter((f3) => f3.endsWith(".jsonl"));
17209
17409
  for (const file of projectFiles) {
17210
17410
  const filePath = join11(projectSessionDir, file);
17211
- const stat = statSync2(filePath);
17411
+ const stat = statSync3(filePath);
17212
17412
  if (stat.mtime < since || stat.mtime > until)
17213
17413
  continue;
17214
17414
  try {
@@ -23819,6 +24019,16 @@ var BOLD = "\x1B[1m";
23819
24019
  var RESET = "\x1B[0m";
23820
24020
  var MAGENTA = "\x1B[35m";
23821
24021
  var CHANGELOG = [
24022
+ {
24023
+ version: "0.6.1",
24024
+ date: "2026-02-16",
24025
+ title: "Phase Navigation",
24026
+ highlights: [
24027
+ "nb scan now lets you switch phases or exit anytime",
24028
+ "Clear next-step prompts for installs or switching",
24029
+ "Cleaner interactive flow for phase optimizations"
24030
+ ]
24031
+ },
23822
24032
  {
23823
24033
  version: "0.6.0",
23824
24034
  date: "2026-02-15",
@@ -24311,7 +24521,7 @@ function showFullChangelog() {
24311
24521
  // package.json
24312
24522
  var package_default = {
24313
24523
  name: "nairon-bench",
24314
- version: "0.6.0",
24524
+ version: "0.6.1",
24315
24525
  description: "AI workflow benchmarking CLI",
24316
24526
  type: "module",
24317
24527
  bin: {
@@ -26838,7 +27048,7 @@ function formatBytes(bytes) {
26838
27048
  // package.json
26839
27049
  var package_default2 = {
26840
27050
  name: "nairon-bench",
26841
- version: "0.6.0",
27051
+ version: "0.6.1",
26842
27052
  description: "AI workflow benchmarking CLI",
26843
27053
  type: "module",
26844
27054
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nairon-bench",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "AI workflow benchmarking CLI",
5
5
  "type": "module",
6
6
  "bin": {