@payconductor/houndsman 0.7.0 → 0.10.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 (88) hide show
  1. package/README.md +20 -0
  2. package/package.json +1 -1
  3. package/src/cli/commands/command.ts +10 -1
  4. package/src/cli/commands/context-command.ts +40 -2
  5. package/src/cli/commands/hunt-command.ts +28 -2
  6. package/src/cli/commands/review-command.ts +30 -2
  7. package/src/cli/commands/worktree-command.ts +118 -0
  8. package/src/cli/help.ts +12 -2
  9. package/src/cli/index.ts +14 -4
  10. package/src/core/ai/ai-client.ts +21 -0
  11. package/src/core/ai/ai-sdk-model-provider.ts +43 -18
  12. package/src/core/ai/features/technical-summarizer.ts +43 -0
  13. package/src/core/ai/index.ts +4 -1
  14. package/src/core/ai/prompts/context.ts +3 -3
  15. package/src/core/ai/prompts/technical.ts +30 -0
  16. package/src/core/ai/provider-capabilities.ts +45 -0
  17. package/src/core/ai/schemas.ts +19 -3
  18. package/src/core/ai/types.ts +3 -1
  19. package/src/core/ast/service.ts +7 -2
  20. package/src/core/brief.ts +26 -4
  21. package/src/core/commands/context.ts +9 -1
  22. package/src/core/commands/findings.ts +11 -5
  23. package/src/core/commands/review.ts +13 -0
  24. package/src/core/commands/scaffold.ts +9 -2
  25. package/src/core/config.ts +30 -1
  26. package/src/core/defaults.ts +7 -0
  27. package/src/core/engine/houndsman-engine.ts +26 -3
  28. package/src/core/engine/review-scope.ts +17 -22
  29. package/src/core/engine/review-session.ts +25 -2
  30. package/src/core/engine/review-workspace.ts +204 -0
  31. package/src/core/git-runner.ts +8 -1
  32. package/src/core/jev/jev-detector.ts +10 -2
  33. package/src/core/orchestrator.ts +176 -15
  34. package/src/core/paths.ts +3 -0
  35. package/src/core/pr-context.ts +11 -6
  36. package/src/core/project-config.ts +21 -1
  37. package/src/core/renderers/resume-renderer.ts +17 -34
  38. package/src/core/report-builder.ts +6 -6
  39. package/src/core/services/git-repository.ts +46 -0
  40. package/src/core/services/pr-resolver.ts +147 -0
  41. package/src/core/services/worktree-manager.ts +293 -0
  42. package/src/core/storage/file/file-storage.ts +14 -1
  43. package/src/core/storage/paths.ts +30 -1
  44. package/src/core/storage/sqlite/mappers.ts +4 -0
  45. package/src/core/storage/sqlite/schema.ts +13 -3
  46. package/src/core/storage/sqlite/sqlite-storage.ts +28 -8
  47. package/src/core/storage/types.ts +13 -0
  48. package/src/core/storage/work-area.ts +20 -1
  49. package/src/core/technical-summary.ts +398 -0
  50. package/src/core/types.ts +2 -0
  51. package/src/harness/agent/agent-factory.ts +1 -0
  52. package/src/harness/agent/review-agent.ts +11 -1
  53. package/src/harness/config/harness-config.ts +6 -1
  54. package/src/harness/hunt/hunt-options.ts +17 -0
  55. package/src/harness/hunt/hunt-session.ts +7 -20
  56. package/src/index.ts +33 -0
  57. package/src/server/client/App.tsx +51 -12
  58. package/src/server/client/DeleteRunModal.tsx +163 -0
  59. package/src/server/client/PrsSection.tsx +296 -0
  60. package/src/server/client/ResumePanel.tsx +17 -20
  61. package/src/server/client/RunsSection.tsx +73 -0
  62. package/src/server/client/Sidebar.tsx +171 -358
  63. package/src/server/client/icons.tsx +10 -0
  64. package/src/server/client/sidebar-sections.tsx +73 -0
  65. package/src/server/client/utils/constants.ts +7 -2
  66. package/src/server/client/utils/sidebar-prefs.ts +64 -0
  67. package/src/server/client/utils/types.ts +27 -0
  68. package/src/server/index.ts +113 -406
  69. package/src/server/pr-inbox.ts +15 -4
  70. package/src/server/run-repository.ts +103 -2
  71. package/src/server/styles.css +454 -0
  72. package/src/skill/README.md +11 -3
  73. package/src/skill/SKILL.md +44 -10
  74. package/src/skill/hunt.md +33 -7
  75. package/src/tests/__snapshots__/baseline.test.ts.snap +99 -24
  76. package/src/tests/ai-core.test.ts +71 -0
  77. package/src/tests/ai-features.test.ts +36 -0
  78. package/src/tests/baseline.test.ts +4 -1
  79. package/src/tests/cli.test.ts +2 -1
  80. package/src/tests/harness-agent.test.ts +19 -0
  81. package/src/tests/review-resume.test.ts +36 -8
  82. package/src/tests/review-scope.test.ts +67 -0
  83. package/src/tests/review-scripts.test.ts +2 -2
  84. package/src/tests/server.test.ts +54 -1
  85. package/src/tests/sidebar-sections.test.ts +94 -0
  86. package/src/tests/technical-summary.test.ts +176 -0
  87. package/src/tests/worktree.test.ts +404 -0
  88. 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";
@@ -17,7 +18,10 @@ import {
17
18
  } from "../core/github-review";
18
19
  import { logger } from "../core/logging";
19
20
  import { GitRepository } from "../core/services/git-repository";
21
+ import { PrResolver } from "../core/services/pr-resolver";
22
+ import { WorktreeManager } from "../core/services/worktree-manager";
20
23
  import { createStorage } from "../core/storage/factory";
24
+ import { resolveWorktreesRoot } from "../core/storage/paths";
21
25
  import { resolveRepoIdentity } from "../core/storage/repo-identity";
22
26
  import type { RepoIdentity, StorageProvider } from "../core/storage/types";
23
27
  import { buildClientBundle } from "./client-build";
@@ -78,6 +82,7 @@ type ServerDeps = {
78
82
 
79
83
  const API_RUN = /^\/api\/runs\/([^/]+)(\/.*)?$/;
80
84
  const LOGO_FILE = "logo.png";
85
+ const STYLES_FILE = "styles.css";
81
86
 
82
87
  /** Serve a logo do pacote (`assets/logo.png`). */
83
88
  async function serveLogo(): Promise<Response> {
@@ -88,12 +93,31 @@ async function serveLogo(): Promise<Response> {
88
93
  });
89
94
  }
90
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
+
91
113
  async function route(
92
114
  request: Request,
93
115
  runs: RunRepository,
94
116
  ghRunner: GhRunner,
95
117
  prs: PrInbox,
96
118
  editor: EditorContext,
119
+ worktrees: WorktreeManager,
120
+ git: GitRepository,
97
121
  ): Promise<Response> {
98
122
  const url = new URL(request.url);
99
123
  const path = url.pathname;
@@ -104,11 +128,66 @@ async function route(
104
128
  if (path === "/assets/app.js") {
105
129
  return noStore(await buildClientBundle(), "text/javascript; charset=utf-8");
106
130
  }
131
+ if (path === `/assets/${STYLES_FILE}`) return serveStyles();
107
132
  if (path === `/assets/${LOGO_FILE}`) return serveLogo();
108
133
  if (path === "/api/health") return json({ ok: true });
109
134
  if (path === "/api/editor" && request.method === "GET") return json(editor);
110
135
  if (path === "/api/prs" && request.method === "GET") {
111
- return json(prs.list(runs.branchIndex()));
136
+ return json(prs.list(runs.workspaceIndex()));
137
+ }
138
+ if (path === "/api/worktrees" && request.method === "GET") {
139
+ return json({
140
+ worktrees: worktrees.managed().map((entry) => ({
141
+ path: entry.path,
142
+ branch: entry.branch?.replace(/^refs\/heads\//, "") ?? null,
143
+ head: entry.head ?? null,
144
+ prunable: entry.prunable,
145
+ })),
146
+ });
147
+ }
148
+ if (path === "/api/prs/worktree" && request.method === "POST") {
149
+ const body = (await request.json().catch(() => ({}))) as { number?: unknown };
150
+ const number = Number(body.number);
151
+ if (!Number.isInteger(number) || number <= 0) {
152
+ return json(
153
+ { ok: false, reason: "invalid", detail: "Número de PR inválido." },
154
+ 400,
155
+ );
156
+ }
157
+ const resolver = new PrResolver(ghRunner, git);
158
+ const resolved = resolver.resolve(number);
159
+ if (!resolved.ok) {
160
+ return json({ ok: false, reason: resolved.reason, detail: resolved.detail });
161
+ }
162
+ const refs = resolver.prepareRefs(resolved.pr);
163
+ try {
164
+ const worktree = worktrees.ensure({
165
+ branch: resolved.pr.headRefName,
166
+ ref: refs.head,
167
+ });
168
+ return json({
169
+ ok: true,
170
+ number,
171
+ branch: resolved.pr.headRefName,
172
+ path: worktree.path,
173
+ head: worktree.head,
174
+ created: worktree.created,
175
+ warning: refs.warning,
176
+ });
177
+ } catch (error) {
178
+ return json({
179
+ ok: false,
180
+ reason: "git",
181
+ detail: error instanceof Error ? error.message : String(error),
182
+ });
183
+ }
184
+ }
185
+ const worktreeMatch = path.match(/^\/api\/worktrees\/(.+)$/);
186
+ if (worktreeMatch && request.method === "DELETE") {
187
+ const branch = decodeURIComponent(worktreeMatch[1]);
188
+ const force = url.searchParams.get("force") === "1";
189
+ const result = worktrees.remove(branch, { force });
190
+ return json(result, result.removed ? 200 : 409);
112
191
  }
113
192
  if (path === "/api/prs/checkout" && request.method === "POST") {
114
193
  const body = (await request.json().catch(() => ({}))) as { number?: unknown };
@@ -132,6 +211,14 @@ async function route(
132
211
  const rest = match[2] ?? "";
133
212
  if (!runs.exists(slug)) return notFound(`run '${slug}' não encontrada`);
134
213
 
214
+ if (rest === "" && request.method === "DELETE") {
215
+ const removeWorktree = ["1", "true"].includes(
216
+ url.searchParams.get("worktree") ?? "",
217
+ );
218
+ const result = runs.delete(slug, { removeWorktree });
219
+ return result ? json(result) : notFound(`run '${slug}' não encontrada`);
220
+ }
221
+
135
222
  if (rest === "" || rest === "/") return json(runs.detail(slug));
136
223
 
137
224
  if (rest === "/comments" && request.method === "GET") {
@@ -195,25 +282,44 @@ export class HoundsmanServer {
195
282
  private readonly ghRunner: GhRunner;
196
283
  private readonly prs: PrInbox;
197
284
  private readonly editor: EditorContext;
285
+ private readonly worktrees: WorktreeManager;
286
+ private readonly git: GitRepository;
198
287
 
199
288
  constructor(config: HoundsmanConfig, deps: ServerDeps = {}) {
200
289
  const repo = deps.repo ?? resolveRepoIdentity();
201
290
  this.ghRunner = deps.ghRunner ?? defaultGhRunner;
291
+ this.git = new GitRepository(deps.gitRunner ?? createGitRunner(repo.root));
292
+ this.prs = new PrInbox(this.ghRunner, this.git);
293
+ this.worktrees = new WorktreeManager(
294
+ new GitRepository(createGitRunner(repo.root)),
295
+ {
296
+ repoKey: repo.key,
297
+ repoRoot: repo.root,
298
+ baseDir: resolveWorktreesRoot(config.worktrees?.location, repo.root),
299
+ },
300
+ );
202
301
  this.runs = new RunRepository(
203
302
  deps.storage ?? createStorage(config),
204
303
  repo,
205
304
  this.ghRunner,
305
+ this.worktrees,
206
306
  );
207
307
  this.editor =
208
308
  deps.editor ?? resolveEditor(deps.editorPreference, process.env, repo.root);
209
- const git = new GitRepository(deps.gitRunner ?? createGitRunner(repo.root));
210
- this.prs = new PrInbox(this.ghRunner, git);
211
309
  }
212
310
 
213
311
  /** Handler de requisições (testável sem abrir porta). */
214
312
  readonly handler = async (request: Request): Promise<Response> => {
215
313
  try {
216
- return await route(request, this.runs, this.ghRunner, this.prs, this.editor);
314
+ return await route(
315
+ request,
316
+ this.runs,
317
+ this.ghRunner,
318
+ this.prs,
319
+ this.editor,
320
+ this.worktrees,
321
+ this.git,
322
+ );
217
323
  } catch (error) {
218
324
  return json(
219
325
  { error: error instanceof Error ? error.message : String(error) },
@@ -290,409 +396,9 @@ export function startServer(options: ServerOptions): { server: BunServer; url: s
290
396
  }
291
397
 
292
398
  // ---------------------------------------------------------------------------
293
- // Shell (HTML + CSS)
399
+ // Shell (HTML)
294
400
  // ---------------------------------------------------------------------------
295
401
 
296
- const STYLES = `
297
- @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');
298
-
299
- :root {
300
- color-scheme: dark;
301
- --bg:#0a0a0c; --surface:#101014; --surface-2:#17171c; --card:#131318;
302
- --border:rgba(255,255,255,.07); --border-strong:rgba(255,255,255,.14);
303
- --fg:#ece9e4; --muted:#9d978e; --muted-2:#6d6862;
304
- --accent:#f0a52c; --accent-strong:#ffc247; --accent-soft:rgba(240,165,44,.07); --accent-line:rgba(240,165,44,.25);
305
- --blocking:#f2595e; --attention:#eda13a; --suggestion:#5aa4f0; --nit:#8d8a85; --success:#45c07a;
306
- --radius:16px; --radius-lg:18px;
307
- --font-sans:"Poppins","Space Grotesk","Geist Sans",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
308
- --font-mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
309
- }
310
- * { box-sizing:border-box; }
311
- html, body { height:100%; }
312
- body { margin:0; font-size:14px; background:var(--bg); color:var(--fg); font-family:var(--font-sans); -webkit-font-smoothing:antialiased; }
313
- body::before { content:""; position:fixed; inset:0; pointer-events:none; z-index:0;
314
- background:radial-gradient(820px 420px at 8% -12%, rgba(240,165,44,.08), transparent 62%); }
315
- code, pre, .mono { font-family:var(--font-mono); tab-size:4; }
316
- ::-webkit-scrollbar { width:10px; height:10px; }
317
- ::-webkit-scrollbar-thumb { background:rgba(255,255,255,.1); border:2px solid transparent; border-radius:999px; background-clip:padding-box; }
318
- ::-webkit-scrollbar-thumb:hover { background:rgba(255,255,255,.18); background-clip:padding-box; }
319
- .app { position:relative; z-index:1; display:flex; height:100vh; overflow:hidden; }
320
- .sidebar { width:292px; flex:0 0 292px; display:flex; flex-direction:column; gap:14px; padding:18px 14px;
321
- background:var(--surface); border-right:1px solid var(--border); overflow-y:auto; }
322
- .brand { display:flex; align-items:center; gap:12px; }
323
- .brand .logo { width:42px; height:42px; object-fit:contain; border-radius:12px; background:var(--surface-2); border:1px solid var(--border); }
324
- .sidebar h1 { font-size:15.5px; font-weight:700; letter-spacing:-.01em; margin:0; }
325
- .sub { color:var(--muted); font-size:11.5px; margin:0; }
326
- .section-label { margin:4px 0 0; font-size:10px; font-weight:700; letter-spacing:.16em; text-transform:uppercase; color:var(--muted-2); }
327
- .runs { display:flex; flex-direction:column; gap:6px; }
328
- .run { display:flex; flex-direction:column; gap:8px; text-align:left; cursor:pointer; font:inherit; color:inherit; padding:11px 13px;
329
- background:transparent; border:1px solid transparent; border-radius:var(--radius); transition:background .15s ease, border-color .15s ease; }
330
- .run:hover { background:var(--surface-2); border-color:var(--border); }
331
- .run.active { background:var(--accent-soft); border-color:var(--accent-line); }
332
- .run-top { display:flex; align-items:flex-start; gap:8px; }
333
- .run-icon { flex:none; margin-top:2px; color:var(--muted-2); }
334
- .run.active .run-icon { color:var(--accent); }
335
- .run-branch { font-size:13.5px; font-weight:600; line-height:1.35; word-break:break-word; }
336
- .run-meta { display:flex; align-items:center; gap:10px; font-family:var(--font-mono); font-size:11px; color:var(--muted); }
337
- .run-when { display:inline-flex; align-items:center; gap:6px; }
338
- .run-count { margin-left:auto; color:var(--muted-2); }
339
- .run-badge { align-self:flex-start; display:inline-flex; align-items:center; gap:6px; padding:4px 9px; border-radius:999px;
340
- font-size:10px; font-weight:700; letter-spacing:.07em; text-transform:uppercase;
341
- color:var(--success); background:rgba(69,192,122,.12); border:1px solid rgba(69,192,122,.32); }
342
- .run-status { font-size:10px; font-weight:600; letter-spacing:.07em; text-transform:uppercase; color:var(--muted-2); }
343
- .status-reviewing, .status-fixing { color:var(--accent); }
344
- .status-review_done { color:var(--suggestion); }
345
- .status-fix_done { color:var(--success); }
346
- .status-timeout { color:var(--blocking); }
347
- .side-tabs { display:flex; gap:4px; padding:4px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
348
- .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);
349
- padding:7px 10px; background:transparent; border:1px solid transparent; border-radius:999px; transition:color .15s ease, background .15s ease; }
350
- .side-tab:hover { color:var(--fg); }
351
- .side-tab.active { color:#1b1305; background:linear-gradient(180deg, var(--accent-strong), var(--accent)); }
352
- .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; }
353
- .side-tab.active .side-count { color:#1b1305; background:rgba(0,0,0,.14); }
354
- .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; }
355
- .icon-btn:hover { color:var(--fg); border-color:var(--border-strong); }
356
- .icon-btn:disabled { opacity:.5; cursor:not-allowed; }
357
- .pr-toolbar { display:flex; align-items:center; justify-content:space-between; gap:8px; }
358
- .pr-toolbar .section-label { margin:0; }
359
- .prs { display:flex; flex-direction:column; gap:8px; }
360
- .pr { display:flex; flex-direction:column; gap:7px; padding:11px 12px; background:var(--card); border:1px solid var(--border); border-radius:var(--radius); }
361
- .pr.current { background:var(--accent-soft); border-color:var(--accent-line); }
362
- .pr-head { display:flex; align-items:center; gap:7px; font-family:var(--font-mono); font-size:11px; color:var(--muted); }
363
- .pr-icon { color:var(--muted-2); }
364
- .pr.current .pr-icon { color:var(--accent); }
365
- .pr-number { font-weight:600; color:var(--fg); }
366
- .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; }
367
- .pr-title { margin:0; font-size:12.5px; font-weight:600; line-height:1.4; word-break:break-word; }
368
- .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); }
369
- .pr-branch { color:var(--suggestion); }
370
- .pr-review { margin:0; font-size:10.5px; font-weight:600; }
371
- .pr-review-APPROVED { color:var(--success); }
372
- .pr-review-CHANGES_REQUESTED { color:var(--blocking); }
373
- .pr-review-REVIEW_REQUIRED { color:var(--attention); }
374
- .pr-actions { display:flex; flex-wrap:wrap; gap:6px; margin-top:2px; }
375
- .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);
376
- background:transparent; border:1px solid var(--border-strong); border-radius:999px; text-decoration:none; }
377
- .pr-btn:hover { color:var(--fg); border-color:var(--accent-line); }
378
- .pr-btn:disabled { opacity:.45; cursor:not-allowed; }
379
- .pr-btn.danger { color:var(--accent); background:var(--accent-soft); border-color:var(--accent-line); }
380
- .pr-warning { margin:0; padding:8px 10px; font-size:11px; line-height:1.5; color:var(--attention);
381
- background:rgba(237,161,58,.08); border:1px solid rgba(237,161,58,.24); border-radius:10px; }
382
- .pr-notice { margin:0; padding:8px 10px; font-size:11px; line-height:1.5; color:var(--success);
383
- background:rgba(69,192,122,.08); border:1px solid rgba(69,192,122,.24); border-radius:10px; }
384
- .pr-current-branch { margin:0; font-family:var(--font-mono); font-size:10.5px; color:var(--muted-2); }
385
- .pr-current-branch code { color:var(--fg); }
386
- .comments { display:flex; flex-direction:column; gap:18px; }
387
- .comments-toolbar { display:flex; align-items:center; justify-content:space-between; gap:12px; }
388
- .comments-counts { display:flex; flex-wrap:wrap; align-items:center; gap:12px; font-size:11.5px; color:var(--muted); }
389
- .comments-counts b { font-family:var(--font-mono); font-weight:600; color:var(--fg); }
390
- .comment-group { display:flex; flex-direction:column; gap:10px; }
391
- .comment-group-title { margin:0; font-size:11px; font-weight:700; letter-spacing:.16em; text-transform:uppercase; color:var(--muted-2); }
392
- .comment-file-group { display:flex; flex-direction:column; gap:8px; }
393
- .comment-group-label { display:flex; align-items:center; gap:8px; margin:0; font-family:var(--font-mono); font-size:11.5px; color:var(--suggestion); }
394
- .comment-thread { display:flex; flex-direction:column; overflow:hidden; background:var(--card); border:1px solid var(--border); border-radius:var(--radius-lg); }
395
- .comment-thread-head { display:flex; align-items:center; gap:8px; padding:9px 13px; font-family:var(--font-mono); font-size:11px; color:var(--muted);
396
- background:var(--surface-2); border-bottom:1px solid var(--border); }
397
- .comment-file { overflow:hidden; color:var(--fg); text-overflow:ellipsis; white-space:nowrap; }
398
- .pr-comment { display:flex; flex-direction:column; gap:7px; padding:12px 14px; border-top:1px solid var(--border); }
399
- .comment-thread .pr-comment:first-of-type { border-top:0; }
400
- .pr-comment.reply { margin-left:26px; background:rgba(255,255,255,.012); border-left:2px solid var(--accent-line); }
401
- .pr-comment-head { display:flex; align-items:center; gap:9px; font-size:11px; color:var(--muted-2); }
402
- .pr-comment-author { font-size:12px; font-weight:600; color:var(--fg); }
403
- .pr-comment-link { display:inline-flex; align-items:center; gap:5px; font-size:11px; color:var(--muted); text-decoration:none; }
404
- .pr-comment-link:hover { color:var(--accent); }
405
- .pr-comment-body { font-size:12.5px; line-height:1.6; color:#f0ece6; }
406
- .pr-comment-body .md-p, .pr-comment-body .md-list { font-size:12.5px; }
407
- .main { flex:1; min-width:0; display:flex; flex-direction:column; overflow-y:auto; }
408
- .topbar { position:sticky; top:0; z-index:10; display:flex; align-items:center; justify-content:space-between; gap:16px;
409
- padding:13px 24px; border-bottom:1px solid var(--border); background:rgba(16,16,20,.74);
410
- backdrop-filter:blur(16px) saturate(140%); -webkit-backdrop-filter:blur(16px) saturate(140%); }
411
- .segmented { display:inline-flex; gap:4px; padding:4px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
412
- .seg-btn { display:inline-flex; align-items:center; gap:8px; font:inherit; font-size:12.5px; font-weight:600; cursor:pointer;
413
- color:var(--muted); background:transparent; border:1px solid transparent; border-radius:999px; padding:7px 16px;
414
- transition:color .15s ease, background .15s ease; }
415
- .seg-btn:hover { color:var(--fg); }
416
- .seg-btn.active { color:#1b1305; background:linear-gradient(180deg, var(--accent-strong), var(--accent)); box-shadow:0 4px 14px rgba(240,165,44,.24); }
417
- .topbar-right { display:flex; align-items:center; gap:14px; }
418
- .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); }
419
- .dot { width:8px; height:8px; border-radius:50%; background:var(--success); box-shadow:0 0 0 3px rgba(69,192,122,.14); }
420
- .dot.busy { background:var(--accent); box-shadow:0 0 0 3px rgba(240,165,44,.18); animation:pulse 1.1s ease-in-out infinite; }
421
- .btn { display:inline-flex; align-items:center; gap:8px; font:inherit; font-size:12.5px; font-weight:500; cursor:pointer; color:var(--fg);
422
- padding:8px 15px; background:var(--surface-2); border:1px solid var(--border-strong); border-radius:999px;
423
- transition:background .15s ease, border-color .15s ease, color .15s ease; }
424
- .btn:hover { background:#202027; border-color:var(--accent-line); }
425
- .btn:disabled { opacity:.55; cursor:not-allowed; }
426
- .icon.spin { animation:spin .8s linear infinite; }
427
- .content { width:100%; max-width:1180px; margin:0 auto; padding:22px 24px 64px; }
428
- .page-header { display:flex; align-items:flex-start; justify-content:space-between; gap:18px; margin-bottom:16px; }
429
- .page-header-text { min-width:0; }
430
- .page-header-actions { display:flex; align-items:center; gap:10px; flex:none; }
431
- .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); }
432
- .btn.submit-review:hover { background:linear-gradient(180deg, var(--accent-strong), var(--accent)); border-color:transparent; filter:brightness(1.07); }
433
- .page-title { margin:0 0 8px; font-size:26px; font-weight:700; letter-spacing:-.02em; word-break:break-word; }
434
- .page-sub { margin:0; max-width:78ch; color:var(--muted); font-size:12.5px; line-height:1.55; }
435
- .page-sub code { color:var(--fg); background:rgba(255,255,255,.06); border-radius:6px; padding:1px 6px; font-size:11.5px; }
436
- .error { color:#ff8b8f; font-size:12.5px; }
437
- .empty { color:var(--muted); font-size:13px; }
438
- .kpis { display:grid; grid-template-columns:repeat(4, minmax(0,1fr)); gap:12px; margin:20px 0 18px; }
439
- .kpi { display:flex; flex-direction:column; gap:8px; padding:15px 16px; background:var(--card);
440
- border:1px solid var(--border); border-radius:var(--radius-lg); }
441
- .kpi-label { font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--muted-2); }
442
- .kpi-value { font-size:30px; font-weight:700; line-height:1; letter-spacing:-.02em; font-variant-numeric:tabular-nums; }
443
- .kpi-hint { font-family:var(--font-mono); font-size:11px; color:var(--muted-2); }
444
- .kpi-execute .kpi-value { color:var(--accent); }
445
- .kpi-ignore .kpi-value { color:var(--muted); }
446
- .kpi-done .kpi-value { color:var(--success); }
447
- .progress { margin:0 0 22px; }
448
- .progress-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:8px; font-size:11.5px; color:var(--muted); }
449
- .progress-head .pct { color:var(--fg); font-family:var(--font-mono); }
450
- .progress-track { height:8px; border-radius:999px; background:var(--surface-2); border:1px solid var(--border); overflow:hidden; }
451
- .progress-fill { display:block; height:100%; border-radius:999px; background:linear-gradient(90deg, #2c8f57, var(--success)); transition:width .35s ease; }
452
- .filters { display:flex; flex-wrap:wrap; align-items:center; gap:10px; margin-bottom:20px; }
453
- .search { display:flex; align-items:center; gap:8px; flex:1 1 240px; max-width:330px; padding:8px 14px; color:var(--muted);
454
- background:var(--surface-2); border:1px solid var(--border-strong); border-radius:999px; }
455
- .search:focus-within { color:var(--accent); border-color:var(--accent-line); }
456
- .search input { flex:1; min-width:0; font:inherit; font-size:12.5px; color:var(--fg); background:transparent; border:0; outline:0; }
457
- .search input::placeholder { color:var(--muted-2); }
458
- .search-clear { padding:0 2px; font-size:15px; line-height:1; color:var(--muted); background:transparent; border:0; cursor:pointer; }
459
- .chips { display:inline-flex; gap:3px; padding:4px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
460
- .chip { display:inline-flex; align-items:center; gap:6px; font:inherit; font-size:12px; font-weight:600; cursor:pointer; color:var(--muted);
461
- padding:6px 13px; background:transparent; border:1px solid transparent; border-radius:999px; transition:color .15s ease, background .15s ease; }
462
- .chip:hover { color:var(--fg); }
463
- .chip.active { color:#14100a; background:#e9e5df!important; }
464
- .sev-group { margin-bottom:24px; }
465
- .sev-head { display:flex; align-items:center; gap:8px; margin:0 0 10px; font-size:11px; font-weight:700;
466
- letter-spacing:.16em; text-transform:uppercase; color:var(--muted-2); }
467
- .sev-head .n { font-family:var(--font-mono); letter-spacing:0; }
468
- .sev-group.sev-blocking .sev-head { color:var(--blocking); }
469
- .sev-group.sev-attention .sev-head { color:var(--attention); }
470
- .sev-group.sev-suggestion .sev-head { color:var(--suggestion); }
471
- .sev-group.sev-nit .sev-head { color:var(--nit); }
472
- .card { position:relative; margin-bottom:20px; overflow:hidden; background:var(--card);
473
- border:1px solid var(--border); border-left:4px solid var(--border-strong); border-radius:var(--radius-lg);
474
- transition:background .15s ease, border-color .15s ease, opacity .2s ease; }
475
- .card:hover { background:#16161b; }
476
- .card.sev-blocking { border-left-color:var(--blocking); }
477
- .card.sev-attention { border-left-color:var(--attention); }
478
- .card.sev-suggestion { border-left-color:var(--suggestion); }
479
- .card.sev-nit { border-left-color:var(--nit); }
480
- .card.absent { opacity:.5; }
481
- .card.done { opacity:.6; }
482
- .card-head { display:flex; align-items:center; gap:10px; padding:12px 14px 0; }
483
- .chev { display:grid; place-items:center; flex:none; width:22px; height:22px; cursor:pointer; color:var(--muted-2);
484
- background:transparent; border:1px solid transparent; border-radius:7px; }
485
- .chev:hover { color:var(--fg); background:rgba(255,255,255,.06); }
486
- .chev:focus-visible { color:var(--fg); border-color:var(--accent-line); outline:none; }
487
- .chev svg { transition:transform .18s ease; }
488
- .chev.open svg { transform:rotate(90deg); }
489
- .sev-badge { display:inline-flex; align-items:center; flex:none; padding:3px 9px; border-radius:999px;
490
- font-size:10px; letter-spacing:.08em; text-transform:uppercase; }
491
- .card.sev-blocking .sev-badge { color:var(--blocking); background:rgba(242,89,94,.12); }
492
- .card.sev-attention .sev-badge { color:var(--attention); background:rgba(237,161,58,.12); }
493
- .card.sev-suggestion .sev-badge { color:var(--suggestion); background:rgba(90,164,240,.12); }
494
- .card.sev-nit .sev-badge { color:var(--nit); background:rgba(255,255,255,.06); }
495
- .loc { display:inline-flex; align-items:center; gap:7px; max-width: 406ch; overflow:hidden; white-space:break-spaces; text-overflow:ellipsis;
496
- padding:4px 9px; font-family:var(--font-mono); font-size:11.5px; color:#aba7a1; text-decoration:none; }
497
- .loc:hover { color:#fff; }
498
- .loc .ext { flex:none; opacity:0; transition:opacity .15s ease; }
499
- .loc:hover .ext { opacity:1; }
500
- .loc.static { color:var(--muted); }
501
- .count { padding:2px 7px; font-family:var(--font-mono); font-size:11px; color:var(--muted);
502
- background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
503
- .spacer { flex:1; }
504
- .done-btn { display:inline-flex; align-items:center; gap:7px; flex:none; font:inherit; font-size:12px; font-weight:600; cursor:pointer;
505
- padding:7px 13px; color:var(--muted); background:transparent; border:1px solid var(--border-strong); border-radius:999px;
506
- transition:color .15s ease, background .15s ease, border-color .15s ease; }
507
- .done-btn:hover { color:var(--fg); border-color:rgba(69,192,122,.55); }
508
- .done-btn:disabled { opacity:.55; cursor:not-allowed; }
509
- .done-btn.is-done { color:var(--success); background:rgba(69,192,122,.14); border-color:rgba(69,192,122,.5); }
510
- .card-title { margin:9px 0 0; padding:0 16px 20px; font-size:14px; font-weight:400; }
511
- .card.done .card-title { color:#b7b7b7; text-decoration:line-through; text-decoration-color:rgba(255,255,255,.15); }
512
- .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); }
513
- .meta-row { display:flex; flex-wrap:wrap; gap:14px; font-size:11.5px; color:var(--muted); }
514
- .meta { display:inline-flex; align-items:center; gap:7px; }
515
- .meta-k { font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--muted-2); }
516
- .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; }
517
- .action-block { display:flex; gap:11px; padding:0; background:transparent; border:0; border-radius:0; }
518
- .action-icon { flex:none; display:inline-block; margin-top:3px; margin-right:2px; color:var(--accent); }
519
- .action-text { flex:1; min-width:0; }
520
- .action-label { display:block; margin-bottom:4px; font-size:10px; font-weight:600; letter-spacing:.12em; text-transform:uppercase; color:var(--accent); }
521
- .action-text p, .md-p { margin:0 0 6px; font-size:13px; line-height:1.65; color:#f0ece6; white-space:pre-wrap; }
522
- .action-text p:last-child { margin-bottom:0; }
523
- .md-list { margin:0 0 6px; padding-left:20px; font-size:13px; line-height:1.65; color:#f0ece6; }
524
- .md-list li { margin:0 0 2px; }
525
- .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; }
526
- .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; }
527
- code.md-path { color:#8fc7ff; background:rgba(90,164,240,.1); border-color:rgba(90,164,240,.24); }
528
- .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; }
529
- .action-text strong, .md-p strong, .md-list strong, .md-quote strong, .md-table strong, .md-h strong { color:#fff; font-weight:600; }
530
- .action-text em, .md-p em, .md-list em, .md-quote em, .md-table em, .md-h em { color:#e9e4dc; }
531
- .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; }
532
- .md-h { margin:16px 0 8px; font-size:14px; font-weight:700; line-height:1.35; letter-spacing:-.01em; color:#eee9e2; }
533
- .md-h:first-child { margin-top:0; }
534
- .md-h1 { font-size:19px; }
535
- .md-h2 { font-size:16px; }
536
- .md-h3 { font-size:14px; }
537
- .md-h4, .md-h5, .md-h6 { font-size:13px; color:#e4dfd7; font-weight:600; }
538
- .md-hr { margin:14px 0; border:0; border-top:1px solid var(--border); }
539
- .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); }
540
- .md-table { width:100%; border-collapse:separate; border-spacing:3px; font-size:12px; line-height:1.5; }
541
- .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; }
542
- .md-table th { color:var(--muted); font-weight:600; background:rgba(255,255,255,.03); }
543
- .md-table td { color:#d8d3cc; }
544
- .decision-tags-block { display:flex; align-items:center; gap:8px; }
545
- .evidence { margin:0; padding: 16px; white-space:break-spaces; font-size:12px; line-height:1.6; color:#cdc7bf;
546
- background:#0b0b0e; border:1px solid var(--border); border-radius:var(--radius); }
547
- .evidence-block { display:flex; flex-direction:column; gap:6px; }
548
- .evidence-head { display:flex; justify-content:flex-end; }
549
- .code-lang { font-family:var(--font-mono); font-size:9.5px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--muted-2); }
550
- .code-evidence { white-space:pre; overflow:auto; }
551
- .tok-keyword { color:#c792ea; }
552
- .tok-string { color:#9ddc8a; }
553
- .tok-comment { color:#6d6862; font-style:italic; }
554
- .tok-number { color:#8fc7ff; }
555
- .tok-type { color:var(--accent-strong); }
556
- .tok-function { color:#82aaff; }
557
- .tok-property { color:#e6c07b; }
558
- .tok-punct { color:#8d8a85; }
559
- .tags { display:flex; flex-wrap:wrap; align-items:center; gap:7px; margin-left: auto; }
560
- .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); }
561
- .tag { display:inline-flex; align-items:center; gap:6px; padding:3px 9px; font-family:var(--font-mono); font-size:11px; color:var(--muted);
562
- background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
563
- .decisions { display:inline-flex; gap:3px; align-self:flex-start; padding:4px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
564
- .dec-btn { font:inherit; font-size:12px; font-weight:600; cursor:pointer; color:var(--muted); padding:6px 15px;
565
- background:transparent; border:1px solid transparent; border-radius:999px; transition:color .15s ease, background .15s ease; }
566
- .dec-btn:hover { color:var(--fg); }
567
- .dec-btn:disabled { opacity:.55; cursor:not-allowed; }
568
- .dec-btn.dec-execute.active { color:#1b1305; background:linear-gradient(180deg, var(--accent-strong), var(--accent)); }
569
- .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); }
570
- .dec-btn.dec-pending.active { color:var(--fg); background:rgba(255,255,255,.08); box-shadow:inset 0 0 0 1px var(--border-strong); }
571
- .comment { position:relative; }
572
- .comment textarea { width:100%; min-height:58px; padding:11px 13px 26px; resize:vertical; font:inherit; font-size:12.5px; line-height:1.5;
573
- color:var(--fg); background:var(--surface-2); border:1px solid var(--border-strong); border-radius:var(--radius); outline:none;
574
- transition:border-color .15s ease, box-shadow .15s ease; }
575
- .comment textarea::placeholder { color:var(--muted-2); }
576
- .comment textarea:focus { border-color:var(--accent-line); box-shadow:0 0 0 3px rgba(240,165,44,.12); }
577
- .comment-status { position:absolute; right:12px; bottom:9px; display:inline-flex; align-items:center; gap:6px;
578
- font-family:var(--font-mono); font-size:11px; color:var(--muted); }
579
- .comment-status.saved { color:var(--success); }
580
- .artifacts { display:flex; flex-direction:column; }
581
- .artifact-layout { display:grid; grid-template-columns:minmax(190px,250px) minmax(0,1fr); gap:16px; align-items:start; }
582
- .artifact-list { display:flex; flex-direction:column; gap:14px; max-height:72vh; overflow:auto; padding:2px 4px 2px 0; }
583
- .artifact-group { display:flex; flex-direction:column; gap:4px; }
584
- .artifact-group-label { margin:0 0 2px; font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--muted); }
585
- .artifact-item { display:flex; flex-direction:column; gap:2px; text-align:left; cursor:pointer; font:inherit; color:var(--muted);
586
- padding:8px 10px; background:transparent; border:1px solid transparent; border-radius:10px;
587
- transition:background .15s ease, border-color .15s ease, color .15s ease; }
588
- .artifact-item:hover { color:var(--fg); background:var(--surface-2); border-color:var(--border); }
589
- .artifact-item.active { color:var(--fg); background:var(--accent-soft); border-color:var(--accent-line); }
590
- .artifact-item-name { font-family:var(--font-mono); font-size:11.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
591
- .artifact-item-kind { font-size:9.5px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--muted-2); }
592
- .artifact-item.active .artifact-item-kind { color:var(--accent); }
593
- .artifact-pane { min-width:0; }
594
- .artifact-head { display:flex; align-items:flex-start; justify-content:space-between; gap:14px; margin-bottom:12px; }
595
- .artifact-title { margin:0 0 5px; font-family:var(--font-mono); font-size:13px; font-weight:600; word-break:break-word; }
596
- .artifact-desc { margin:0; max-width:78ch; color:var(--muted); font-size:12.5px; line-height:1.55; }
597
- .artifact-kind-badge { flex:none; display:inline-flex; align-items:center; padding:3px 9px; font-size:10px; font-weight:700;
598
- letter-spacing:.08em; text-transform:uppercase; color:var(--accent); background:var(--accent-soft);
599
- border:1px solid var(--accent-line); border-radius:999px; }
600
- .artifact-view { margin:0; padding:14px 16px; max-height:70vh; overflow:auto; font-size:12px; line-height:1.6; color:#cdc7bf;
601
- background:#0b0b0e; border:1px solid var(--border); border-radius:var(--radius); }
602
- .artifact-markdown { white-space:normal; }
603
- .artifact-markdown .md-p, .artifact-markdown .md-list, .artifact-markdown .md-quote { font-size:12.5px; }
604
- .artifact-markdown .md-p, .artifact-markdown .md-list { color:#d8d3cc; }
605
- .artifact-markdown .md-code-block { border:1px solid var(--border); }
606
- .json-view { white-space:pre; }
607
- .json-key { color:var(--accent-strong); }
608
- .json-string { color:#9ddc8a; }
609
- .json-number { color:#8fc7ff; }
610
- .json-literal { color:#d7a6ff; }
611
- .json-punct { color:#8d8a85; }
612
- @media (max-width:900px) { .artifact-layout { grid-template-columns:1fr; } .artifact-list { max-height:240px; } }
613
- .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;
614
- color:var(--success); background:rgba(69,192,122,.12); border:1px solid rgba(69,192,122,.32); border-radius:999px; text-decoration:none; }
615
- .sent-tag:hover { color:#fff; }
616
- .sent-tag.gh { color:#b9c7d6; background:rgba(139,164,191,.12); border-color:rgba(139,164,191,.35); }
617
- .sent-tag.gh:hover { color:#fff; background:rgba(139,164,191,.2); }
618
- .resume { display:flex; flex-direction:column; }
619
- .resume-hero { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; padding:18px 20px;
620
- background:linear-gradient(135deg, rgba(240,165,44,.08), rgba(255,255,255,.015) 55%);
621
- border:1px solid var(--border); border-radius:var(--radius-lg); }
622
- .resume-kicker { display:block; font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--accent); }
623
- .resume-title { margin:5px 0 7px; font-size:22px; font-weight:700; letter-spacing:-.02em; word-break:break-word; }
624
- .resume-meta { display:flex; flex-wrap:wrap; align-items:center; gap:12px; margin:0; font-size:12px; color:var(--muted); }
625
- .resume-pr { display:inline-flex; align-items:center; gap:5px; color:var(--accent-strong); text-decoration:none; }
626
- .resume-pr:hover { text-decoration:underline; }
627
- .resume-hero-side { display:flex; flex:none; flex-direction:column; align-items:flex-end; gap:10px; }
628
- .resume-status { display:inline-flex; align-items:center; padding:5px 12px; font-size:11px; font-weight:700; letter-spacing:.04em; border-radius:999px; }
629
- .resume-status.blocking { color:var(--blocking); background:rgba(242,89,94,.12); border:1px solid rgba(242,89,94,.35); }
630
- .resume-status.attention { color:var(--attention); background:rgba(237,161,58,.12); border:1px solid rgba(237,161,58,.35); }
631
- .resume-status.clean { color:var(--success); background:rgba(69,192,122,.12); border:1px solid rgba(69,192,122,.35); }
632
- .resume-kpis { margin:14px 0 18px; }
633
- .resume-kpis .kpi-value { font-size:24px; }
634
- .churn-add { color:var(--success); }
635
- .churn-del { color:var(--blocking); }
636
- .churn-sep { margin:0 3px; color:var(--muted-2); font-weight:400; }
637
- .kpi-blocking .kpi-value { color:var(--blocking); }
638
- .kpi-attention .kpi-value { color:var(--attention); }
639
- .kpi-total .kpi-value { color:var(--fg); }
640
- .resume-grid { display:grid; grid-template-columns:minmax(0,1.35fr) minmax(0,1fr); gap:16px; align-items:start; }
641
- .resume-card { min-width:0; padding:16px 18px; background:var(--card); border:1px solid var(--border); border-radius:var(--radius-lg); }
642
- .resume-card-head { display:flex; align-items:center; justify-content:space-between; gap:10px; }
643
- .resume-card-title { margin:0 0 10px; font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--muted); }
644
- .resume-card-count { display:inline-flex; align-items:center; justify-content:center; min-width:22px; height:22px; margin-bottom:10px; padding:0 7px;
645
- font-family:var(--font-mono); font-size:11px; color:var(--accent); background:var(--accent-soft); border:1px solid var(--accent-line); border-radius:999px; }
646
- .resume-narrative .md-p:last-child { margin-bottom:0; }
647
- .resume-domains { display:flex; flex-direction:column; gap:8px; margin:0; padding:0; list-style:none; }
648
- .resume-domain { display:flex; flex-direction:column; gap:7px; padding:10px 12px; background:var(--surface-2);
649
- border:1px solid var(--border); border-radius:10px; }
650
- .resume-domain-head { display:flex; align-items:baseline; justify-content:space-between; gap:10px; }
651
- .resume-domain-name { font-size:12.5px; font-weight:600; color:var(--fg); }
652
- .resume-domain-files { flex:none; font-family:var(--font-mono); font-size:10.5px; color:var(--muted-2); }
653
- .resume-domain-sev { display:flex; flex-wrap:wrap; gap:6px; }
654
- .resume-domain-sev .sev-badge { padding:2px 7px; font-size:9.5px; }
655
- .resume-domain-ok { font-size:11.5px; color:var(--success); }
656
- @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; } }
657
- .modal-layer { position:fixed; inset:0; z-index:50; display:grid; place-items:center; padding:24px; }
658
- .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); }
659
- .modal { position:relative; width:min(640px, 100%); max-height:min(78vh, 780px); display:flex; flex-direction:column; overflow:hidden;
660
- background:var(--surface); border:1px solid var(--border-strong); border-radius:var(--radius-lg); box-shadow:0 24px 70px rgba(0,0,0,.5); }
661
- .modal-head { display:flex; align-items:flex-start; justify-content:space-between; gap:14px; padding:18px 20px 14px; border-bottom:1px solid var(--border); }
662
- .modal-title { margin:0; font-size:17px; font-weight:700; letter-spacing:-.01em; }
663
- .modal-sub { margin:5px 0 0; font-size:12px; color:var(--muted); line-height:1.5; }
664
- .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; }
665
- .modal-close:hover { color:var(--fg); border-color:var(--border-strong); }
666
- .modal-body { display:flex; flex-direction:column; gap:16px; padding:16px 20px; overflow-y:auto; }
667
- .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); }
668
- .submit-field { display:flex; flex-direction:column; gap:8px; }
669
- .submit-label { font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--muted-2); }
670
- .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); }
671
- .submit-summary b { font-family:var(--font-mono); }
672
- .submit-list { display:flex; flex-direction:column; gap:6px; max-height:240px; overflow-y:auto; padding-right:2px; }
673
- .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; }
674
- .submit-item.sent { opacity:.7; }
675
- .submit-item .rule { flex:1; min-width:0; color:var(--muted); overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }
676
- .submit-empty { color:var(--muted); font-size:12.5px; }
677
- .submit-field .chips { flex-wrap:wrap; }
678
- .submit-check { display:inline-flex; align-items:center; gap:8px; font-size:12.5px; color:var(--muted); cursor:pointer; }
679
- .submit-check input { accent-color:var(--accent); }
680
- .submit-actions { display:flex; align-items:center; gap:8px; }
681
- .sev-badge.sev-blocking { color:var(--blocking); background:rgba(242,89,94,.12); }
682
- .sev-badge.sev-attention { color:var(--attention); background:rgba(237,161,58,.12); }
683
- .sev-badge.sev-suggestion { color:var(--suggestion); background:rgba(90,164,240,.12); }
684
- .sev-badge.sev-nit { color:var(--nit); background:rgba(255,255,255,.06); }
685
- @keyframes pulse { 0%,100% { opacity:1; transform:scale(1); } 50% { opacity:.4; transform:scale(.8); } }
686
- @keyframes spin { to { transform:rotate(360deg); } }
687
- @media (max-width:980px) { .kpis { grid-template-columns:repeat(2, minmax(0,1fr)); } }
688
- @media (max-width:820px) {
689
- .sidebar { width:232px; flex-basis:232px; }
690
- .topbar { padding:12px 16px; }
691
- .content { padding:18px 16px 56px; }
692
- .auto-refresh { display:none; }
693
- }
694
- `;
695
-
696
402
  /** Id de build por processo — muda a URL do bundle a cada reinício do servidor. */
697
403
  const BUILD_ID = Date.now().toString(36);
698
404
 
@@ -704,11 +410,12 @@ function renderShell(): string {
704
410
  <meta name="viewport" content="width=device-width, initial-scale=1" />
705
411
  <meta name="color-scheme" content="dark" />
706
412
  <title>Houndsman — Revisões</title>
413
+ <link rel="icon" type="image/png" href="/assets/${LOGO_FILE}" />
707
414
  <!-- Fontes de CDN: se estiver offline, o CSS cai para a pilha de sistema. -->
708
415
  <link rel="preconnect" href="https://fonts.googleapis.com" />
709
416
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
710
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" />
711
- <style>${STYLES}</style>
418
+ <link rel="stylesheet" href="/assets/${STYLES_FILE}?v=${BUILD_ID}" />
712
419
  </head>
713
420
  <body>
714
421
  <div id="app"></div>