@payconductor/houndsman 0.8.0 → 0.11.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.
Files changed (55) hide show
  1. package/package.json +1 -1
  2. package/src/cli/index.ts +9 -1
  3. package/src/core/ai/ai-client.ts +21 -0
  4. package/src/core/ai/ai-sdk-model-provider.ts +43 -18
  5. package/src/core/ai/features/technical-summarizer.ts +43 -0
  6. package/src/core/ai/index.ts +4 -1
  7. package/src/core/ai/prompts/base.ts +7 -0
  8. package/src/core/ai/prompts/context.ts +5 -4
  9. package/src/core/ai/prompts/technical.ts +31 -0
  10. package/src/core/ai/provider-capabilities.ts +45 -0
  11. package/src/core/ai/schemas.ts +19 -3
  12. package/src/core/ai/types.ts +3 -1
  13. package/src/core/brief.ts +25 -4
  14. package/src/core/commands/scaffold.ts +5 -0
  15. package/src/core/config-loader.ts +18 -4
  16. package/src/core/config.ts +17 -1
  17. package/src/core/engine/review-workspace.ts +6 -2
  18. package/src/core/git-runner.ts +8 -1
  19. package/src/core/orchestrator.ts +114 -2
  20. package/src/core/paths.ts +3 -0
  21. package/src/core/pr-context.ts +11 -6
  22. package/src/core/project-config.ts +9 -1
  23. package/src/core/renderers/resume-renderer.ts +17 -34
  24. package/src/core/report-builder.ts +6 -6
  25. package/src/core/services/worktree-manager.ts +90 -7
  26. package/src/core/storage/types.ts +1 -0
  27. package/src/core/technical-summary.ts +398 -0
  28. package/src/harness/agent/agent-factory.ts +1 -0
  29. package/src/harness/agent/review-agent.ts +11 -1
  30. package/src/harness/config/harness-config.ts +6 -1
  31. package/src/index.ts +2 -0
  32. package/src/server/client/App.tsx +2 -1
  33. package/src/server/client/PrsSection.tsx +296 -0
  34. package/src/server/client/ResumePanel.tsx +10 -13
  35. package/src/server/client/RunsSection.tsx +73 -0
  36. package/src/server/client/Sidebar.tsx +171 -413
  37. package/src/server/client/sidebar-sections.tsx +73 -0
  38. package/src/server/client/utils/constants.ts +7 -2
  39. package/src/server/client/utils/sidebar-prefs.ts +64 -0
  40. package/src/server/client/utils/types.ts +1 -0
  41. package/src/server/index.ts +23 -409
  42. package/src/server/styles.css +454 -0
  43. package/src/tests/__snapshots__/baseline.test.ts.snap +5 -0
  44. package/src/tests/ai-core.test.ts +71 -0
  45. package/src/tests/ai-features.test.ts +47 -0
  46. package/src/tests/baseline.test.ts +4 -1
  47. package/src/tests/harness-agent.test.ts +19 -0
  48. package/src/tests/review-resume.test.ts +36 -8
  49. package/src/tests/review-scope.test.ts +67 -0
  50. package/src/tests/review-scripts.test.ts +2 -2
  51. package/src/tests/server.test.ts +16 -1
  52. package/src/tests/sidebar-sections.test.ts +94 -0
  53. package/src/tests/technical-summary.test.ts +176 -0
  54. package/src/tests/worktree.test.ts +110 -5
  55. package/assets/logo-legacy.png +0 -0
@@ -6,6 +6,7 @@
6
6
  * Bun e do Preact. Bind padrão em `127.0.0.1` — ferramenta local, sem autenticação.
7
7
  */
8
8
 
9
+ import { join } from "node:path";
9
10
  import { mediaPath } from "../core/assets";
10
11
  import type { HoundsmanConfig } from "../core/config";
11
12
  import type { FindingUpdate } from "../core/findings-state";
@@ -81,6 +82,7 @@ type ServerDeps = {
81
82
 
82
83
  const API_RUN = /^\/api\/runs\/([^/]+)(\/.*)?$/;
83
84
  const LOGO_FILE = "logo.png";
85
+ const STYLES_FILE = "styles.css";
84
86
 
85
87
  /** Serve a logo do pacote (`assets/logo.png`). */
86
88
  async function serveLogo(): Promise<Response> {
@@ -91,6 +93,23 @@ async function serveLogo(): Promise<Response> {
91
93
  });
92
94
  }
93
95
 
96
+ /**
97
+ * Serve o CSS da UI (`styles.css`, ao lado deste módulo) direto do disco.
98
+ *
99
+ * Ler a cada request mantém o hot reload: editar o CSS só exige recarregar a
100
+ * página — igual ao bundle do cliente.
101
+ */
102
+ async function serveStyles(): Promise<Response> {
103
+ const file = Bun.file(join(import.meta.dir, STYLES_FILE));
104
+ if (!(await file.exists())) return notFound("styles não encontrado");
105
+ return new Response(file, {
106
+ headers: {
107
+ "content-type": "text/css; charset=utf-8",
108
+ "cache-control": "no-store, no-cache, must-revalidate",
109
+ },
110
+ });
111
+ }
112
+
94
113
  async function route(
95
114
  request: Request,
96
115
  runs: RunRepository,
@@ -109,6 +128,7 @@ async function route(
109
128
  if (path === "/assets/app.js") {
110
129
  return noStore(await buildClientBundle(), "text/javascript; charset=utf-8");
111
130
  }
131
+ if (path === `/assets/${STYLES_FILE}`) return serveStyles();
112
132
  if (path === `/assets/${LOGO_FILE}`) return serveLogo();
113
133
  if (path === "/api/health") return json({ ok: true });
114
134
  if (path === "/api/editor" && request.method === "GET") return json(editor);
@@ -376,416 +396,9 @@ export function startServer(options: ServerOptions): { server: BunServer; url: s
376
396
  }
377
397
 
378
398
  // ---------------------------------------------------------------------------
379
- // Shell (HTML + CSS)
399
+ // Shell (HTML)
380
400
  // ---------------------------------------------------------------------------
381
401
 
382
- const STYLES = `
383
- @import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
384
-
385
- :root {
386
- color-scheme: dark;
387
- --bg:#0a0a0c; --surface:#101014; --surface-2:#17171c; --card:#131318;
388
- --border:rgba(255,255,255,.07); --border-strong:rgba(255,255,255,.14);
389
- --fg:#ece9e4; --muted:#9d978e; --muted-2:#6d6862;
390
- --accent:#f0a52c; --accent-strong:#ffc247; --accent-soft:rgba(240,165,44,.07); --accent-line:rgba(240,165,44,.25);
391
- --blocking:#f2595e; --attention:#eda13a; --suggestion:#5aa4f0; --nit:#8d8a85; --success:#45c07a;
392
- --radius:16px; --radius-lg:18px;
393
- --font-sans:"Poppins","Space Grotesk","Geist Sans",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
394
- --font-mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
395
- }
396
- * { box-sizing:border-box; }
397
- html, body { height:100%; }
398
- body { margin:0; font-size:14px; background:var(--bg); color:var(--fg); font-family:var(--font-sans); -webkit-font-smoothing:antialiased; }
399
- body::before { content:""; position:fixed; inset:0; pointer-events:none; z-index:0;
400
- background:radial-gradient(820px 420px at 8% -12%, rgba(240,165,44,.08), transparent 62%); }
401
- code, pre, .mono { font-family:var(--font-mono); tab-size:4; }
402
- ::-webkit-scrollbar { width:10px; height:10px; }
403
- ::-webkit-scrollbar-thumb { background:rgba(255,255,255,.1); border:2px solid transparent; border-radius:999px; background-clip:padding-box; }
404
- ::-webkit-scrollbar-thumb:hover { background:rgba(255,255,255,.18); background-clip:padding-box; }
405
- .app { position:relative; z-index:1; display:flex; height:100vh; overflow:hidden; }
406
- .sidebar { width:292px; flex:0 0 292px; display:flex; flex-direction:column; gap:14px; padding:18px 14px;
407
- background:var(--surface); border-right:1px solid var(--border); overflow-y:auto; }
408
- .brand { display:flex; align-items:center; gap:12px; }
409
- .brand .logo { width:42px; height:42px; object-fit:contain; border-radius:12px; background:var(--surface-2); border:1px solid var(--border); }
410
- .sidebar h1 { font-size:15.5px; font-weight:700; letter-spacing:-.01em; margin:0; }
411
- .sub { color:var(--muted); font-size:11.5px; margin:0; }
412
- .section-label { margin:4px 0 0; font-size:10px; font-weight:700; letter-spacing:.16em; text-transform:uppercase; color:var(--muted-2); }
413
- .runs { display:flex; flex-direction:column; gap:6px; }
414
- .run { display:flex; flex-direction:column; gap:8px; text-align:left; cursor:pointer; font:inherit; color:inherit; padding:11px 13px;
415
- background:transparent; border:1px solid transparent; border-radius:var(--radius); transition:background .15s ease, border-color .15s ease; }
416
- .run:hover { background:var(--surface-2); border-color:var(--border); }
417
- .run.active { background:var(--accent-soft); border-color:var(--accent-line); }
418
- .run-top { display:flex; align-items:flex-start; gap:8px; }
419
- .run-icon { flex:none; margin-top:2px; color:var(--muted-2); }
420
- .run.active .run-icon { color:var(--accent); }
421
- .run-branch { font-size:13.5px; font-weight:600; line-height:1.35; word-break:break-word; }
422
- .run-meta { display:flex; align-items:center; gap:10px; font-family:var(--font-mono); font-size:11px; color:var(--muted); }
423
- .run-when { display:inline-flex; align-items:center; gap:6px; }
424
- .run-count { margin-left:auto; color:var(--muted-2); }
425
- .run-badge { align-self:flex-start; display:inline-flex; align-items:center; gap:6px; padding:4px 9px; border-radius:999px;
426
- font-size:10px; font-weight:700; letter-spacing:.07em; text-transform:uppercase;
427
- color:var(--success); background:rgba(69,192,122,.12); border:1px solid rgba(69,192,122,.32); }
428
- .run-status { font-size:10px; font-weight:600; letter-spacing:.07em; text-transform:uppercase; color:var(--muted-2); }
429
- .status-reviewing, .status-fixing { color:var(--accent); }
430
- .status-review_done { color:var(--suggestion); }
431
- .status-fix_done { color:var(--success); }
432
- .status-timeout { color:var(--blocking); }
433
- .side-tabs { display:flex; gap:4px; padding:4px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
434
- .side-tab { flex:1; display:inline-flex; align-items:center; justify-content:center; gap:7px; font:inherit; font-size:12px; font-weight:600; cursor:pointer; color:var(--muted);
435
- padding:7px 10px; background:transparent; border:1px solid transparent; border-radius:999px; transition:color .15s ease, background .15s ease; }
436
- .side-tab:hover { color:var(--fg); }
437
- .side-tab.active { color:#1b1305; background:linear-gradient(180deg, var(--accent-strong), var(--accent)); }
438
- .side-count { padding:1px 6px; font-family:var(--font-mono); font-size:10px; color:var(--muted); background:rgba(255,255,255,.08); border-radius:999px; }
439
- .side-tab.active .side-count { color:#1b1305; background:rgba(0,0,0,.14); }
440
- .icon-btn { display:inline-grid; place-items:center; width:26px; height:26px; color:var(--muted); background:transparent; border:1px solid var(--border); border-radius:8px; cursor:pointer; text-decoration:none; }
441
- .icon-btn:hover { color:var(--fg); border-color:var(--border-strong); }
442
- .icon-btn:disabled { opacity:.5; cursor:not-allowed; }
443
- .pr-toolbar { display:flex; align-items:center; justify-content:space-between; gap:8px; }
444
- .pr-toolbar .section-label { margin:0; }
445
- .prs { display:flex; flex-direction:column; gap:8px; }
446
- .pr { display:flex; flex-direction:column; gap:7px; padding:11px 12px; background:var(--card); border:1px solid var(--border); border-radius:var(--radius); }
447
- .pr.current { background:var(--accent-soft); border-color:var(--accent-line); }
448
- .pr-head { display:flex; align-items:center; gap:7px; font-family:var(--font-mono); font-size:11px; color:var(--muted); }
449
- .pr-icon { color:var(--muted-2); }
450
- .pr.current .pr-icon { color:var(--accent); }
451
- .pr-number { font-weight:600; color:var(--fg); }
452
- .pr-current { padding:1px 6px; font-size:9px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:var(--accent); background:rgba(240,165,44,.12); border-radius:999px; }
453
- .pr-title { margin:0; font-size:12.5px; font-weight:600; line-height:1.4; word-break:break-word; }
454
- .pr-meta { display:flex; flex-wrap:wrap; align-items:center; gap:1px 5px; margin:0; font-family:var(--font-mono); font-size:10.5px; color:var(--muted-2); }
455
- .pr-branch { color:var(--suggestion); }
456
- .pr-review { margin:0; font-size:10.5px; font-weight:600; }
457
- .pr-review-APPROVED { color:var(--success); }
458
- .pr-review-CHANGES_REQUESTED { color:var(--blocking); }
459
- .pr-review-REVIEW_REQUIRED { color:var(--attention); }
460
- .pr-actions { display:flex; flex-wrap:wrap; gap:6px; margin-top:2px; }
461
- .pr-btn { display:inline-flex; align-items:center; gap:5px; font:inherit; font-size:10.5px; font-weight:600; cursor:pointer; padding:4px 9px; color:var(--muted);
462
- background:transparent; border:1px solid var(--border-strong); border-radius:999px; text-decoration:none; }
463
- .pr-btn:hover { color:var(--fg); border-color:var(--accent-line); }
464
- .pr-btn:disabled { opacity:.45; cursor:not-allowed; }
465
- .pr-btn.danger { color:var(--accent); background:var(--accent-soft); border-color:var(--accent-line); }
466
- .pr-warning { margin:0; padding:8px 10px; font-size:11px; line-height:1.5; color:var(--attention);
467
- background:rgba(237,161,58,.08); border:1px solid rgba(237,161,58,.24); border-radius:10px; }
468
- .pr-notice { margin:0; padding:8px 10px; font-size:11px; line-height:1.5; color:var(--success);
469
- background:rgba(69,192,122,.08); border:1px solid rgba(69,192,122,.24); border-radius:10px; }
470
- .pr-current-branch { margin:0; font-family:var(--font-mono); font-size:10.5px; color:var(--muted-2); }
471
- .pr-current-branch code { color:var(--fg); }
472
- .comments { display:flex; flex-direction:column; gap:18px; }
473
- .comments-toolbar { display:flex; align-items:center; justify-content:space-between; gap:12px; }
474
- .comments-counts { display:flex; flex-wrap:wrap; align-items:center; gap:12px; font-size:11.5px; color:var(--muted); }
475
- .comments-counts b { font-family:var(--font-mono); font-weight:600; color:var(--fg); }
476
- .comment-group { display:flex; flex-direction:column; gap:10px; }
477
- .comment-group-title { margin:0; font-size:11px; font-weight:700; letter-spacing:.16em; text-transform:uppercase; color:var(--muted-2); }
478
- .comment-file-group { display:flex; flex-direction:column; gap:8px; }
479
- .comment-group-label { display:flex; align-items:center; gap:8px; margin:0; font-family:var(--font-mono); font-size:11.5px; color:var(--suggestion); }
480
- .comment-thread { display:flex; flex-direction:column; overflow:hidden; background:var(--card); border:1px solid var(--border); border-radius:var(--radius-lg); }
481
- .comment-thread-head { display:flex; align-items:center; gap:8px; padding:9px 13px; font-family:var(--font-mono); font-size:11px; color:var(--muted);
482
- background:var(--surface-2); border-bottom:1px solid var(--border); }
483
- .comment-file { overflow:hidden; color:var(--fg); text-overflow:ellipsis; white-space:nowrap; }
484
- .pr-comment { display:flex; flex-direction:column; gap:7px; padding:12px 14px; border-top:1px solid var(--border); }
485
- .comment-thread .pr-comment:first-of-type { border-top:0; }
486
- .pr-comment.reply { margin-left:26px; background:rgba(255,255,255,.012); border-left:2px solid var(--accent-line); }
487
- .pr-comment-head { display:flex; align-items:center; gap:9px; font-size:11px; color:var(--muted-2); }
488
- .pr-comment-author { font-size:12px; font-weight:600; color:var(--fg); }
489
- .pr-comment-link { display:inline-flex; align-items:center; gap:5px; font-size:11px; color:var(--muted); text-decoration:none; }
490
- .pr-comment-link:hover { color:var(--accent); }
491
- .pr-comment-body { font-size:12.5px; line-height:1.6; color:#f0ece6; }
492
- .pr-comment-body .md-p, .pr-comment-body .md-list { font-size:12.5px; }
493
- .main { flex:1; min-width:0; display:flex; flex-direction:column; overflow-y:auto; }
494
- .topbar { position:sticky; top:0; z-index:10; display:flex; align-items:center; justify-content:space-between; gap:16px;
495
- padding:13px 24px; border-bottom:1px solid var(--border); background:rgba(16,16,20,.74);
496
- backdrop-filter:blur(16px) saturate(140%); -webkit-backdrop-filter:blur(16px) saturate(140%); }
497
- .segmented { display:inline-flex; gap:4px; padding:4px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
498
- .seg-btn { display:inline-flex; align-items:center; gap:8px; font:inherit; font-size:12.5px; font-weight:600; cursor:pointer;
499
- color:var(--muted); background:transparent; border:1px solid transparent; border-radius:999px; padding:7px 16px;
500
- transition:color .15s ease, background .15s ease; }
501
- .seg-btn:hover { color:var(--fg); }
502
- .seg-btn.active { color:#1b1305; background:linear-gradient(180deg, var(--accent-strong), var(--accent)); box-shadow:0 4px 14px rgba(240,165,44,.24); }
503
- .topbar-right { display:flex; align-items:center; gap:14px; }
504
- .auto-refresh { display:inline-flex; align-items:center; gap:8px; white-space:nowrap; font-family:var(--font-mono); font-size:11.5px; color:var(--muted); }
505
- .dot { width:8px; height:8px; border-radius:50%; background:var(--success); box-shadow:0 0 0 3px rgba(69,192,122,.14); }
506
- .dot.busy { background:var(--accent); box-shadow:0 0 0 3px rgba(240,165,44,.18); animation:pulse 1.1s ease-in-out infinite; }
507
- .btn { display:inline-flex; align-items:center; gap:8px; font:inherit; font-size:12.5px; font-weight:500; cursor:pointer; color:var(--fg);
508
- padding:8px 15px; background:var(--surface-2); border:1px solid var(--border-strong); border-radius:999px;
509
- transition:background .15s ease, border-color .15s ease, color .15s ease; }
510
- .btn:hover { background:#202027; border-color:var(--accent-line); }
511
- .btn:disabled { opacity:.55; cursor:not-allowed; }
512
- .btn.danger { color:#ffb3b3; background:rgba(255,86,86,.12); border-color:rgba(255,86,86,.4); }
513
- .btn.danger:hover { background:rgba(255,86,86,.2); border-color:rgba(255,86,86,.62); }
514
- .modal-check { display:flex; align-items:flex-start; gap:10px; padding:12px 14px; font-size:12.5px; line-height:1.55;
515
- color:var(--fg); background:var(--surface-2); border:1px solid var(--border); border-radius:12px; cursor:pointer; }
516
- .modal-check input { margin:3px 0 0; flex:none; accent-color:var(--accent); }
517
- .modal-check code { font-family:var(--font-mono); font-size:11.5px; color:var(--muted); word-break:break-all; }
518
- .modal-warn { margin:0; font-size:12px; line-height:1.55; color:var(--muted); }
519
- .icon.spin { animation:spin .8s linear infinite; }
520
- .content { width:100%; max-width:1180px; margin:0 auto; padding:22px 24px 64px; }
521
- .page-header { display:flex; align-items:flex-start; justify-content:space-between; gap:18px; margin-bottom:16px; }
522
- .page-header-text { min-width:0; }
523
- .page-header-actions { display:flex; align-items:center; gap:10px; flex:none; }
524
- .btn.submit-review { color:#1b1305; background:linear-gradient(180deg, var(--accent-strong), var(--accent)); border-color:transparent; box-shadow:0 4px 14px rgba(240,165,44,.22); }
525
- .btn.submit-review:hover { background:linear-gradient(180deg, var(--accent-strong), var(--accent)); border-color:transparent; filter:brightness(1.07); }
526
- .page-title { margin:0 0 8px; font-size:26px; font-weight:700; letter-spacing:-.02em; word-break:break-word; }
527
- .page-sub { margin:0; max-width:78ch; color:var(--muted); font-size:12.5px; line-height:1.55; }
528
- .page-sub code { color:var(--fg); background:rgba(255,255,255,.06); border-radius:6px; padding:1px 6px; font-size:11.5px; }
529
- .error { color:#ff8b8f; font-size:12.5px; }
530
- .empty { color:var(--muted); font-size:13px; }
531
- .kpis { display:grid; grid-template-columns:repeat(4, minmax(0,1fr)); gap:12px; margin:20px 0 18px; }
532
- .kpi { display:flex; flex-direction:column; gap:8px; padding:15px 16px; background:var(--card);
533
- border:1px solid var(--border); border-radius:var(--radius-lg); }
534
- .kpi-label { font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--muted-2); }
535
- .kpi-value { font-size:30px; font-weight:700; line-height:1; letter-spacing:-.02em; font-variant-numeric:tabular-nums; }
536
- .kpi-hint { font-family:var(--font-mono); font-size:11px; color:var(--muted-2); }
537
- .kpi-execute .kpi-value { color:var(--accent); }
538
- .kpi-ignore .kpi-value { color:var(--muted); }
539
- .kpi-done .kpi-value { color:var(--success); }
540
- .progress { margin:0 0 22px; }
541
- .progress-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:8px; font-size:11.5px; color:var(--muted); }
542
- .progress-head .pct { color:var(--fg); font-family:var(--font-mono); }
543
- .progress-track { height:8px; border-radius:999px; background:var(--surface-2); border:1px solid var(--border); overflow:hidden; }
544
- .progress-fill { display:block; height:100%; border-radius:999px; background:linear-gradient(90deg, #2c8f57, var(--success)); transition:width .35s ease; }
545
- .filters { display:flex; flex-wrap:wrap; align-items:center; gap:10px; margin-bottom:20px; }
546
- .search { display:flex; align-items:center; gap:8px; flex:1 1 240px; max-width:330px; padding:8px 14px; color:var(--muted);
547
- background:var(--surface-2); border:1px solid var(--border-strong); border-radius:999px; }
548
- .search:focus-within { color:var(--accent); border-color:var(--accent-line); }
549
- .search input { flex:1; min-width:0; font:inherit; font-size:12.5px; color:var(--fg); background:transparent; border:0; outline:0; }
550
- .search input::placeholder { color:var(--muted-2); }
551
- .search-clear { padding:0 2px; font-size:15px; line-height:1; color:var(--muted); background:transparent; border:0; cursor:pointer; }
552
- .chips { display:inline-flex; gap:3px; padding:4px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
553
- .chip { display:inline-flex; align-items:center; gap:6px; font:inherit; font-size:12px; font-weight:600; cursor:pointer; color:var(--muted);
554
- padding:6px 13px; background:transparent; border:1px solid transparent; border-radius:999px; transition:color .15s ease, background .15s ease; }
555
- .chip:hover { color:var(--fg); }
556
- .chip.active { color:#14100a; background:#e9e5df!important; }
557
- .sev-group { margin-bottom:24px; }
558
- .sev-head { display:flex; align-items:center; gap:8px; margin:0 0 10px; font-size:11px; font-weight:700;
559
- letter-spacing:.16em; text-transform:uppercase; color:var(--muted-2); }
560
- .sev-head .n { font-family:var(--font-mono); letter-spacing:0; }
561
- .sev-group.sev-blocking .sev-head { color:var(--blocking); }
562
- .sev-group.sev-attention .sev-head { color:var(--attention); }
563
- .sev-group.sev-suggestion .sev-head { color:var(--suggestion); }
564
- .sev-group.sev-nit .sev-head { color:var(--nit); }
565
- .card { position:relative; margin-bottom:20px; overflow:hidden; background:var(--card);
566
- border:1px solid var(--border); border-left:4px solid var(--border-strong); border-radius:var(--radius-lg);
567
- transition:background .15s ease, border-color .15s ease, opacity .2s ease; }
568
- .card:hover { background:#16161b; }
569
- .card.sev-blocking { border-left-color:var(--blocking); }
570
- .card.sev-attention { border-left-color:var(--attention); }
571
- .card.sev-suggestion { border-left-color:var(--suggestion); }
572
- .card.sev-nit { border-left-color:var(--nit); }
573
- .card.absent { opacity:.5; }
574
- .card.done { opacity:.6; }
575
- .card-head { display:flex; align-items:center; gap:10px; padding:12px 14px 0; }
576
- .chev { display:grid; place-items:center; flex:none; width:22px; height:22px; cursor:pointer; color:var(--muted-2);
577
- background:transparent; border:1px solid transparent; border-radius:7px; }
578
- .chev:hover { color:var(--fg); background:rgba(255,255,255,.06); }
579
- .chev:focus-visible { color:var(--fg); border-color:var(--accent-line); outline:none; }
580
- .chev svg { transition:transform .18s ease; }
581
- .chev.open svg { transform:rotate(90deg); }
582
- .sev-badge { display:inline-flex; align-items:center; flex:none; padding:3px 9px; border-radius:999px;
583
- font-size:10px; letter-spacing:.08em; text-transform:uppercase; }
584
- .card.sev-blocking .sev-badge { color:var(--blocking); background:rgba(242,89,94,.12); }
585
- .card.sev-attention .sev-badge { color:var(--attention); background:rgba(237,161,58,.12); }
586
- .card.sev-suggestion .sev-badge { color:var(--suggestion); background:rgba(90,164,240,.12); }
587
- .card.sev-nit .sev-badge { color:var(--nit); background:rgba(255,255,255,.06); }
588
- .loc { display:inline-flex; align-items:center; gap:7px; max-width: 406ch; overflow:hidden; white-space:break-spaces; text-overflow:ellipsis;
589
- padding:4px 9px; font-family:var(--font-mono); font-size:11.5px; color:#aba7a1; text-decoration:none; }
590
- .loc:hover { color:#fff; }
591
- .loc .ext { flex:none; opacity:0; transition:opacity .15s ease; }
592
- .loc:hover .ext { opacity:1; }
593
- .loc.static { color:var(--muted); }
594
- .count { padding:2px 7px; font-family:var(--font-mono); font-size:11px; color:var(--muted);
595
- background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
596
- .spacer { flex:1; }
597
- .done-btn { display:inline-flex; align-items:center; gap:7px; flex:none; font:inherit; font-size:12px; font-weight:600; cursor:pointer;
598
- padding:7px 13px; color:var(--muted); background:transparent; border:1px solid var(--border-strong); border-radius:999px;
599
- transition:color .15s ease, background .15s ease, border-color .15s ease; }
600
- .done-btn:hover { color:var(--fg); border-color:rgba(69,192,122,.55); }
601
- .done-btn:disabled { opacity:.55; cursor:not-allowed; }
602
- .done-btn.is-done { color:var(--success); background:rgba(69,192,122,.14); border-color:rgba(69,192,122,.5); }
603
- .card-title { margin:9px 0 0; padding:0 16px 20px; font-size:14px; font-weight:400; }
604
- .card.done .card-title { color:#b7b7b7; text-decoration:line-through; text-decoration-color:rgba(255,255,255,.15); }
605
- .card-body { display:flex; flex-direction:column; gap:12px; padding:20px 16px 15px; background:rgba(255,255,255,.012); border-top:1px solid var(--border); }
606
- .meta-row { display:flex; flex-wrap:wrap; gap:14px; font-size:11.5px; color:var(--muted); }
607
- .meta { display:inline-flex; align-items:center; gap:7px; }
608
- .meta-k { font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--muted-2); }
609
- .meta code { padding:2px 6px; font-size:11.5px; color:var(--fg); background:rgba(255,255,255,.04); border:1px solid var(--border); border-radius:6px; }
610
- .action-block { display:flex; gap:11px; padding:0; background:transparent; border:0; border-radius:0; }
611
- .action-icon { flex:none; display:inline-block; margin-top:3px; margin-right:2px; color:var(--accent); }
612
- .action-text { flex:1; min-width:0; }
613
- .action-label { display:block; margin-bottom:4px; font-size:10px; font-weight:600; letter-spacing:.12em; text-transform:uppercase; color:var(--accent); }
614
- .action-text p, .md-p { margin:0 0 6px; font-size:13px; line-height:1.65; color:#f0ece6; white-space:pre-wrap; }
615
- .action-text p:last-child { margin-bottom:0; }
616
- .md-list { margin:0 0 6px; padding-left:20px; font-size:13px; line-height:1.65; color:#f0ece6; }
617
- .md-list li { margin:0 0 2px; }
618
- .md-quote { margin:0 0 6px; padding:2px 0 2px 10px; border-left:2px solid var(--accent-line); color:var(--muted); font-size:13px; line-height:1.65; white-space:pre-wrap; }
619
- .action-text code, .md-code { font-family:var(--font-mono); font-size:11.5px; padding:1px 5px; color:#f0ece6; background:rgba(255,255,255,.06); border:1px solid var(--border); border-radius:5px; }
620
- code.md-path { color:#8fc7ff; background:rgba(90,164,240,.1); border-color:rgba(90,164,240,.24); }
621
- .action-text a, .md-p a, .md-list a, .md-quote a, .md-table a, .md-h a { color:var(--accent-strong); text-decoration:underline; text-underline-offset:2px; }
622
- .action-text strong, .md-p strong, .md-list strong, .md-quote strong, .md-table strong, .md-h strong { color:#fff; font-weight:600; }
623
- .action-text em, .md-p em, .md-list em, .md-quote em, .md-table em, .md-h em { color:#e9e4dc; }
624
- .md-code-block { margin:6px 0; padding:10px 12px; max-height:320px; overflow:auto; white-space:pre; font-size:11.5px; line-height:1.6; color:#cdc7bf; background:#0b0b0e; border-radius:10px; }
625
- .md-h { margin:16px 0 8px; font-size:14px; font-weight:700; line-height:1.35; letter-spacing:-.01em; color:#eee9e2; }
626
- .md-h:first-child { margin-top:0; }
627
- .md-h1 { font-size:19px; }
628
- .md-h2 { font-size:16px; }
629
- .md-h3 { font-size:14px; }
630
- .md-h4, .md-h5, .md-h6 { font-size:13px; color:#e4dfd7; font-weight:600; }
631
- .md-hr { margin:14px 0; border:0; border-top:1px solid var(--border); }
632
- .md-table-wrap { margin:8px 0 12px; padding:4px; overflow-x:auto; border:1px solid rgba(255,255,255,.07); border-radius:10px; background:rgba(255,255,255,.012); }
633
- .md-table { width:100%; border-collapse:separate; border-spacing:3px; font-size:12px; line-height:1.5; }
634
- .md-table th, .md-table td { padding:6px 9px; text-align:left; vertical-align:top; border:1px solid rgba(255,255,255,.035); border-radius:6px; }
635
- .md-table th { color:var(--muted); font-weight:600; background:rgba(255,255,255,.03); }
636
- .md-table td { color:#d8d3cc; }
637
- .decision-tags-block { display:flex; align-items:center; gap:8px; }
638
- .evidence { margin:0; padding: 16px; white-space:break-spaces; font-size:12px; line-height:1.6; color:#cdc7bf;
639
- background:#0b0b0e; border:1px solid var(--border); border-radius:var(--radius); }
640
- .evidence-block { display:flex; flex-direction:column; gap:6px; }
641
- .evidence-head { display:flex; justify-content:flex-end; }
642
- .code-lang { font-family:var(--font-mono); font-size:9.5px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--muted-2); }
643
- .code-evidence { white-space:pre; overflow:auto; }
644
- .tok-keyword { color:#c792ea; }
645
- .tok-string { color:#9ddc8a; }
646
- .tok-comment { color:#6d6862; font-style:italic; }
647
- .tok-number { color:#8fc7ff; }
648
- .tok-type { color:var(--accent-strong); }
649
- .tok-function { color:#82aaff; }
650
- .tok-property { color:#e6c07b; }
651
- .tok-punct { color:#8d8a85; }
652
- .tags { display:flex; flex-wrap:wrap; align-items:center; gap:7px; margin-left: auto; }
653
- .tags-label { display:inline-flex; align-items:center; gap:6px; font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--muted-2); }
654
- .tag { display:inline-flex; align-items:center; gap:6px; padding:3px 9px; font-family:var(--font-mono); font-size:11px; color:var(--muted);
655
- background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
656
- .decisions { display:inline-flex; gap:3px; align-self:flex-start; padding:4px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
657
- .dec-btn { font:inherit; font-size:12px; font-weight:600; cursor:pointer; color:var(--muted); padding:6px 15px;
658
- background:transparent; border:1px solid transparent; border-radius:999px; transition:color .15s ease, background .15s ease; }
659
- .dec-btn:hover { color:var(--fg); }
660
- .dec-btn:disabled { opacity:.55; cursor:not-allowed; }
661
- .dec-btn.dec-execute.active { color:#1b1305; background:linear-gradient(180deg, var(--accent-strong), var(--accent)); }
662
- .dec-btn.dec-ignore.active { color:#ffd2d3; background:rgba(242,89,94,.18); box-shadow:inset 0 0 0 1px rgba(242,89,94,.45); }
663
- .dec-btn.dec-pending.active { color:var(--fg); background:rgba(255,255,255,.08); box-shadow:inset 0 0 0 1px var(--border-strong); }
664
- .comment { position:relative; }
665
- .comment textarea { width:100%; min-height:58px; padding:11px 13px 26px; resize:vertical; font:inherit; font-size:12.5px; line-height:1.5;
666
- color:var(--fg); background:var(--surface-2); border:1px solid var(--border-strong); border-radius:var(--radius); outline:none;
667
- transition:border-color .15s ease, box-shadow .15s ease; }
668
- .comment textarea::placeholder { color:var(--muted-2); }
669
- .comment textarea:focus { border-color:var(--accent-line); box-shadow:0 0 0 3px rgba(240,165,44,.12); }
670
- .comment-status { position:absolute; right:12px; bottom:9px; display:inline-flex; align-items:center; gap:6px;
671
- font-family:var(--font-mono); font-size:11px; color:var(--muted); }
672
- .comment-status.saved { color:var(--success); }
673
- .artifacts { display:flex; flex-direction:column; }
674
- .artifact-layout { display:grid; grid-template-columns:minmax(190px,250px) minmax(0,1fr); gap:16px; align-items:start; }
675
- .artifact-list { display:flex; flex-direction:column; gap:14px; max-height:72vh; overflow:auto; padding:2px 4px 2px 0; }
676
- .artifact-group { display:flex; flex-direction:column; gap:4px; }
677
- .artifact-group-label { margin:0 0 2px; font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--muted); }
678
- .artifact-item { display:flex; flex-direction:column; gap:2px; text-align:left; cursor:pointer; font:inherit; color:var(--muted);
679
- padding:8px 10px; background:transparent; border:1px solid transparent; border-radius:10px;
680
- transition:background .15s ease, border-color .15s ease, color .15s ease; }
681
- .artifact-item:hover { color:var(--fg); background:var(--surface-2); border-color:var(--border); }
682
- .artifact-item.active { color:var(--fg); background:var(--accent-soft); border-color:var(--accent-line); }
683
- .artifact-item-name { font-family:var(--font-mono); font-size:11.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
684
- .artifact-item-kind { font-size:9.5px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--muted-2); }
685
- .artifact-item.active .artifact-item-kind { color:var(--accent); }
686
- .artifact-pane { min-width:0; }
687
- .artifact-head { display:flex; align-items:flex-start; justify-content:space-between; gap:14px; margin-bottom:12px; }
688
- .artifact-title { margin:0 0 5px; font-family:var(--font-mono); font-size:13px; font-weight:600; word-break:break-word; }
689
- .artifact-desc { margin:0; max-width:78ch; color:var(--muted); font-size:12.5px; line-height:1.55; }
690
- .artifact-kind-badge { flex:none; display:inline-flex; align-items:center; padding:3px 9px; font-size:10px; font-weight:700;
691
- letter-spacing:.08em; text-transform:uppercase; color:var(--accent); background:var(--accent-soft);
692
- border:1px solid var(--accent-line); border-radius:999px; }
693
- .artifact-view { margin:0; padding:14px 16px; max-height:70vh; overflow:auto; font-size:12px; line-height:1.6; color:#cdc7bf;
694
- background:#0b0b0e; border:1px solid var(--border); border-radius:var(--radius); }
695
- .artifact-markdown { white-space:normal; }
696
- .artifact-markdown .md-p, .artifact-markdown .md-list, .artifact-markdown .md-quote { font-size:12.5px; }
697
- .artifact-markdown .md-p, .artifact-markdown .md-list { color:#d8d3cc; }
698
- .artifact-markdown .md-code-block { border:1px solid var(--border); }
699
- .json-view { white-space:pre; }
700
- .json-key { color:var(--accent-strong); }
701
- .json-string { color:#9ddc8a; }
702
- .json-number { color:#8fc7ff; }
703
- .json-literal { color:#d7a6ff; }
704
- .json-punct { color:#8d8a85; }
705
- @media (max-width:900px) { .artifact-layout { grid-template-columns:1fr; } .artifact-list { max-height:240px; } }
706
- .sent-tag { display:inline-flex; align-items:center; gap:5px; padding:2px 8px; font-size:10px; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
707
- color:var(--success); background:rgba(69,192,122,.12); border:1px solid rgba(69,192,122,.32); border-radius:999px; text-decoration:none; }
708
- .sent-tag:hover { color:#fff; }
709
- .sent-tag.gh { color:#b9c7d6; background:rgba(139,164,191,.12); border-color:rgba(139,164,191,.35); }
710
- .sent-tag.gh:hover { color:#fff; background:rgba(139,164,191,.2); }
711
- .resume { display:flex; flex-direction:column; }
712
- .resume-hero { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; padding:18px 20px;
713
- background:linear-gradient(135deg, rgba(240,165,44,.08), rgba(255,255,255,.015) 55%);
714
- border:1px solid var(--border); border-radius:var(--radius-lg); }
715
- .resume-kicker { display:block; font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--accent); }
716
- .resume-title { margin:5px 0 7px; font-size:22px; font-weight:700; letter-spacing:-.02em; word-break:break-word; }
717
- .resume-meta { display:flex; flex-wrap:wrap; align-items:center; gap:12px; margin:0; font-size:12px; color:var(--muted); }
718
- .resume-pr { display:inline-flex; align-items:center; gap:5px; color:var(--accent-strong); text-decoration:none; }
719
- .resume-pr:hover { text-decoration:underline; }
720
- .resume-hero-side { display:flex; flex:none; flex-direction:column; align-items:flex-end; gap:10px; }
721
- .resume-status { display:inline-flex; align-items:center; padding:5px 12px; font-size:11px; font-weight:700; letter-spacing:.04em; border-radius:999px; }
722
- .resume-status.blocking { color:var(--blocking); background:rgba(242,89,94,.12); border:1px solid rgba(242,89,94,.35); }
723
- .resume-status.attention { color:var(--attention); background:rgba(237,161,58,.12); border:1px solid rgba(237,161,58,.35); }
724
- .resume-status.clean { color:var(--success); background:rgba(69,192,122,.12); border:1px solid rgba(69,192,122,.35); }
725
- .resume-kpis { margin:14px 0 18px; }
726
- .resume-kpis .kpi-value { font-size:24px; }
727
- .churn-add { color:var(--success); }
728
- .churn-del { color:var(--blocking); }
729
- .churn-sep { margin:0 3px; color:var(--muted-2); font-weight:400; }
730
- .kpi-blocking .kpi-value { color:var(--blocking); }
731
- .kpi-attention .kpi-value { color:var(--attention); }
732
- .kpi-total .kpi-value { color:var(--fg); }
733
- .resume-grid { display:grid; grid-template-columns:minmax(0,1.35fr) minmax(0,1fr); gap:16px; align-items:start; }
734
- .resume-card { min-width:0; padding:16px 18px; background:var(--card); border:1px solid var(--border); border-radius:var(--radius-lg); }
735
- .resume-card-head { display:flex; align-items:center; justify-content:space-between; gap:10px; }
736
- .resume-card-title { margin:0 0 10px; font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--muted); }
737
- .resume-card-count { display:inline-flex; align-items:center; justify-content:center; min-width:22px; height:22px; margin-bottom:10px; padding:0 7px;
738
- font-family:var(--font-mono); font-size:11px; color:var(--accent); background:var(--accent-soft); border:1px solid var(--accent-line); border-radius:999px; }
739
- .resume-narrative .md-p:last-child { margin-bottom:0; }
740
- .resume-domains { display:flex; flex-direction:column; gap:8px; margin:0; padding:0; list-style:none; }
741
- .resume-domain { display:flex; flex-direction:column; gap:7px; padding:10px 12px; background:var(--surface-2);
742
- border:1px solid var(--border); border-radius:10px; }
743
- .resume-domain-head { display:flex; align-items:baseline; justify-content:space-between; gap:10px; }
744
- .resume-domain-name { font-size:12.5px; font-weight:600; color:var(--fg); }
745
- .resume-domain-files { flex:none; font-family:var(--font-mono); font-size:10.5px; color:var(--muted-2); }
746
- .resume-domain-sev { display:flex; flex-wrap:wrap; gap:6px; }
747
- .resume-domain-sev .sev-badge { padding:2px 7px; font-size:9.5px; }
748
- .resume-domain-ok { font-size:11.5px; color:var(--success); }
749
- @media (max-width:900px) { .resume-grid { grid-template-columns:1fr; } .resume-hero { flex-direction:column; } .resume-hero-side { flex-direction:row; align-items:flex-start; } }
750
- .modal-layer { position:fixed; inset:0; z-index:50; display:grid; place-items:center; padding:24px; }
751
- .modal-backdrop { position:absolute; inset:0; width:100%; padding:0; background:rgba(5,5,7,.66); border:0; cursor:default; backdrop-filter:blur(5px); -webkit-backdrop-filter:blur(5px); }
752
- .modal { position:relative; width:min(640px, 100%); max-height:min(78vh, 780px); display:flex; flex-direction:column; overflow:hidden;
753
- background:var(--surface); border:1px solid var(--border-strong); border-radius:var(--radius-lg); box-shadow:0 24px 70px rgba(0,0,0,.5); }
754
- .modal-head { display:flex; align-items:flex-start; justify-content:space-between; gap:14px; padding:18px 20px 14px; border-bottom:1px solid var(--border); }
755
- .modal-title { margin:0; font-size:17px; font-weight:700; letter-spacing:-.01em; }
756
- .modal-sub { margin:5px 0 0; font-size:12px; color:var(--muted); line-height:1.5; }
757
- .modal-close { flex:none; display:grid; place-items:center; width:30px; height:30px; font:inherit; font-size:19px; line-height:1; color:var(--muted); background:transparent; border:1px solid var(--border); border-radius:9px; cursor:pointer; }
758
- .modal-close:hover { color:var(--fg); border-color:var(--border-strong); }
759
- .modal-body { display:flex; flex-direction:column; gap:16px; padding:16px 20px; overflow-y:auto; }
760
- .modal-foot { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:14px 20px; background:var(--surface-2); border-top:1px solid var(--border); }
761
- .submit-field { display:flex; flex-direction:column; gap:8px; }
762
- .submit-label { font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--muted-2); }
763
- .submit-summary { padding:13px 15px; font-size:12.5px; color:var(--fg); background:var(--accent-soft); border:1px solid var(--accent-line); border-radius:var(--radius); }
764
- .submit-summary b { font-family:var(--font-mono); }
765
- .submit-list { display:flex; flex-direction:column; gap:6px; max-height:240px; overflow-y:auto; padding-right:2px; }
766
- .submit-item { display:flex; align-items:center; gap:10px; padding:8px 11px; font-size:12px; background:var(--surface-2); border:1px solid var(--border); border-radius:10px; }
767
- .submit-item.sent { opacity:.7; }
768
- .submit-item .rule { flex:1; min-width:0; color:var(--muted); overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }
769
- .submit-empty { color:var(--muted); font-size:12.5px; }
770
- .submit-field .chips { flex-wrap:wrap; }
771
- .submit-check { display:inline-flex; align-items:center; gap:8px; font-size:12.5px; color:var(--muted); cursor:pointer; }
772
- .submit-check input { accent-color:var(--accent); }
773
- .submit-actions { display:flex; align-items:center; gap:8px; }
774
- .sev-badge.sev-blocking { color:var(--blocking); background:rgba(242,89,94,.12); }
775
- .sev-badge.sev-attention { color:var(--attention); background:rgba(237,161,58,.12); }
776
- .sev-badge.sev-suggestion { color:var(--suggestion); background:rgba(90,164,240,.12); }
777
- .sev-badge.sev-nit { color:var(--nit); background:rgba(255,255,255,.06); }
778
- @keyframes pulse { 0%,100% { opacity:1; transform:scale(1); } 50% { opacity:.4; transform:scale(.8); } }
779
- @keyframes spin { to { transform:rotate(360deg); } }
780
- @media (max-width:980px) { .kpis { grid-template-columns:repeat(2, minmax(0,1fr)); } }
781
- @media (max-width:820px) {
782
- .sidebar { width:232px; flex-basis:232px; }
783
- .topbar { padding:12px 16px; }
784
- .content { padding:18px 16px 56px; }
785
- .auto-refresh { display:none; }
786
- }
787
- `;
788
-
789
402
  /** Id de build por processo — muda a URL do bundle a cada reinício do servidor. */
790
403
  const BUILD_ID = Date.now().toString(36);
791
404
 
@@ -797,11 +410,12 @@ function renderShell(): string {
797
410
  <meta name="viewport" content="width=device-width, initial-scale=1" />
798
411
  <meta name="color-scheme" content="dark" />
799
412
  <title>Houndsman — Revisões</title>
413
+ <link rel="icon" type="image/png" href="/assets/${LOGO_FILE}" />
800
414
  <!-- Fontes de CDN: se estiver offline, o CSS cai para a pilha de sistema. -->
801
415
  <link rel="preconnect" href="https://fonts.googleapis.com" />
802
416
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
803
417
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" />
804
- <style>${STYLES}</style>
418
+ <link rel="stylesheet" href="/assets/${STYLES_FILE}?v=${BUILD_ID}" />
805
419
  </head>
806
420
  <body>
807
421
  <div id="app"></div>