nairon-bench 0.3.12 → 0.3.14

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 +2302 -1143
  2. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -4447,8 +4447,8 @@ function defineCommand2(def) {
4447
4447
 
4448
4448
  // src/commands/scan.ts
4449
4449
  init_dist();
4450
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, readFileSync as readFileSync8 } from "node:fs";
4451
- import { join as join8 } from "node:path";
4450
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, readFileSync as readFileSync9 } from "node:fs";
4451
+ import { join as join9 } from "node:path";
4452
4452
 
4453
4453
  // ../../node_modules/simple-git/dist/esm/index.js
4454
4454
  var import_file_exists = __toESM(require_dist(), 1);
@@ -10767,7 +10767,7 @@ function getTechStackSpecificRecommendations(projectContext) {
10767
10767
  description: "Vercel's React and Next.js patterns for your stack",
10768
10768
  impact: "medium",
10769
10769
  type: "skill",
10770
- installCommand: "npx skills add vercel/react-best-practices"
10770
+ installCommand: "npx skills add vercel-labs/agent-skills@vercel-react-best-practices --yes"
10771
10771
  });
10772
10772
  }
10773
10773
  if (database2?.includes("convex")) {
@@ -11458,652 +11458,246 @@ function renderBar(value, width) {
11458
11458
  return "█".repeat(filled) + "░".repeat(empty);
11459
11459
  }
11460
11460
 
11461
- // src/lib/optimization-installer.ts
11462
- import { execSync as execSync2, spawnSync } from "node:child_process";
11463
- import { existsSync as existsSync7, writeFileSync as writeFileSync3, readFileSync as readFileSync7, mkdirSync as mkdirSync3, readdirSync as readdirSync4 } from "node:fs";
11464
- import { join as join7 } from "node:path";
11461
+ // src/lib/frustration-detector.ts
11462
+ import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync as fsReaddirSync } from "node:fs";
11465
11463
  import { homedir as homedir6 } from "node:os";
11466
- async function installOptimizations(optimizations, projectDir, options = {}) {
11467
- const results = [];
11468
- for (const opt of optimizations) {
11469
- if (!opt.selected)
11470
- continue;
11471
- try {
11472
- const result = await installSingleOptimization(opt, projectDir, options);
11473
- results.push(result);
11474
- } catch (err) {
11475
- results.push({
11476
- id: opt.id,
11477
- name: opt.name,
11478
- success: false,
11479
- message: "Installation failed",
11480
- error: err instanceof Error ? err.message : String(err)
11481
- });
11482
- }
11483
- }
11484
- return results;
11485
- }
11486
- async function installSingleOptimization(opt, projectDir, options) {
11487
- if (options.dryRun) {
11488
- return {
11489
- id: opt.id,
11490
- name: opt.name,
11491
- success: true,
11492
- message: `[DRY RUN] Would install: ${opt.installCommand || opt.configPath || "unknown"}`
11493
- };
11494
- }
11495
- switch (opt.type) {
11496
- case "mcp":
11497
- return installMCP(opt, projectDir, options.agent);
11498
- case "skill":
11499
- return installSkill(opt);
11500
- case "config":
11501
- return installConfig(opt, projectDir);
11502
- case "hook":
11503
- return installHook(opt, projectDir);
11504
- default:
11505
- return {
11506
- id: opt.id,
11507
- name: opt.name,
11508
- success: false,
11509
- message: "Unknown optimization type"
11510
- };
11511
- }
11512
- }
11513
- function installMCP(opt, projectDir, agent) {
11514
- if (opt.installCommand?.startsWith("claude mcp add")) {
11515
- try {
11516
- execSync2(opt.installCommand, {
11517
- encoding: "utf-8",
11518
- stdio: "pipe",
11519
- timeout: 60000
11520
- });
11521
- return {
11522
- id: opt.id,
11523
- name: opt.name,
11524
- success: true,
11525
- message: `Installed via: ${opt.installCommand}`
11526
- };
11527
- } catch (err) {}
11528
- }
11529
- if (opt.id === "beads" || opt.name.toLowerCase().includes("beads")) {
11530
- try {
11531
- execSync2("bun add -g beads", { encoding: "utf-8", stdio: "pipe", timeout: 60000 });
11532
- execSync2("bd init", { cwd: projectDir, encoding: "utf-8", stdio: "pipe", timeout: 30000 });
11533
- return {
11534
- id: opt.id,
11535
- name: opt.name,
11536
- success: true,
11537
- message: "Installed Beads globally and initialized in project"
11538
- };
11539
- } catch (err) {
11540
- return {
11541
- id: opt.id,
11542
- name: opt.name,
11543
- success: false,
11544
- message: "Failed to install Beads",
11545
- error: err instanceof Error ? err.message : String(err)
11546
- };
11547
- }
11464
+ import { join as join7 } from "node:path";
11465
+
11466
+ // ../../node_modules/ora/index.js
11467
+ import process8 from "node:process";
11468
+ import { stripVTControlCharacters } from "node:util";
11469
+
11470
+ // ../../node_modules/chalk/source/vendor/ansi-styles/index.js
11471
+ var ANSI_BACKGROUND_OFFSET = 10;
11472
+ var wrapAnsi16 = (offset = 0) => (code2) => `\x1B[${code2 + offset}m`;
11473
+ var wrapAnsi256 = (offset = 0) => (code2) => `\x1B[${38 + offset};5;${code2}m`;
11474
+ var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
11475
+ var styles = {
11476
+ modifier: {
11477
+ reset: [0, 0],
11478
+ bold: [1, 22],
11479
+ dim: [2, 22],
11480
+ italic: [3, 23],
11481
+ underline: [4, 24],
11482
+ overline: [53, 55],
11483
+ inverse: [7, 27],
11484
+ hidden: [8, 28],
11485
+ strikethrough: [9, 29]
11486
+ },
11487
+ color: {
11488
+ black: [30, 39],
11489
+ red: [31, 39],
11490
+ green: [32, 39],
11491
+ yellow: [33, 39],
11492
+ blue: [34, 39],
11493
+ magenta: [35, 39],
11494
+ cyan: [36, 39],
11495
+ white: [37, 39],
11496
+ blackBright: [90, 39],
11497
+ gray: [90, 39],
11498
+ grey: [90, 39],
11499
+ redBright: [91, 39],
11500
+ greenBright: [92, 39],
11501
+ yellowBright: [93, 39],
11502
+ blueBright: [94, 39],
11503
+ magentaBright: [95, 39],
11504
+ cyanBright: [96, 39],
11505
+ whiteBright: [97, 39]
11506
+ },
11507
+ bgColor: {
11508
+ bgBlack: [40, 49],
11509
+ bgRed: [41, 49],
11510
+ bgGreen: [42, 49],
11511
+ bgYellow: [43, 49],
11512
+ bgBlue: [44, 49],
11513
+ bgMagenta: [45, 49],
11514
+ bgCyan: [46, 49],
11515
+ bgWhite: [47, 49],
11516
+ bgBlackBright: [100, 49],
11517
+ bgGray: [100, 49],
11518
+ bgGrey: [100, 49],
11519
+ bgRedBright: [101, 49],
11520
+ bgGreenBright: [102, 49],
11521
+ bgYellowBright: [103, 49],
11522
+ bgBlueBright: [104, 49],
11523
+ bgMagentaBright: [105, 49],
11524
+ bgCyanBright: [106, 49],
11525
+ bgWhiteBright: [107, 49]
11548
11526
  }
11549
- if (opt.installCommand) {
11550
- try {
11551
- execSync2(opt.installCommand, {
11552
- encoding: "utf-8",
11553
- stdio: "pipe",
11554
- timeout: 60000
11555
- });
11556
- return {
11557
- id: opt.id,
11558
- name: opt.name,
11559
- success: true,
11560
- message: `Installed via: ${opt.installCommand}`
11561
- };
11562
- } catch (err) {
11563
- return {
11564
- id: opt.id,
11565
- name: opt.name,
11566
- success: false,
11567
- message: "Failed to install MCP",
11568
- error: err instanceof Error ? err.message : String(err)
11527
+ };
11528
+ var modifierNames = Object.keys(styles.modifier);
11529
+ var foregroundColorNames = Object.keys(styles.color);
11530
+ var backgroundColorNames = Object.keys(styles.bgColor);
11531
+ var colorNames = [...foregroundColorNames, ...backgroundColorNames];
11532
+ function assembleStyles() {
11533
+ const codes = new Map;
11534
+ for (const [groupName, group] of Object.entries(styles)) {
11535
+ for (const [styleName, style] of Object.entries(group)) {
11536
+ styles[styleName] = {
11537
+ open: `\x1B[${style[0]}m`,
11538
+ close: `\x1B[${style[1]}m`
11569
11539
  };
11540
+ group[styleName] = styles[styleName];
11541
+ codes.set(style[0], style[1]);
11570
11542
  }
11571
- }
11572
- return {
11573
- id: opt.id,
11574
- name: opt.name,
11575
- success: false,
11576
- message: "No install command available"
11577
- };
11578
- }
11579
- function installSkill(opt) {
11580
- if (!opt.installCommand) {
11581
- return {
11582
- id: opt.id,
11583
- name: opt.name,
11584
- success: false,
11585
- message: "No install command for skill"
11586
- };
11587
- }
11588
- try {
11589
- const skillPath = opt.installCommand.replace(/^npx\s+/, "").replace(/^bunx\s+/, "").replace(/^skills\s+add\s+/, "").trim();
11590
- let result = spawnSync("bunx", ["--bun", "skills", "add", skillPath], {
11591
- encoding: "utf-8",
11592
- stdio: "pipe",
11593
- timeout: 120000
11543
+ Object.defineProperty(styles, groupName, {
11544
+ value: group,
11545
+ enumerable: false
11594
11546
  });
11595
- if (result.status !== 0) {
11596
- result = spawnSync("npx", ["-y", "skills", "add", skillPath], {
11597
- encoding: "utf-8",
11598
- stdio: "pipe",
11599
- timeout: 120000
11600
- });
11601
- }
11602
- if (result.status === 0) {
11603
- return {
11604
- id: opt.id,
11605
- name: opt.name,
11606
- success: true,
11607
- message: `Skill installed successfully`
11608
- };
11609
- } else {
11610
- const output = (result.stderr || result.stdout || "").toLowerCase();
11611
- if (output.includes("already exists") || output.includes("already installed")) {
11612
- return {
11613
- id: opt.id,
11614
- name: opt.name,
11615
- success: true,
11616
- message: `Skill already installed`
11617
- };
11618
- }
11619
- return {
11620
- id: opt.id,
11621
- name: opt.name,
11622
- success: false,
11623
- message: "Skill installation failed",
11624
- error: result.stderr || result.stdout || "Unknown error"
11625
- };
11626
- }
11627
- } catch (err) {
11628
- return {
11629
- id: opt.id,
11630
- name: opt.name,
11631
- success: false,
11632
- message: "Failed to install skill",
11633
- error: err instanceof Error ? err.message : String(err)
11634
- };
11635
11547
  }
11636
- }
11637
- function installConfig(opt, projectDir) {
11638
- if (!opt.configPath || !opt.configContent) {
11639
- return {
11640
- id: opt.id,
11641
- name: opt.name,
11642
- success: false,
11643
- message: "No config path or content provided"
11644
- };
11645
- }
11646
- try {
11647
- const fullPath = opt.configPath.startsWith("/") || opt.configPath.startsWith("~") ? opt.configPath.replace("~", homedir6()) : join7(projectDir, opt.configPath);
11648
- if (existsSync7(fullPath)) {
11649
- return {
11650
- id: opt.id,
11651
- name: opt.name,
11652
- success: true,
11653
- message: `Config already exists: ${opt.configPath}`
11654
- };
11655
- }
11656
- const parentDir = fullPath.substring(0, fullPath.lastIndexOf("/"));
11657
- if (parentDir && !existsSync7(parentDir)) {
11658
- mkdirSync3(parentDir, { recursive: true });
11548
+ Object.defineProperty(styles, "codes", {
11549
+ value: codes,
11550
+ enumerable: false
11551
+ });
11552
+ styles.color.close = "\x1B[39m";
11553
+ styles.bgColor.close = "\x1B[49m";
11554
+ styles.color.ansi = wrapAnsi16();
11555
+ styles.color.ansi256 = wrapAnsi256();
11556
+ styles.color.ansi16m = wrapAnsi16m();
11557
+ styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
11558
+ styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
11559
+ styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
11560
+ Object.defineProperties(styles, {
11561
+ rgbToAnsi256: {
11562
+ value(red, green, blue) {
11563
+ if (red === green && green === blue) {
11564
+ if (red < 8) {
11565
+ return 16;
11566
+ }
11567
+ if (red > 248) {
11568
+ return 231;
11569
+ }
11570
+ return Math.round((red - 8) / 247 * 24) + 232;
11571
+ }
11572
+ return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
11573
+ },
11574
+ enumerable: false
11575
+ },
11576
+ hexToRgb: {
11577
+ value(hex) {
11578
+ const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
11579
+ if (!matches) {
11580
+ return [0, 0, 0];
11581
+ }
11582
+ let [colorString] = matches;
11583
+ if (colorString.length === 3) {
11584
+ colorString = [...colorString].map((character) => character + character).join("");
11585
+ }
11586
+ const integer = Number.parseInt(colorString, 16);
11587
+ return [
11588
+ integer >> 16 & 255,
11589
+ integer >> 8 & 255,
11590
+ integer & 255
11591
+ ];
11592
+ },
11593
+ enumerable: false
11594
+ },
11595
+ hexToAnsi256: {
11596
+ value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
11597
+ enumerable: false
11598
+ },
11599
+ ansi256ToAnsi: {
11600
+ value(code2) {
11601
+ if (code2 < 8) {
11602
+ return 30 + code2;
11603
+ }
11604
+ if (code2 < 16) {
11605
+ return 90 + (code2 - 8);
11606
+ }
11607
+ let red;
11608
+ let green;
11609
+ let blue;
11610
+ if (code2 >= 232) {
11611
+ red = ((code2 - 232) * 10 + 8) / 255;
11612
+ green = red;
11613
+ blue = red;
11614
+ } else {
11615
+ code2 -= 16;
11616
+ const remainder = code2 % 36;
11617
+ red = Math.floor(code2 / 36) / 5;
11618
+ green = Math.floor(remainder / 6) / 5;
11619
+ blue = remainder % 6 / 5;
11620
+ }
11621
+ const value = Math.max(red, green, blue) * 2;
11622
+ if (value === 0) {
11623
+ return 30;
11624
+ }
11625
+ let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
11626
+ if (value === 2) {
11627
+ result += 60;
11628
+ }
11629
+ return result;
11630
+ },
11631
+ enumerable: false
11632
+ },
11633
+ rgbToAnsi: {
11634
+ value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
11635
+ enumerable: false
11636
+ },
11637
+ hexToAnsi: {
11638
+ value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
11639
+ enumerable: false
11659
11640
  }
11660
- writeFileSync3(fullPath, opt.configContent);
11661
- return {
11662
- id: opt.id,
11663
- name: opt.name,
11664
- success: true,
11665
- message: `Created: ${opt.configPath}`
11666
- };
11667
- } catch (err) {
11668
- return {
11669
- id: opt.id,
11670
- name: opt.name,
11671
- success: false,
11672
- message: "Failed to create config",
11673
- error: err instanceof Error ? err.message : String(err)
11674
- };
11675
- }
11641
+ });
11642
+ return styles;
11676
11643
  }
11677
- function installHook(opt, projectDir) {
11678
- const agentsMdPath = join7(projectDir, "AGENTS.md");
11679
- try {
11680
- let content = "";
11681
- if (existsSync7(agentsMdPath)) {
11682
- content = readFileSync7(agentsMdPath, "utf-8");
11683
- } else {
11684
- content = `# Agent Instructions
11644
+ var ansiStyles = assembleStyles();
11645
+ var ansi_styles_default = ansiStyles;
11685
11646
 
11686
- `;
11647
+ // ../../node_modules/chalk/source/vendor/supports-color/index.js
11648
+ import process2 from "node:process";
11649
+ import os from "node:os";
11650
+ import tty2 from "node:tty";
11651
+ function hasFlag(flag, argv2 = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
11652
+ const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
11653
+ const position = argv2.indexOf(prefix + flag);
11654
+ const terminatorPosition = argv2.indexOf("--");
11655
+ return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
11656
+ }
11657
+ var { env: env2 } = process2;
11658
+ var flagForceColor;
11659
+ if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
11660
+ flagForceColor = 0;
11661
+ } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
11662
+ flagForceColor = 1;
11663
+ }
11664
+ function envForceColor() {
11665
+ if ("FORCE_COLOR" in env2) {
11666
+ if (env2.FORCE_COLOR === "true") {
11667
+ return 1;
11687
11668
  }
11688
- const hookName = opt.name.toLowerCase();
11689
- if (content.toLowerCase().includes(hookName)) {
11690
- return {
11691
- id: opt.id,
11692
- name: opt.name,
11693
- success: true,
11694
- message: "Hook already configured in AGENTS.md"
11695
- };
11669
+ if (env2.FORCE_COLOR === "false") {
11670
+ return 0;
11696
11671
  }
11697
- const hookSection = opt.configContent || `
11698
- ## ${opt.name}
11699
- ${opt.description}
11700
- `;
11701
- content += `
11702
- ` + hookSection;
11703
- writeFileSync3(agentsMdPath, content);
11704
- return {
11705
- id: opt.id,
11706
- name: opt.name,
11707
- success: true,
11708
- message: "Added hook to AGENTS.md"
11709
- };
11710
- } catch (err) {
11711
- return {
11712
- id: opt.id,
11713
- name: opt.name,
11714
- success: false,
11715
- message: "Failed to add hook",
11716
- error: err instanceof Error ? err.message : String(err)
11717
- };
11672
+ return env2.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env2.FORCE_COLOR, 10), 3);
11718
11673
  }
11719
11674
  }
11720
- function phaseRecToInstallable(rec) {
11675
+ function translateLevel(level) {
11676
+ if (level === 0) {
11677
+ return false;
11678
+ }
11721
11679
  return {
11722
- id: rec.title.toLowerCase().replace(/\s+/g, "-"),
11723
- name: rec.title,
11724
- type: rec.type,
11725
- description: rec.description,
11726
- installCommand: rec.installCommand,
11727
- selected: false
11680
+ level,
11681
+ hasBasic: true,
11682
+ has256: level >= 2,
11683
+ has16m: level >= 3
11728
11684
  };
11729
11685
  }
11730
- function checkInstalledStatus(projectDir) {
11731
- const home = homedir6();
11732
- const status = {
11733
- context7: false,
11734
- supermemory: false,
11735
- nia: false,
11736
- beads: false,
11737
- skills: []
11738
- };
11739
- const mcpConfigPaths = [
11740
- join7(home, ".claude.json"),
11741
- join7(home, ".claude", "claude_desktop_config.json"),
11742
- join7(home, ".claude", "settings.json"),
11743
- join7(home, ".claude", "settings.local.json"),
11744
- join7(projectDir, ".mcp.json"),
11745
- join7(projectDir, ".claude", "settings.local.json")
11746
- ];
11747
- for (const configPath of mcpConfigPaths) {
11748
- if (existsSync7(configPath)) {
11749
- try {
11750
- const config = JSON.parse(readFileSync7(configPath, "utf-8"));
11751
- const mcpServers = config.mcpServers || config.mcp_servers || {};
11752
- for (const name of Object.keys(mcpServers)) {
11753
- const mcpName = name.toLowerCase();
11754
- if (mcpName.includes("context7") || mcpName === "c7") {
11755
- status.context7 = true;
11756
- }
11757
- if (mcpName.includes("supermemory") || mcpName.includes("memory")) {
11758
- status.supermemory = true;
11759
- }
11760
- if (mcpName.includes("nia")) {
11761
- status.nia = true;
11762
- }
11763
- }
11764
- } catch {}
11765
- }
11686
+ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
11687
+ const noFlagForceColor = envForceColor();
11688
+ if (noFlagForceColor !== undefined) {
11689
+ flagForceColor = noFlagForceColor;
11766
11690
  }
11767
- status.beads = existsSync7(join7(projectDir, ".beads"));
11768
- const skillsDirs = [
11769
- join7(home, ".claude", "skills"),
11770
- join7(home, ".config", "opencode", "skills"),
11771
- join7(home, ".agents", "skills"),
11772
- join7(projectDir, ".opencode", "skill")
11773
- ];
11774
- for (const dir of skillsDirs) {
11775
- if (existsSync7(dir)) {
11776
- try {
11777
- const skills = readdirSync4(dir);
11778
- for (const skill of skills) {
11779
- if (!status.skills.includes(skill.toLowerCase())) {
11780
- status.skills.push(skill.toLowerCase());
11781
- }
11782
- }
11783
- } catch {}
11784
- }
11785
- }
11786
- return status;
11787
- }
11788
- function filterAlreadyInstalled(optimizations, status) {
11789
- return optimizations.filter((opt) => {
11790
- const id = opt.id.toLowerCase();
11791
- if (id === "context7" && status.context7)
11792
- return false;
11793
- if (id === "supermemory" && status.supermemory)
11794
- return false;
11795
- if (id === "nia" && status.nia)
11796
- return false;
11797
- if (id === "beads" && status.beads)
11798
- return false;
11799
- if (opt.type === "skill") {
11800
- const skillName = opt.name.toLowerCase().replace(/\s+/g, "-");
11801
- if (status.skills.some((s2) => s2.includes(skillName) || skillName.includes(s2) || s2.includes("tdd") && id.includes("tdd") || s2.includes("debug") && id.includes("debug") || s2.includes("plan") && id.includes("plan"))) {
11802
- return false;
11803
- }
11804
- }
11805
- return true;
11806
- });
11807
- }
11808
- var QUICK_INSTALL_PRESETS = {
11809
- essential: [
11810
- {
11811
- id: "context7",
11812
- name: "Context7 MCP",
11813
- type: "mcp",
11814
- description: "Up-to-date library documentation",
11815
- installCommand: "claude mcp add context7 https://mcp.context7.com/mcp --transport http",
11816
- selected: true
11817
- },
11818
- {
11819
- id: "supermemory",
11820
- name: "Supermemory MCP",
11821
- type: "mcp",
11822
- description: "Persistent memory across sessions",
11823
- installCommand: "claude mcp add supermemory -- npx -y @supermemory/mcp@latest",
11824
- selected: true
11825
- },
11826
- {
11827
- id: "beads",
11828
- name: "Beads Task Manager",
11829
- type: "mcp",
11830
- description: "Persistent task tracking",
11831
- installCommand: "bun add -g beads && bd init",
11832
- selected: true
11833
- }
11834
- ],
11835
- productivity: [
11836
- {
11837
- id: "claude-md",
11838
- name: "CLAUDE.md",
11839
- type: "config",
11840
- description: "Project context file",
11841
- configPath: "CLAUDE.md",
11842
- configContent: `# Project Instructions
11843
-
11844
- ## Build & Test Commands
11845
- \`\`\`bash
11846
- bun test # Run tests
11847
- bun run build # Build project
11848
- bun run lint # Run linter
11849
- \`\`\`
11850
-
11851
- ## Architecture
11852
- Describe your project structure here.
11853
-
11854
- ## Conventions
11855
- - Use TypeScript
11856
- - Write tests for new features
11857
- - Use conventional commits
11858
- `,
11859
- selected: true
11860
- },
11861
- {
11862
- id: "vitest",
11863
- name: "Vitest",
11864
- type: "library",
11865
- description: "Fast unit testing framework",
11866
- installCommand: "bun add -D vitest",
11867
- selected: true
11868
- }
11869
- ]
11870
- };
11871
-
11872
- // ../../node_modules/ora/index.js
11873
- import process8 from "node:process";
11874
- import { stripVTControlCharacters } from "node:util";
11875
-
11876
- // ../../node_modules/chalk/source/vendor/ansi-styles/index.js
11877
- var ANSI_BACKGROUND_OFFSET = 10;
11878
- var wrapAnsi16 = (offset = 0) => (code2) => `\x1B[${code2 + offset}m`;
11879
- var wrapAnsi256 = (offset = 0) => (code2) => `\x1B[${38 + offset};5;${code2}m`;
11880
- var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
11881
- var styles = {
11882
- modifier: {
11883
- reset: [0, 0],
11884
- bold: [1, 22],
11885
- dim: [2, 22],
11886
- italic: [3, 23],
11887
- underline: [4, 24],
11888
- overline: [53, 55],
11889
- inverse: [7, 27],
11890
- hidden: [8, 28],
11891
- strikethrough: [9, 29]
11892
- },
11893
- color: {
11894
- black: [30, 39],
11895
- red: [31, 39],
11896
- green: [32, 39],
11897
- yellow: [33, 39],
11898
- blue: [34, 39],
11899
- magenta: [35, 39],
11900
- cyan: [36, 39],
11901
- white: [37, 39],
11902
- blackBright: [90, 39],
11903
- gray: [90, 39],
11904
- grey: [90, 39],
11905
- redBright: [91, 39],
11906
- greenBright: [92, 39],
11907
- yellowBright: [93, 39],
11908
- blueBright: [94, 39],
11909
- magentaBright: [95, 39],
11910
- cyanBright: [96, 39],
11911
- whiteBright: [97, 39]
11912
- },
11913
- bgColor: {
11914
- bgBlack: [40, 49],
11915
- bgRed: [41, 49],
11916
- bgGreen: [42, 49],
11917
- bgYellow: [43, 49],
11918
- bgBlue: [44, 49],
11919
- bgMagenta: [45, 49],
11920
- bgCyan: [46, 49],
11921
- bgWhite: [47, 49],
11922
- bgBlackBright: [100, 49],
11923
- bgGray: [100, 49],
11924
- bgGrey: [100, 49],
11925
- bgRedBright: [101, 49],
11926
- bgGreenBright: [102, 49],
11927
- bgYellowBright: [103, 49],
11928
- bgBlueBright: [104, 49],
11929
- bgMagentaBright: [105, 49],
11930
- bgCyanBright: [106, 49],
11931
- bgWhiteBright: [107, 49]
11932
- }
11933
- };
11934
- var modifierNames = Object.keys(styles.modifier);
11935
- var foregroundColorNames = Object.keys(styles.color);
11936
- var backgroundColorNames = Object.keys(styles.bgColor);
11937
- var colorNames = [...foregroundColorNames, ...backgroundColorNames];
11938
- function assembleStyles() {
11939
- const codes = new Map;
11940
- for (const [groupName, group] of Object.entries(styles)) {
11941
- for (const [styleName, style] of Object.entries(group)) {
11942
- styles[styleName] = {
11943
- open: `\x1B[${style[0]}m`,
11944
- close: `\x1B[${style[1]}m`
11945
- };
11946
- group[styleName] = styles[styleName];
11947
- codes.set(style[0], style[1]);
11948
- }
11949
- Object.defineProperty(styles, groupName, {
11950
- value: group,
11951
- enumerable: false
11952
- });
11953
- }
11954
- Object.defineProperty(styles, "codes", {
11955
- value: codes,
11956
- enumerable: false
11957
- });
11958
- styles.color.close = "\x1B[39m";
11959
- styles.bgColor.close = "\x1B[49m";
11960
- styles.color.ansi = wrapAnsi16();
11961
- styles.color.ansi256 = wrapAnsi256();
11962
- styles.color.ansi16m = wrapAnsi16m();
11963
- styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
11964
- styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
11965
- styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
11966
- Object.defineProperties(styles, {
11967
- rgbToAnsi256: {
11968
- value(red, green, blue) {
11969
- if (red === green && green === blue) {
11970
- if (red < 8) {
11971
- return 16;
11972
- }
11973
- if (red > 248) {
11974
- return 231;
11975
- }
11976
- return Math.round((red - 8) / 247 * 24) + 232;
11977
- }
11978
- return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
11979
- },
11980
- enumerable: false
11981
- },
11982
- hexToRgb: {
11983
- value(hex) {
11984
- const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
11985
- if (!matches) {
11986
- return [0, 0, 0];
11987
- }
11988
- let [colorString] = matches;
11989
- if (colorString.length === 3) {
11990
- colorString = [...colorString].map((character) => character + character).join("");
11991
- }
11992
- const integer = Number.parseInt(colorString, 16);
11993
- return [
11994
- integer >> 16 & 255,
11995
- integer >> 8 & 255,
11996
- integer & 255
11997
- ];
11998
- },
11999
- enumerable: false
12000
- },
12001
- hexToAnsi256: {
12002
- value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
12003
- enumerable: false
12004
- },
12005
- ansi256ToAnsi: {
12006
- value(code2) {
12007
- if (code2 < 8) {
12008
- return 30 + code2;
12009
- }
12010
- if (code2 < 16) {
12011
- return 90 + (code2 - 8);
12012
- }
12013
- let red;
12014
- let green;
12015
- let blue;
12016
- if (code2 >= 232) {
12017
- red = ((code2 - 232) * 10 + 8) / 255;
12018
- green = red;
12019
- blue = red;
12020
- } else {
12021
- code2 -= 16;
12022
- const remainder = code2 % 36;
12023
- red = Math.floor(code2 / 36) / 5;
12024
- green = Math.floor(remainder / 6) / 5;
12025
- blue = remainder % 6 / 5;
12026
- }
12027
- const value = Math.max(red, green, blue) * 2;
12028
- if (value === 0) {
12029
- return 30;
12030
- }
12031
- let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
12032
- if (value === 2) {
12033
- result += 60;
12034
- }
12035
- return result;
12036
- },
12037
- enumerable: false
12038
- },
12039
- rgbToAnsi: {
12040
- value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
12041
- enumerable: false
12042
- },
12043
- hexToAnsi: {
12044
- value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
12045
- enumerable: false
12046
- }
12047
- });
12048
- return styles;
12049
- }
12050
- var ansiStyles = assembleStyles();
12051
- var ansi_styles_default = ansiStyles;
12052
-
12053
- // ../../node_modules/chalk/source/vendor/supports-color/index.js
12054
- import process2 from "node:process";
12055
- import os from "node:os";
12056
- import tty2 from "node:tty";
12057
- function hasFlag(flag, argv2 = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
12058
- const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
12059
- const position = argv2.indexOf(prefix + flag);
12060
- const terminatorPosition = argv2.indexOf("--");
12061
- return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
12062
- }
12063
- var { env: env2 } = process2;
12064
- var flagForceColor;
12065
- if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
12066
- flagForceColor = 0;
12067
- } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
12068
- flagForceColor = 1;
12069
- }
12070
- function envForceColor() {
12071
- if ("FORCE_COLOR" in env2) {
12072
- if (env2.FORCE_COLOR === "true") {
12073
- return 1;
12074
- }
12075
- if (env2.FORCE_COLOR === "false") {
12076
- return 0;
12077
- }
12078
- return env2.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env2.FORCE_COLOR, 10), 3);
12079
- }
12080
- }
12081
- function translateLevel(level) {
12082
- if (level === 0) {
12083
- return false;
12084
- }
12085
- return {
12086
- level,
12087
- hasBasic: true,
12088
- has256: level >= 2,
12089
- has16m: level >= 3
12090
- };
12091
- }
12092
- function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
12093
- const noFlagForceColor = envForceColor();
12094
- if (noFlagForceColor !== undefined) {
12095
- flagForceColor = noFlagForceColor;
12096
- }
12097
- const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
12098
- if (forceColor === 0) {
12099
- return 0;
12100
- }
12101
- if (sniffFlags) {
12102
- if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
12103
- return 3;
12104
- }
12105
- if (hasFlag("color=256")) {
12106
- return 2;
11691
+ const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
11692
+ if (forceColor === 0) {
11693
+ return 0;
11694
+ }
11695
+ if (sniffFlags) {
11696
+ if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
11697
+ return 3;
11698
+ }
11699
+ if (hasFlag("color=256")) {
11700
+ return 2;
12107
11701
  }
12108
11702
  }
12109
11703
  if ("TF_BUILD" in env2 && "AGENT_NAME" in env2) {
@@ -14878,394 +14472,1290 @@ class Ora {
14878
14472
  `)) {
14879
14473
  count += Math.max(1, Math.ceil(stringWidth2(line) / columns));
14880
14474
  }
14881
- return count;
14882
- }
14883
- get isEnabled() {
14884
- return this.#options.isEnabled && !this.#options.isSilent;
14475
+ return count;
14476
+ }
14477
+ get isEnabled() {
14478
+ return this.#options.isEnabled && !this.#options.isSilent;
14479
+ }
14480
+ set isEnabled(value) {
14481
+ if (typeof value !== "boolean") {
14482
+ throw new TypeError("The `isEnabled` option must be a boolean");
14483
+ }
14484
+ this.#options.isEnabled = value;
14485
+ }
14486
+ get isSilent() {
14487
+ return this.#options.isSilent;
14488
+ }
14489
+ set isSilent(value) {
14490
+ if (typeof value !== "boolean") {
14491
+ throw new TypeError("The `isSilent` option must be a boolean");
14492
+ }
14493
+ this.#options.isSilent = value;
14494
+ }
14495
+ frame() {
14496
+ const now = Date.now();
14497
+ if (this.#frameIndex === -1 || now - this.#lastFrameTime >= this.interval) {
14498
+ this.#frameIndex = (this.#frameIndex + 1) % this.#spinner.frames.length;
14499
+ this.#lastFrameTime = now;
14500
+ }
14501
+ const { frames } = this.#spinner;
14502
+ let frame = frames[this.#frameIndex];
14503
+ if (this.color) {
14504
+ frame = source_default[this.color](frame);
14505
+ }
14506
+ const fullPrefixText = this.#getFullPrefixText(this.#options.prefixText, " ");
14507
+ const fullText = typeof this.text === "string" ? " " + this.text : "";
14508
+ const fullSuffixText = this.#getFullSuffixText(this.#options.suffixText, " ");
14509
+ return fullPrefixText + frame + fullText + fullSuffixText;
14510
+ }
14511
+ clear() {
14512
+ if (!this.isEnabled || !this.#stream.isTTY) {
14513
+ return this;
14514
+ }
14515
+ this.#internalWrite(() => {
14516
+ this.#stream.cursorTo(0);
14517
+ for (let index = 0;index < this.#linesToClear; index++) {
14518
+ if (index > 0) {
14519
+ this.#stream.moveCursor(0, -1);
14520
+ }
14521
+ this.#stream.clearLine(1);
14522
+ }
14523
+ if (this.#options.indent) {
14524
+ this.#stream.cursorTo(this.#options.indent);
14525
+ }
14526
+ });
14527
+ this.#linesToClear = 0;
14528
+ return this;
14529
+ }
14530
+ #hookStream(stream) {
14531
+ if (!stream || this.#hookedStreams.has(stream) || !stream.isTTY || typeof stream.write !== "function") {
14532
+ return;
14533
+ }
14534
+ if (activeHooksPerStream.has(stream)) {
14535
+ console.warn("[ora] Multiple concurrent spinners detected. This may cause visual corruption. Use one spinner at a time.");
14536
+ }
14537
+ const originalWrite = stream.write;
14538
+ this.#hookedStreams.set(stream, originalWrite);
14539
+ activeHooksPerStream.set(stream, this);
14540
+ stream.write = (chunk, encoding, callback) => this.#hookedWrite(stream, originalWrite, chunk, encoding, callback);
14541
+ }
14542
+ #installHook() {
14543
+ if (!this.isEnabled || this.#hookedStreams.size > 0) {
14544
+ return;
14545
+ }
14546
+ const streamsToHook = new Set([this.#stream, process8.stdout, process8.stderr]);
14547
+ for (const stream of streamsToHook) {
14548
+ this.#hookStream(stream);
14549
+ }
14550
+ }
14551
+ #uninstallHook() {
14552
+ for (const [stream, originalWrite] of this.#hookedStreams) {
14553
+ stream.write = originalWrite;
14554
+ if (activeHooksPerStream.get(stream) === this) {
14555
+ activeHooksPerStream.delete(stream);
14556
+ }
14557
+ }
14558
+ this.#hookedStreams.clear();
14559
+ }
14560
+ #hookedWrite(stream, originalWrite, chunk, encoding, callback) {
14561
+ if (typeof encoding === "function") {
14562
+ callback = encoding;
14563
+ encoding = undefined;
14564
+ }
14565
+ if (this.#isInternalWrite) {
14566
+ return originalWrite.call(stream, chunk, encoding, callback);
14567
+ }
14568
+ this.clear();
14569
+ const chunkString = this.#stringifyChunk(chunk, encoding);
14570
+ const chunkTerminatesLine = this.#chunkTerminatesLine(chunkString);
14571
+ const writeResult = originalWrite.call(stream, chunk, encoding, callback);
14572
+ if (chunkTerminatesLine) {
14573
+ this.#clearRenderDeferral();
14574
+ } else if (chunkString.length > 0) {
14575
+ this.#scheduleRenderDeferral();
14576
+ }
14577
+ if (this.isSpinning && !this.#deferRenderTimer) {
14578
+ this.render();
14579
+ }
14580
+ return writeResult;
14581
+ }
14582
+ render() {
14583
+ if (!this.isEnabled || this.#drainHandler || this.#deferRenderTimer) {
14584
+ return this;
14585
+ }
14586
+ const useSynchronizedOutput = this.#stream.isTTY;
14587
+ let shouldDisableSynchronizedOutput = false;
14588
+ try {
14589
+ if (useSynchronizedOutput) {
14590
+ this.#internalWrite(() => this.#stream.write(SYNCHRONIZED_OUTPUT_ENABLE));
14591
+ shouldDisableSynchronizedOutput = true;
14592
+ }
14593
+ this.clear();
14594
+ let frameContent = this.frame();
14595
+ const columns = this.#stream.columns ?? 80;
14596
+ const actualLineCount = this.#computeLineCountFrom(frameContent, columns);
14597
+ const consoleHeight = this.#stream.rows;
14598
+ if (consoleHeight && consoleHeight > 1 && actualLineCount > consoleHeight) {
14599
+ const lines = frameContent.split(`
14600
+ `);
14601
+ const maxLines = consoleHeight - 1;
14602
+ frameContent = [...lines.slice(0, maxLines), "... (content truncated to fit terminal)"].join(`
14603
+ `);
14604
+ }
14605
+ const canContinue = this.#internalWrite(() => this.#stream.write(frameContent));
14606
+ if (canContinue === false && this.#stream.isTTY) {
14607
+ this.#drainHandler = () => {
14608
+ this.#drainHandler = undefined;
14609
+ this.#tryRender();
14610
+ };
14611
+ this.#stream.once("drain", this.#drainHandler);
14612
+ }
14613
+ this.#linesToClear = this.#computeLineCountFrom(frameContent, columns);
14614
+ } finally {
14615
+ if (shouldDisableSynchronizedOutput) {
14616
+ this.#internalWrite(() => this.#stream.write(SYNCHRONIZED_OUTPUT_DISABLE));
14617
+ }
14618
+ }
14619
+ return this;
14620
+ }
14621
+ start(text) {
14622
+ if (text) {
14623
+ this.text = text;
14624
+ }
14625
+ if (this.isSilent) {
14626
+ return this;
14627
+ }
14628
+ if (!this.isEnabled) {
14629
+ const symbol = this.text ? "-" : "";
14630
+ const line = " ".repeat(this.#options.indent) + this.#buildOutputLine(symbol, this.text, this.#options.prefixText, this.#options.suffixText);
14631
+ if (line.trim() !== "") {
14632
+ this.#internalWrite(() => this.#stream.write(line + `
14633
+ `));
14634
+ }
14635
+ return this;
14636
+ }
14637
+ if (this.isSpinning) {
14638
+ return this;
14639
+ }
14640
+ if (this.#options.hideCursor) {
14641
+ cli_cursor_default.hide(this.#stream);
14642
+ }
14643
+ if (this.#options.discardStdin && process8.stdin.isTTY) {
14644
+ stdin_discarder_default.start();
14645
+ this.#isDiscardingStdin = true;
14646
+ }
14647
+ this.#installHook();
14648
+ this.render();
14649
+ this.#id = setInterval(this.render.bind(this), this.interval);
14650
+ return this;
14885
14651
  }
14886
- set isEnabled(value) {
14887
- if (typeof value !== "boolean") {
14888
- throw new TypeError("The `isEnabled` option must be a boolean");
14652
+ stop() {
14653
+ clearInterval(this.#id);
14654
+ this.#id = undefined;
14655
+ this.#frameIndex = -1;
14656
+ this.#lastFrameTime = 0;
14657
+ this.#clearRenderDeferral();
14658
+ this.#uninstallHook();
14659
+ if (this.#drainHandler) {
14660
+ this.#stream.removeListener("drain", this.#drainHandler);
14661
+ this.#drainHandler = undefined;
14889
14662
  }
14890
- this.#options.isEnabled = value;
14663
+ if (this.isEnabled) {
14664
+ this.clear();
14665
+ if (this.#options.hideCursor) {
14666
+ cli_cursor_default.show(this.#stream);
14667
+ }
14668
+ }
14669
+ if (this.#isDiscardingStdin) {
14670
+ this.#isDiscardingStdin = false;
14671
+ stdin_discarder_default.stop();
14672
+ }
14673
+ return this;
14891
14674
  }
14892
- get isSilent() {
14893
- return this.#options.isSilent;
14675
+ succeed(text) {
14676
+ return this.stopAndPersist({ symbol: exports_symbols.success, text });
14894
14677
  }
14895
- set isSilent(value) {
14896
- if (typeof value !== "boolean") {
14897
- throw new TypeError("The `isSilent` option must be a boolean");
14898
- }
14899
- this.#options.isSilent = value;
14678
+ fail(text) {
14679
+ return this.stopAndPersist({ symbol: exports_symbols.error, text });
14900
14680
  }
14901
- frame() {
14902
- const now = Date.now();
14903
- if (this.#frameIndex === -1 || now - this.#lastFrameTime >= this.interval) {
14904
- this.#frameIndex = (this.#frameIndex + 1) % this.#spinner.frames.length;
14905
- this.#lastFrameTime = now;
14906
- }
14907
- const { frames } = this.#spinner;
14908
- let frame = frames[this.#frameIndex];
14909
- if (this.color) {
14910
- frame = source_default[this.color](frame);
14911
- }
14912
- const fullPrefixText = this.#getFullPrefixText(this.#options.prefixText, " ");
14913
- const fullText = typeof this.text === "string" ? " " + this.text : "";
14914
- const fullSuffixText = this.#getFullSuffixText(this.#options.suffixText, " ");
14915
- return fullPrefixText + frame + fullText + fullSuffixText;
14681
+ warn(text) {
14682
+ return this.stopAndPersist({ symbol: exports_symbols.warning, text });
14916
14683
  }
14917
- clear() {
14918
- if (!this.isEnabled || !this.#stream.isTTY) {
14684
+ info(text) {
14685
+ return this.stopAndPersist({ symbol: exports_symbols.info, text });
14686
+ }
14687
+ stopAndPersist(options = {}) {
14688
+ if (this.isSilent) {
14919
14689
  return this;
14920
14690
  }
14921
- this.#internalWrite(() => {
14922
- this.#stream.cursorTo(0);
14923
- for (let index = 0;index < this.#linesToClear; index++) {
14924
- if (index > 0) {
14925
- this.#stream.moveCursor(0, -1);
14926
- }
14927
- this.#stream.clearLine(1);
14928
- }
14929
- if (this.#options.indent) {
14930
- this.#stream.cursorTo(this.#options.indent);
14931
- }
14932
- });
14933
- this.#linesToClear = 0;
14691
+ const symbol = options.symbol ?? " ";
14692
+ const text = options.text ?? this.text;
14693
+ const prefixText = options.prefixText ?? this.#options.prefixText;
14694
+ const suffixText = options.suffixText ?? this.#options.suffixText;
14695
+ const textToWrite = this.#buildOutputLine(symbol, text, prefixText, suffixText) + `
14696
+ `;
14697
+ this.stop();
14698
+ this.#internalWrite(() => this.#stream.write(textToWrite));
14934
14699
  return this;
14935
14700
  }
14936
- #hookStream(stream) {
14937
- if (!stream || this.#hookedStreams.has(stream) || !stream.isTTY || typeof stream.write !== "function") {
14938
- return;
14701
+ }
14702
+ function ora(options) {
14703
+ return new Ora(options);
14704
+ }
14705
+
14706
+ // src/lib/ui.ts
14707
+ var import_picocolors = __toESM(require_picocolors(), 1);
14708
+ import * as readline from "readline";
14709
+ var colors2 = {
14710
+ primary: import_picocolors.default.cyan,
14711
+ secondary: import_picocolors.default.blue,
14712
+ accent: import_picocolors.default.magenta,
14713
+ success: import_picocolors.default.green,
14714
+ warning: import_picocolors.default.yellow,
14715
+ error: import_picocolors.default.red,
14716
+ info: import_picocolors.default.blue,
14717
+ dim: import_picocolors.default.dim,
14718
+ bold: import_picocolors.default.bold,
14719
+ italic: import_picocolors.default.italic,
14720
+ excellent: import_picocolors.default.green,
14721
+ good: import_picocolors.default.cyan,
14722
+ moderate: import_picocolors.default.yellow,
14723
+ poor: import_picocolors.default.red,
14724
+ highlight: (text) => import_picocolors.default.bold(import_picocolors.default.cyan(text)),
14725
+ muted: (text) => import_picocolors.default.dim(import_picocolors.default.gray(text)),
14726
+ link: (text) => import_picocolors.default.underline(import_picocolors.default.blue(text))
14727
+ };
14728
+ function createSpinner(text) {
14729
+ return ora({
14730
+ text,
14731
+ spinner: "dots",
14732
+ color: "cyan"
14733
+ });
14734
+ }
14735
+ function formatTier(tier) {
14736
+ const tierColors = {
14737
+ elite: (s2) => import_picocolors.default.bold(import_picocolors.default.magenta(s2)),
14738
+ expert: (s2) => import_picocolors.default.bold(import_picocolors.default.cyan(s2)),
14739
+ advanced: (s2) => import_picocolors.default.bold(import_picocolors.default.green(s2)),
14740
+ proficient: (s2) => import_picocolors.default.blue(s2),
14741
+ intermediate: (s2) => import_picocolors.default.yellow(s2),
14742
+ developing: (s2) => import_picocolors.default.dim(s2)
14743
+ };
14744
+ const colorFn = tierColors[tier.toLowerCase()] ?? colors2.dim;
14745
+ return colorFn(tier.charAt(0).toUpperCase() + tier.slice(1));
14746
+ }
14747
+ var icons = {
14748
+ success: colors2.success("✔"),
14749
+ error: colors2.error("✖"),
14750
+ warning: colors2.warning("⚠"),
14751
+ info: colors2.info("ℹ"),
14752
+ arrow: colors2.primary("→"),
14753
+ bullet: colors2.dim("•"),
14754
+ star: colors2.warning("★"),
14755
+ check: colors2.success("✓"),
14756
+ cross: colors2.error("✗"),
14757
+ pending: colors2.dim("○"),
14758
+ lightning: "⚡",
14759
+ fire: "\uD83D\uDD25",
14760
+ rocket: "\uD83D\uDE80",
14761
+ chart: "\uD83D\uDCCA",
14762
+ money: "\uD83D\uDCB0",
14763
+ brain: "\uD83E\uDDE0",
14764
+ target: "\uD83C\uDFAF"
14765
+ };
14766
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
14767
+ function clearLine2() {
14768
+ readline.clearLine(process.stdout, 0);
14769
+ readline.cursorTo(process.stdout, 0);
14770
+ }
14771
+ function write(text) {
14772
+ process.stdout.write(text);
14773
+ }
14774
+ async function animateProgressBar(label, score, options = {}) {
14775
+ const { width = 20, charDelay = 40, labelWidth = 20 } = options;
14776
+ const filled = Math.round(score / 100 * width);
14777
+ let barColor = colors2.excellent;
14778
+ if (score < 40)
14779
+ barColor = colors2.poor;
14780
+ else if (score < 60)
14781
+ barColor = colors2.moderate;
14782
+ else if (score < 75)
14783
+ barColor = colors2.good;
14784
+ const paddedLabel = label.padEnd(labelWidth);
14785
+ const scoreStr = `${score}/100`.padStart(7);
14786
+ write(` ${colors2.dim(paddedLabel)} ${colors2.dim(scoreStr)} `);
14787
+ for (let i3 = 0;i3 < width; i3++) {
14788
+ if (i3 < filled) {
14789
+ write(barColor("█"));
14790
+ } else {
14791
+ write(colors2.dim("░"));
14939
14792
  }
14940
- if (activeHooksPerStream.has(stream)) {
14941
- console.warn("[ora] Multiple concurrent spinners detected. This may cause visual corruption. Use one spinner at a time.");
14793
+ await sleep(charDelay);
14794
+ }
14795
+ console.log();
14796
+ }
14797
+ async function thinkingStep(thinkingText, duration = 800, successText) {
14798
+ const spinner = createSpinner(thinkingText);
14799
+ spinner.start();
14800
+ await sleep(duration);
14801
+ spinner.succeed(successText ?? thinkingText);
14802
+ }
14803
+ async function revealDiscovery(icon, text, delay2 = 300) {
14804
+ await sleep(delay2);
14805
+ console.log(` ${icon} ${text}`);
14806
+ }
14807
+ async function showPhaseHeader(phase, total, title, delay2 = 400) {
14808
+ await sleep(delay2);
14809
+ console.log();
14810
+ console.log(` ${colors2.dim(`Phase ${phase}/${total}:`)} ${colors2.bold(colors2.primary(title))}`);
14811
+ console.log(colors2.dim(" " + "─".repeat(40)));
14812
+ }
14813
+ async function animateScoreReveal(score, tier, options = {}) {
14814
+ const { countDuration = 1000, barDelay = 50 } = options;
14815
+ const scoreColor = score >= 80 ? colors2.excellent : score >= 60 ? colors2.good : score >= 40 ? colors2.moderate : colors2.poor;
14816
+ console.log();
14817
+ console.log(colors2.dim(" " + "═".repeat(45)));
14818
+ console.log();
14819
+ const steps = 30;
14820
+ const stepDuration = countDuration / steps;
14821
+ for (let i3 = 1;i3 <= steps; i3++) {
14822
+ const current = Math.round(i3 / steps * score);
14823
+ clearLine2();
14824
+ write(` ${colors2.bold("Your Score:")} ${scoreColor(colors2.bold(current.toString()))}/100`);
14825
+ await sleep(stepDuration);
14826
+ }
14827
+ write(` ${colors2.dim("(")}${formatTier(tier)}${colors2.dim(")")}`);
14828
+ console.log();
14829
+ console.log();
14830
+ const barWidth = 30;
14831
+ const filled = Math.round(score / 100 * barWidth);
14832
+ write(" ");
14833
+ for (let i3 = 0;i3 < barWidth; i3++) {
14834
+ if (i3 < filled) {
14835
+ write(scoreColor("█"));
14836
+ } else {
14837
+ write(colors2.dim("░"));
14942
14838
  }
14943
- const originalWrite = stream.write;
14944
- this.#hookedStreams.set(stream, originalWrite);
14945
- activeHooksPerStream.set(stream, this);
14946
- stream.write = (chunk, encoding, callback) => this.#hookedWrite(stream, originalWrite, chunk, encoding, callback);
14839
+ await sleep(barDelay);
14947
14840
  }
14948
- #installHook() {
14949
- if (!this.isEnabled || this.#hookedStreams.size > 0) {
14950
- return;
14841
+ console.log();
14842
+ console.log();
14843
+ console.log(colors2.dim(" " + "═".repeat(45)));
14844
+ }
14845
+ async function showFinding(type, label, value, unit = "", delay2 = 250) {
14846
+ await sleep(delay2);
14847
+ const iconMap = {
14848
+ warning: icons.warning,
14849
+ error: icons.error,
14850
+ info: icons.info,
14851
+ success: icons.success
14852
+ };
14853
+ const colorMap = {
14854
+ warning: colors2.warning,
14855
+ error: colors2.error,
14856
+ info: colors2.info,
14857
+ success: colors2.success
14858
+ };
14859
+ const icon = iconMap[type];
14860
+ const valueColor = colorMap[type];
14861
+ console.log(` ${icon} ${label}: ${valueColor(value.toString())}${unit}`);
14862
+ }
14863
+
14864
+ // src/lib/frustration-detector.ts
14865
+ var ROOT_CAUSE_PATTERNS = {
14866
+ hallucination: {
14867
+ patterns: [
14868
+ /doesn'?t exist/i,
14869
+ /not a valid/i,
14870
+ /no such (file|method|function|class|property|module)/i,
14871
+ /wrong api/i,
14872
+ /outdated/i,
14873
+ /deprecated/i,
14874
+ /that'?s not (how|the)/i,
14875
+ /method doesn'?t/i,
14876
+ /property doesn'?t/i,
14877
+ /hallucinating/i,
14878
+ /made up/i,
14879
+ /invented/i,
14880
+ /that method/i,
14881
+ /wrong syntax/i
14882
+ ],
14883
+ description: "AI hallucinated non-existent API/method/syntax",
14884
+ fix: {
14885
+ tool: "Context7",
14886
+ description: "Provides up-to-date library documentation to prevent hallucinations",
14887
+ installCommand: "claude mcp add context7 https://mcp.context7.com/mcp --transport http",
14888
+ estimatedImprovementPercent: 70
14951
14889
  }
14952
- const streamsToHook = new Set([this.#stream, process8.stdout, process8.stderr]);
14953
- for (const stream of streamsToHook) {
14954
- this.#hookStream(stream);
14890
+ },
14891
+ missing_docs: {
14892
+ patterns: [
14893
+ /how do (i|you)/i,
14894
+ /what'?s the (syntax|api|method|way)/i,
14895
+ /documentation/i,
14896
+ /check the docs/i,
14897
+ /look up/i,
14898
+ /find the right/i,
14899
+ /correct (api|method|syntax)/i,
14900
+ /which (api|method|function)/i
14901
+ ],
14902
+ description: "Needed documentation that AI didn't have access to",
14903
+ fix: {
14904
+ tool: "Context7",
14905
+ description: "Live documentation lookup prevents outdated information",
14906
+ installCommand: "claude mcp add context7 https://mcp.context7.com/mcp --transport http",
14907
+ estimatedImprovementPercent: 65
14955
14908
  }
14956
- }
14957
- #uninstallHook() {
14958
- for (const [stream, originalWrite] of this.#hookedStreams) {
14959
- stream.write = originalWrite;
14960
- if (activeHooksPerStream.get(stream) === this) {
14961
- activeHooksPerStream.delete(stream);
14962
- }
14909
+ },
14910
+ context_loss: {
14911
+ patterns: [
14912
+ /i already told you/i,
14913
+ /as i (said|mentioned)/i,
14914
+ /we discussed/i,
14915
+ /you forgot/i,
14916
+ /remember/i,
14917
+ /i explained/i,
14918
+ /sessions ago/i,
14919
+ /previously/i,
14920
+ /earlier/i,
14921
+ /context/i,
14922
+ /last time/i,
14923
+ /before/i
14924
+ ],
14925
+ description: "AI forgot previously established context",
14926
+ fix: {
14927
+ tool: "Supermemory",
14928
+ description: "Persistent memory across sessions prevents context loss",
14929
+ installCommand: "claude mcp add supermemory -- npx -y @supermemory/mcp@latest",
14930
+ estimatedImprovementPercent: 80
14963
14931
  }
14964
- this.#hookedStreams.clear();
14965
- }
14966
- #hookedWrite(stream, originalWrite, chunk, encoding, callback) {
14967
- if (typeof encoding === "function") {
14968
- callback = encoding;
14969
- encoding = undefined;
14932
+ },
14933
+ unclear_prompt: {
14934
+ patterns: [
14935
+ /what do you mean/i,
14936
+ /clarify/i,
14937
+ /specify/i,
14938
+ /more (details|context|information)/i,
14939
+ /not sure what/i,
14940
+ /which (file|one|version)/i,
14941
+ /be more specific/i
14942
+ ],
14943
+ description: "Prompt was unclear, leading to misunderstanding",
14944
+ fix: {
14945
+ tool: "Writing Plans Skill",
14946
+ description: "Helps write clearer, more structured prompts",
14947
+ installCommand: "npx skills add obra/superpowers@writing-plans --yes",
14948
+ estimatedImprovementPercent: 50
14970
14949
  }
14971
- if (this.#isInternalWrite) {
14972
- return originalWrite.call(stream, chunk, encoding, callback);
14950
+ },
14951
+ tool_failure: {
14952
+ patterns: [
14953
+ /tool failed/i,
14954
+ /mcp error/i,
14955
+ /error executing/i,
14956
+ /command failed/i,
14957
+ /couldn'?t run/i,
14958
+ /failed to/i,
14959
+ /error:/i,
14960
+ /exception/i
14961
+ ],
14962
+ description: "Tool or MCP server failed to execute",
14963
+ fix: {
14964
+ tool: "MCP Doctor",
14965
+ description: "Fix MCP configuration issues",
14966
+ installCommand: "claude doctor && claude mcp list",
14967
+ estimatedImprovementPercent: 40
14973
14968
  }
14974
- this.clear();
14975
- const chunkString = this.#stringifyChunk(chunk, encoding);
14976
- const chunkTerminatesLine = this.#chunkTerminatesLine(chunkString);
14977
- const writeResult = originalWrite.call(stream, chunk, encoding, callback);
14978
- if (chunkTerminatesLine) {
14979
- this.#clearRenderDeferral();
14980
- } else if (chunkString.length > 0) {
14981
- this.#scheduleRenderDeferral();
14969
+ },
14970
+ undo_loop: {
14971
+ patterns: [
14972
+ /undo/i,
14973
+ /revert/i,
14974
+ /go back/i,
14975
+ /try again/i,
14976
+ /start over/i,
14977
+ /that'?s wrong/i,
14978
+ /not what i wanted/i,
14979
+ /roll back/i,
14980
+ /put it back/i,
14981
+ /restore/i,
14982
+ /wrong/i
14983
+ ],
14984
+ description: "Got stuck in undo/redo loop from incorrect changes",
14985
+ fix: {
14986
+ tool: "Systematic Debugging Skill",
14987
+ description: "Structured approach to fixing issues without loops",
14988
+ installCommand: "npx skills add obra/superpowers@systematic-debugging --yes",
14989
+ estimatedImprovementPercent: 55
14982
14990
  }
14983
- if (this.isSpinning && !this.#deferRenderTimer) {
14984
- this.render();
14991
+ }
14992
+ };
14993
+ function detectFrustrations(sessions) {
14994
+ const frustrations = [];
14995
+ for (const session of sessions) {
14996
+ const frustrationPatterns = session.patterns.filter((p) => p.type !== "smooth_flow" && p.type !== "long_back_forth");
14997
+ if (frustrationPatterns.length === 0)
14998
+ continue;
14999
+ const sessionPath = findSessionPath(session.sessionId, session.agent);
15000
+ const exactPrompts = sessionPath ? extractFrustrationPrompts(sessionPath, frustrationPatterns) : [];
15001
+ for (let i3 = 0;i3 < frustrationPatterns.length; i3++) {
15002
+ const pattern = frustrationPatterns[i3];
15003
+ const exactPrompt = exactPrompts[i3] || "(Unable to extract prompt from session file)";
15004
+ const rootCause = analyzeRootCause(exactPrompt, pattern.type);
15005
+ const timeWasted = Math.round(pattern.estimatedWastedTokens / 1000);
15006
+ const costWasted = pattern.estimatedWastedTokens * 0.000015;
15007
+ frustrations.push({
15008
+ sessionId: session.sessionId,
15009
+ sessionPath: sessionPath || `${session.agent}/${session.sessionId}`,
15010
+ agent: session.agent,
15011
+ timestamp: session.startedAt,
15012
+ exactPrompt: exactPrompt.slice(0, 500),
15013
+ promptTruncated: exactPrompt.length > 500,
15014
+ patternType: pattern.type,
15015
+ patternDescription: getPatternDescription(pattern.type),
15016
+ rootCause,
15017
+ estimatedTimeWastedMinutes: timeWasted,
15018
+ estimatedTokensWasted: pattern.estimatedWastedTokens,
15019
+ estimatedCostWasted: Math.round(costWasted * 100) / 100,
15020
+ recommendedFix: getRecommendedFix(rootCause.category)
15021
+ });
14985
15022
  }
14986
- return writeResult;
14987
15023
  }
14988
- render() {
14989
- if (!this.isEnabled || this.#drainHandler || this.#deferRenderTimer) {
14990
- return this;
15024
+ frustrations.sort((a2, b2) => b2.estimatedTimeWastedMinutes - a2.estimatedTimeWastedMinutes);
15025
+ const totalTimeWasted = frustrations.reduce((a2, f3) => a2 + f3.estimatedTimeWastedMinutes, 0);
15026
+ const totalTokensWasted = frustrations.reduce((a2, f3) => a2 + f3.estimatedTokensWasted, 0);
15027
+ const totalCostWasted = frustrations.reduce((a2, f3) => a2 + f3.estimatedCostWasted, 0);
15028
+ const byRootCause = {};
15029
+ for (const f3 of frustrations) {
15030
+ byRootCause[f3.rootCause.category] = (byRootCause[f3.rootCause.category] || 0) + 1;
15031
+ }
15032
+ const recommendations = aggregateRecommendations(frustrations);
15033
+ return {
15034
+ totalFrustrations: frustrations.length,
15035
+ totalTimeWastedMinutes: totalTimeWasted,
15036
+ totalTokensWasted,
15037
+ totalCostWasted: Math.round(totalCostWasted * 100) / 100,
15038
+ byRootCause,
15039
+ topFrustrations: frustrations.slice(0, 5),
15040
+ recommendations
15041
+ };
15042
+ }
15043
+ function findSessionPath(sessionId, agent) {
15044
+ const home = homedir6();
15045
+ if (agent === "claude") {
15046
+ const projectsDir = join7(home, ".claude", "projects");
15047
+ if (!existsSync7(projectsDir))
15048
+ return null;
15049
+ try {
15050
+ const projectDirs = fsReaddirSync(projectsDir, { withFileTypes: true }).filter((d2) => d2.isDirectory()).map((d2) => join7(projectsDir, d2.name));
15051
+ for (const projectDir of projectDirs) {
15052
+ const sessionFile = join7(projectDir, `${sessionId}.jsonl`);
15053
+ if (existsSync7(sessionFile)) {
15054
+ return sessionFile;
15055
+ }
15056
+ }
15057
+ } catch {
15058
+ return null;
14991
15059
  }
14992
- const useSynchronizedOutput = this.#stream.isTTY;
14993
- let shouldDisableSynchronizedOutput = false;
15060
+ }
15061
+ if (agent === "opencode") {
15062
+ const baseDir = join7(home, ".local", "share", "opencode", "storage", "session");
15063
+ if (!existsSync7(baseDir))
15064
+ return null;
14994
15065
  try {
14995
- if (useSynchronizedOutput) {
14996
- this.#internalWrite(() => this.#stream.write(SYNCHRONIZED_OUTPUT_ENABLE));
14997
- shouldDisableSynchronizedOutput = true;
15066
+ const projectDirs = fsReaddirSync(baseDir, { withFileTypes: true }).filter((d2) => d2.isDirectory()).map((d2) => join7(baseDir, d2.name));
15067
+ for (const projectDir of projectDirs) {
15068
+ const sessionFile = join7(projectDir, `${sessionId}.json`);
15069
+ if (existsSync7(sessionFile)) {
15070
+ return sessionFile;
15071
+ }
14998
15072
  }
14999
- this.clear();
15000
- let frameContent = this.frame();
15001
- const columns = this.#stream.columns ?? 80;
15002
- const actualLineCount = this.#computeLineCountFrom(frameContent, columns);
15003
- const consoleHeight = this.#stream.rows;
15004
- if (consoleHeight && consoleHeight > 1 && actualLineCount > consoleHeight) {
15005
- const lines = frameContent.split(`
15006
- `);
15007
- const maxLines = consoleHeight - 1;
15008
- frameContent = [...lines.slice(0, maxLines), "... (content truncated to fit terminal)"].join(`
15073
+ } catch {
15074
+ return null;
15075
+ }
15076
+ }
15077
+ return null;
15078
+ }
15079
+ function extractFrustrationPrompts(sessionPath, patterns) {
15080
+ try {
15081
+ const content = readFileSync7(sessionPath, "utf-8");
15082
+ const prompts = [];
15083
+ if (sessionPath.endsWith(".jsonl")) {
15084
+ const lines = content.trim().split(`
15009
15085
  `);
15086
+ const messages = [];
15087
+ for (const line of lines) {
15088
+ try {
15089
+ messages.push(JSON.parse(line));
15090
+ } catch {}
15010
15091
  }
15011
- const canContinue = this.#internalWrite(() => this.#stream.write(frameContent));
15012
- if (canContinue === false && this.#stream.isTTY) {
15013
- this.#drainHandler = () => {
15014
- this.#drainHandler = undefined;
15015
- this.#tryRender();
15016
- };
15017
- this.#stream.once("drain", this.#drainHandler);
15092
+ const userMessages = messages.map((m2, i3) => ({ msg: m2, index: i3 })).filter((m2) => {
15093
+ const role = m2.msg.role ?? m2.msg.message?.role;
15094
+ return role === "user";
15095
+ });
15096
+ for (const pattern of patterns) {
15097
+ if (pattern.messageIndices && pattern.messageIndices.length > 0) {
15098
+ const idx = pattern.messageIndices[0];
15099
+ const userMsg = userMessages.find((m2) => m2.index === idx);
15100
+ if (userMsg) {
15101
+ const text = extractMessageText(userMsg.msg);
15102
+ prompts.push(text);
15103
+ } else {
15104
+ const frustrationMsg = findFrustrationMessage(userMessages.map((m2) => extractMessageText(m2.msg)));
15105
+ prompts.push(frustrationMsg || "");
15106
+ }
15107
+ } else {
15108
+ const frustrationMsg = findFrustrationMessage(userMessages.map((m2) => extractMessageText(m2.msg)));
15109
+ prompts.push(frustrationMsg || "");
15110
+ }
15018
15111
  }
15019
- this.#linesToClear = this.#computeLineCountFrom(frameContent, columns);
15020
- } finally {
15021
- if (shouldDisableSynchronizedOutput) {
15022
- this.#internalWrite(() => this.#stream.write(SYNCHRONIZED_OUTPUT_DISABLE));
15112
+ } else if (sessionPath.endsWith(".json")) {
15113
+ for (const _3 of patterns) {
15114
+ prompts.push("");
15023
15115
  }
15024
15116
  }
15025
- return this;
15117
+ return prompts;
15118
+ } catch {
15119
+ return [];
15026
15120
  }
15027
- start(text) {
15028
- if (text) {
15029
- this.text = text;
15121
+ }
15122
+ function extractMessageText(msg) {
15123
+ if (typeof msg.content === "string")
15124
+ return msg.content;
15125
+ if (Array.isArray(msg.content)) {
15126
+ return msg.content.filter((c3) => c3.type === "text" && c3.text).map((c3) => c3.text).join(" ");
15127
+ }
15128
+ return "";
15129
+ }
15130
+ function findFrustrationMessage(messages) {
15131
+ const frustrationIndicators = [
15132
+ /[A-Z]{5,}/,
15133
+ /!{2,}/,
15134
+ /\?{2,}/,
15135
+ /\b(ugh|argh|wtf|wrong|no|stop|don't|undo|revert)\b/i
15136
+ ];
15137
+ for (const msg of messages) {
15138
+ for (const indicator of frustrationIndicators) {
15139
+ if (indicator.test(msg)) {
15140
+ return msg;
15141
+ }
15030
15142
  }
15031
- if (this.isSilent) {
15032
- return this;
15143
+ }
15144
+ return null;
15145
+ }
15146
+ function analyzeRootCause(prompt2, patternType) {
15147
+ if (patternType === "memory_loss") {
15148
+ return {
15149
+ category: "context_loss",
15150
+ description: ROOT_CAUSE_PATTERNS.context_loss.description,
15151
+ confidence: "high"
15152
+ };
15153
+ }
15154
+ if (patternType === "undo_loop") {
15155
+ for (const pattern of ROOT_CAUSE_PATTERNS.hallucination.patterns) {
15156
+ if (pattern.test(prompt2)) {
15157
+ return {
15158
+ category: "hallucination",
15159
+ description: ROOT_CAUSE_PATTERNS.hallucination.description,
15160
+ confidence: "medium"
15161
+ };
15162
+ }
15033
15163
  }
15034
- if (!this.isEnabled) {
15035
- const symbol = this.text ? "-" : "";
15036
- const line = " ".repeat(this.#options.indent) + this.#buildOutputLine(symbol, this.text, this.#options.prefixText, this.#options.suffixText);
15037
- if (line.trim() !== "") {
15038
- this.#internalWrite(() => this.#stream.write(line + `
15039
- `));
15164
+ return {
15165
+ category: "undo_loop",
15166
+ description: ROOT_CAUSE_PATTERNS.undo_loop.description,
15167
+ confidence: "high"
15168
+ };
15169
+ }
15170
+ if (patternType === "tool_failure") {
15171
+ return {
15172
+ category: "tool_failure",
15173
+ description: ROOT_CAUSE_PATTERNS.tool_failure.description,
15174
+ confidence: "high"
15175
+ };
15176
+ }
15177
+ for (const [category, config] of Object.entries(ROOT_CAUSE_PATTERNS)) {
15178
+ for (const pattern of config.patterns) {
15179
+ if (pattern.test(prompt2)) {
15180
+ return {
15181
+ category,
15182
+ description: config.description,
15183
+ confidence: "medium"
15184
+ };
15040
15185
  }
15041
- return this;
15042
15186
  }
15043
- if (this.isSpinning) {
15044
- return this;
15187
+ }
15188
+ return {
15189
+ category: "unknown",
15190
+ description: "Unable to determine specific root cause",
15191
+ confidence: "low"
15192
+ };
15193
+ }
15194
+ function getRecommendedFix(category) {
15195
+ const fixes = {
15196
+ hallucination: ROOT_CAUSE_PATTERNS.hallucination.fix,
15197
+ missing_docs: ROOT_CAUSE_PATTERNS.missing_docs.fix,
15198
+ context_loss: ROOT_CAUSE_PATTERNS.context_loss.fix,
15199
+ unclear_prompt: ROOT_CAUSE_PATTERNS.unclear_prompt.fix,
15200
+ tool_failure: ROOT_CAUSE_PATTERNS.tool_failure.fix,
15201
+ undo_loop: ROOT_CAUSE_PATTERNS.undo_loop.fix,
15202
+ unknown: {
15203
+ tool: "CLAUDE.md",
15204
+ description: "Create project instructions to prevent common issues",
15205
+ installCommand: `echo '# Project Instructions
15206
+ ' > CLAUDE.md`,
15207
+ estimatedImprovementPercent: 30
15045
15208
  }
15046
- if (this.#options.hideCursor) {
15047
- cli_cursor_default.hide(this.#stream);
15209
+ };
15210
+ return fixes[category];
15211
+ }
15212
+ function getPatternDescription(patternType) {
15213
+ const descriptions = {
15214
+ undo_loop: "Got stuck in undo/redo cycle",
15215
+ memory_loss: "Had to re-explain context",
15216
+ frustration_caps: "Expressed frustration (ALL CAPS)",
15217
+ repeated_rephrasing: "Had to rephrase multiple times",
15218
+ context_compaction: "Lost context due to compaction",
15219
+ tool_failure: "Tool or MCP failed",
15220
+ long_back_forth: "Excessive back-and-forth"
15221
+ };
15222
+ return descriptions[patternType] || patternType;
15223
+ }
15224
+ function aggregateRecommendations(frustrations) {
15225
+ const byTool = {};
15226
+ for (const f3 of frustrations) {
15227
+ const key = f3.recommendedFix.tool;
15228
+ if (!byTool[key]) {
15229
+ byTool[key] = { count: 0, timeSaved: 0, costSaved: 0, fix: f3.recommendedFix };
15048
15230
  }
15049
- if (this.#options.discardStdin && process8.stdin.isTTY) {
15050
- stdin_discarder_default.start();
15051
- this.#isDiscardingStdin = true;
15231
+ byTool[key].count++;
15232
+ byTool[key].timeSaved += f3.estimatedTimeWastedMinutes * (f3.recommendedFix.estimatedImprovementPercent / 100);
15233
+ byTool[key].costSaved += f3.estimatedCostWasted * (f3.recommendedFix.estimatedImprovementPercent / 100);
15234
+ }
15235
+ const recommendations = [];
15236
+ for (const [tool, data] of Object.entries(byTool)) {
15237
+ recommendations.push({
15238
+ tool,
15239
+ description: data.fix.description,
15240
+ installCommand: data.fix.installCommand,
15241
+ wouldHavePreventedCount: data.count,
15242
+ estimatedTimeSavedMinutes: Math.round(data.timeSaved),
15243
+ estimatedCostSaved: Math.round(data.costSaved * 100) / 100
15244
+ });
15245
+ }
15246
+ recommendations.sort((a2, b2) => b2.estimatedTimeSavedMinutes - a2.estimatedTimeSavedMinutes);
15247
+ return recommendations;
15248
+ }
15249
+ function formatFrustrationSummary(summary) {
15250
+ const lines = [];
15251
+ if (summary.totalFrustrations === 0) {
15252
+ lines.push(` ${icons.success} ${colors2.success("No frustration events detected!")}`);
15253
+ return lines;
15254
+ }
15255
+ lines.push("");
15256
+ lines.push(colors2.dim(" " + "═".repeat(50)));
15257
+ lines.push(` ${colors2.error("!")} ${colors2.bold("Frustration Analysis")}`);
15258
+ lines.push(colors2.dim(" " + "═".repeat(50)));
15259
+ lines.push("");
15260
+ lines.push(` ${colors2.bold("Summary:")}`);
15261
+ lines.push(` ${icons.warning} ${summary.totalFrustrations} frustration events detected`);
15262
+ lines.push(` ${colors2.dim(">")} ~${summary.totalTimeWastedMinutes} min wasted`);
15263
+ lines.push(` ${icons.money} ~$${summary.totalCostWasted.toFixed(2)} in tokens`);
15264
+ lines.push("");
15265
+ if (Object.keys(summary.byRootCause).length > 0) {
15266
+ lines.push(` ${colors2.bold("Root Causes:")}`);
15267
+ for (const [cause, count] of Object.entries(summary.byRootCause)) {
15268
+ const label = formatCauseLabel(cause);
15269
+ lines.push(` ${colors2.dim("•")} ${label}: ${count}x`);
15052
15270
  }
15053
- this.#installHook();
15054
- this.render();
15055
- this.#id = setInterval(this.render.bind(this), this.interval);
15056
- return this;
15271
+ lines.push("");
15057
15272
  }
15058
- stop() {
15059
- clearInterval(this.#id);
15060
- this.#id = undefined;
15061
- this.#frameIndex = -1;
15062
- this.#lastFrameTime = 0;
15063
- this.#clearRenderDeferral();
15064
- this.#uninstallHook();
15065
- if (this.#drainHandler) {
15066
- this.#stream.removeListener("drain", this.#drainHandler);
15067
- this.#drainHandler = undefined;
15273
+ if (summary.topFrustrations.length > 0) {
15274
+ lines.push(` ${colors2.bold("Top Frustrations:")}`);
15275
+ lines.push("");
15276
+ for (let i3 = 0;i3 < Math.min(3, summary.topFrustrations.length); i3++) {
15277
+ const f3 = summary.topFrustrations[i3];
15278
+ lines.push(...formatFrustrationEvent(f3, i3 + 1));
15279
+ lines.push("");
15068
15280
  }
15069
- if (this.isEnabled) {
15070
- this.clear();
15071
- if (this.#options.hideCursor) {
15072
- cli_cursor_default.show(this.#stream);
15073
- }
15281
+ }
15282
+ if (summary.recommendations.length > 0) {
15283
+ lines.push(` ${colors2.bold(colors2.primary("Recommended Fixes:"))}`);
15284
+ lines.push("");
15285
+ for (const rec of summary.recommendations.slice(0, 3)) {
15286
+ lines.push(` ${colors2.success("+")} ${colors2.bold(rec.tool)}`);
15287
+ lines.push(` Would have prevented ${rec.wouldHavePreventedCount} issue${rec.wouldHavePreventedCount > 1 ? "s" : ""}`);
15288
+ lines.push(` Est. time saved: ${colors2.success(`~${rec.estimatedTimeSavedMinutes} min`)}`);
15289
+ lines.push(` ${colors2.dim("$")} ${colors2.primary(rec.installCommand)}`);
15290
+ lines.push("");
15074
15291
  }
15075
- if (this.#isDiscardingStdin) {
15076
- this.#isDiscardingStdin = false;
15077
- stdin_discarder_default.stop();
15292
+ }
15293
+ lines.push(colors2.dim(" " + "═".repeat(50)));
15294
+ return lines;
15295
+ }
15296
+ function formatFrustrationEvent(f3, index) {
15297
+ const lines = [];
15298
+ lines.push(` ${colors2.warning(`${index}.`)} ${colors2.bold(f3.patternDescription)} ${colors2.dim(`(${f3.agent})`)}`);
15299
+ lines.push(` ${colors2.dim("Session:")} ${f3.sessionId.slice(0, 12)}...`);
15300
+ lines.push(` ${colors2.dim("Time wasted:")} ~${f3.estimatedTimeWastedMinutes} min`);
15301
+ lines.push(` ${colors2.dim("Cause:")} ${formatCauseLabel(f3.rootCause.category)}`);
15302
+ if (f3.exactPrompt && f3.exactPrompt.length > 0 && !f3.exactPrompt.startsWith("(Unable")) {
15303
+ lines.push("");
15304
+ lines.push(` ${colors2.dim("┌" + "─".repeat(40))}`);
15305
+ const promptLines = wrapText(f3.exactPrompt, 38);
15306
+ for (const line of promptLines.slice(0, 4)) {
15307
+ lines.push(` ${colors2.dim("│")} ${line}`);
15308
+ }
15309
+ if (promptLines.length > 4) {
15310
+ lines.push(` ${colors2.dim("│")} ${colors2.dim("...")}`);
15311
+ }
15312
+ lines.push(` ${colors2.dim("└" + "─".repeat(40))}`);
15313
+ }
15314
+ lines.push("");
15315
+ lines.push(` ${colors2.success("Fix:")} ${f3.recommendedFix.tool}`);
15316
+ lines.push(` ${colors2.dim("$")} ${colors2.primary(f3.recommendedFix.installCommand)}`);
15317
+ return lines;
15318
+ }
15319
+ function formatCauseLabel(cause) {
15320
+ const labels = {
15321
+ hallucination: colors2.error("API Hallucination"),
15322
+ missing_docs: colors2.warning("Missing Docs"),
15323
+ context_loss: colors2.warning("Context Loss"),
15324
+ unclear_prompt: colors2.dim("Unclear Prompt"),
15325
+ tool_failure: colors2.error("Tool Failure"),
15326
+ undo_loop: colors2.warning("Undo Loop"),
15327
+ unknown: colors2.dim("Unknown")
15328
+ };
15329
+ return labels[cause] || cause;
15330
+ }
15331
+ function wrapText(text, width) {
15332
+ const words = text.split(/\s+/);
15333
+ const lines = [];
15334
+ let currentLine = "";
15335
+ for (const word of words) {
15336
+ if (currentLine.length + word.length + 1 <= width) {
15337
+ currentLine += (currentLine ? " " : "") + word;
15338
+ } else {
15339
+ if (currentLine)
15340
+ lines.push(currentLine);
15341
+ currentLine = word.slice(0, width);
15342
+ }
15343
+ }
15344
+ if (currentLine)
15345
+ lines.push(currentLine);
15346
+ return lines;
15347
+ }
15348
+
15349
+ // src/lib/optimization-installer.ts
15350
+ import { execSync as execSync2, spawnSync } from "node:child_process";
15351
+ import { existsSync as existsSync8, writeFileSync as writeFileSync3, readFileSync as readFileSync8, mkdirSync as mkdirSync3, readdirSync as readdirSync4 } from "node:fs";
15352
+ import { join as join8 } from "node:path";
15353
+ import { homedir as homedir7 } from "node:os";
15354
+ async function installOptimizations(optimizations, projectDir, options = {}) {
15355
+ const results = [];
15356
+ for (const opt of optimizations) {
15357
+ if (!opt.selected)
15358
+ continue;
15359
+ try {
15360
+ const result = await installSingleOptimization(opt, projectDir, options);
15361
+ results.push(result);
15362
+ } catch (err) {
15363
+ results.push({
15364
+ id: opt.id,
15365
+ name: opt.name,
15366
+ success: false,
15367
+ message: "Installation failed",
15368
+ error: err instanceof Error ? err.message : String(err)
15369
+ });
15078
15370
  }
15079
- return this;
15080
15371
  }
15081
- succeed(text) {
15082
- return this.stopAndPersist({ symbol: exports_symbols.success, text });
15372
+ return results;
15373
+ }
15374
+ async function installSingleOptimization(opt, projectDir, options) {
15375
+ if (options.dryRun) {
15376
+ return {
15377
+ id: opt.id,
15378
+ name: opt.name,
15379
+ success: true,
15380
+ message: `[DRY RUN] Would install: ${opt.installCommand || opt.configPath || "unknown"}`
15381
+ };
15083
15382
  }
15084
- fail(text) {
15085
- return this.stopAndPersist({ symbol: exports_symbols.error, text });
15383
+ switch (opt.type) {
15384
+ case "mcp":
15385
+ return installMCP(opt, projectDir, options.agent);
15386
+ case "skill":
15387
+ return installSkill(opt);
15388
+ case "config":
15389
+ return installConfig(opt, projectDir);
15390
+ case "hook":
15391
+ return installHook(opt, projectDir);
15392
+ default:
15393
+ return {
15394
+ id: opt.id,
15395
+ name: opt.name,
15396
+ success: false,
15397
+ message: "Unknown optimization type"
15398
+ };
15086
15399
  }
15087
- warn(text) {
15088
- return this.stopAndPersist({ symbol: exports_symbols.warning, text });
15400
+ }
15401
+ function installMCP(opt, projectDir, agent) {
15402
+ if (opt.installCommand?.startsWith("claude mcp add")) {
15403
+ try {
15404
+ execSync2(opt.installCommand, {
15405
+ encoding: "utf-8",
15406
+ stdio: "pipe",
15407
+ timeout: 60000
15408
+ });
15409
+ return {
15410
+ id: opt.id,
15411
+ name: opt.name,
15412
+ success: true,
15413
+ message: `Installed via: ${opt.installCommand}`
15414
+ };
15415
+ } catch (err) {}
15089
15416
  }
15090
- info(text) {
15091
- return this.stopAndPersist({ symbol: exports_symbols.info, text });
15417
+ if (opt.id === "beads" || opt.name.toLowerCase().includes("beads")) {
15418
+ try {
15419
+ execSync2("bun add -g beads", { encoding: "utf-8", stdio: "pipe", timeout: 60000 });
15420
+ execSync2("bd init", { cwd: projectDir, encoding: "utf-8", stdio: "pipe", timeout: 30000 });
15421
+ return {
15422
+ id: opt.id,
15423
+ name: opt.name,
15424
+ success: true,
15425
+ message: "Installed Beads globally and initialized in project"
15426
+ };
15427
+ } catch (err) {
15428
+ return {
15429
+ id: opt.id,
15430
+ name: opt.name,
15431
+ success: false,
15432
+ message: "Failed to install Beads",
15433
+ error: err instanceof Error ? err.message : String(err)
15434
+ };
15435
+ }
15092
15436
  }
15093
- stopAndPersist(options = {}) {
15094
- if (this.isSilent) {
15095
- return this;
15437
+ if (opt.installCommand) {
15438
+ try {
15439
+ execSync2(opt.installCommand, {
15440
+ encoding: "utf-8",
15441
+ stdio: "pipe",
15442
+ timeout: 60000
15443
+ });
15444
+ return {
15445
+ id: opt.id,
15446
+ name: opt.name,
15447
+ success: true,
15448
+ message: `Installed via: ${opt.installCommand}`
15449
+ };
15450
+ } catch (err) {
15451
+ return {
15452
+ id: opt.id,
15453
+ name: opt.name,
15454
+ success: false,
15455
+ message: "Failed to install MCP",
15456
+ error: err instanceof Error ? err.message : String(err)
15457
+ };
15096
15458
  }
15097
- const symbol = options.symbol ?? " ";
15098
- const text = options.text ?? this.text;
15099
- const prefixText = options.prefixText ?? this.#options.prefixText;
15100
- const suffixText = options.suffixText ?? this.#options.suffixText;
15101
- const textToWrite = this.#buildOutputLine(symbol, text, prefixText, suffixText) + `
15102
- `;
15103
- this.stop();
15104
- this.#internalWrite(() => this.#stream.write(textToWrite));
15105
- return this;
15106
15459
  }
15107
- }
15108
- function ora(options) {
15109
- return new Ora(options);
15110
- }
15111
-
15112
- // src/lib/ui.ts
15113
- var import_picocolors = __toESM(require_picocolors(), 1);
15114
- import * as readline from "readline";
15115
- var colors2 = {
15116
- primary: import_picocolors.default.cyan,
15117
- secondary: import_picocolors.default.blue,
15118
- accent: import_picocolors.default.magenta,
15119
- success: import_picocolors.default.green,
15120
- warning: import_picocolors.default.yellow,
15121
- error: import_picocolors.default.red,
15122
- info: import_picocolors.default.blue,
15123
- dim: import_picocolors.default.dim,
15124
- bold: import_picocolors.default.bold,
15125
- italic: import_picocolors.default.italic,
15126
- excellent: import_picocolors.default.green,
15127
- good: import_picocolors.default.cyan,
15128
- moderate: import_picocolors.default.yellow,
15129
- poor: import_picocolors.default.red,
15130
- highlight: (text) => import_picocolors.default.bold(import_picocolors.default.cyan(text)),
15131
- muted: (text) => import_picocolors.default.dim(import_picocolors.default.gray(text)),
15132
- link: (text) => import_picocolors.default.underline(import_picocolors.default.blue(text))
15133
- };
15134
- function createSpinner(text) {
15135
- return ora({
15136
- text,
15137
- spinner: "dots",
15138
- color: "cyan"
15139
- });
15140
- }
15141
- function formatTier(tier) {
15142
- const tierColors = {
15143
- elite: (s2) => import_picocolors.default.bold(import_picocolors.default.magenta(s2)),
15144
- expert: (s2) => import_picocolors.default.bold(import_picocolors.default.cyan(s2)),
15145
- advanced: (s2) => import_picocolors.default.bold(import_picocolors.default.green(s2)),
15146
- proficient: (s2) => import_picocolors.default.blue(s2),
15147
- intermediate: (s2) => import_picocolors.default.yellow(s2),
15148
- developing: (s2) => import_picocolors.default.dim(s2)
15460
+ return {
15461
+ id: opt.id,
15462
+ name: opt.name,
15463
+ success: false,
15464
+ message: "No install command available"
15149
15465
  };
15150
- const colorFn = tierColors[tier.toLowerCase()] ?? colors2.dim;
15151
- return colorFn(tier.charAt(0).toUpperCase() + tier.slice(1));
15152
15466
  }
15153
- var icons = {
15154
- success: colors2.success("✔"),
15155
- error: colors2.error("✖"),
15156
- warning: colors2.warning("⚠"),
15157
- info: colors2.info("ℹ"),
15158
- arrow: colors2.primary("→"),
15159
- bullet: colors2.dim(""),
15160
- star: colors2.warning("★"),
15161
- check: colors2.success("✓"),
15162
- cross: colors2.error("✗"),
15163
- pending: colors2.dim(""),
15164
- lightning: "",
15165
- fire: "\uD83D\uDD25",
15166
- rocket: "\uD83D\uDE80",
15167
- chart: "\uD83D\uDCCA",
15168
- money: "\uD83D\uDCB0",
15169
- brain: "\uD83E\uDDE0",
15170
- target: "\uD83C\uDFAF"
15171
- };
15172
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
15173
- function clearLine2() {
15174
- readline.clearLine(process.stdout, 0);
15175
- readline.cursorTo(process.stdout, 0);
15467
+ function installSkill(opt) {
15468
+ if (!opt.installCommand) {
15469
+ return {
15470
+ id: opt.id,
15471
+ name: opt.name,
15472
+ success: false,
15473
+ message: "No install command for skill"
15474
+ };
15475
+ }
15476
+ try {
15477
+ const skillPath = opt.installCommand.replace(/^npx\s+/, "").replace(/^bunx\s+/, "").replace(/^skills\s+add\s+/, "").trim();
15478
+ let result = spawnSync("bunx", ["--bun", "skills", "add", skillPath], {
15479
+ encoding: "utf-8",
15480
+ stdio: "pipe",
15481
+ timeout: 120000
15482
+ });
15483
+ if (result.status !== 0) {
15484
+ result = spawnSync("npx", ["-y", "skills", "add", skillPath], {
15485
+ encoding: "utf-8",
15486
+ stdio: "pipe",
15487
+ timeout: 120000
15488
+ });
15489
+ }
15490
+ if (result.status === 0) {
15491
+ return {
15492
+ id: opt.id,
15493
+ name: opt.name,
15494
+ success: true,
15495
+ message: `Skill installed successfully`
15496
+ };
15497
+ } else {
15498
+ const output = (result.stderr || result.stdout || "").toLowerCase();
15499
+ if (output.includes("already exists") || output.includes("already installed")) {
15500
+ return {
15501
+ id: opt.id,
15502
+ name: opt.name,
15503
+ success: true,
15504
+ message: `Skill already installed`
15505
+ };
15506
+ }
15507
+ return {
15508
+ id: opt.id,
15509
+ name: opt.name,
15510
+ success: false,
15511
+ message: "Skill installation failed",
15512
+ error: result.stderr || result.stdout || "Unknown error"
15513
+ };
15514
+ }
15515
+ } catch (err) {
15516
+ return {
15517
+ id: opt.id,
15518
+ name: opt.name,
15519
+ success: false,
15520
+ message: "Failed to install skill",
15521
+ error: err instanceof Error ? err.message : String(err)
15522
+ };
15523
+ }
15176
15524
  }
15177
- function write(text) {
15178
- process.stdout.write(text);
15525
+ function installConfig(opt, projectDir) {
15526
+ if (!opt.configPath || !opt.configContent) {
15527
+ return {
15528
+ id: opt.id,
15529
+ name: opt.name,
15530
+ success: false,
15531
+ message: "No config path or content provided"
15532
+ };
15533
+ }
15534
+ try {
15535
+ const fullPath = opt.configPath.startsWith("/") || opt.configPath.startsWith("~") ? opt.configPath.replace("~", homedir7()) : join8(projectDir, opt.configPath);
15536
+ if (existsSync8(fullPath)) {
15537
+ return {
15538
+ id: opt.id,
15539
+ name: opt.name,
15540
+ success: true,
15541
+ message: `Config already exists: ${opt.configPath}`
15542
+ };
15543
+ }
15544
+ const parentDir = fullPath.substring(0, fullPath.lastIndexOf("/"));
15545
+ if (parentDir && !existsSync8(parentDir)) {
15546
+ mkdirSync3(parentDir, { recursive: true });
15547
+ }
15548
+ writeFileSync3(fullPath, opt.configContent);
15549
+ return {
15550
+ id: opt.id,
15551
+ name: opt.name,
15552
+ success: true,
15553
+ message: `Created: ${opt.configPath}`
15554
+ };
15555
+ } catch (err) {
15556
+ return {
15557
+ id: opt.id,
15558
+ name: opt.name,
15559
+ success: false,
15560
+ message: "Failed to create config",
15561
+ error: err instanceof Error ? err.message : String(err)
15562
+ };
15563
+ }
15179
15564
  }
15180
- async function animateProgressBar(label, score, options = {}) {
15181
- const { width = 20, charDelay = 40, labelWidth = 20 } = options;
15182
- const filled = Math.round(score / 100 * width);
15183
- let barColor = colors2.excellent;
15184
- if (score < 40)
15185
- barColor = colors2.poor;
15186
- else if (score < 60)
15187
- barColor = colors2.moderate;
15188
- else if (score < 75)
15189
- barColor = colors2.good;
15190
- const paddedLabel = label.padEnd(labelWidth);
15191
- const scoreStr = `${score}/100`.padStart(7);
15192
- write(` ${colors2.dim(paddedLabel)} ${colors2.dim(scoreStr)} `);
15193
- for (let i3 = 0;i3 < width; i3++) {
15194
- if (i3 < filled) {
15195
- write(barColor("█"));
15565
+ function installHook(opt, projectDir) {
15566
+ const agentsMdPath = join8(projectDir, "AGENTS.md");
15567
+ try {
15568
+ let content = "";
15569
+ if (existsSync8(agentsMdPath)) {
15570
+ content = readFileSync8(agentsMdPath, "utf-8");
15196
15571
  } else {
15197
- write(colors2.dim("░"));
15572
+ content = `# Agent Instructions
15573
+
15574
+ `;
15198
15575
  }
15199
- await sleep(charDelay);
15576
+ const hookName = opt.name.toLowerCase();
15577
+ if (content.toLowerCase().includes(hookName)) {
15578
+ return {
15579
+ id: opt.id,
15580
+ name: opt.name,
15581
+ success: true,
15582
+ message: "Hook already configured in AGENTS.md"
15583
+ };
15584
+ }
15585
+ const hookSection = opt.configContent || `
15586
+ ## ${opt.name}
15587
+ ${opt.description}
15588
+ `;
15589
+ content += `
15590
+ ` + hookSection;
15591
+ writeFileSync3(agentsMdPath, content);
15592
+ return {
15593
+ id: opt.id,
15594
+ name: opt.name,
15595
+ success: true,
15596
+ message: "Added hook to AGENTS.md"
15597
+ };
15598
+ } catch (err) {
15599
+ return {
15600
+ id: opt.id,
15601
+ name: opt.name,
15602
+ success: false,
15603
+ message: "Failed to add hook",
15604
+ error: err instanceof Error ? err.message : String(err)
15605
+ };
15200
15606
  }
15201
- console.log();
15202
- }
15203
- async function thinkingStep(thinkingText, duration = 800, successText) {
15204
- const spinner = createSpinner(thinkingText);
15205
- spinner.start();
15206
- await sleep(duration);
15207
- spinner.succeed(successText ?? thinkingText);
15208
- }
15209
- async function revealDiscovery(icon, text, delay2 = 300) {
15210
- await sleep(delay2);
15211
- console.log(` ${icon} ${text}`);
15212
15607
  }
15213
- async function showPhaseHeader(phase, total, title, delay2 = 400) {
15214
- await sleep(delay2);
15215
- console.log();
15216
- console.log(` ${colors2.dim(`Phase ${phase}/${total}:`)} ${colors2.bold(colors2.primary(title))}`);
15217
- console.log(colors2.dim(" " + "─".repeat(40)));
15608
+ function phaseRecToInstallable(rec) {
15609
+ return {
15610
+ id: rec.title.toLowerCase().replace(/\s+/g, "-"),
15611
+ name: rec.title,
15612
+ type: rec.type,
15613
+ description: rec.description,
15614
+ installCommand: rec.installCommand,
15615
+ selected: false
15616
+ };
15218
15617
  }
15219
- async function animateScoreReveal(score, tier, options = {}) {
15220
- const { countDuration = 1000, barDelay = 50 } = options;
15221
- const scoreColor = score >= 80 ? colors2.excellent : score >= 60 ? colors2.good : score >= 40 ? colors2.moderate : colors2.poor;
15222
- console.log();
15223
- console.log(colors2.dim(" " + "═".repeat(45)));
15224
- console.log();
15225
- const steps = 30;
15226
- const stepDuration = countDuration / steps;
15227
- for (let i3 = 1;i3 <= steps; i3++) {
15228
- const current = Math.round(i3 / steps * score);
15229
- clearLine2();
15230
- write(` ${colors2.bold("Your Score:")} ${scoreColor(colors2.bold(current.toString()))}/100`);
15231
- await sleep(stepDuration);
15618
+ function checkInstalledStatus(projectDir) {
15619
+ const home = homedir7();
15620
+ const status = {
15621
+ context7: false,
15622
+ supermemory: false,
15623
+ nia: false,
15624
+ beads: false,
15625
+ skills: []
15626
+ };
15627
+ const mcpConfigPaths = [
15628
+ join8(home, ".claude.json"),
15629
+ join8(home, ".claude", "claude_desktop_config.json"),
15630
+ join8(home, ".claude", "settings.json"),
15631
+ join8(home, ".claude", "settings.local.json"),
15632
+ join8(projectDir, ".mcp.json"),
15633
+ join8(projectDir, ".claude", "settings.local.json")
15634
+ ];
15635
+ for (const configPath of mcpConfigPaths) {
15636
+ if (existsSync8(configPath)) {
15637
+ try {
15638
+ const config = JSON.parse(readFileSync8(configPath, "utf-8"));
15639
+ const mcpServers = config.mcpServers || config.mcp_servers || {};
15640
+ for (const name of Object.keys(mcpServers)) {
15641
+ const mcpName = name.toLowerCase();
15642
+ if (mcpName.includes("context7") || mcpName === "c7") {
15643
+ status.context7 = true;
15644
+ }
15645
+ if (mcpName.includes("supermemory") || mcpName.includes("memory")) {
15646
+ status.supermemory = true;
15647
+ }
15648
+ if (mcpName.includes("nia")) {
15649
+ status.nia = true;
15650
+ }
15651
+ }
15652
+ } catch {}
15653
+ }
15232
15654
  }
15233
- write(` ${colors2.dim("(")}${formatTier(tier)}${colors2.dim(")")}`);
15234
- console.log();
15235
- console.log();
15236
- const barWidth = 30;
15237
- const filled = Math.round(score / 100 * barWidth);
15238
- write(" ");
15239
- for (let i3 = 0;i3 < barWidth; i3++) {
15240
- if (i3 < filled) {
15241
- write(scoreColor("█"));
15242
- } else {
15243
- write(colors2.dim("░"));
15655
+ status.beads = existsSync8(join8(projectDir, ".beads"));
15656
+ const skillsDirs = [
15657
+ join8(home, ".claude", "skills"),
15658
+ join8(home, ".config", "opencode", "skills"),
15659
+ join8(home, ".agents", "skills"),
15660
+ join8(projectDir, ".opencode", "skill")
15661
+ ];
15662
+ for (const dir of skillsDirs) {
15663
+ if (existsSync8(dir)) {
15664
+ try {
15665
+ const skills = readdirSync4(dir);
15666
+ for (const skill of skills) {
15667
+ if (!status.skills.includes(skill.toLowerCase())) {
15668
+ status.skills.push(skill.toLowerCase());
15669
+ }
15670
+ }
15671
+ } catch {}
15244
15672
  }
15245
- await sleep(barDelay);
15246
15673
  }
15247
- console.log();
15248
- console.log();
15249
- console.log(colors2.dim(" " + "═".repeat(45)));
15674
+ return status;
15250
15675
  }
15251
- async function showFinding(type, label, value, unit = "", delay2 = 250) {
15252
- await sleep(delay2);
15253
- const iconMap = {
15254
- warning: icons.warning,
15255
- error: icons.error,
15256
- info: icons.info,
15257
- success: icons.success
15258
- };
15259
- const colorMap = {
15260
- warning: colors2.warning,
15261
- error: colors2.error,
15262
- info: colors2.info,
15263
- success: colors2.success
15264
- };
15265
- const icon = iconMap[type];
15266
- const valueColor = colorMap[type];
15267
- console.log(` ${icon} ${label}: ${valueColor(value.toString())}${unit}`);
15676
+ function filterAlreadyInstalled(optimizations, status) {
15677
+ return optimizations.filter((opt) => {
15678
+ const id = opt.id.toLowerCase();
15679
+ if (id === "context7" && status.context7)
15680
+ return false;
15681
+ if (id === "supermemory" && status.supermemory)
15682
+ return false;
15683
+ if (id === "nia" && status.nia)
15684
+ return false;
15685
+ if (id === "beads" && status.beads)
15686
+ return false;
15687
+ if (opt.type === "skill") {
15688
+ const skillName = opt.name.toLowerCase().replace(/\s+/g, "-");
15689
+ if (status.skills.some((s2) => s2.includes(skillName) || skillName.includes(s2) || s2.includes("tdd") && id.includes("tdd") || s2.includes("debug") && id.includes("debug") || s2.includes("plan") && id.includes("plan"))) {
15690
+ return false;
15691
+ }
15692
+ }
15693
+ return true;
15694
+ });
15268
15695
  }
15696
+ var QUICK_INSTALL_PRESETS = {
15697
+ essential: [
15698
+ {
15699
+ id: "context7",
15700
+ name: "Context7 MCP",
15701
+ type: "mcp",
15702
+ description: "Up-to-date library documentation",
15703
+ installCommand: "claude mcp add context7 https://mcp.context7.com/mcp --transport http",
15704
+ selected: true
15705
+ },
15706
+ {
15707
+ id: "supermemory",
15708
+ name: "Supermemory MCP",
15709
+ type: "mcp",
15710
+ description: "Persistent memory across sessions",
15711
+ installCommand: "claude mcp add supermemory -- npx -y @supermemory/mcp@latest",
15712
+ selected: true
15713
+ },
15714
+ {
15715
+ id: "beads",
15716
+ name: "Beads Task Manager",
15717
+ type: "mcp",
15718
+ description: "Persistent task tracking",
15719
+ installCommand: "bun add -g beads && bd init",
15720
+ selected: true
15721
+ }
15722
+ ],
15723
+ productivity: [
15724
+ {
15725
+ id: "claude-md",
15726
+ name: "CLAUDE.md",
15727
+ type: "config",
15728
+ description: "Project context file",
15729
+ configPath: "CLAUDE.md",
15730
+ configContent: `# Project Instructions
15731
+
15732
+ ## Build & Test Commands
15733
+ \`\`\`bash
15734
+ bun test # Run tests
15735
+ bun run build # Build project
15736
+ bun run lint # Run linter
15737
+ \`\`\`
15738
+
15739
+ ## Architecture
15740
+ Describe your project structure here.
15741
+
15742
+ ## Conventions
15743
+ - Use TypeScript
15744
+ - Write tests for new features
15745
+ - Use conventional commits
15746
+ `,
15747
+ selected: true
15748
+ },
15749
+ {
15750
+ id: "vitest",
15751
+ name: "Vitest",
15752
+ type: "library",
15753
+ description: "Fast unit testing framework",
15754
+ installCommand: "bun add -D vitest",
15755
+ selected: true
15756
+ }
15757
+ ]
15758
+ };
15269
15759
 
15270
15760
  // src/commands/scan.ts
15271
15761
  var scanCommand = defineCommand2({
@@ -15489,10 +15979,19 @@ var scanCommand = defineCommand2({
15489
15979
  console.log(colors2.dim(" " + "═".repeat(45)));
15490
15980
  console.log();
15491
15981
  if (!args["no-report"]) {
15492
- const reportDir = join8(projectDir, args["report-dir"]);
15982
+ const reportDir = join9(projectDir, args["report-dir"]);
15493
15983
  const reportPath = generateReport(reportDir, score, git, agents, tests, args.since, sdlcAnalysis, scanCost, analysis);
15494
15984
  console.log(` ${icons.success} Report saved: ${colors2.dim(reportPath)}`);
15495
15985
  }
15986
+ if (agents && agents.sessions.length > 0 && !args.brief) {
15987
+ const frustrationSummary = detectFrustrations(agents.sessions);
15988
+ if (frustrationSummary.totalFrustrations > 0) {
15989
+ const frustrationLines = formatFrustrationSummary(frustrationSummary);
15990
+ for (const line of frustrationLines) {
15991
+ console.log(line);
15992
+ }
15993
+ }
15994
+ }
15496
15995
  if (!args.brief) {
15497
15996
  const installedStatus = checkInstalledStatus(projectDir);
15498
15997
  const projectContext = loadProjectContext(projectDir);
@@ -15754,12 +16253,12 @@ var scanCommand = defineCommand2({
15754
16253
  }
15755
16254
  });
15756
16255
  function loadProjectContext(projectDir) {
15757
- const contextPath = join8(projectDir, ".nairon", "context.json");
15758
- if (!existsSync8(contextPath)) {
16256
+ const contextPath = join9(projectDir, ".nairon", "context.json");
16257
+ if (!existsSync9(contextPath)) {
15759
16258
  return null;
15760
16259
  }
15761
16260
  try {
15762
- const raw = readFileSync8(contextPath, "utf-8");
16261
+ const raw = readFileSync9(contextPath, "utf-8");
15763
16262
  const data = JSON.parse(raw);
15764
16263
  const context = {
15765
16264
  painPoints: data.painPoints || [],
@@ -15812,12 +16311,12 @@ function parseSince(since) {
15812
16311
  return new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
15813
16312
  }
15814
16313
  function generateReport(reportDir, score, git, agents, tests, since, sdlcAnalysis, scanCost, analysis) {
15815
- if (!existsSync8(reportDir)) {
16314
+ if (!existsSync9(reportDir)) {
15816
16315
  mkdirSync4(reportDir, { recursive: true });
15817
16316
  }
15818
16317
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
15819
16318
  const filename = `scan-${timestamp}.md`;
15820
- const filepath = join8(reportDir, filename);
16319
+ const filepath = join9(reportDir, filename);
15821
16320
  const lines = [];
15822
16321
  lines.push(`# NaironAI Scan Report`);
15823
16322
  lines.push("");
@@ -15958,11 +16457,11 @@ init_dist();
15958
16457
  init_client();
15959
16458
 
15960
16459
  // src/collectors/report-data.ts
15961
- import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync9, statSync as statSync2 } from "node:fs";
15962
- import { homedir as homedir7 } from "node:os";
15963
- import { join as join9, basename as basename2 } from "node:path";
16460
+ import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync10, statSync as statSync2 } from "node:fs";
16461
+ import { homedir as homedir8 } from "node:os";
16462
+ import { join as join10, basename as basename3 } from "node:path";
15964
16463
  async function collectReportData(projectDir, since, until = new Date, harness) {
15965
- const projectName = basename2(projectDir);
16464
+ const projectName = basename3(projectDir);
15966
16465
  const [commits, allSessions, mcpConfigs] = await Promise.all([
15967
16466
  collectGitCommits(projectDir, since, until),
15968
16467
  collectAllSessions(since, until, projectDir),
@@ -16043,12 +16542,12 @@ function detectAIAssistedCommit(message) {
16043
16542
  }
16044
16543
  async function collectAllSessions(since, until, projectDir) {
16045
16544
  const sessions = [];
16046
- const claudeDir = join9(homedir7(), ".claude");
16047
- if (existsSync9(claudeDir)) {
16545
+ const claudeDir = join10(homedir8(), ".claude");
16546
+ if (existsSync10(claudeDir)) {
16048
16547
  sessions.push(...collectClaudeCodeSessions(claudeDir, since, until, projectDir));
16049
16548
  }
16050
- const openCodeDir = join9(homedir7(), ".local", "share", "opencode");
16051
- if (existsSync9(openCodeDir)) {
16549
+ const openCodeDir = join10(homedir8(), ".local", "share", "opencode");
16550
+ if (existsSync10(openCodeDir)) {
16052
16551
  sessions.push(...collectOpenCodeSessions2(openCodeDir, since, until));
16053
16552
  }
16054
16553
  sessions.sort((a2, b2) => a2.startTime.getTime() - b2.startTime.getTime());
@@ -16056,15 +16555,15 @@ async function collectAllSessions(since, until, projectDir) {
16056
16555
  }
16057
16556
  function collectClaudeCodeSessions(claudeDir, since, until, projectDir) {
16058
16557
  const sessions = [];
16059
- const transcriptsDir = join9(claudeDir, "transcripts");
16060
- if (!existsSync9(transcriptsDir))
16558
+ const transcriptsDir = join10(claudeDir, "transcripts");
16559
+ if (!existsSync10(transcriptsDir))
16061
16560
  return sessions;
16062
16561
  const projectHash = projectDir ? hashPath(projectDir) : null;
16063
- const projectsDir = join9(claudeDir, "projects");
16562
+ const projectsDir = join10(claudeDir, "projects");
16064
16563
  try {
16065
16564
  const transcriptFiles = readdirSync5(transcriptsDir).filter((f3) => f3.endsWith(".jsonl"));
16066
16565
  for (const file of transcriptFiles) {
16067
- const filePath = join9(transcriptsDir, file);
16566
+ const filePath = join10(transcriptsDir, file);
16068
16567
  const stat = statSync2(filePath);
16069
16568
  if (stat.mtime < since || stat.mtime > until)
16070
16569
  continue;
@@ -16075,12 +16574,12 @@ function collectClaudeCodeSessions(claudeDir, since, until, projectDir) {
16075
16574
  }
16076
16575
  } catch {}
16077
16576
  }
16078
- if (projectHash && existsSync9(projectsDir)) {
16079
- const projectSessionDir = join9(projectsDir, projectHash);
16080
- if (existsSync9(projectSessionDir)) {
16577
+ if (projectHash && existsSync10(projectsDir)) {
16578
+ const projectSessionDir = join10(projectsDir, projectHash);
16579
+ if (existsSync10(projectSessionDir)) {
16081
16580
  const projectFiles = readdirSync5(projectSessionDir).filter((f3) => f3.endsWith(".jsonl"));
16082
16581
  for (const file of projectFiles) {
16083
- const filePath = join9(projectSessionDir, file);
16582
+ const filePath = join10(projectSessionDir, file);
16084
16583
  const stat = statSync2(filePath);
16085
16584
  if (stat.mtime < since || stat.mtime > until)
16086
16585
  continue;
@@ -16099,7 +16598,7 @@ function collectClaudeCodeSessions(claudeDir, since, until, projectDir) {
16099
16598
  return sessions;
16100
16599
  }
16101
16600
  function parseClaudeTranscript(filePath, fileName) {
16102
- const content = readFileSync9(filePath, "utf-8");
16601
+ const content = readFileSync10(filePath, "utf-8");
16103
16602
  const lines = content.trim().split(`
16104
16603
  `).filter((l2) => l2.trim());
16105
16604
  if (lines.length === 0)
@@ -16189,25 +16688,25 @@ function parseClaudeTranscript(filePath, fileName) {
16189
16688
  }
16190
16689
  function collectOpenCodeSessions2(openCodeDir, since, until) {
16191
16690
  const sessions = [];
16192
- const sessionDir = join9(openCodeDir, "storage", "session");
16193
- const messageDir = join9(openCodeDir, "storage", "message");
16194
- const partDir = join9(openCodeDir, "storage", "part");
16195
- if (!existsSync9(sessionDir))
16691
+ const sessionDir = join10(openCodeDir, "storage", "session");
16692
+ const messageDir = join10(openCodeDir, "storage", "message");
16693
+ const partDir = join10(openCodeDir, "storage", "part");
16694
+ if (!existsSync10(sessionDir))
16196
16695
  return sessions;
16197
16696
  try {
16198
- const projectDirs = readdirSync5(sessionDir, { withFileTypes: true }).filter((d2) => d2.isDirectory()).map((d2) => join9(sessionDir, d2.name));
16697
+ const projectDirs = readdirSync5(sessionDir, { withFileTypes: true }).filter((d2) => d2.isDirectory()).map((d2) => join10(sessionDir, d2.name));
16199
16698
  for (const projectDir of projectDirs) {
16200
16699
  const sessionFiles = readdirSync5(projectDir).filter((f3) => f3.endsWith(".json"));
16201
16700
  for (const sessionFile of sessionFiles) {
16202
16701
  try {
16203
- const sessionPath = join9(projectDir, sessionFile);
16204
- const sessionData = JSON.parse(readFileSync9(sessionPath, "utf-8"));
16702
+ const sessionPath = join10(projectDir, sessionFile);
16703
+ const sessionData = JSON.parse(readFileSync10(sessionPath, "utf-8"));
16205
16704
  const createdAt = new Date(sessionData.time?.created || 0);
16206
16705
  const updatedAt = new Date(sessionData.time?.updated || sessionData.time?.created || 0);
16207
16706
  if (updatedAt < since || createdAt > until)
16208
16707
  continue;
16209
16708
  const sessionId = sessionData.id;
16210
- const sessionMsgDir = join9(messageDir, sessionId);
16709
+ const sessionMsgDir = join10(messageDir, sessionId);
16211
16710
  const prompts = [];
16212
16711
  const responses = [];
16213
16712
  const toolsUsed = new Set;
@@ -16218,12 +16717,12 @@ function collectOpenCodeSessions2(openCodeDir, since, until) {
16218
16717
  let model = "unknown";
16219
16718
  let startTime = createdAt;
16220
16719
  let endTime = updatedAt;
16221
- if (existsSync9(sessionMsgDir)) {
16720
+ if (existsSync10(sessionMsgDir)) {
16222
16721
  const msgFiles = readdirSync5(sessionMsgDir).filter((f3) => f3.endsWith(".json")).sort();
16223
16722
  for (const msgFile of msgFiles) {
16224
16723
  try {
16225
- const msgPath = join9(sessionMsgDir, msgFile);
16226
- const msgData = JSON.parse(readFileSync9(msgPath, "utf-8"));
16724
+ const msgPath = join10(sessionMsgDir, msgFile);
16725
+ const msgData = JSON.parse(readFileSync10(msgPath, "utf-8"));
16227
16726
  const msgId = msgData.id;
16228
16727
  if (msgData.model?.modelID) {
16229
16728
  model = msgData.model.modelID;
@@ -16233,14 +16732,14 @@ function collectOpenCodeSessions2(openCodeDir, since, until) {
16233
16732
  startTime = msgTime;
16234
16733
  if (msgTime > endTime)
16235
16734
  endTime = msgTime;
16236
- const msgPartDir = join9(partDir, msgId);
16735
+ const msgPartDir = join10(partDir, msgId);
16237
16736
  let messageText = "";
16238
- if (existsSync9(msgPartDir)) {
16737
+ if (existsSync10(msgPartDir)) {
16239
16738
  const partFiles = readdirSync5(msgPartDir).filter((f3) => f3.endsWith(".json")).sort();
16240
16739
  for (const partFile of partFiles) {
16241
16740
  try {
16242
- const partPath = join9(msgPartDir, partFile);
16243
- const partData = JSON.parse(readFileSync9(partPath, "utf-8"));
16741
+ const partPath = join10(msgPartDir, partFile);
16742
+ const partData = JSON.parse(readFileSync10(partPath, "utf-8"));
16244
16743
  if (partData.type === "text" && partData.text && !partData.synthetic) {
16245
16744
  messageText += partData.text + `
16246
16745
  `;
@@ -16414,10 +16913,10 @@ function analyzeResponse(text, sessionId, index, promptId, timestamp) {
16414
16913
  }
16415
16914
  function collectMCPConfigs() {
16416
16915
  const configs = [];
16417
- const claudeConfig = join9(homedir7(), ".claude.json");
16418
- if (existsSync9(claudeConfig)) {
16916
+ const claudeConfig = join10(homedir8(), ".claude.json");
16917
+ if (existsSync10(claudeConfig)) {
16419
16918
  try {
16420
- const data = JSON.parse(readFileSync9(claudeConfig, "utf-8"));
16919
+ const data = JSON.parse(readFileSync10(claudeConfig, "utf-8"));
16421
16920
  const mcpServers = data.mcpServers || {};
16422
16921
  for (const [name, config] of Object.entries(mcpServers)) {
16423
16922
  configs.push({
@@ -16430,13 +16929,13 @@ function collectMCPConfigs() {
16430
16929
  } catch {}
16431
16930
  }
16432
16931
  const openCodeConfigs = [
16433
- join9(homedir7(), ".opencode.json"),
16434
- join9(homedir7(), ".config", "opencode", "opencode.json")
16932
+ join10(homedir8(), ".opencode.json"),
16933
+ join10(homedir8(), ".config", "opencode", "opencode.json")
16435
16934
  ];
16436
16935
  for (const configPath of openCodeConfigs) {
16437
- if (existsSync9(configPath)) {
16936
+ if (existsSync10(configPath)) {
16438
16937
  try {
16439
- const data = JSON.parse(readFileSync9(configPath, "utf-8"));
16938
+ const data = JSON.parse(readFileSync10(configPath, "utf-8"));
16440
16939
  const mcpServers = data.mcp || {};
16441
16940
  for (const [name, config] of Object.entries(mcpServers)) {
16442
16941
  if (!configs.find((c3) => c3.name === name)) {
@@ -16451,10 +16950,10 @@ function collectMCPConfigs() {
16451
16950
  } catch {}
16452
16951
  }
16453
16952
  }
16454
- const cursorConfig = join9(homedir7(), ".cursor", "mcp.json");
16455
- if (existsSync9(cursorConfig)) {
16953
+ const cursorConfig = join10(homedir8(), ".cursor", "mcp.json");
16954
+ if (existsSync10(cursorConfig)) {
16456
16955
  try {
16457
- const data = JSON.parse(readFileSync9(cursorConfig, "utf-8"));
16956
+ const data = JSON.parse(readFileSync10(cursorConfig, "utf-8"));
16458
16957
  const mcpServers = data.mcpServers || {};
16459
16958
  for (const [name, config] of Object.entries(mcpServers)) {
16460
16959
  if (!configs.find((c3) => c3.name === name)) {
@@ -18655,9 +19154,9 @@ function getSeverityEmoji(severity) {
18655
19154
  }
18656
19155
 
18657
19156
  // src/lib/tool-analyzer.ts
18658
- import { existsSync as existsSync10, readdirSync as readdirSync6 } from "node:fs";
18659
- import { homedir as homedir8 } from "node:os";
18660
- import { join as join10 } from "node:path";
19157
+ import { existsSync as existsSync11, readdirSync as readdirSync6 } from "node:fs";
19158
+ import { homedir as homedir9 } from "node:os";
19159
+ import { join as join11 } from "node:path";
18661
19160
  function analyzeToolUtilization(data) {
18662
19161
  const mcpServers = analyzeMCPServers(data);
18663
19162
  const mcpSummary = buildMCPSummary(mcpServers);
@@ -18807,24 +19306,24 @@ function categorizeTools(tools) {
18807
19306
  }
18808
19307
  function analyzeSkills() {
18809
19308
  const skills = [];
18810
- const home = homedir8();
19309
+ const home = homedir9();
18811
19310
  const skillsDirs = [
18812
- join10(home, ".claude", "skills"),
18813
- join10(home, ".agents", "skills"),
18814
- join10(home, ".config", "claude", "skills")
19311
+ join11(home, ".claude", "skills"),
19312
+ join11(home, ".agents", "skills"),
19313
+ join11(home, ".config", "claude", "skills")
18815
19314
  ];
18816
19315
  const projectSkillsDirs = [
18817
- join10(process.cwd(), ".claude", "skills"),
18818
- join10(process.cwd(), ".agents", "skills")
19316
+ join11(process.cwd(), ".claude", "skills"),
19317
+ join11(process.cwd(), ".agents", "skills")
18819
19318
  ];
18820
19319
  const openCodeSkillDirs = [
18821
- join10(home, ".config", "opencode", "skills"),
18822
- join10(home, ".local", "share", "opencode", "skills")
19320
+ join11(home, ".config", "opencode", "skills"),
19321
+ join11(home, ".local", "share", "opencode", "skills")
18823
19322
  ];
18824
19323
  const allSkillDirs = [...skillsDirs, ...projectSkillsDirs, ...openCodeSkillDirs];
18825
19324
  const seenSkills = new Set;
18826
19325
  for (const skillsDir of allSkillDirs) {
18827
- if (existsSync10(skillsDir)) {
19326
+ if (existsSync11(skillsDir)) {
18828
19327
  try {
18829
19328
  const entries = readdirSync6(skillsDir, { withFileTypes: true });
18830
19329
  for (const entry of entries) {
@@ -18835,7 +19334,7 @@ function analyzeSkills() {
18835
19334
  if (entry.isDirectory() || entry.isSymbolicLink() || entry.name.endsWith(".md")) {
18836
19335
  skills.push({
18837
19336
  name: skillName,
18838
- path: join10(skillsDir, entry.name),
19337
+ path: join11(skillsDir, entry.name),
18839
19338
  used: false,
18840
19339
  usageCount: 0
18841
19340
  });
@@ -19132,22 +19631,22 @@ function renderToolUtilizationMarkdown(analysis) {
19132
19631
  }
19133
19632
 
19134
19633
  // src/lib/coverage-parser.ts
19135
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "node:fs";
19136
- import { join as join11 } from "node:path";
19634
+ import { existsSync as existsSync12, readFileSync as readFileSync12 } from "node:fs";
19635
+ import { join as join12 } from "node:path";
19137
19636
  function parseCoverageReport(projectDir) {
19138
19637
  const coveragePaths = [
19139
- { path: join11(projectDir, "coverage", "lcov.info"), parser: parseLcov },
19140
- { path: join11(projectDir, "coverage", "coverage-summary.json"), parser: parseIstanbulSummary },
19141
- { path: join11(projectDir, "coverage", "coverage-final.json"), parser: parseIstanbulFinal },
19142
- { path: join11(projectDir, "coverage", "cobertura-coverage.xml"), parser: parseCobertura },
19143
- { path: join11(projectDir, "coverage", "clover.xml"), parser: parseClover },
19144
- { path: join11(projectDir, "lcov.info"), parser: parseLcov },
19145
- { path: join11(projectDir, ".nyc_output", "coverage-summary.json"), parser: parseIstanbulSummary }
19638
+ { path: join12(projectDir, "coverage", "lcov.info"), parser: parseLcov },
19639
+ { path: join12(projectDir, "coverage", "coverage-summary.json"), parser: parseIstanbulSummary },
19640
+ { path: join12(projectDir, "coverage", "coverage-final.json"), parser: parseIstanbulFinal },
19641
+ { path: join12(projectDir, "coverage", "cobertura-coverage.xml"), parser: parseCobertura },
19642
+ { path: join12(projectDir, "coverage", "clover.xml"), parser: parseClover },
19643
+ { path: join12(projectDir, "lcov.info"), parser: parseLcov },
19644
+ { path: join12(projectDir, ".nyc_output", "coverage-summary.json"), parser: parseIstanbulSummary }
19146
19645
  ];
19147
19646
  for (const { path, parser: parser4 } of coveragePaths) {
19148
- if (existsSync11(path)) {
19647
+ if (existsSync12(path)) {
19149
19648
  try {
19150
- const content = readFileSync11(path, "utf-8");
19649
+ const content = readFileSync12(path, "utf-8");
19151
19650
  return parser4(content, path);
19152
19651
  } catch {}
19153
19652
  }
@@ -21357,16 +21856,655 @@ function renderBar4(value, width) {
21357
21856
  }
21358
21857
 
21359
21858
  // src/commands/doctor.ts
21360
- import { existsSync as existsSync12 } from "node:fs";
21361
- import { homedir as homedir9 } from "node:os";
21362
- import { join as join12 } from "node:path";
21859
+ import { existsSync as existsSync13 } from "node:fs";
21860
+ import { homedir as homedir10 } from "node:os";
21861
+ import { join as join13 } from "node:path";
21363
21862
  init_client();
21863
+
21864
+ // src/lib/recommendations-db.ts
21865
+ var MCP_SERVERS = [
21866
+ {
21867
+ id: "context7",
21868
+ name: "Context7",
21869
+ type: "mcp",
21870
+ description: "Live documentation for any library - reduces API hallucinations by 40%+",
21871
+ installCommand: "claude mcp add context7 https://mcp.context7.com/mcp --transport http",
21872
+ phases: ["requirements", "implementation"],
21873
+ impact: "high",
21874
+ detectionKey: "context7",
21875
+ website: "https://context7.com",
21876
+ badge: "Essential",
21877
+ agents: {
21878
+ "claude-code": { installCommand: "claude mcp add context7 https://mcp.context7.com/mcp --transport http" },
21879
+ opencode: { installCommand: "Add to ~/.config/opencode/config.json mcpServers" }
21880
+ }
21881
+ },
21882
+ {
21883
+ id: "supermemory",
21884
+ name: "Supermemory",
21885
+ type: "mcp",
21886
+ description: "Persistent memory across sessions - never re-explain context",
21887
+ installCommand: "claude mcp add supermemory -- npx -y @supermemory/mcp@latest",
21888
+ phases: ["requirements", "implementation"],
21889
+ impact: "high",
21890
+ detectionKey: "supermemory",
21891
+ website: "https://supermemory.ai",
21892
+ badge: "Essential",
21893
+ agents: {
21894
+ "claude-code": { installCommand: "claude mcp add supermemory -- npx -y @supermemory/mcp@latest" }
21895
+ }
21896
+ },
21897
+ {
21898
+ id: "nia",
21899
+ name: "Nia",
21900
+ type: "mcp",
21901
+ description: "Index and search external codebases and documentation",
21902
+ installCommand: "pipx run --no-cache nia-mcp-server",
21903
+ phases: ["requirements"],
21904
+ impact: "medium",
21905
+ detectionKey: "nia",
21906
+ website: "https://trynia.ai"
21907
+ },
21908
+ {
21909
+ id: "greptile",
21910
+ name: "Greptile",
21911
+ type: "mcp",
21912
+ description: "AI-powered code review and PR analysis",
21913
+ installCommand: "claude mcp add greptile -- npx -y @greptile/mcp",
21914
+ phases: ["review"],
21915
+ impact: "high",
21916
+ detectionKey: "greptile",
21917
+ website: "https://www.greptile.com",
21918
+ badge: "Recommended"
21919
+ },
21920
+ {
21921
+ id: "agent-browser",
21922
+ name: "Agent Browser",
21923
+ type: "mcp",
21924
+ description: "Live end-to-end browser testing and automation",
21925
+ installCommand: "claude mcp add agent-browser -- npx -y agent-browser-mcp",
21926
+ phases: ["review"],
21927
+ impact: "high",
21928
+ detectionKey: "agent-browser",
21929
+ website: "https://agent-browser.dev",
21930
+ badge: "Recommended"
21931
+ },
21932
+ {
21933
+ id: "playwright-mcp",
21934
+ name: "Playwright MCP",
21935
+ type: "mcp",
21936
+ description: "Browser automation for E2E testing and web scraping",
21937
+ installCommand: "claude mcp add playwright -- npx -y @anthropic/playwright-mcp",
21938
+ phases: ["review"],
21939
+ impact: "medium",
21940
+ detectionKey: "playwright",
21941
+ website: "https://playwright.dev"
21942
+ },
21943
+ {
21944
+ id: "github-mcp",
21945
+ name: "GitHub MCP",
21946
+ type: "mcp",
21947
+ description: "GitHub integration for PRs, issues, and repo management",
21948
+ installCommand: "claude mcp add github -- npx -y @anthropic/github-mcp",
21949
+ phases: ["planning", "review"],
21950
+ impact: "medium",
21951
+ detectionKey: "github"
21952
+ }
21953
+ ];
21954
+ var PLUGINS = [
21955
+ {
21956
+ id: "beads",
21957
+ name: "Beads",
21958
+ type: "plugin",
21959
+ description: "Lightweight issue tracking in git - survives context compaction",
21960
+ installCommand: "bun add -g beads && bd init",
21961
+ phases: ["planning"],
21962
+ impact: "high",
21963
+ detectionKey: "beads",
21964
+ website: "https://github.com/steveyegge/beads",
21965
+ badge: "Recommended"
21966
+ },
21967
+ {
21968
+ id: "linear",
21969
+ name: "Linear",
21970
+ type: "plugin",
21971
+ description: "Sync with Linear issues for team collaboration",
21972
+ phases: ["planning"],
21973
+ impact: "medium",
21974
+ website: "https://linear.app"
21975
+ },
21976
+ {
21977
+ id: "slack",
21978
+ name: "Slack",
21979
+ type: "plugin",
21980
+ description: "Team notifications and updates",
21981
+ phases: ["review"],
21982
+ impact: "low",
21983
+ website: "https://slack.com"
21984
+ }
21985
+ ];
21986
+ var SKILLS = [
21987
+ {
21988
+ id: "tdd-skill",
21989
+ name: "Test-Driven Development",
21990
+ type: "skill",
21991
+ description: "Write tests first, then implement - reduces bugs by 40%",
21992
+ installCommand: "npx skills add obra/superpowers@test-driven-development --yes",
21993
+ phases: ["review"],
21994
+ impact: "high",
21995
+ website: "https://skills.sh"
21996
+ },
21997
+ {
21998
+ id: "debugging-skill",
21999
+ name: "Systematic Debugging",
22000
+ type: "skill",
22001
+ description: "Structured approach to finding and fixing bugs",
22002
+ installCommand: "npx skills add obra/superpowers@systematic-debugging --yes",
22003
+ phases: ["implementation", "review"],
22004
+ impact: "medium",
22005
+ website: "https://skills.sh"
22006
+ },
22007
+ {
22008
+ id: "planning-skill",
22009
+ name: "Writing Plans",
22010
+ type: "skill",
22011
+ description: "Structured planning before implementation",
22012
+ installCommand: "npx skills add obra/superpowers@writing-plans --yes",
22013
+ phases: ["planning"],
22014
+ impact: "medium",
22015
+ website: "https://skills.sh"
22016
+ },
22017
+ {
22018
+ id: "code-review-skill",
22019
+ name: "Code Review",
22020
+ type: "skill",
22021
+ description: "Thorough code review checklist and workflow",
22022
+ installCommand: "npx skills add obra/superpowers@requesting-code-review --yes",
22023
+ phases: ["review"],
22024
+ impact: "medium",
22025
+ website: "https://skills.sh"
22026
+ },
22027
+ {
22028
+ id: "react-skill",
22029
+ name: "React Best Practices",
22030
+ type: "skill",
22031
+ description: "Vercel's React and Next.js patterns",
22032
+ installCommand: "npx skills add vercel-labs/agent-skills@vercel-react-best-practices --yes",
22033
+ phases: ["implementation"],
22034
+ impact: "medium",
22035
+ website: "https://skills.sh"
22036
+ },
22037
+ {
22038
+ id: "remotion-skill",
22039
+ name: "Remotion",
22040
+ type: "skill",
22041
+ description: "Video creation with React",
22042
+ installCommand: "npx skills add remotion-dev/skills@remotion-best-practices --yes",
22043
+ phases: ["implementation"],
22044
+ impact: "low",
22045
+ website: "https://skills.sh"
22046
+ },
22047
+ {
22048
+ id: "frontend-design-skill",
22049
+ name: "Frontend Design",
22050
+ type: "skill",
22051
+ description: "Anthropic's frontend design patterns",
22052
+ installCommand: "npx skills add anthropics/skills@frontend-design --yes",
22053
+ phases: ["implementation"],
22054
+ impact: "medium",
22055
+ website: "https://skills.sh"
22056
+ },
22057
+ {
22058
+ id: "web-design-skill",
22059
+ name: "Web Design Guidelines",
22060
+ type: "skill",
22061
+ description: "Vercel's web design guidelines",
22062
+ installCommand: "npx skills add vercel-labs/agent-skills@web-design-guidelines --yes",
22063
+ phases: ["implementation"],
22064
+ impact: "medium",
22065
+ website: "https://skills.sh"
22066
+ }
22067
+ ];
22068
+ var LIBRARIES = [
22069
+ {
22070
+ id: "vitest",
22071
+ name: "Vitest",
22072
+ type: "library",
22073
+ description: "Fast unit testing framework for TypeScript/JavaScript",
22074
+ installCommand: "bun add -D vitest",
22075
+ phases: ["review"],
22076
+ impact: "high",
22077
+ website: "https://vitest.dev"
22078
+ },
22079
+ {
22080
+ id: "playwright",
22081
+ name: "Playwright",
22082
+ type: "library",
22083
+ description: "E2E testing framework for web applications",
22084
+ installCommand: "bun add -D playwright @playwright/test",
22085
+ phases: ["review"],
22086
+ impact: "medium",
22087
+ website: "https://playwright.dev"
22088
+ },
22089
+ {
22090
+ id: "biome",
22091
+ name: "Biome",
22092
+ type: "library",
22093
+ description: "Fast linter and formatter (replaces ESLint + Prettier)",
22094
+ installCommand: "bun add -D @biomejs/biome && bunx biome init",
22095
+ phases: ["review"],
22096
+ impact: "medium",
22097
+ website: "https://biomejs.dev"
22098
+ }
22099
+ ];
22100
+ var CONFIG_FILES = [
22101
+ {
22102
+ id: "claude-md",
22103
+ name: "CLAUDE.md",
22104
+ type: "config",
22105
+ description: "Project-specific instructions for AI - commands, conventions, architecture",
22106
+ configPath: "CLAUDE.md",
22107
+ configContent: `# Project Instructions
22108
+
22109
+ ## Build & Test Commands
22110
+ \`\`\`bash
22111
+ bun test # Run tests
22112
+ bun run build # Build project
22113
+ bun run lint # Run linter
22114
+ \`\`\`
22115
+
22116
+ ## Architecture
22117
+ Describe your project structure here.
22118
+
22119
+ ## Conventions
22120
+ - Use TypeScript
22121
+ - Write tests for new features
22122
+ - Use conventional commits
22123
+ `,
22124
+ phases: ["requirements"],
22125
+ impact: "high"
22126
+ },
22127
+ {
22128
+ id: "agents-md",
22129
+ name: "AGENTS.md",
22130
+ type: "config",
22131
+ description: "Agent-specific instructions and workflows",
22132
+ configPath: "AGENTS.md",
22133
+ configContent: `# Agent Instructions
22134
+
22135
+ ## Package Manager
22136
+ This project uses **bun** exclusively.
22137
+
22138
+ ## Workflow
22139
+ 1. Read existing code before making changes
22140
+ 2. Write tests for new features
22141
+ 3. Run \`bun test\` before committing
22142
+ `,
22143
+ phases: ["requirements", "planning"],
22144
+ impact: "medium"
22145
+ }
22146
+ ];
22147
+ var WORKFLOW_TOOLS = [
22148
+ {
22149
+ id: "granola",
22150
+ name: "Granola",
22151
+ type: "tool",
22152
+ description: "AI meeting notes and requirements capture",
22153
+ phases: ["requirements"],
22154
+ impact: "medium",
22155
+ website: "https://www.granola.ai/"
22156
+ },
22157
+ {
22158
+ id: "repoprompt",
22159
+ name: "RepoPrompt",
22160
+ type: "tool",
22161
+ description: "Generate AI prompts from your codebase",
22162
+ phases: ["requirements"],
22163
+ impact: "medium",
22164
+ website: "https://repoprompt.com"
22165
+ },
22166
+ {
22167
+ id: "figma",
22168
+ name: "Figma / v0",
22169
+ type: "tool",
22170
+ description: "Design tools for UI/UX planning",
22171
+ phases: ["planning"],
22172
+ impact: "medium",
22173
+ website: "https://figma.com"
22174
+ }
22175
+ ];
22176
+ var ALL_RECOMMENDATIONS = [
22177
+ ...MCP_SERVERS,
22178
+ ...PLUGINS,
22179
+ ...SKILLS,
22180
+ ...LIBRARIES,
22181
+ ...CONFIG_FILES,
22182
+ ...WORKFLOW_TOOLS
22183
+ ];
22184
+ var INSTALLABLE_RECOMMENDATIONS = ALL_RECOMMENDATIONS.filter((r3) => r3.installCommand || r3.configPath);
22185
+ if (false) {}
22186
+
22187
+ // src/lib/verify-recommendations.ts
22188
+ function validateSkillFormat(command) {
22189
+ if (!command.match(/^(npx|bunx)\s+skills\s+add\s+/)) {
22190
+ return { valid: false, message: "Must start with 'npx skills add' or 'bunx skills add'" };
22191
+ }
22192
+ const match = command.match(/skills\s+add\s+([^\s]+)/);
22193
+ if (!match) {
22194
+ return { valid: false, message: "Could not extract skill path" };
22195
+ }
22196
+ const skillPath = match[1] ?? "";
22197
+ if (!skillPath.includes("@")) {
22198
+ return {
22199
+ valid: false,
22200
+ message: `Invalid format '${skillPath}'. Must use @ separator: owner/repo@skill-name`
22201
+ };
22202
+ }
22203
+ const skillMatch = skillPath.match(/^([^\/]+)\/([^@]+)@([^\/\s]+)$/);
22204
+ if (!skillMatch) {
22205
+ return {
22206
+ valid: false,
22207
+ message: `Invalid skill path '${skillPath}'. Expected: owner/repo@skill-name`
22208
+ };
22209
+ }
22210
+ if (!command.includes("--yes")) {
22211
+ return {
22212
+ valid: false,
22213
+ message: "Missing --yes flag for non-interactive installation"
22214
+ };
22215
+ }
22216
+ return { valid: true, message: "Valid skills.sh format" };
22217
+ }
22218
+ function validateMCPFormat(command) {
22219
+ if (command.startsWith("claude mcp add")) {
22220
+ if (command.includes("-- npx") || command.includes("--transport")) {
22221
+ return { valid: true, message: "Valid Claude MCP format" };
22222
+ }
22223
+ return { valid: false, message: "MCP command should have '-- npx' or '--transport' pattern" };
22224
+ }
22225
+ if (command.startsWith("pipx run")) {
22226
+ return { valid: true, message: "Valid pipx MCP format" };
22227
+ }
22228
+ if (command.includes("config") || command.includes("mcpServers")) {
22229
+ return { valid: true, message: "Manual configuration instruction" };
22230
+ }
22231
+ return { valid: false, message: "Unknown MCP install format" };
22232
+ }
22233
+ function validatePackageFormat(command) {
22234
+ if (command.match(/^bun\s+add\s+(-[dDgG]\s+)?[@\w\/-]+/)) {
22235
+ return { valid: true, message: "Valid bun add format" };
22236
+ }
22237
+ if (command.match(/^npm\s+(install|i)\s+(-[dDgG]\s+)?[@\w\/-]+/)) {
22238
+ return { valid: true, message: "Valid npm install format" };
22239
+ }
22240
+ if (command.match(/^(bunx|npx)\s+/)) {
22241
+ return { valid: true, message: "Valid bunx/npx format" };
22242
+ }
22243
+ return { valid: false, message: "Unknown package install format" };
22244
+ }
22245
+ async function checkNpmPackageExists(packageName) {
22246
+ try {
22247
+ const response = await fetch(`https://registry.npmjs.org/${packageName}`, {
22248
+ method: "HEAD",
22249
+ signal: AbortSignal.timeout(5000)
22250
+ });
22251
+ if (response.ok) {
22252
+ return { exists: true, message: "Package exists on npm" };
22253
+ }
22254
+ return { exists: false, message: `Package not found on npm (${response.status})` };
22255
+ } catch (error2) {
22256
+ return { exists: false, message: `Could not verify: ${error2 instanceof Error ? error2.message : String(error2)}` };
22257
+ }
22258
+ }
22259
+ async function checkGitHubRepoExists(owner, repo) {
22260
+ try {
22261
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
22262
+ method: "HEAD",
22263
+ signal: AbortSignal.timeout(5000)
22264
+ });
22265
+ if (response.ok) {
22266
+ return { exists: true, message: "GitHub repo exists" };
22267
+ }
22268
+ if (response.status === 404) {
22269
+ return { exists: false, message: `GitHub repo ${owner}/${repo} not found` };
22270
+ }
22271
+ return { exists: false, message: `GitHub API error (${response.status})` };
22272
+ } catch (error2) {
22273
+ return { exists: false, message: `Could not verify: ${error2 instanceof Error ? error2.message : String(error2)}` };
22274
+ }
22275
+ }
22276
+ function verifyRecommendation(rec) {
22277
+ const base = {
22278
+ id: rec.id,
22279
+ name: rec.name,
22280
+ type: rec.type,
22281
+ installCommand: rec.installCommand
22282
+ };
22283
+ if (!rec.installCommand && !rec.configPath) {
22284
+ return { ...base, status: "skipped", message: "No install command (informational only)" };
22285
+ }
22286
+ if (rec.type === "config" && rec.configPath) {
22287
+ return { ...base, status: "valid", message: `Config file: ${rec.configPath}` };
22288
+ }
22289
+ if (!rec.installCommand) {
22290
+ return { ...base, status: "skipped", message: "No install command" };
22291
+ }
22292
+ switch (rec.type) {
22293
+ case "skill": {
22294
+ const result = validateSkillFormat(rec.installCommand);
22295
+ return {
22296
+ ...base,
22297
+ status: result.valid ? "valid" : "invalid",
22298
+ message: result.message
22299
+ };
22300
+ }
22301
+ case "mcp": {
22302
+ const result = validateMCPFormat(rec.installCommand);
22303
+ return {
22304
+ ...base,
22305
+ status: result.valid ? "valid" : "invalid",
22306
+ message: result.message
22307
+ };
22308
+ }
22309
+ case "library":
22310
+ case "plugin": {
22311
+ const result = validatePackageFormat(rec.installCommand);
22312
+ return {
22313
+ ...base,
22314
+ status: result.valid ? "valid" : "invalid",
22315
+ message: result.message
22316
+ };
22317
+ }
22318
+ default:
22319
+ return { ...base, status: "warning", message: `Unknown type: ${rec.type}` };
22320
+ }
22321
+ }
22322
+ function verifyAllRecommendations() {
22323
+ const results = [];
22324
+ for (const rec of ALL_RECOMMENDATIONS) {
22325
+ results.push(verifyRecommendation(rec));
22326
+ }
22327
+ const valid = results.filter((r3) => r3.status === "valid").length;
22328
+ const invalid = results.filter((r3) => r3.status === "invalid").length;
22329
+ const warnings = results.filter((r3) => r3.status === "warning").length;
22330
+ const skipped = results.filter((r3) => r3.status === "skipped").length;
22331
+ return {
22332
+ total: results.length,
22333
+ valid,
22334
+ invalid,
22335
+ warnings,
22336
+ skipped,
22337
+ results
22338
+ };
22339
+ }
22340
+ async function verifyRecommendationsThorough() {
22341
+ const basicResults = verifyAllRecommendations();
22342
+ const enhancedResults = [];
22343
+ for (const result of basicResults.results) {
22344
+ if (result.status === "invalid" || result.status === "skipped") {
22345
+ enhancedResults.push(result);
22346
+ continue;
22347
+ }
22348
+ if (result.installCommand?.includes("skills add")) {
22349
+ const match = result.installCommand.match(/skills\s+add\s+([^\/]+)\/([^@]+)@/);
22350
+ if (match && match[1] && match[2]) {
22351
+ const owner = match[1];
22352
+ const repo = match[2];
22353
+ const check = await checkGitHubRepoExists(owner, repo);
22354
+ if (!check.exists) {
22355
+ enhancedResults.push({
22356
+ ...result,
22357
+ status: "invalid",
22358
+ message: check.message
22359
+ });
22360
+ continue;
22361
+ }
22362
+ }
22363
+ }
22364
+ if (result.installCommand?.match(/npx\s+-y\s+@?[\w\/-]+/)) {
22365
+ const match = result.installCommand.match(/npx\s+-y\s+(@?[\w\/-]+)/);
22366
+ if (match && match[1]) {
22367
+ const packageName = match[1];
22368
+ const check = await checkNpmPackageExists(packageName);
22369
+ if (!check.exists) {
22370
+ enhancedResults.push({
22371
+ ...result,
22372
+ status: "warning",
22373
+ message: check.message
22374
+ });
22375
+ continue;
22376
+ }
22377
+ }
22378
+ }
22379
+ enhancedResults.push(result);
22380
+ }
22381
+ const valid = enhancedResults.filter((r3) => r3.status === "valid").length;
22382
+ const invalid = enhancedResults.filter((r3) => r3.status === "invalid").length;
22383
+ const warnings = enhancedResults.filter((r3) => r3.status === "warning").length;
22384
+ const skipped = enhancedResults.filter((r3) => r3.status === "skipped").length;
22385
+ return {
22386
+ total: enhancedResults.length,
22387
+ valid,
22388
+ invalid,
22389
+ warnings,
22390
+ skipped,
22391
+ results: enhancedResults
22392
+ };
22393
+ }
22394
+ function printVerificationResults(summary, verbose = false) {
22395
+ console.log(`
22396
+ ═══════════════════════════════════════════════════════════════`);
22397
+ console.log(" RECOMMENDATION VERIFICATION REPORT");
22398
+ console.log(`═══════════════════════════════════════════════════════════════
22399
+ `);
22400
+ const invalid = summary.results.filter((r3) => r3.status === "invalid");
22401
+ if (invalid.length > 0) {
22402
+ console.log(`❌ INVALID RECOMMENDATIONS:
22403
+ `);
22404
+ for (const r3 of invalid) {
22405
+ console.log(` ${r3.name} (${r3.type})`);
22406
+ console.log(` ID: ${r3.id}`);
22407
+ console.log(` Issue: ${r3.message}`);
22408
+ if (r3.installCommand) {
22409
+ console.log(` Command: ${r3.installCommand}`);
22410
+ }
22411
+ console.log();
22412
+ }
22413
+ }
22414
+ const warnings = summary.results.filter((r3) => r3.status === "warning");
22415
+ if (warnings.length > 0) {
22416
+ console.log(`⚠️ WARNINGS:
22417
+ `);
22418
+ for (const r3 of warnings) {
22419
+ console.log(` ${r3.name}: ${r3.message}`);
22420
+ }
22421
+ console.log();
22422
+ }
22423
+ if (verbose) {
22424
+ const valid = summary.results.filter((r3) => r3.status === "valid");
22425
+ if (valid.length > 0) {
22426
+ console.log(`✓ VALID RECOMMENDATIONS:
22427
+ `);
22428
+ for (const r3 of valid) {
22429
+ console.log(` ${r3.name} (${r3.type}): ${r3.message}`);
22430
+ }
22431
+ console.log();
22432
+ }
22433
+ }
22434
+ console.log("───────────────────────────────────────────────────────────────");
22435
+ console.log(` Total: ${summary.total}`);
22436
+ console.log(` ✓ Valid: ${summary.valid}`);
22437
+ console.log(` ❌ Invalid: ${summary.invalid}`);
22438
+ console.log(` ⚠️ Warnings: ${summary.warnings}`);
22439
+ console.log(` ○ Skipped: ${summary.skipped}`);
22440
+ console.log(`───────────────────────────────────────────────────────────────
22441
+ `);
22442
+ if (summary.invalid > 0) {
22443
+ console.log(`❌ VERIFICATION FAILED - Fix invalid recommendations before release!
22444
+ `);
22445
+ } else if (summary.warnings > 0) {
22446
+ console.log(`⚠️ VERIFICATION PASSED WITH WARNINGS
22447
+ `);
22448
+ } else {
22449
+ console.log(`✓ ALL RECOMMENDATIONS VERIFIED
22450
+ `);
22451
+ }
22452
+ }
22453
+
22454
+ // src/commands/doctor.ts
21364
22455
  var doctorCommand = defineCommand2({
21365
22456
  meta: {
21366
22457
  name: "doctor",
21367
22458
  description: "Check tool detection, data sources, and connectivity health"
21368
22459
  },
21369
- async run() {
22460
+ args: {
22461
+ "verify-recommendations": {
22462
+ type: "boolean",
22463
+ description: "Verify all recommendations in the database are valid",
22464
+ alias: "verify",
22465
+ default: false
22466
+ },
22467
+ thorough: {
22468
+ type: "boolean",
22469
+ description: "Run thorough verification with network checks",
22470
+ alias: "t",
22471
+ default: false
22472
+ },
22473
+ verbose: {
22474
+ type: "boolean",
22475
+ description: "Show all results including valid recommendations",
22476
+ alias: "v",
22477
+ default: false
22478
+ }
22479
+ },
22480
+ async run({ args }) {
22481
+ if (args["verify-recommendations"]) {
22482
+ console.log();
22483
+ console.log(colors2.bold(colors2.primary(" NaironAI Recommendation Verifier")));
22484
+ console.log(colors2.dim(` Validating all recommendation install commands...
22485
+ `));
22486
+ const spinner2 = createSpinner("Verifying recommendations...");
22487
+ spinner2.start();
22488
+ let summary;
22489
+ if (args.thorough) {
22490
+ spinner2.text = "Verifying recommendations (thorough mode with network checks)...";
22491
+ summary = await verifyRecommendationsThorough();
22492
+ } else {
22493
+ summary = verifyAllRecommendations();
22494
+ }
22495
+ if (summary.invalid > 0) {
22496
+ spinner2.fail(`Found ${summary.invalid} invalid recommendation(s)`);
22497
+ } else if (summary.warnings > 0) {
22498
+ spinner2.warn(`Verification passed with ${summary.warnings} warning(s)`);
22499
+ } else {
22500
+ spinner2.succeed("All recommendations verified");
22501
+ }
22502
+ printVerificationResults(summary, args.verbose);
22503
+ if (summary.invalid > 0) {
22504
+ process.exit(1);
22505
+ }
22506
+ return;
22507
+ }
21370
22508
  console.log();
21371
22509
  console.log(colors2.bold(colors2.primary(" NaironAI Doctor")));
21372
22510
  console.log(colors2.dim(` Checking system health...
@@ -21377,16 +22515,16 @@ var doctorCommand = defineCommand2({
21377
22515
  results.push({ label: "OS", status: "info", value: `${process.platform} ${process.arch}` });
21378
22516
  results.push({ label: "Bun", status: "info", value: typeof Bun !== "undefined" ? Bun.version : "N/A" });
21379
22517
  results.push({ label: "Node", status: "info", value: process.version });
21380
- results.push({ label: "Home", status: "info", value: homedir9() });
21381
- const gitDir = join12(process.cwd(), ".git");
21382
- if (existsSync12(gitDir)) {
22518
+ results.push({ label: "Home", status: "info", value: homedir10() });
22519
+ const gitDir = join13(process.cwd(), ".git");
22520
+ if (existsSync13(gitDir)) {
21383
22521
  results.push({ label: "Git", status: "success", value: "Repository detected" });
21384
22522
  } else {
21385
22523
  results.push({ label: "Git", status: "warn", value: "No repository in current directory" });
21386
22524
  }
21387
22525
  for (const [agent, pathTemplate] of Object.entries(AGENT_LOG_PATHS)) {
21388
- const resolvedPath = pathTemplate.replace("~", homedir9());
21389
- if (existsSync12(resolvedPath)) {
22526
+ const resolvedPath = pathTemplate.replace("~", homedir10());
22527
+ if (existsSync13(resolvedPath)) {
21390
22528
  results.push({ label: agent, status: "success", value: `found at ${resolvedPath}` });
21391
22529
  }
21392
22530
  }
@@ -21440,9 +22578,9 @@ var doctorCommand = defineCommand2({
21440
22578
  });
21441
22579
 
21442
22580
  // src/commands/setup.ts
21443
- import { existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, copyFileSync } from "node:fs";
21444
- import { join as join13, dirname as dirname2 } from "node:path";
21445
- import { homedir as homedir10, platform as platform2 } from "node:os";
22581
+ import { existsSync as existsSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, copyFileSync } from "node:fs";
22582
+ import { join as join14, dirname as dirname2 } from "node:path";
22583
+ import { homedir as homedir11, platform as platform2 } from "node:os";
21446
22584
  import { execSync as execSync3 } from "node:child_process";
21447
22585
  import { createInterface } from "node:readline";
21448
22586
  var CONVEX_SITE_URL = "https://steady-bass-841.convex.site";
@@ -21461,7 +22599,7 @@ var FALLBACK_HARNESSES = [
21461
22599
  id: "claude-code",
21462
22600
  name: "Claude Code",
21463
22601
  configPaths: [
21464
- join13(homedir10(), ".claude.json")
22602
+ join14(homedir11(), ".claude.json")
21465
22603
  ],
21466
22604
  mcpConfigKey: "mcpServers",
21467
22605
  detected: false
@@ -21470,8 +22608,8 @@ var FALLBACK_HARNESSES = [
21470
22608
  id: "opencode",
21471
22609
  name: "OpenCode",
21472
22610
  configPaths: [
21473
- join13(homedir10(), ".opencode.json"),
21474
- join13(homedir10(), ".config", "opencode", "opencode.json")
22611
+ join14(homedir11(), ".opencode.json"),
22612
+ join14(homedir11(), ".config", "opencode", "opencode.json")
21475
22613
  ],
21476
22614
  mcpConfigKey: "mcp",
21477
22615
  detected: false
@@ -21480,8 +22618,8 @@ var FALLBACK_HARNESSES = [
21480
22618
  id: "cursor",
21481
22619
  name: "Cursor",
21482
22620
  configPaths: [
21483
- join13(homedir10(), ".cursor", "mcp.json"),
21484
- join13(homedir10(), "Library", "Application Support", "Cursor", "User", "globalStorage", "mcp.json")
22621
+ join14(homedir11(), ".cursor", "mcp.json"),
22622
+ join14(homedir11(), "Library", "Application Support", "Cursor", "User", "globalStorage", "mcp.json")
21485
22623
  ],
21486
22624
  mcpConfigKey: "mcpServers",
21487
22625
  detected: false
@@ -21498,7 +22636,7 @@ function getConfigPathsForPlatform(harness) {
21498
22636
  } else {
21499
22637
  paths = pathMap.linux;
21500
22638
  }
21501
- return paths.map((p) => p.replace(/^~/, homedir10()).replace(/%USERPROFILE%/gi, homedir10()).replace(/%APPDATA%/gi, join13(homedir10(), "AppData", "Roaming")));
22639
+ return paths.map((p) => p.replace(/^~/, homedir11()).replace(/%USERPROFILE%/gi, homedir11()).replace(/%APPDATA%/gi, join14(homedir11(), "AppData", "Roaming")));
21502
22640
  }
21503
22641
  function convertAPIHarnessToConfig(harness) {
21504
22642
  return {
@@ -21513,7 +22651,7 @@ function detectHarnesses(harnesses) {
21513
22651
  const detected = [];
21514
22652
  for (const harness of harnesses) {
21515
22653
  for (const configPath of harness.configPaths) {
21516
- if (existsSync13(configPath)) {
22654
+ if (existsSync14(configPath)) {
21517
22655
  detected.push({
21518
22656
  ...harness,
21519
22657
  detected: true,
@@ -21526,19 +22664,19 @@ function detectHarnesses(harnesses) {
21526
22664
  return detected;
21527
22665
  }
21528
22666
  function backupConfig(configPath) {
21529
- const backupDir = join13(homedir10(), ".nairon", "backups");
21530
- if (!existsSync13(backupDir)) {
22667
+ const backupDir = join14(homedir11(), ".nairon", "backups");
22668
+ if (!existsSync14(backupDir)) {
21531
22669
  mkdirSync5(backupDir, { recursive: true });
21532
22670
  }
21533
22671
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
21534
22672
  const filename = configPath.replace(/[/\\]/g, "_").replace(/^_/, "");
21535
- const backupPath = join13(backupDir, `${timestamp}_${filename}`);
22673
+ const backupPath = join14(backupDir, `${timestamp}_${filename}`);
21536
22674
  copyFileSync(configPath, backupPath);
21537
22675
  return backupPath;
21538
22676
  }
21539
22677
  function readJsonConfig(configPath) {
21540
22678
  try {
21541
- const content = readFileSync12(configPath, "utf-8");
22679
+ const content = readFileSync13(configPath, "utf-8");
21542
22680
  return JSON.parse(content);
21543
22681
  } catch {
21544
22682
  return {};
@@ -21546,7 +22684,7 @@ function readJsonConfig(configPath) {
21546
22684
  }
21547
22685
  function writeJsonConfig(configPath, config) {
21548
22686
  const dir = dirname2(configPath);
21549
- if (!existsSync13(dir)) {
22687
+ if (!existsSync14(dir)) {
21550
22688
  mkdirSync5(dir, { recursive: true });
21551
22689
  }
21552
22690
  writeFileSync6(configPath, JSON.stringify(config, null, 2));
@@ -21590,7 +22728,7 @@ function getInstalledMCPs(harnesses) {
21590
22728
  const installed = new Set;
21591
22729
  for (const harness of harnesses) {
21592
22730
  for (const configPath of harness.configPaths) {
21593
- if (!existsSync13(configPath))
22731
+ if (!existsSync14(configPath))
21594
22732
  continue;
21595
22733
  const config = readJsonConfig(configPath);
21596
22734
  const mcpServers = config[harness.mcpConfigKey];
@@ -21614,7 +22752,7 @@ async function installMCPServer(serverName, instruction, harness) {
21614
22752
  if (!harness.configPath || !instruction.mcpServerConfig)
21615
22753
  return false;
21616
22754
  try {
21617
- if (existsSync13(harness.configPath)) {
22755
+ if (existsSync14(harness.configPath)) {
21618
22756
  backupConfig(harness.configPath);
21619
22757
  }
21620
22758
  const config = readJsonConfig(harness.configPath);
@@ -21659,8 +22797,8 @@ async function installBeads(projectDir) {
21659
22797
  }
21660
22798
  }
21661
22799
  }
21662
- const beadsDir = join13(projectDir, ".beads");
21663
- if (!existsSync13(beadsDir)) {
22800
+ const beadsDir = join14(projectDir, ".beads");
22801
+ if (!existsSync14(beadsDir)) {
21664
22802
  const projectName = projectDir.split(/[/\\]/).pop() ?? "project";
21665
22803
  const initSpinner = createSpinner(`Initializing Beads in ${projectName}...`);
21666
22804
  initSpinner.start();
@@ -21679,9 +22817,9 @@ async function installBeads(projectDir) {
21679
22817
  }
21680
22818
  }
21681
22819
  async function createClaudeRules(projectDir) {
21682
- const claudeMdPath = join13(projectDir, "CLAUDE.md");
21683
- const agentsMdPath = join13(projectDir, "AGENTS.md");
21684
- if (existsSync13(claudeMdPath) || existsSync13(agentsMdPath)) {
22820
+ const claudeMdPath = join14(projectDir, "CLAUDE.md");
22821
+ const agentsMdPath = join14(projectDir, "AGENTS.md");
22822
+ if (existsSync14(claudeMdPath) || existsSync14(agentsMdPath)) {
21685
22823
  console.log(` ${icons.info} CLAUDE.md or AGENTS.md already exists`);
21686
22824
  return true;
21687
22825
  }
@@ -22004,6 +23142,25 @@ var BOLD = "\x1B[1m";
22004
23142
  var RESET = "\x1B[0m";
22005
23143
  var MAGENTA = "\x1B[35m";
22006
23144
  var CHANGELOG = [
23145
+ {
23146
+ version: "0.3.14",
23147
+ date: "2026-02-14",
23148
+ title: "Enhanced Frustration Detection",
23149
+ highlights: [
23150
+ "Scan now shows exact prompts that caused frustration",
23151
+ "Root cause analysis: hallucination, context loss, undo loops",
23152
+ "Each frustration linked to a specific fix with install command"
23153
+ ]
23154
+ },
23155
+ {
23156
+ version: "0.3.13",
23157
+ date: "2026-02-14",
23158
+ title: "Reliable Recommendations",
23159
+ highlights: [
23160
+ "Fixed skills.sh format - skills now install correctly",
23161
+ "New: nb doctor --verify-recommendations to validate install commands"
23162
+ ]
23163
+ },
22007
23164
  {
22008
23165
  version: "0.3.12",
22009
23166
  date: "2026-02-14",
@@ -22403,7 +23560,7 @@ function showFullChangelog() {
22403
23560
  // package.json
22404
23561
  var package_default = {
22405
23562
  name: "nairon-bench",
22406
- version: "0.3.12",
23563
+ version: "0.3.14",
22407
23564
  description: "AI workflow benchmarking CLI",
22408
23565
  type: "module",
22409
23566
  bin: {
@@ -22435,8 +23592,9 @@ var package_default = {
22435
23592
  "build:linux-arm64": "bun build --compile --target=bun-linux-arm64 --outfile=dist/nb-linux-arm64 src/index.ts",
22436
23593
  "build:windows-x64": "bun build --compile --target=bun-windows-x64 --outfile=dist/nb-windows-x64.exe src/index.ts",
22437
23594
  typecheck: "tsc --noEmit",
22438
- test: "bun test",
22439
- "test:e2e": "bun test --test-name-pattern e2e",
23595
+ test: "vitest run",
23596
+ "test:e2e": "vitest run tests/e2e/",
23597
+ "test:watch": "vitest",
22440
23598
  clean: "rm -rf dist",
22441
23599
  prepublishOnly: "bun run build"
22442
23600
  },
@@ -22461,9 +23619,9 @@ var versionCommand = defineCommand2({
22461
23619
 
22462
23620
  // src/commands/init.ts
22463
23621
  init_dist();
22464
- import { existsSync as existsSync14 } from "node:fs";
22465
- import { homedir as homedir11, platform as platform3, arch } from "node:os";
22466
- import { join as join14 } from "node:path";
23622
+ import { existsSync as existsSync15 } from "node:fs";
23623
+ import { homedir as homedir12, platform as platform3, arch } from "node:os";
23624
+ import { join as join15 } from "node:path";
22467
23625
  init_client();
22468
23626
 
22469
23627
  // src/lib/github-auth.ts
@@ -22633,8 +23791,8 @@ var initCommand = defineCommand2({
22633
23791
  consola.start("Step 2/6: Detecting AI agents...");
22634
23792
  const detectedAgents = [];
22635
23793
  for (const [agent, pathTemplate] of Object.entries(AGENT_LOG_PATHS)) {
22636
- const resolvedPath = pathTemplate.replace("~", homedir11());
22637
- if (existsSync14(resolvedPath)) {
23794
+ const resolvedPath = pathTemplate.replace("~", homedir12());
23795
+ if (existsSync15(resolvedPath)) {
22638
23796
  detectedAgents.push(agent);
22639
23797
  consola.success(` Found: ${agent}`);
22640
23798
  }
@@ -22737,7 +23895,7 @@ var initCommand = defineCommand2({
22737
23895
  { name: "playwright", configs: ["playwright.config.ts", "playwright.config.js"] }
22738
23896
  ];
22739
23897
  for (const fw of testFrameworks) {
22740
- if (fw.configs.some((c3) => existsSync14(join14(process.cwd(), c3)))) {
23898
+ if (fw.configs.some((c3) => existsSync15(join15(process.cwd(), c3)))) {
22741
23899
  toolStack.testing.push(fw.name);
22742
23900
  }
22743
23901
  }
@@ -22806,13 +23964,13 @@ var initCommand = defineCommand2({
22806
23964
  });
22807
23965
  function detectPackageManager() {
22808
23966
  const cwd = process.cwd();
22809
- if (existsSync14(join14(cwd, "bun.lock")) || existsSync14(join14(cwd, "bun.lockb")))
23967
+ if (existsSync15(join15(cwd, "bun.lock")) || existsSync15(join15(cwd, "bun.lockb")))
22810
23968
  return "bun";
22811
- if (existsSync14(join14(cwd, "pnpm-lock.yaml")))
23969
+ if (existsSync15(join15(cwd, "pnpm-lock.yaml")))
22812
23970
  return "pnpm";
22813
- if (existsSync14(join14(cwd, "yarn.lock")))
23971
+ if (existsSync15(join15(cwd, "yarn.lock")))
22814
23972
  return "yarn";
22815
- if (existsSync14(join14(cwd, "package-lock.json")))
23973
+ if (existsSync15(join15(cwd, "package-lock.json")))
22816
23974
  return "npm";
22817
23975
  return;
22818
23976
  }
@@ -22827,15 +23985,15 @@ var CLAUDE_CODE_SKILLS = [
22827
23985
  ];
22828
23986
  function detectEasyWins(detectedAgents) {
22829
23987
  const wins = [];
22830
- const home = homedir11();
23988
+ const home = homedir12();
22831
23989
  const hasClaudeCode = detectedAgents.some((a2) => a2.toLowerCase().includes("claude") || a2.toLowerCase().includes("opencode"));
22832
- const claudeConfigDir = join14(home, ".claude");
22833
- const hasClaudeConfig = existsSync14(claudeConfigDir);
23990
+ const claudeConfigDir = join15(home, ".claude");
23991
+ const hasClaudeConfig = existsSync15(claudeConfigDir);
22834
23992
  if (hasClaudeCode || hasClaudeConfig) {
22835
- const skillsDir = join14(home, ".claude", "skills");
23993
+ const skillsDir = join15(home, ".claude", "skills");
22836
23994
  for (const skill of CLAUDE_CODE_SKILLS) {
22837
- const skillPath = join14(skillsDir, skill.id);
22838
- const isInstalled = existsSync14(skillPath);
23995
+ const skillPath = join15(skillsDir, skill.id);
23996
+ const isInstalled = existsSync15(skillPath);
22839
23997
  if (!isInstalled) {
22840
23998
  wins.push({
22841
23999
  icon: skill.icon,
@@ -22851,14 +24009,14 @@ function detectEasyWins(detectedAgents) {
22851
24009
  // src/commands/onboard.ts
22852
24010
  init_dist();
22853
24011
  init_client();
22854
- import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync7, readdirSync as readdirSync9 } from "node:fs";
22855
- import { join as join16 } from "node:path";
24012
+ import { existsSync as existsSync17, mkdirSync as mkdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync7, readdirSync as readdirSync9 } from "node:fs";
24013
+ import { join as join17 } from "node:path";
22856
24014
 
22857
24015
  // src/lib/project-context-detector.ts
22858
- import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync8 } from "node:fs";
22859
- import { join as join15, basename as basename3 } from "node:path";
24016
+ import { existsSync as existsSync16, readFileSync as readFileSync14, readdirSync as readdirSync8 } from "node:fs";
24017
+ import { join as join16, basename as basename4 } from "node:path";
22860
24018
  function detectProjectContext(projectDir) {
22861
- const projectName = basename3(projectDir);
24019
+ const projectName = basename4(projectDir);
22862
24020
  const projectId = generateProjectId(projectDir);
22863
24021
  const techStack = detectTechStack(projectDir);
22864
24022
  const projectType = inferProjectType(projectDir, techStack);
@@ -22892,11 +24050,11 @@ function detectTechStack(projectDir) {
22892
24050
  };
22893
24051
  }
22894
24052
  function readPackageJson(projectDir) {
22895
- const pkgPath = join15(projectDir, "package.json");
22896
- if (!existsSync15(pkgPath))
24053
+ const pkgPath = join16(projectDir, "package.json");
24054
+ if (!existsSync16(pkgPath))
22897
24055
  return {};
22898
24056
  try {
22899
- return JSON.parse(readFileSync13(pkgPath, "utf-8"));
24057
+ return JSON.parse(readFileSync14(pkgPath, "utf-8"));
22900
24058
  } catch {
22901
24059
  return {};
22902
24060
  }
@@ -22917,34 +24075,34 @@ function detectFramework(deps) {
22917
24075
  return null;
22918
24076
  }
22919
24077
  function detectMetaFramework(deps, projectDir) {
22920
- if (deps.next || existsSync15(join15(projectDir, "next.config.js")) || existsSync15(join15(projectDir, "next.config.mjs")))
24078
+ if (deps.next || existsSync16(join16(projectDir, "next.config.js")) || existsSync16(join16(projectDir, "next.config.mjs")))
22921
24079
  return "next";
22922
- if (deps.nuxt || existsSync15(join15(projectDir, "nuxt.config.ts")))
24080
+ if (deps.nuxt || existsSync16(join16(projectDir, "nuxt.config.ts")))
22923
24081
  return "nuxt";
22924
24082
  if (deps["@sveltejs/kit"])
22925
24083
  return "sveltekit";
22926
- if (deps.astro || existsSync15(join15(projectDir, "astro.config.mjs")))
24084
+ if (deps.astro || existsSync16(join16(projectDir, "astro.config.mjs")))
22927
24085
  return "astro";
22928
24086
  if (deps["@remix-run/react"])
22929
24087
  return "remix";
22930
24088
  if (deps.gatsby)
22931
24089
  return "gatsby";
22932
- if (deps.vite || existsSync15(join15(projectDir, "vite.config.ts")))
24090
+ if (deps.vite || existsSync16(join16(projectDir, "vite.config.ts")))
22933
24091
  return "vite";
22934
24092
  return null;
22935
24093
  }
22936
24094
  function detectRuntime(projectDir) {
22937
- if (existsSync15(join15(projectDir, "bun.lockb")) || existsSync15(join15(projectDir, "bun.lock")))
24095
+ if (existsSync16(join16(projectDir, "bun.lockb")) || existsSync16(join16(projectDir, "bun.lock")))
22938
24096
  return "bun";
22939
- if (existsSync15(join15(projectDir, "deno.json")) || existsSync15(join15(projectDir, "deno.lock")))
24097
+ if (existsSync16(join16(projectDir, "deno.json")) || existsSync16(join16(projectDir, "deno.lock")))
22940
24098
  return "deno";
22941
24099
  return "node";
22942
24100
  }
22943
24101
  function detectLanguage(projectDir) {
22944
- if (existsSync15(join15(projectDir, "tsconfig.json")))
24102
+ if (existsSync16(join16(projectDir, "tsconfig.json")))
22945
24103
  return "typescript";
22946
- const srcDir = join15(projectDir, "src");
22947
- if (existsSync15(srcDir)) {
24104
+ const srcDir = join16(projectDir, "src");
24105
+ if (existsSync16(srcDir)) {
22948
24106
  try {
22949
24107
  const files = readdirSync8(srcDir);
22950
24108
  if (files.some((f3) => f3.endsWith(".ts") || f3.endsWith(".tsx")))
@@ -22955,7 +24113,7 @@ function detectLanguage(projectDir) {
22955
24113
  }
22956
24114
  function detectStyling(deps, projectDir) {
22957
24115
  const tools = [];
22958
- if (deps.tailwindcss || existsSync15(join15(projectDir, "tailwind.config.js")) || existsSync15(join15(projectDir, "tailwind.config.ts")))
24116
+ if (deps.tailwindcss || existsSync16(join16(projectDir, "tailwind.config.js")) || existsSync16(join16(projectDir, "tailwind.config.ts")))
22959
24117
  tools.push("tailwind");
22960
24118
  if (deps["styled-components"])
22961
24119
  tools.push("styled-components");
@@ -23000,13 +24158,13 @@ function detectORM(deps) {
23000
24158
  }
23001
24159
  function detectTesting(deps, projectDir) {
23002
24160
  const tools = [];
23003
- if (deps.vitest || existsSync15(join15(projectDir, "vitest.config.ts")))
24161
+ if (deps.vitest || existsSync16(join16(projectDir, "vitest.config.ts")))
23004
24162
  tools.push("vitest");
23005
- if (deps.jest || existsSync15(join15(projectDir, "jest.config.js")))
24163
+ if (deps.jest || existsSync16(join16(projectDir, "jest.config.js")))
23006
24164
  tools.push("jest");
23007
- if (deps["@playwright/test"] || existsSync15(join15(projectDir, "playwright.config.ts")))
24165
+ if (deps["@playwright/test"] || existsSync16(join16(projectDir, "playwright.config.ts")))
23008
24166
  tools.push("playwright");
23009
- if (deps.cypress || existsSync15(join15(projectDir, "cypress.config.ts")))
24167
+ if (deps.cypress || existsSync16(join16(projectDir, "cypress.config.ts")))
23010
24168
  tools.push("cypress");
23011
24169
  if (deps["@testing-library/react"] || deps["@testing-library/vue"])
23012
24170
  tools.push("testing-library");
@@ -23038,7 +24196,7 @@ function detectBuildTools(deps, projectDir) {
23038
24196
  tools.push("esbuild");
23039
24197
  if (deps.webpack)
23040
24198
  tools.push("webpack");
23041
- if (deps.turbo || existsSync15(join15(projectDir, "turbo.json")))
24199
+ if (deps.turbo || existsSync16(join16(projectDir, "turbo.json")))
23042
24200
  tools.push("turborepo");
23043
24201
  if (deps.tsup)
23044
24202
  tools.push("tsup");
@@ -23048,17 +24206,17 @@ function detectBuildTools(deps, projectDir) {
23048
24206
  }
23049
24207
  function detectDeployment(projectDir) {
23050
24208
  const tools = [];
23051
- if (existsSync15(join15(projectDir, "vercel.json")) || existsSync15(join15(projectDir, ".vercel")))
24209
+ if (existsSync16(join16(projectDir, "vercel.json")) || existsSync16(join16(projectDir, ".vercel")))
23052
24210
  tools.push("vercel");
23053
- if (existsSync15(join15(projectDir, "netlify.toml")))
24211
+ if (existsSync16(join16(projectDir, "netlify.toml")))
23054
24212
  tools.push("netlify");
23055
- if (existsSync15(join15(projectDir, "fly.toml")))
24213
+ if (existsSync16(join16(projectDir, "fly.toml")))
23056
24214
  tools.push("fly");
23057
- if (existsSync15(join15(projectDir, "railway.json")))
24215
+ if (existsSync16(join16(projectDir, "railway.json")))
23058
24216
  tools.push("railway");
23059
- if (existsSync15(join15(projectDir, "Dockerfile")))
24217
+ if (existsSync16(join16(projectDir, "Dockerfile")))
23060
24218
  tools.push("docker");
23061
- if (existsSync15(join15(projectDir, ".github", "workflows")))
24219
+ if (existsSync16(join16(projectDir, ".github", "workflows")))
23062
24220
  tools.push("github-actions");
23063
24221
  return tools;
23064
24222
  }
@@ -23081,20 +24239,20 @@ function detectAIML(deps) {
23081
24239
  return tools;
23082
24240
  }
23083
24241
  function inferProjectType(projectDir, techStack) {
23084
- if (existsSync15(join15(projectDir, "packages")) || existsSync15(join15(projectDir, "apps"))) {
24242
+ if (existsSync16(join16(projectDir, "packages")) || existsSync16(join16(projectDir, "apps"))) {
23085
24243
  return "monorepo";
23086
24244
  }
23087
24245
  const pkg = readPackageJson(projectDir);
23088
- if (pkg.bin || existsSync15(join15(projectDir, "src", "cli.ts")) || existsSync15(join15(projectDir, "src", "index.ts"))) {
24246
+ if (pkg.bin || existsSync16(join16(projectDir, "src", "cli.ts")) || existsSync16(join16(projectDir, "src", "index.ts"))) {
23089
24247
  const hasBin = !!pkg.bin;
23090
- const hasCommands = existsSync15(join15(projectDir, "src", "commands"));
24248
+ const hasCommands = existsSync16(join16(projectDir, "src", "commands"));
23091
24249
  if (hasBin || hasCommands)
23092
24250
  return "cli";
23093
24251
  }
23094
24252
  if (!techStack.framework && !techStack.metaFramework && pkg.main) {
23095
24253
  return "library";
23096
24254
  }
23097
- if (existsSync15(join15(projectDir, "app.json")) || existsSync15(join15(projectDir, "expo"))) {
24255
+ if (existsSync16(join16(projectDir, "app.json")) || existsSync16(join16(projectDir, "expo"))) {
23098
24256
  return "mobile";
23099
24257
  }
23100
24258
  if (techStack.database.length > 0 && !techStack.framework) {
@@ -23111,10 +24269,10 @@ function extractDescription(projectDir) {
23111
24269
  return pkg.description;
23112
24270
  const readmePaths = ["README.md", "readme.md", "Readme.md"];
23113
24271
  for (const readme of readmePaths) {
23114
- const path = join15(projectDir, readme);
23115
- if (existsSync15(path)) {
24272
+ const path = join16(projectDir, readme);
24273
+ if (existsSync16(path)) {
23116
24274
  try {
23117
- const content = readFileSync13(path, "utf-8");
24275
+ const content = readFileSync14(path, "utf-8");
23118
24276
  const lines = content.split(`
23119
24277
  `);
23120
24278
  let foundTitle = false;
@@ -24532,9 +25690,9 @@ var onboardCommand = defineCommand2({
24532
25690
  },
24533
25691
  async run({ args }) {
24534
25692
  const projectPath = process.cwd();
24535
- const contextPath = join16(projectPath, ".nairon", "context.json");
24536
- if (existsSync16(contextPath) && !args.force) {
24537
- const existing = JSON.parse(readFileSync14(contextPath, "utf-8"));
25693
+ const contextPath = join17(projectPath, ".nairon", "context.json");
25694
+ if (existsSync17(contextPath) && !args.force) {
25695
+ const existing = JSON.parse(readFileSync15(contextPath, "utf-8"));
24538
25696
  consola.info("Project already onboarded. Use --force to re-run.");
24539
25697
  consola.info(`Business: ${existing.businessContext?.domain || existing.businessContext?.slice?.(0, 50) || "Not set"}...`);
24540
25698
  consola.info(`Stack: ${formatTechStackSummary(existing)}`);
@@ -24682,8 +25840,8 @@ var onboardCommand = defineCommand2({
24682
25840
  createdAt: new Date().toISOString(),
24683
25841
  updatedAt: new Date().toISOString()
24684
25842
  };
24685
- const naironDir = join16(projectPath, ".nairon");
24686
- if (!existsSync16(naironDir)) {
25843
+ const naironDir = join17(projectPath, ".nairon");
25844
+ if (!existsSync17(naironDir)) {
24687
25845
  mkdirSync6(naironDir, { recursive: true });
24688
25846
  }
24689
25847
  writeFileSync7(contextPath, JSON.stringify(fullContext, null, 2));
@@ -24774,51 +25932,51 @@ function formatTechStackSummary(context) {
24774
25932
  function detectInstalledTools(projectPath) {
24775
25933
  const tools = [];
24776
25934
  const home = process.env.HOME || "";
24777
- const userClaudeJson = join16(home, ".claude.json");
24778
- if (existsSync16(userClaudeJson)) {
25935
+ const userClaudeJson = join17(home, ".claude.json");
25936
+ if (existsSync17(userClaudeJson)) {
24779
25937
  try {
24780
- const config = JSON.parse(readFileSync14(userClaudeJson, "utf-8"));
25938
+ const config = JSON.parse(readFileSync15(userClaudeJson, "utf-8"));
24781
25939
  if (config.mcpServers) {
24782
25940
  tools.push(...Object.keys(config.mcpServers));
24783
25941
  }
24784
25942
  } catch {}
24785
25943
  }
24786
- const claudeDesktopConfig = join16(home, ".claude", "claude_desktop_config.json");
24787
- if (existsSync16(claudeDesktopConfig)) {
25944
+ const claudeDesktopConfig = join17(home, ".claude", "claude_desktop_config.json");
25945
+ if (existsSync17(claudeDesktopConfig)) {
24788
25946
  try {
24789
- const config = JSON.parse(readFileSync14(claudeDesktopConfig, "utf-8"));
25947
+ const config = JSON.parse(readFileSync15(claudeDesktopConfig, "utf-8"));
24790
25948
  if (config.mcpServers) {
24791
25949
  tools.push(...Object.keys(config.mcpServers));
24792
25950
  }
24793
25951
  } catch {}
24794
25952
  }
24795
- const projectMcpJson = join16(projectPath, ".mcp.json");
24796
- if (existsSync16(projectMcpJson)) {
25953
+ const projectMcpJson = join17(projectPath, ".mcp.json");
25954
+ if (existsSync17(projectMcpJson)) {
24797
25955
  try {
24798
- const config = JSON.parse(readFileSync14(projectMcpJson, "utf-8"));
25956
+ const config = JSON.parse(readFileSync15(projectMcpJson, "utf-8"));
24799
25957
  if (config.mcpServers) {
24800
25958
  tools.push(...Object.keys(config.mcpServers));
24801
25959
  }
24802
25960
  } catch {}
24803
25961
  }
24804
- const projectClaudeConfig = join16(projectPath, ".claude", "settings.json");
24805
- if (existsSync16(projectClaudeConfig)) {
25962
+ const projectClaudeConfig = join17(projectPath, ".claude", "settings.json");
25963
+ if (existsSync17(projectClaudeConfig)) {
24806
25964
  try {
24807
- const config = JSON.parse(readFileSync14(projectClaudeConfig, "utf-8"));
25965
+ const config = JSON.parse(readFileSync15(projectClaudeConfig, "utf-8"));
24808
25966
  if (config.mcpServers) {
24809
25967
  tools.push(...Object.keys(config.mcpServers));
24810
25968
  }
24811
25969
  } catch {}
24812
25970
  }
24813
- const skillsDir = join16(home, ".config", "opencode", "skills");
24814
- if (existsSync16(skillsDir)) {
25971
+ const skillsDir = join17(home, ".config", "opencode", "skills");
25972
+ if (existsSync17(skillsDir)) {
24815
25973
  try {
24816
25974
  const skills = readdirSync9(skillsDir);
24817
25975
  tools.push(...skills.filter((s2) => !s2.startsWith(".")));
24818
25976
  } catch {}
24819
25977
  }
24820
- const agentsSkillsDir = join16(home, ".agents", "skills");
24821
- if (existsSync16(agentsSkillsDir)) {
25978
+ const agentsSkillsDir = join17(home, ".agents", "skills");
25979
+ if (existsSync17(agentsSkillsDir)) {
24822
25980
  try {
24823
25981
  const skills = readdirSync9(agentsSkillsDir);
24824
25982
  tools.push(...skills.filter((s2) => !s2.startsWith(".")));
@@ -24828,11 +25986,11 @@ function detectInstalledTools(projectPath) {
24828
25986
  }
24829
25987
  function detectPrimaryAgent() {
24830
25988
  const home = process.env.HOME || "";
24831
- if (existsSync16(join16(home, ".claude")))
25989
+ if (existsSync17(join17(home, ".claude")))
24832
25990
  return "claude-code";
24833
- if (existsSync16(join16(home, ".cursor")))
25991
+ if (existsSync17(join17(home, ".cursor")))
24834
25992
  return "cursor";
24835
- if (existsSync16(join16(home, ".config", "opencode")))
25993
+ if (existsSync17(join17(home, ".config", "opencode")))
24836
25994
  return "opencode";
24837
25995
  return;
24838
25996
  }
@@ -24927,7 +26085,7 @@ function formatBytes(bytes) {
24927
26085
  // package.json
24928
26086
  var package_default2 = {
24929
26087
  name: "nairon-bench",
24930
- version: "0.3.12",
26088
+ version: "0.3.14",
24931
26089
  description: "AI workflow benchmarking CLI",
24932
26090
  type: "module",
24933
26091
  bin: {
@@ -24959,8 +26117,9 @@ var package_default2 = {
24959
26117
  "build:linux-arm64": "bun build --compile --target=bun-linux-arm64 --outfile=dist/nb-linux-arm64 src/index.ts",
24960
26118
  "build:windows-x64": "bun build --compile --target=bun-windows-x64 --outfile=dist/nb-windows-x64.exe src/index.ts",
24961
26119
  typecheck: "tsc --noEmit",
24962
- test: "bun test",
24963
- "test:e2e": "bun test --test-name-pattern e2e",
26120
+ test: "vitest run",
26121
+ "test:e2e": "vitest run tests/e2e/",
26122
+ "test:watch": "vitest",
24964
26123
  clean: "rm -rf dist",
24965
26124
  prepublishOnly: "bun run build"
24966
26125
  },