diffprism 0.2.14 → 0.4.0

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.
package/dist/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startReview
4
- } from "./chunk-WTTAKQX3.js";
4
+ } from "./chunk-IZLVSGFJ.js";
5
5
 
6
6
  // cli/src/index.ts
7
7
  import { Command } from "commander";
@@ -22,7 +22,8 @@ async function review(ref, flags) {
22
22
  const result = await startReview({
23
23
  diffRef,
24
24
  title: flags.title,
25
- cwd: process.cwd()
25
+ cwd: process.cwd(),
26
+ dev: flags.dev
26
27
  });
27
28
  console.log(JSON.stringify(result, null, 2));
28
29
  process.exit(0);
@@ -41,7 +42,7 @@ async function serve() {
41
42
 
42
43
  // cli/src/index.ts
43
44
  var program = new Command();
44
- program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.2.14" : "0.0.0-dev");
45
- program.command("review [ref]").description("Open a browser-based diff review").option("--staged", "Review staged changes").option("--unstaged", "Review unstaged changes").option("-t, --title <title>", "Review title").action(review);
45
+ program.name("diffprism").description("Local-first code review tool for agent-generated changes").version(true ? "0.4.0" : "0.0.0-dev");
46
+ program.command("review [ref]").description("Open a browser-based diff review").option("--staged", "Review staged changes").option("--unstaged", "Review unstaged changes").option("-t, --title <title>", "Review title").option("--dev", "Use Vite dev server with HMR instead of static files").action(review);
46
47
  program.command("serve").description("Start the MCP server for Claude Code integration").action(serve);
47
48
  program.parse();
@@ -415,6 +415,176 @@ function generateSummary(files) {
415
415
  const breakdown = parts.length > 0 ? `: ${parts.join(", ")}` : "";
416
416
  return `${totalFiles} files changed${breakdown} (+${totalAdditions} -${totalDeletions})`;
417
417
  }
418
+ var BRANCH_PATTERN = /\b(if|else|switch|case|catch)\b|\?\s|&&|\|\|/;
419
+ function computeComplexityScores(files) {
420
+ const results = [];
421
+ for (const file of files) {
422
+ let score = 0;
423
+ const factors = [];
424
+ const totalChanges = file.additions + file.deletions;
425
+ if (totalChanges > 100) {
426
+ score += 3;
427
+ factors.push(`large diff (+${file.additions} -${file.deletions})`);
428
+ } else if (totalChanges > 50) {
429
+ score += 2;
430
+ factors.push(`medium diff (+${file.additions} -${file.deletions})`);
431
+ } else if (totalChanges > 20) {
432
+ score += 1;
433
+ factors.push(`moderate diff (+${file.additions} -${file.deletions})`);
434
+ }
435
+ const hunkCount = file.hunks.length;
436
+ if (hunkCount > 4) {
437
+ score += 2;
438
+ factors.push(`many hunks (${hunkCount})`);
439
+ } else if (hunkCount > 2) {
440
+ score += 1;
441
+ factors.push(`multiple hunks (${hunkCount})`);
442
+ }
443
+ let branchCount = 0;
444
+ let deepNestCount = 0;
445
+ for (const hunk of file.hunks) {
446
+ for (const change of hunk.changes) {
447
+ if (change.type !== "add") continue;
448
+ const line = change.content;
449
+ if (BRANCH_PATTERN.test(line)) {
450
+ branchCount++;
451
+ }
452
+ const leadingSpaces = line.match(/^(\s*)/);
453
+ if (leadingSpaces) {
454
+ const ws = leadingSpaces[1];
455
+ const tabCount = (ws.match(/\t/g) || []).length;
456
+ const spaceCount = ws.replace(/\t/g, "").length;
457
+ if (tabCount >= 4 || spaceCount >= 16) {
458
+ deepNestCount++;
459
+ }
460
+ }
461
+ }
462
+ }
463
+ const branchScore = Math.floor(branchCount / 5);
464
+ if (branchScore > 0) {
465
+ score += branchScore;
466
+ factors.push(`${branchCount} logic branches`);
467
+ }
468
+ const nestScore = Math.floor(deepNestCount / 5);
469
+ if (nestScore > 0) {
470
+ score += nestScore;
471
+ factors.push(`${deepNestCount} deeply nested lines`);
472
+ }
473
+ score = Math.max(1, Math.min(10, score));
474
+ results.push({ path: file.path, score, factors });
475
+ }
476
+ results.sort((a, b) => b.score - a.score);
477
+ return results;
478
+ }
479
+ var NON_CODE_EXTENSIONS = /* @__PURE__ */ new Set([
480
+ ".json",
481
+ ".md",
482
+ ".css",
483
+ ".scss",
484
+ ".less",
485
+ ".svg",
486
+ ".png",
487
+ ".jpg",
488
+ ".gif",
489
+ ".ico",
490
+ ".yaml",
491
+ ".yml",
492
+ ".toml",
493
+ ".lock",
494
+ ".html"
495
+ ]);
496
+ var CONFIG_PATTERNS = [
497
+ /\.config\./,
498
+ /\.rc\./,
499
+ /eslint/,
500
+ /prettier/,
501
+ /tsconfig/,
502
+ /tailwind/,
503
+ /vite\.config/,
504
+ /vitest\.config/
505
+ ];
506
+ function isTestFile(path4) {
507
+ return TEST_PATTERNS.some((re) => re.test(path4));
508
+ }
509
+ function isNonCodeFile(path4) {
510
+ const ext = path4.slice(path4.lastIndexOf("."));
511
+ return NON_CODE_EXTENSIONS.has(ext);
512
+ }
513
+ function isConfigFile(path4) {
514
+ return CONFIG_PATTERNS.some((re) => re.test(path4));
515
+ }
516
+ function detectTestCoverageGaps(files) {
517
+ const filePaths = new Set(files.map((f) => f.path));
518
+ const results = [];
519
+ for (const file of files) {
520
+ if (file.status !== "added" && file.status !== "modified") continue;
521
+ if (isTestFile(file.path)) continue;
522
+ if (isNonCodeFile(file.path)) continue;
523
+ if (isConfigFile(file.path)) continue;
524
+ const dir = file.path.slice(0, file.path.lastIndexOf("/") + 1);
525
+ const basename = file.path.slice(file.path.lastIndexOf("/") + 1);
526
+ const extDot = basename.lastIndexOf(".");
527
+ const name = extDot > 0 ? basename.slice(0, extDot) : basename;
528
+ const ext = extDot > 0 ? basename.slice(extDot) : "";
529
+ const candidates = [
530
+ `${dir}${name}.test${ext}`,
531
+ `${dir}${name}.spec${ext}`,
532
+ `${dir}__tests__/${name}${ext}`,
533
+ `${dir}__tests__/${name}.test${ext}`,
534
+ `${dir}__tests__/${name}.spec${ext}`
535
+ ];
536
+ const matchedTest = candidates.find((c) => filePaths.has(c));
537
+ results.push({
538
+ sourceFile: file.path,
539
+ testFile: matchedTest ?? null
540
+ });
541
+ }
542
+ return results;
543
+ }
544
+ var PATTERN_MATCHERS = [
545
+ { pattern: "todo", test: (l) => /\btodo\b/i.test(l) },
546
+ { pattern: "fixme", test: (l) => /\bfixme\b/i.test(l) },
547
+ { pattern: "hack", test: (l) => /\bhack\b/i.test(l) },
548
+ {
549
+ pattern: "console",
550
+ test: (l) => /\bconsole\.(log|debug|warn|error)\b/.test(l)
551
+ },
552
+ { pattern: "debug", test: (l) => /\bdebugger\b/.test(l) },
553
+ {
554
+ pattern: "disabled_test",
555
+ test: (l) => /\.(skip)\(|(\bxit|xdescribe|xtest)\(/.test(l)
556
+ }
557
+ ];
558
+ function detectPatterns(files) {
559
+ const results = [];
560
+ for (const file of files) {
561
+ if (file.status === "added" && file.additions > 500) {
562
+ results.push({
563
+ file: file.path,
564
+ line: 0,
565
+ pattern: "large_file",
566
+ content: `Large added file: ${file.additions} lines`
567
+ });
568
+ }
569
+ for (const hunk of file.hunks) {
570
+ for (const change of hunk.changes) {
571
+ if (change.type !== "add") continue;
572
+ for (const matcher of PATTERN_MATCHERS) {
573
+ if (matcher.test(change.content)) {
574
+ results.push({
575
+ file: file.path,
576
+ line: change.lineNumber,
577
+ pattern: matcher.pattern,
578
+ content: change.content.trim()
579
+ });
580
+ }
581
+ }
582
+ }
583
+ }
584
+ }
585
+ results.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
586
+ return results;
587
+ }
418
588
 
419
589
  // packages/analysis/src/index.ts
420
590
  function analyze(diffSet) {
@@ -425,6 +595,9 @@ function analyze(diffSet) {
425
595
  const affectedTests = detectAffectedTests(files);
426
596
  const newDependencies = detectNewDependencies(files);
427
597
  const summary = generateSummary(files);
598
+ const complexity = computeComplexityScores(files);
599
+ const testCoverage = detectTestCoverageGaps(files);
600
+ const patterns = detectPatterns(files);
428
601
  return {
429
602
  summary,
430
603
  triage,
@@ -440,7 +613,10 @@ function analyze(diffSet) {
440
613
  typeCheck: null,
441
614
  lintClean: null
442
615
  },
443
- fileStats
616
+ fileStats,
617
+ complexity,
618
+ testCoverage,
619
+ patterns
444
620
  };
445
621
  }
446
622
 
@@ -571,6 +747,28 @@ function resolveUiDist() {
571
747
  "Could not find built UI. Run 'pnpm -F @diffprism/ui build' first."
572
748
  );
573
749
  }
750
+ function resolveUiRoot() {
751
+ const thisFile = fileURLToPath(import.meta.url);
752
+ const thisDir = path3.dirname(thisFile);
753
+ const workspaceRoot = path3.resolve(thisDir, "..", "..", "..");
754
+ const uiRoot = path3.join(workspaceRoot, "packages", "ui");
755
+ if (fs.existsSync(path3.join(uiRoot, "index.html"))) {
756
+ return uiRoot;
757
+ }
758
+ throw new Error(
759
+ "Could not find UI source directory. Dev mode requires the diffprism workspace."
760
+ );
761
+ }
762
+ async function startViteDevServer(uiRoot, port, silent) {
763
+ const { createServer } = await import("vite");
764
+ const vite = await createServer({
765
+ root: uiRoot,
766
+ server: { port, strictPort: true, open: false },
767
+ logLevel: silent ? "silent" : "warn"
768
+ });
769
+ await vite.listen();
770
+ return vite;
771
+ }
574
772
  function createStaticServer(distPath, port) {
575
773
  const server = http.createServer((req, res) => {
576
774
  const urlPath = req.url?.split("?")[0] ?? "/";
@@ -595,7 +793,7 @@ function createStaticServer(distPath, port) {
595
793
  });
596
794
  }
597
795
  async function startReview(options) {
598
- const { diffRef, title, description, reasoning, cwd, silent } = options;
796
+ const { diffRef, title, description, reasoning, cwd, silent, dev } = options;
599
797
  const { diffSet, rawDiff } = getDiff(diffRef, { cwd });
600
798
  if (diffSet.files.length === 0) {
601
799
  if (!silent) {
@@ -615,10 +813,16 @@ async function startReview(options) {
615
813
  getPort()
616
814
  ]);
617
815
  const bridge = createWsBridge(wsPort);
618
- const uiDist = resolveUiDist();
619
816
  let httpServer = null;
817
+ let viteServer = null;
620
818
  try {
621
- httpServer = await createStaticServer(uiDist, httpPort);
819
+ if (dev) {
820
+ const uiRoot = resolveUiRoot();
821
+ viteServer = await startViteDevServer(uiRoot, httpPort, !!silent);
822
+ } else {
823
+ const uiDist = resolveUiDist();
824
+ httpServer = await createStaticServer(uiDist, httpPort);
825
+ }
622
826
  const url = `http://localhost:${httpPort}?wsPort=${wsPort}&reviewId=${session.id}`;
623
827
  if (!silent) {
624
828
  console.log(`
@@ -640,6 +844,9 @@ DiffPrism Review: ${title ?? briefing.summary}`);
640
844
  return result;
641
845
  } finally {
642
846
  bridge.close();
847
+ if (viteServer) {
848
+ await viteServer.close();
849
+ }
643
850
  if (httpServer) {
644
851
  httpServer.close();
645
852
  }
@@ -1,15 +1,17 @@
1
1
  import {
2
2
  startReview
3
- } from "./chunk-WTTAKQX3.js";
3
+ } from "./chunk-IZLVSGFJ.js";
4
4
 
5
5
  // packages/mcp-server/src/index.ts
6
+ import fs from "fs";
7
+ import path from "path";
6
8
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
9
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
10
  import { z } from "zod";
9
11
  async function startMcpServer() {
10
12
  const server = new McpServer({
11
13
  name: "diffprism",
12
- version: true ? "0.2.14" : "0.0.0-dev"
14
+ version: true ? "0.4.0" : "0.0.0-dev"
13
15
  });
14
16
  server.tool(
15
17
  "open_review",
@@ -24,14 +26,18 @@ async function startMcpServer() {
24
26
  },
25
27
  async ({ diff_ref, title, description, reasoning }) => {
26
28
  try {
29
+ const isDev = fs.existsSync(
30
+ path.join(process.cwd(), "packages", "ui", "src", "App.tsx")
31
+ );
27
32
  const result = await startReview({
28
33
  diffRef: diff_ref,
29
34
  title,
30
35
  description,
31
36
  reasoning,
32
37
  cwd: process.cwd(),
33
- silent: true
38
+ silent: true,
34
39
  // Suppress stdout — MCP uses stdio
40
+ dev: isDev
35
41
  });
36
42
  return {
37
43
  content: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diffprism",
3
- "version": "0.2.14",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Local-first code review tool for agent-generated code changes",
6
6
  "bin": {
@@ -0,0 +1 @@
1
+ :root{--diff-background-color:initial;--diff-text-color:initial;--diff-font-family:Consolas,Courier,monospace;--diff-selection-background-color:#b3d7ff;--diff-selection-text-color:var(--diff-text-color);--diff-gutter-insert-background-color:#d6fedb;--diff-gutter-insert-text-color:var(--diff-text-color);--diff-gutter-delete-background-color:#fadde0;--diff-gutter-delete-text-color:var(--diff-text-color);--diff-gutter-selected-background-color:#fffce0;--diff-gutter-selected-text-color:var(--diff-text-color);--diff-code-insert-background-color:#eaffee;--diff-code-insert-text-color:var(--diff-text-color);--diff-code-delete-background-color:#fdeff0;--diff-code-delete-text-color:var(--diff-text-color);--diff-code-insert-edit-background-color:#c0dc91;--diff-code-insert-edit-text-color:var(--diff-text-color);--diff-code-delete-edit-background-color:#f39ea2;--diff-code-delete-edit-text-color:var(--diff-text-color);--diff-code-selected-background-color:#fffce0;--diff-code-selected-text-color:var(--diff-text-color);--diff-omit-gutter-line-color:#cb2a1d}.diff{background-color:var(--diff-background-color);border-collapse:collapse;color:var(--diff-text-color);table-layout:fixed;width:100%}.diff::-moz-selection{background-color:#b3d7ff;background-color:var(--diff-selection-background-color);color:var(--diff-text-color);color:var(--diff-selection-text-color)}.diff::selection{background-color:#b3d7ff;background-color:var(--diff-selection-background-color);color:var(--diff-text-color);color:var(--diff-selection-text-color)}.diff td{padding-bottom:0;padding-top:0;vertical-align:top}.diff-line{font-family:Consolas,Courier,monospace;font-family:var(--diff-font-family);line-height:1.5}.diff-gutter>a{color:inherit;display:block}.diff-gutter{cursor:pointer;padding:0 1ch;text-align:right;-webkit-user-select:none;-moz-user-select:none;user-select:none}.diff-gutter-insert{background-color:#d6fedb;background-color:var(--diff-gutter-insert-background-color);color:var(--diff-text-color);color:var(--diff-gutter-insert-text-color)}.diff-gutter-delete{background-color:#fadde0;background-color:var(--diff-gutter-delete-background-color);color:var(--diff-text-color);color:var(--diff-gutter-delete-text-color)}.diff-gutter-omit{cursor:default}.diff-gutter-selected{background-color:#fffce0;background-color:var(--diff-gutter-selected-background-color);color:var(--diff-text-color);color:var(--diff-gutter-selected-text-color)}.diff-code{word-wrap:break-word;padding:0 0 0 .5em;white-space:pre-wrap;word-break:break-all}.diff-code-edit{color:inherit}.diff-code-insert{background-color:#eaffee;background-color:var(--diff-code-insert-background-color);color:var(--diff-text-color);color:var(--diff-code-insert-text-color)}.diff-code-insert .diff-code-edit{background-color:#c0dc91;background-color:var(--diff-code-insert-edit-background-color);color:var(--diff-text-color);color:var(--diff-code-insert-edit-text-color)}.diff-code-delete{background-color:#fdeff0;background-color:var(--diff-code-delete-background-color);color:var(--diff-text-color);color:var(--diff-code-delete-text-color)}.diff-code-delete .diff-code-edit{background-color:#f39ea2;background-color:var(--diff-code-delete-edit-background-color);color:var(--diff-text-color);color:var(--diff-code-delete-edit-text-color)}.diff-code-selected{background-color:#fffce0;background-color:var(--diff-code-selected-background-color);color:var(--diff-text-color);color:var(--diff-code-selected-text-color)}.diff-widget-content{vertical-align:top}.diff-gutter-col{width:7ch}.diff-gutter-omit{height:0}.diff-gutter-omit:before{background-color:#cb2a1d;background-color:var(--diff-omit-gutter-line-color);content:" ";display:block;height:100%;margin-left:4.6ch;overflow:hidden;white-space:pre;width:2px}.diff-decoration{line-height:1.5;-webkit-user-select:none;-moz-user-select:none;user-select:none}.diff-decoration-content{font-family:Consolas,Courier,monospace;font-family:var(--diff-font-family);padding:0}*,:before,:after{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / .5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / .5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-1{margin-left:.25rem}.ml-auto{margin-left:auto}.mr-1\.5{margin-right:.375rem}.mt-1{margin-top:.25rem}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.h-12{height:3rem}.h-16{height:4rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-full{height:100%}.h-screen{height:100vh}.min-h-0{min-height:0px}.w-12{width:3rem}.w-16{width:4rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-\[280px\]{width:280px}.w-full{width:100%}.min-w-0{min-width:0px}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.gap-y-3{row-gap:.75rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-accent{--tw-border-opacity:1;border-color:rgb(88 166 255 / var(--tw-border-opacity, 1))}.border-accent\/30{border-color:#58a6ff4d}.border-blue-500\/30{border-color:#3b82f64d}.border-border{--tw-border-opacity:1;border-color:rgb(48 54 61 / var(--tw-border-opacity, 1))}.border-border\/50{border-color:#30363d80}.border-green-500\/30{border-color:#22c55e4d}.border-orange-500\/30{border-color:#f973164d}.border-purple-500\/30{border-color:#a855f74d}.border-red-500\/30{border-color:#ef44444d}.border-transparent{border-color:transparent}.border-yellow-500\/30{border-color:#eab3084d}.border-t-accent{--tw-border-opacity:1;border-top-color:rgb(88 166 255 / var(--tw-border-opacity, 1))}.bg-accent\/10{background-color:#58a6ff1a}.bg-background{--tw-bg-opacity:1;background-color:rgb(13 17 23 / var(--tw-bg-opacity, 1))}.bg-blue-600\/20{background-color:#2563eb33}.bg-green-600\/20{background-color:#16a34a33}.bg-orange-600\/20{background-color:#ea580c33}.bg-purple-600\/20{background-color:#9333ea33}.bg-red-600\/20{background-color:#dc262633}.bg-surface{--tw-bg-opacity:1;background-color:rgb(22 27 34 / var(--tw-bg-opacity, 1))}.bg-yellow-600\/20{background-color:#ca8a0433}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.pb-3{padding-bottom:.75rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-\[10px\]{font-size:10px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wide{letter-spacing:.025em}.text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity:1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-orange-400{--tw-text-opacity:1;color:rgb(251 146 60 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity:1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-text-primary{--tw-text-opacity:1;color:rgb(230 237 243 / var(--tw-text-opacity, 1))}.text-text-secondary{--tw-text-opacity:1;color:rgb(139 148 158 / var(--tw-text-opacity, 1))}.text-text-secondary\/40{color:#8b949e66}.text-text-secondary\/60{color:#8b949e99}.text-yellow-400{--tw-text-opacity:1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-100{transition-duration:.1s}.diff-unified{background-color:#0d1117;color:#e6edf3;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;font-size:13px;line-height:20px}.diff-unified .diff-gutter{background-color:#161b22;color:#8b949e;border-right:1px solid #30363d;padding:0 8px;min-width:50px;text-align:right;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:default}.diff-unified .diff-gutter-col{width:60px;min-width:60px}.diff-unified .diff-code{padding:0 12px;white-space:pre}.diff-unified .diff-code-insert{background-color:#2ea04326}.diff-unified .diff-code-insert .diff-code-text{background-color:transparent}.diff-unified .diff-gutter-insert{background-color:#2ea04333;color:#7ee787}.diff-unified .diff-code-delete{background-color:#f8514926}.diff-unified .diff-code-delete .diff-code-text{background-color:transparent}.diff-unified .diff-gutter-delete{background-color:#f8514933;color:#f85149}.diff-unified .diff-code-normal{background-color:transparent}.diff-unified .diff-gutter-normal{background-color:#161b22}.diff-unified .diff-hunk-header{background-color:#58a6ff1a;border-top:1px solid #30363d;border-bottom:1px solid #30363d}.diff-unified .diff-hunk-header-gutter{background-color:#58a6ff26;color:#58a6ff}.diff-unified .diff-hunk-header-content{color:#8b949e;padding:4px 12px;font-style:italic}.diff-unified .diff-code-edit .diff-code-text .diff-code-edit-text{background-color:#2ea04366;border-radius:2px}.diff-unified .diff-code-delete .diff-code-text .diff-code-edit-text{background-color:#f8514966;border-radius:2px}.diff-unified table{width:100%;border-collapse:collapse;table-layout:fixed}.diff-unified td{vertical-align:top}.diff-unified .token.comment,.diff-unified .token.prolog,.diff-unified .token.doctype,.diff-unified .token.cdata{color:#8b949e}.diff-unified .token.punctuation{color:#e6edf3}.diff-unified .token.property,.diff-unified .token.tag,.diff-unified .token.boolean,.diff-unified .token.number,.diff-unified .token.constant,.diff-unified .token.symbol{color:#79c0ff}.diff-unified .token.selector,.diff-unified .token.attr-name,.diff-unified .token.string,.diff-unified .token.char,.diff-unified .token.builtin{color:#a5d6ff}.diff-unified .token.operator,.diff-unified .token.entity,.diff-unified .token.url,.diff-unified .token.atrule,.diff-unified .token.attr-value,.diff-unified .token.keyword{color:#ff7b72}.diff-unified .token.function,.diff-unified .token.class-name{color:#d2a8ff}.diff-unified .token.regex,.diff-unified .token.important,.diff-unified .token.variable{color:#ffa657}.diff-unified .token.string{color:#a5d6ff}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:#0d1117}::-webkit-scrollbar-thumb{background:#30363d;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#484f58}.placeholder\:text-text-secondary\/50::-moz-placeholder{color:#8b949e80}.placeholder\:text-text-secondary\/50::placeholder{color:#8b949e80}.hover\:border-blue-500\/50:hover{border-color:#3b82f680}.hover\:border-green-500\/50:hover{border-color:#22c55e80}.hover\:border-red-500\/50:hover{border-color:#ef444480}.hover\:bg-blue-600\/30:hover{background-color:#2563eb4d}.hover\:bg-green-600\/30:hover{background-color:#16a34a4d}.hover\:bg-red-600\/30:hover{background-color:#dc26264d}.hover\:bg-white\/5:hover{background-color:#ffffff0d}.focus\:border-accent:focus{--tw-border-opacity:1;border-color:rgb(88 166 255 / var(--tw-border-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-accent:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(88 166 255 / var(--tw-ring-opacity, 1))}.group:hover .group-hover\:text-text-primary{--tw-text-opacity:1;color:rgb(230 237 243 / var(--tw-text-opacity, 1))}