@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.
- package/package.json +1 -1
- package/src/cli/index.ts +9 -1
- package/src/core/ai/ai-client.ts +21 -0
- package/src/core/ai/ai-sdk-model-provider.ts +43 -18
- package/src/core/ai/features/technical-summarizer.ts +43 -0
- package/src/core/ai/index.ts +4 -1
- package/src/core/ai/prompts/base.ts +7 -0
- package/src/core/ai/prompts/context.ts +5 -4
- package/src/core/ai/prompts/technical.ts +31 -0
- package/src/core/ai/provider-capabilities.ts +45 -0
- package/src/core/ai/schemas.ts +19 -3
- package/src/core/ai/types.ts +3 -1
- package/src/core/brief.ts +25 -4
- package/src/core/commands/scaffold.ts +5 -0
- package/src/core/config-loader.ts +18 -4
- package/src/core/config.ts +17 -1
- package/src/core/engine/review-workspace.ts +6 -2
- package/src/core/git-runner.ts +8 -1
- package/src/core/orchestrator.ts +114 -2
- package/src/core/paths.ts +3 -0
- package/src/core/pr-context.ts +11 -6
- package/src/core/project-config.ts +9 -1
- package/src/core/renderers/resume-renderer.ts +17 -34
- package/src/core/report-builder.ts +6 -6
- package/src/core/services/worktree-manager.ts +90 -7
- package/src/core/storage/types.ts +1 -0
- package/src/core/technical-summary.ts +398 -0
- package/src/harness/agent/agent-factory.ts +1 -0
- package/src/harness/agent/review-agent.ts +11 -1
- package/src/harness/config/harness-config.ts +6 -1
- package/src/index.ts +2 -0
- package/src/server/client/App.tsx +2 -1
- package/src/server/client/PrsSection.tsx +296 -0
- package/src/server/client/ResumePanel.tsx +10 -13
- package/src/server/client/RunsSection.tsx +73 -0
- package/src/server/client/Sidebar.tsx +171 -413
- package/src/server/client/sidebar-sections.tsx +73 -0
- package/src/server/client/utils/constants.ts +7 -2
- package/src/server/client/utils/sidebar-prefs.ts +64 -0
- package/src/server/client/utils/types.ts +1 -0
- package/src/server/index.ts +23 -409
- package/src/server/styles.css +454 -0
- package/src/tests/__snapshots__/baseline.test.ts.snap +5 -0
- package/src/tests/ai-core.test.ts +71 -0
- package/src/tests/ai-features.test.ts +47 -0
- package/src/tests/baseline.test.ts +4 -1
- package/src/tests/harness-agent.test.ts +19 -0
- package/src/tests/review-resume.test.ts +36 -8
- package/src/tests/review-scope.test.ts +67 -0
- package/src/tests/review-scripts.test.ts +2 -2
- package/src/tests/server.test.ts +16 -1
- package/src/tests/sidebar-sections.test.ts +94 -0
- package/src/tests/technical-summary.test.ts +176 -0
- package/src/tests/worktree.test.ts +110 -5
- package/assets/logo-legacy.png +0 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Estilos da UI de triagem (`houndsman server`) — tema escuro, primária âmbar.
|
|
3
|
+
* Servido em `/assets/styles.css`; o shell (`index.ts`) só aponta o <link>.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
@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');
|
|
7
|
+
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: dark;
|
|
10
|
+
--bg:#0a0a0c; --surface:#101014; --surface-2:#17171c; --card:#131318;
|
|
11
|
+
--border:rgba(255,255,255,.07); --border-strong:rgba(255,255,255,.14);
|
|
12
|
+
--fg:#ece9e4; --muted:#9d978e; --muted-2:#6d6862;
|
|
13
|
+
--accent:#f0a52c; --accent-strong:#ffc247; --accent-soft:rgba(240,165,44,.07); --accent-line:rgba(240,165,44,.25);
|
|
14
|
+
--blocking:#f2595e; --attention:#eda13a; --suggestion:#5aa4f0; --nit:#8d8a85; --success:#45c07a;
|
|
15
|
+
--radius:16px; --radius-lg:18px;
|
|
16
|
+
--font-sans:"Poppins","Space Grotesk","Geist Sans",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
|
|
17
|
+
--font-mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
|
18
|
+
}
|
|
19
|
+
* { box-sizing:border-box; }
|
|
20
|
+
html, body { height:100%; }
|
|
21
|
+
body { margin:0; font-size:14px; background:var(--bg); color:var(--fg); font-family:var(--font-sans); -webkit-font-smoothing:antialiased; }
|
|
22
|
+
body::before { content:""; position:fixed; inset:0; pointer-events:none; z-index:0;
|
|
23
|
+
background:radial-gradient(820px 420px at 8% -12%, rgba(240,165,44,.08), transparent 62%); }
|
|
24
|
+
code, pre, .mono { font-family:var(--font-mono); tab-size:4; }
|
|
25
|
+
::-webkit-scrollbar { width:10px; height:10px; }
|
|
26
|
+
::-webkit-scrollbar-thumb { background:rgba(255,255,255,.1); border:2px solid transparent; border-radius:999px; background-clip:padding-box; }
|
|
27
|
+
::-webkit-scrollbar-thumb:hover { background:rgba(255,255,255,.18); background-clip:padding-box; }
|
|
28
|
+
.app { position:relative; z-index:1; display:flex; height:100vh; overflow:hidden; }
|
|
29
|
+
/* Sidebar = rail fixa de seções + painel expansível (largura em --side-width). */
|
|
30
|
+
.sidebar { position:relative; flex:0 0 auto; display:flex; height:100%; overflow:hidden;
|
|
31
|
+
--rail-w:52px; --side-width:360px;
|
|
32
|
+
width:calc(var(--rail-w) + var(--side-width));
|
|
33
|
+
background:var(--surface); border-right:1px solid var(--border); }
|
|
34
|
+
.sidebar.collapsed { width:var(--rail-w); }
|
|
35
|
+
.sidebar.collapsed .side-panel, .sidebar.collapsed .side-resizer { display:none; }
|
|
36
|
+
.side-rail { flex:0 0 var(--rail-w); width:var(--rail-w); display:flex; flex-direction:column; align-items:center; gap:4px;
|
|
37
|
+
padding:10px 0; background:var(--surface-2); border-right:1px solid var(--border);
|
|
38
|
+
overflow-x:hidden; overflow-y:auto; }
|
|
39
|
+
.rail-btn { position:relative; flex:none; display:grid; place-items:center; width:40px; height:40px; padding:0;
|
|
40
|
+
color:var(--muted); background:transparent; border:1px solid transparent; border-radius:11px; cursor:pointer;
|
|
41
|
+
transition:color .15s ease, background .15s ease, border-color .15s ease; }
|
|
42
|
+
.rail-btn:hover { color:var(--fg); background:rgba(255,255,255,.05); }
|
|
43
|
+
.rail-btn:focus-visible { outline:none; color:var(--fg); border-color:var(--accent-line); }
|
|
44
|
+
.rail-btn.active { color:var(--accent); background:var(--accent-soft); }
|
|
45
|
+
.rail-btn.active::before { content:""; position:absolute; left:-6px; top:9px; bottom:9px; width:2px; border-radius:2px; background:var(--accent); }
|
|
46
|
+
.rail-badge { position:absolute; top:2px; right:0; min-width:16px; padding:0 4px; font-family:var(--font-mono);
|
|
47
|
+
font-size:9px; font-weight:600; line-height:15px; text-align:center; color:var(--muted); background:var(--surface);
|
|
48
|
+
border:1px solid var(--border-strong); border-radius:999px; }
|
|
49
|
+
.rail-btn.active .rail-badge { color:var(--accent); border-color:var(--accent-line); }
|
|
50
|
+
.rail-spacer { flex:1; }
|
|
51
|
+
.rail-toggle { color:var(--muted-2); }
|
|
52
|
+
.rail-toggle .icon.flip { transform:rotate(180deg); }
|
|
53
|
+
.side-panel { flex:1; min-width:0; display:flex; flex-direction:column; overflow:hidden; }
|
|
54
|
+
.side-head { display:flex; flex-direction:column; gap:12px; padding:16px 14px 12px; border-bottom:1px solid var(--border); }
|
|
55
|
+
.side-title { min-width:0; }
|
|
56
|
+
.side-title h2 { margin:0 0 3px; font-size:13px; font-weight:700; letter-spacing:.01em; }
|
|
57
|
+
.side-hint { margin:0; color:var(--muted-2); font-size:11px; line-height:1.45; }
|
|
58
|
+
.side-body { flex:1; min-height:0; display:flex; flex-direction:column; gap:14px; padding:14px;
|
|
59
|
+
overflow-x:hidden; overflow-y:auto; }
|
|
60
|
+
.side-section { display:flex; flex-direction:column; gap:14px; min-width:0; }
|
|
61
|
+
.side-section[hidden] { display:none; }
|
|
62
|
+
.side-resizer { position:absolute; top:0; right:-3px; bottom:0; z-index:20; width:7px; background:transparent;
|
|
63
|
+
cursor:col-resize; touch-action:none; }
|
|
64
|
+
.side-resizer:hover, .side-resizer:focus-visible { background:var(--accent-line); outline:none; }
|
|
65
|
+
body.resizing-sidebar { cursor:col-resize; user-select:none; }
|
|
66
|
+
.brand { display:flex; align-items:center; gap:12px; }
|
|
67
|
+
.brand .logo { width:42px; height:42px; object-fit:contain; border-radius:12px; background:var(--surface-2); border:1px solid var(--border); }
|
|
68
|
+
.sidebar h1 { font-size:15.5px; font-weight:700; letter-spacing:-.01em; margin:0; }
|
|
69
|
+
.sub { color:var(--muted); font-size:11.5px; margin:0; }
|
|
70
|
+
.section-label { margin:4px 0 0; font-size:10px; font-weight:700; letter-spacing:.16em; text-transform:uppercase; color:var(--muted-2); }
|
|
71
|
+
.runs { display:flex; flex-direction:column; gap:6px; }
|
|
72
|
+
.run { display:flex; flex-direction:column; gap:8px; text-align:left; cursor:pointer; font:inherit; color:inherit; padding:11px 13px;
|
|
73
|
+
background:transparent; border:1px solid transparent; border-radius:var(--radius); transition:background .15s ease, border-color .15s ease; }
|
|
74
|
+
.run:hover { background:var(--surface-2); border-color:var(--border); }
|
|
75
|
+
.run.active { background:var(--accent-soft); border-color:var(--accent-line); }
|
|
76
|
+
.run-top { display:flex; align-items:flex-start; gap:8px; }
|
|
77
|
+
.run-icon { flex:none; margin-top:2px; color:var(--muted-2); }
|
|
78
|
+
.run.active .run-icon { color:var(--accent); }
|
|
79
|
+
.run-branch { font-size:12.5px; font-weight:600; line-height:1.35; word-break:break-word; }
|
|
80
|
+
.run-meta { display:flex; align-items:center; gap:10px; font-family:var(--font-mono); font-size:10.5px; color:var(--muted); }
|
|
81
|
+
.run-when { display:inline-flex; align-items:center; gap:6px; }
|
|
82
|
+
.run-count { margin-left:auto; color:var(--muted-2); }
|
|
83
|
+
.run-badge { align-self:flex-start; display:inline-flex; align-items:center; gap:6px; padding:4px 9px; border-radius:999px;
|
|
84
|
+
font-size:10px; font-weight:700; letter-spacing:.07em; text-transform:uppercase;
|
|
85
|
+
color:var(--success); background:rgba(69,192,122,.12); border:1px solid rgba(69,192,122,.32); }
|
|
86
|
+
.run-status { font-size:10px; font-weight:600; letter-spacing:.07em; text-transform:uppercase; color:var(--muted-2); }
|
|
87
|
+
.status-reviewing, .status-fixing { color:var(--accent); }
|
|
88
|
+
.status-review_done { color:var(--suggestion); }
|
|
89
|
+
.status-fix_done { color:var(--success); }
|
|
90
|
+
.status-timeout { color:var(--blocking); }
|
|
91
|
+
.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; }
|
|
92
|
+
.icon-btn:hover { color:var(--fg); border-color:var(--border-strong); }
|
|
93
|
+
.icon-btn:disabled { opacity:.5; cursor:not-allowed; }
|
|
94
|
+
.pr-toolbar { display:flex; align-items:center; justify-content:space-between; gap:8px; }
|
|
95
|
+
.pr-toolbar .section-label { margin:0; }
|
|
96
|
+
.prs { display:flex; flex-direction:column; gap:8px; }
|
|
97
|
+
.pr { display:flex; flex-direction:column; gap:7px; padding:11px 12px; background:var(--card); border:1px solid var(--border); border-radius:var(--radius); }
|
|
98
|
+
.pr.current { background:var(--accent-soft); border-color:var(--accent-line); }
|
|
99
|
+
.pr-head { display:flex; align-items:center; gap:7px; font-family:var(--font-mono); font-size:11px; color:var(--muted); }
|
|
100
|
+
.pr-icon { color:var(--muted-2); }
|
|
101
|
+
.pr.current .pr-icon { color:var(--accent); }
|
|
102
|
+
.pr-number { font-weight:600; color:var(--fg); }
|
|
103
|
+
.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; }
|
|
104
|
+
.pr-title { margin:0; font-size:12.5px; font-weight:600; line-height:1.4; word-break:break-word; }
|
|
105
|
+
.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); }
|
|
106
|
+
.pr-branch { color:var(--suggestion); }
|
|
107
|
+
.pr-worktree-badge { display:inline-flex; align-items:center; gap:4px; padding:1px 6px; font-family:var(--font-mono); font-size:9px;
|
|
108
|
+
font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--success);
|
|
109
|
+
background:rgba(69,192,122,.12); border:1px solid rgba(69,192,122,.3); border-radius:999px; }
|
|
110
|
+
.pr-review { margin:0; font-size:10.5px; font-weight:600; }
|
|
111
|
+
.pr-review-APPROVED { color:var(--success); }
|
|
112
|
+
.pr-review-CHANGES_REQUESTED { color:var(--blocking); }
|
|
113
|
+
.pr-review-REVIEW_REQUIRED { color:var(--attention); }
|
|
114
|
+
.pr-actions { display:flex; flex-wrap:wrap; gap:6px; margin-top:2px; }
|
|
115
|
+
.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);
|
|
116
|
+
background:transparent; border:1px solid var(--border-strong); border-radius:999px; text-decoration:none; }
|
|
117
|
+
.pr-btn:hover { color:var(--fg); border-color:var(--accent-line); }
|
|
118
|
+
.pr-btn:disabled { opacity:.45; cursor:not-allowed; }
|
|
119
|
+
.pr-btn.danger { color:var(--accent); background:var(--accent-soft); border-color:var(--accent-line); }
|
|
120
|
+
.pr-warning { margin:0; padding:8px 10px; font-size:11px; line-height:1.5; color:var(--attention);
|
|
121
|
+
background:rgba(237,161,58,.08); border:1px solid rgba(237,161,58,.24); border-radius:10px; }
|
|
122
|
+
.pr-notice { margin:0; padding:8px 10px; font-size:11px; line-height:1.5; color:var(--success);
|
|
123
|
+
background:rgba(69,192,122,.08); border:1px solid rgba(69,192,122,.24); border-radius:10px; }
|
|
124
|
+
.pr-current-branch { margin:0; font-family:var(--font-mono); font-size:10.5px; color:var(--muted-2); }
|
|
125
|
+
.pr-current-branch code { color:var(--fg); }
|
|
126
|
+
.comments { display:flex; flex-direction:column; gap:18px; }
|
|
127
|
+
.comments-toolbar { display:flex; align-items:center; justify-content:space-between; gap:12px; }
|
|
128
|
+
.comments-counts { display:flex; flex-wrap:wrap; align-items:center; gap:12px; font-size:11.5px; color:var(--muted); }
|
|
129
|
+
.comments-counts b { font-family:var(--font-mono); font-weight:600; color:var(--fg); }
|
|
130
|
+
.comment-group { display:flex; flex-direction:column; gap:10px; }
|
|
131
|
+
.comment-group-title { margin:0; font-size:11px; font-weight:700; letter-spacing:.16em; text-transform:uppercase; color:var(--muted-2); }
|
|
132
|
+
.comment-file-group { display:flex; flex-direction:column; gap:8px; }
|
|
133
|
+
.comment-group-label { display:flex; align-items:center; gap:8px; margin:0; font-family:var(--font-mono); font-size:11.5px; color:var(--suggestion); }
|
|
134
|
+
.comment-thread { display:flex; flex-direction:column; overflow:hidden; background:var(--card); border:1px solid var(--border); border-radius:var(--radius-lg); }
|
|
135
|
+
.comment-thread-head { display:flex; align-items:center; gap:8px; padding:9px 13px; font-family:var(--font-mono); font-size:11px; color:var(--muted);
|
|
136
|
+
background:var(--surface-2); border-bottom:1px solid var(--border); }
|
|
137
|
+
.comment-file { overflow:hidden; color:var(--fg); text-overflow:ellipsis; white-space:nowrap; }
|
|
138
|
+
.pr-comment { display:flex; flex-direction:column; gap:7px; padding:12px 14px; border-top:1px solid var(--border); }
|
|
139
|
+
.comment-thread .pr-comment:first-of-type { border-top:0; }
|
|
140
|
+
.pr-comment.reply { margin-left:26px; background:rgba(255,255,255,.012); border-left:2px solid var(--accent-line); }
|
|
141
|
+
.pr-comment-head { display:flex; align-items:center; gap:9px; font-size:11px; color:var(--muted-2); }
|
|
142
|
+
.pr-comment-author { font-size:12px; font-weight:600; color:var(--fg); }
|
|
143
|
+
.pr-comment-link { display:inline-flex; align-items:center; gap:5px; font-size:11px; color:var(--muted); text-decoration:none; }
|
|
144
|
+
.pr-comment-link:hover { color:var(--accent); }
|
|
145
|
+
.pr-comment-body { font-size:12.5px; line-height:1.6; color:#f0ece6; }
|
|
146
|
+
.pr-comment-body .md-p, .pr-comment-body .md-list { font-size:12.5px; }
|
|
147
|
+
.main { flex:1; min-width:0; display:flex; flex-direction:column; overflow-y:auto; }
|
|
148
|
+
.topbar { position:sticky; top:0; z-index:10; display:flex; align-items:center; justify-content:space-between; gap:16px;
|
|
149
|
+
padding:13px 24px; border-bottom:1px solid var(--border); background:rgba(16,16,20,.74);
|
|
150
|
+
backdrop-filter:blur(16px) saturate(140%); -webkit-backdrop-filter:blur(16px) saturate(140%); }
|
|
151
|
+
.segmented { display:inline-flex; gap:4px; padding:4px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
|
|
152
|
+
.seg-btn { display:inline-flex; align-items:center; gap:8px; font:inherit; font-size:12.5px; font-weight:600; cursor:pointer;
|
|
153
|
+
color:var(--muted); background:transparent; border:1px solid transparent; border-radius:999px; padding:7px 16px;
|
|
154
|
+
transition:color .15s ease, background .15s ease; }
|
|
155
|
+
.seg-btn:hover { color:var(--fg); }
|
|
156
|
+
.seg-btn.active { color:#1b1305; background:linear-gradient(180deg, var(--accent-strong), var(--accent)); box-shadow:0 4px 14px rgba(240,165,44,.24); }
|
|
157
|
+
.topbar-right { display:flex; align-items:center; gap:14px; }
|
|
158
|
+
.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); }
|
|
159
|
+
.dot { width:8px; height:8px; border-radius:50%; background:var(--success); box-shadow:0 0 0 3px rgba(69,192,122,.14); }
|
|
160
|
+
.dot.busy { background:var(--accent); box-shadow:0 0 0 3px rgba(240,165,44,.18); animation:pulse 1.1s ease-in-out infinite; }
|
|
161
|
+
.btn { display:inline-flex; align-items:center; gap:8px; font:inherit; font-size:12.5px; font-weight:500; cursor:pointer; color:var(--fg);
|
|
162
|
+
padding:8px 15px; background:var(--surface-2); border:1px solid var(--border-strong); border-radius:999px;
|
|
163
|
+
transition:background .15s ease, border-color .15s ease, color .15s ease; }
|
|
164
|
+
.btn:hover { background:#202027; border-color:var(--accent-line); }
|
|
165
|
+
.btn:disabled { opacity:.55; cursor:not-allowed; }
|
|
166
|
+
.btn.danger { color:#ffb3b3; background:rgba(255,86,86,.12); border-color:rgba(255,86,86,.4); }
|
|
167
|
+
.btn.danger:hover { background:rgba(255,86,86,.2); border-color:rgba(255,86,86,.62); }
|
|
168
|
+
.modal-check { display:flex; align-items:flex-start; gap:10px; padding:12px 14px; font-size:12.5px; line-height:1.55;
|
|
169
|
+
color:var(--fg); background:var(--surface-2); border:1px solid var(--border); border-radius:12px; cursor:pointer; }
|
|
170
|
+
.modal-check input { margin:3px 0 0; flex:none; accent-color:var(--accent); }
|
|
171
|
+
.modal-check code { font-family:var(--font-mono); font-size:11.5px; color:var(--muted); word-break:break-all; }
|
|
172
|
+
.modal-warn { margin:0; font-size:12px; line-height:1.55; color:var(--muted); }
|
|
173
|
+
.icon.spin { animation:spin .8s linear infinite; }
|
|
174
|
+
.content { width:100%; max-width:1180px; margin:0 auto; padding:22px 24px 64px; }
|
|
175
|
+
.page-header { display:flex; align-items:flex-start; justify-content:space-between; gap:18px; margin-bottom:16px; }
|
|
176
|
+
.page-header-text { min-width:0; }
|
|
177
|
+
.page-header-actions { display:flex; align-items:center; gap:10px; flex:none; }
|
|
178
|
+
.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); }
|
|
179
|
+
.btn.submit-review:hover { background:linear-gradient(180deg, var(--accent-strong), var(--accent)); border-color:transparent; filter:brightness(1.07); }
|
|
180
|
+
.page-title { margin:0 0 8px; font-size:26px; font-weight:700; letter-spacing:-.02em; word-break:break-word; }
|
|
181
|
+
.page-sub { margin:0; max-width:78ch; color:var(--muted); font-size:12.5px; line-height:1.55; }
|
|
182
|
+
.page-sub code { color:var(--fg); background:rgba(255,255,255,.06); border-radius:6px; padding:1px 6px; font-size:11.5px; }
|
|
183
|
+
.error { color:#ff8b8f; font-size:12.5px; }
|
|
184
|
+
.empty { color:var(--muted); font-size:13px; }
|
|
185
|
+
.kpis { display:grid; grid-template-columns:repeat(4, minmax(0,1fr)); gap:12px; margin:20px 0 18px; }
|
|
186
|
+
.kpi { display:flex; flex-direction:column; gap:8px; padding:15px 16px; background:var(--card);
|
|
187
|
+
border:1px solid var(--border); border-radius:var(--radius-lg); }
|
|
188
|
+
.kpi-label { font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--muted-2); }
|
|
189
|
+
.kpi-value { font-size:30px; font-weight:700; line-height:1; letter-spacing:-.02em; font-variant-numeric:tabular-nums; }
|
|
190
|
+
.kpi-hint { font-family:var(--font-mono); font-size:11px; color:var(--muted-2); }
|
|
191
|
+
.kpi-execute .kpi-value { color:var(--accent); }
|
|
192
|
+
.kpi-ignore .kpi-value { color:var(--muted); }
|
|
193
|
+
.kpi-done .kpi-value { color:var(--success); }
|
|
194
|
+
.progress { margin:0 0 22px; }
|
|
195
|
+
.progress-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:8px; font-size:11.5px; color:var(--muted); }
|
|
196
|
+
.progress-head .pct { color:var(--fg); font-family:var(--font-mono); }
|
|
197
|
+
.progress-track { height:8px; border-radius:999px; background:var(--surface-2); border:1px solid var(--border); overflow:hidden; }
|
|
198
|
+
.progress-fill { display:block; height:100%; border-radius:999px; background:linear-gradient(90deg, #2c8f57, var(--success)); transition:width .35s ease; }
|
|
199
|
+
.filters { display:flex; flex-wrap:wrap; align-items:center; gap:10px; margin-bottom:20px; }
|
|
200
|
+
.search { display:flex; align-items:center; gap:8px; flex:1 1 240px; max-width:330px; padding:8px 14px; color:var(--muted);
|
|
201
|
+
background:var(--surface-2); border:1px solid var(--border-strong); border-radius:999px; }
|
|
202
|
+
.search:focus-within { color:var(--accent); border-color:var(--accent-line); }
|
|
203
|
+
.search input { flex:1; min-width:0; font:inherit; font-size:12.5px; color:var(--fg); background:transparent; border:0; outline:0; }
|
|
204
|
+
.search input::placeholder { color:var(--muted-2); }
|
|
205
|
+
.search-clear { padding:0 2px; font-size:15px; line-height:1; color:var(--muted); background:transparent; border:0; cursor:pointer; }
|
|
206
|
+
.chips { display:inline-flex; gap:3px; padding:4px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
|
|
207
|
+
.chip { display:inline-flex; align-items:center; gap:6px; font:inherit; font-size:12px; font-weight:600; cursor:pointer; color:var(--muted);
|
|
208
|
+
padding:6px 13px; background:transparent; border:1px solid transparent; border-radius:999px; transition:color .15s ease, background .15s ease; }
|
|
209
|
+
.chip:hover { color:var(--fg); }
|
|
210
|
+
.chip.active { color:#14100a; background:#e9e5df!important; }
|
|
211
|
+
.sev-group { margin-bottom:24px; }
|
|
212
|
+
.sev-head { display:flex; align-items:center; gap:8px; margin:0 0 10px; font-size:11px; font-weight:700;
|
|
213
|
+
letter-spacing:.16em; text-transform:uppercase; color:var(--muted-2); }
|
|
214
|
+
.sev-head .n { font-family:var(--font-mono); letter-spacing:0; }
|
|
215
|
+
.sev-group.sev-blocking .sev-head { color:var(--blocking); }
|
|
216
|
+
.sev-group.sev-attention .sev-head { color:var(--attention); }
|
|
217
|
+
.sev-group.sev-suggestion .sev-head { color:var(--suggestion); }
|
|
218
|
+
.sev-group.sev-nit .sev-head { color:var(--nit); }
|
|
219
|
+
.card { position:relative; margin-bottom:20px; overflow:hidden; background:var(--card);
|
|
220
|
+
border:1px solid var(--border); border-left:4px solid var(--border-strong); border-radius:var(--radius-lg);
|
|
221
|
+
transition:background .15s ease, border-color .15s ease, opacity .2s ease; }
|
|
222
|
+
.card:hover { background:#16161b; }
|
|
223
|
+
.card.sev-blocking { border-left-color:var(--blocking); }
|
|
224
|
+
.card.sev-attention { border-left-color:var(--attention); }
|
|
225
|
+
.card.sev-suggestion { border-left-color:var(--suggestion); }
|
|
226
|
+
.card.sev-nit { border-left-color:var(--nit); }
|
|
227
|
+
.card.absent { opacity:.5; }
|
|
228
|
+
.card.done { opacity:.6; }
|
|
229
|
+
.card-head { display:flex; align-items:center; gap:10px; padding:12px 14px 0; }
|
|
230
|
+
.chev { display:grid; place-items:center; flex:none; width:22px; height:22px; cursor:pointer; color:var(--muted-2);
|
|
231
|
+
background:transparent; border:1px solid transparent; border-radius:7px; }
|
|
232
|
+
.chev:hover { color:var(--fg); background:rgba(255,255,255,.06); }
|
|
233
|
+
.chev:focus-visible { color:var(--fg); border-color:var(--accent-line); outline:none; }
|
|
234
|
+
.chev svg { transition:transform .18s ease; }
|
|
235
|
+
.chev.open svg { transform:rotate(90deg); }
|
|
236
|
+
.sev-badge { display:inline-flex; align-items:center; flex:none; padding:3px 9px; border-radius:999px;
|
|
237
|
+
font-size:10px; letter-spacing:.08em; text-transform:uppercase; }
|
|
238
|
+
.card.sev-blocking .sev-badge { color:var(--blocking); background:rgba(242,89,94,.12); }
|
|
239
|
+
.card.sev-attention .sev-badge { color:var(--attention); background:rgba(237,161,58,.12); }
|
|
240
|
+
.card.sev-suggestion .sev-badge { color:var(--suggestion); background:rgba(90,164,240,.12); }
|
|
241
|
+
.card.sev-nit .sev-badge { color:var(--nit); background:rgba(255,255,255,.06); }
|
|
242
|
+
.loc { display:inline-flex; align-items:center; gap:7px; max-width: 406ch; overflow:hidden; white-space:break-spaces; text-overflow:ellipsis;
|
|
243
|
+
padding:4px 9px; font-family:var(--font-mono); font-size:11.5px; color:#aba7a1; text-decoration:none; }
|
|
244
|
+
.loc:hover { color:#fff; }
|
|
245
|
+
.loc .ext { flex:none; opacity:0; transition:opacity .15s ease; }
|
|
246
|
+
.loc:hover .ext { opacity:1; }
|
|
247
|
+
.loc.static { color:var(--muted); }
|
|
248
|
+
.count { padding:2px 7px; font-family:var(--font-mono); font-size:11px; color:var(--muted);
|
|
249
|
+
background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
|
|
250
|
+
.spacer { flex:1; }
|
|
251
|
+
.done-btn { display:inline-flex; align-items:center; gap:7px; flex:none; font:inherit; font-size:12px; font-weight:600; cursor:pointer;
|
|
252
|
+
padding:7px 13px; color:var(--muted); background:transparent; border:1px solid var(--border-strong); border-radius:999px;
|
|
253
|
+
transition:color .15s ease, background .15s ease, border-color .15s ease; }
|
|
254
|
+
.done-btn:hover { color:var(--fg); border-color:rgba(69,192,122,.55); }
|
|
255
|
+
.done-btn:disabled { opacity:.55; cursor:not-allowed; }
|
|
256
|
+
.done-btn.is-done { color:var(--success); background:rgba(69,192,122,.14); border-color:rgba(69,192,122,.5); }
|
|
257
|
+
.card-title { margin:9px 0 0; padding:0 16px 20px; font-size:14px; font-weight:400; }
|
|
258
|
+
.card.done .card-title { color:#b7b7b7; text-decoration:line-through; text-decoration-color:rgba(255,255,255,.15); }
|
|
259
|
+
.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); }
|
|
260
|
+
.meta-row { display:flex; flex-wrap:wrap; gap:14px; font-size:11.5px; color:var(--muted); }
|
|
261
|
+
.meta { display:inline-flex; align-items:center; gap:7px; }
|
|
262
|
+
.meta-k { font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--muted-2); }
|
|
263
|
+
.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; }
|
|
264
|
+
.action-block { display:flex; gap:11px; padding:0; background:transparent; border:0; border-radius:0; }
|
|
265
|
+
.action-icon { flex:none; display:inline-block; margin-top:3px; margin-right:2px; color:var(--accent); }
|
|
266
|
+
.action-text { flex:1; min-width:0; }
|
|
267
|
+
.action-label { display:block; margin-bottom:4px; font-size:10px; font-weight:600; letter-spacing:.12em; text-transform:uppercase; color:var(--accent); }
|
|
268
|
+
.action-text p, .md-p { margin:0 0 6px; font-size:13px; line-height:1.65; color:#f0ece6; white-space:pre-wrap; }
|
|
269
|
+
.action-text p:last-child { margin-bottom:0; }
|
|
270
|
+
.md-list { margin:0 0 6px; padding-left:20px; font-size:13px; line-height:1.65; color:#f0ece6; }
|
|
271
|
+
.md-list li { margin:0 0 2px; }
|
|
272
|
+
.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; }
|
|
273
|
+
.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; }
|
|
274
|
+
code.md-path { color:#8fc7ff; background:rgba(90,164,240,.1); border-color:rgba(90,164,240,.24); }
|
|
275
|
+
.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; }
|
|
276
|
+
.action-text strong, .md-p strong, .md-list strong, .md-quote strong, .md-table strong, .md-h strong { color:#fff; font-weight:600; }
|
|
277
|
+
.action-text em, .md-p em, .md-list em, .md-quote em, .md-table em, .md-h em { color:#e9e4dc; }
|
|
278
|
+
.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; }
|
|
279
|
+
.md-h { margin:16px 0 8px; font-size:14px; font-weight:700; line-height:1.35; letter-spacing:-.01em; color:#eee9e2; }
|
|
280
|
+
.md-h:first-child { margin-top:0; }
|
|
281
|
+
.md-h1 { font-size:19px; }
|
|
282
|
+
.md-h2 { font-size:16px; }
|
|
283
|
+
.md-h3 { font-size:14px; }
|
|
284
|
+
.md-h4, .md-h5, .md-h6 { font-size:13px; color:#e4dfd7; font-weight:600; }
|
|
285
|
+
.md-hr { margin:14px 0; border:0; border-top:1px solid var(--border); }
|
|
286
|
+
.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); }
|
|
287
|
+
.md-table { width:100%; border-collapse:separate; border-spacing:3px; font-size:12px; line-height:1.5; }
|
|
288
|
+
.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; }
|
|
289
|
+
.md-table th { color:var(--muted); font-weight:600; background:rgba(255,255,255,.03); }
|
|
290
|
+
.md-table td { color:#d8d3cc; }
|
|
291
|
+
.decision-tags-block { display:flex; align-items:center; gap:8px; }
|
|
292
|
+
.evidence { margin:0; padding: 16px; white-space:break-spaces; font-size:12px; line-height:1.6; color:#cdc7bf;
|
|
293
|
+
background:#0b0b0e; border:1px solid var(--border); border-radius:var(--radius); }
|
|
294
|
+
.evidence-block { display:flex; flex-direction:column; gap:6px; }
|
|
295
|
+
.evidence-head { display:flex; justify-content:flex-end; }
|
|
296
|
+
.code-lang { font-family:var(--font-mono); font-size:9.5px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--muted-2); }
|
|
297
|
+
.code-evidence { white-space:pre; overflow:auto; }
|
|
298
|
+
.tok-keyword { color:#c792ea; }
|
|
299
|
+
.tok-string { color:#9ddc8a; }
|
|
300
|
+
.tok-comment { color:#6d6862; font-style:italic; }
|
|
301
|
+
.tok-number { color:#8fc7ff; }
|
|
302
|
+
.tok-type { color:var(--accent-strong); }
|
|
303
|
+
.tok-function { color:#82aaff; }
|
|
304
|
+
.tok-property { color:#e6c07b; }
|
|
305
|
+
.tok-punct { color:#8d8a85; }
|
|
306
|
+
.tags { display:flex; flex-wrap:wrap; align-items:center; gap:7px; margin-left: auto; }
|
|
307
|
+
.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); }
|
|
308
|
+
.tag { display:inline-flex; align-items:center; gap:6px; padding:3px 9px; font-family:var(--font-mono); font-size:11px; color:var(--muted);
|
|
309
|
+
background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
|
|
310
|
+
.decisions { display:inline-flex; gap:3px; align-self:flex-start; padding:4px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; }
|
|
311
|
+
.dec-btn { font:inherit; font-size:12px; font-weight:600; cursor:pointer; color:var(--muted); padding:6px 15px;
|
|
312
|
+
background:transparent; border:1px solid transparent; border-radius:999px; transition:color .15s ease, background .15s ease; }
|
|
313
|
+
.dec-btn:hover { color:var(--fg); }
|
|
314
|
+
.dec-btn:disabled { opacity:.55; cursor:not-allowed; }
|
|
315
|
+
.dec-btn.dec-execute.active { color:#1b1305; background:linear-gradient(180deg, var(--accent-strong), var(--accent)); }
|
|
316
|
+
.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); }
|
|
317
|
+
.dec-btn.dec-pending.active { color:var(--fg); background:rgba(255,255,255,.08); box-shadow:inset 0 0 0 1px var(--border-strong); }
|
|
318
|
+
.comment { position:relative; }
|
|
319
|
+
.comment textarea { width:100%; min-height:58px; padding:11px 13px 26px; resize:vertical; font:inherit; font-size:12.5px; line-height:1.5;
|
|
320
|
+
color:var(--fg); background:var(--surface-2); border:1px solid var(--border-strong); border-radius:var(--radius); outline:none;
|
|
321
|
+
transition:border-color .15s ease, box-shadow .15s ease; }
|
|
322
|
+
.comment textarea::placeholder { color:var(--muted-2); }
|
|
323
|
+
.comment textarea:focus { border-color:var(--accent-line); box-shadow:0 0 0 3px rgba(240,165,44,.12); }
|
|
324
|
+
.comment-status { position:absolute; right:12px; bottom:9px; display:inline-flex; align-items:center; gap:6px;
|
|
325
|
+
font-family:var(--font-mono); font-size:11px; color:var(--muted); }
|
|
326
|
+
.comment-status.saved { color:var(--success); }
|
|
327
|
+
.artifacts { display:flex; flex-direction:column; }
|
|
328
|
+
.artifact-layout { display:grid; grid-template-columns:minmax(190px,250px) minmax(0,1fr); gap:16px; align-items:start; }
|
|
329
|
+
.artifact-list { display:flex; flex-direction:column; gap:14px; max-height:72vh; overflow:auto; padding:2px 4px 2px 0; }
|
|
330
|
+
.artifact-group { display:flex; flex-direction:column; gap:4px; }
|
|
331
|
+
.artifact-group-label { margin:0 0 2px; font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--muted); }
|
|
332
|
+
.artifact-item { display:flex; flex-direction:column; gap:2px; text-align:left; cursor:pointer; font:inherit; color:var(--muted);
|
|
333
|
+
padding:8px 10px; background:transparent; border:1px solid transparent; border-radius:10px;
|
|
334
|
+
transition:background .15s ease, border-color .15s ease, color .15s ease; }
|
|
335
|
+
.artifact-item:hover { color:var(--fg); background:var(--surface-2); border-color:var(--border); }
|
|
336
|
+
.artifact-item.active { color:var(--fg); background:var(--accent-soft); border-color:var(--accent-line); }
|
|
337
|
+
.artifact-item-name { font-family:var(--font-mono); font-size:11.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
338
|
+
.artifact-item-kind { font-size:9.5px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--muted-2); }
|
|
339
|
+
.artifact-item.active .artifact-item-kind { color:var(--accent); }
|
|
340
|
+
.artifact-pane { min-width:0; }
|
|
341
|
+
.artifact-head { display:flex; align-items:flex-start; justify-content:space-between; gap:14px; margin-bottom:12px; }
|
|
342
|
+
.artifact-title { margin:0 0 5px; font-family:var(--font-mono); font-size:13px; font-weight:600; word-break:break-word; }
|
|
343
|
+
.artifact-desc { margin:0; max-width:78ch; color:var(--muted); font-size:12.5px; line-height:1.55; }
|
|
344
|
+
.artifact-kind-badge { flex:none; display:inline-flex; align-items:center; padding:3px 9px; font-size:10px; font-weight:700;
|
|
345
|
+
letter-spacing:.08em; text-transform:uppercase; color:var(--accent); background:var(--accent-soft);
|
|
346
|
+
border:1px solid var(--accent-line); border-radius:999px; }
|
|
347
|
+
.artifact-view { margin:0; padding:14px 16px; max-height:70vh; overflow:auto; font-size:12px; line-height:1.6; color:#cdc7bf;
|
|
348
|
+
background:#0b0b0e; border:1px solid var(--border); border-radius:var(--radius); }
|
|
349
|
+
.artifact-markdown { white-space:normal; }
|
|
350
|
+
.artifact-markdown .md-p, .artifact-markdown .md-list, .artifact-markdown .md-quote { font-size:12.5px; }
|
|
351
|
+
.artifact-markdown .md-p, .artifact-markdown .md-list { color:#d8d3cc; }
|
|
352
|
+
.artifact-markdown .md-code-block { border:1px solid var(--border); }
|
|
353
|
+
.json-view { white-space:pre; }
|
|
354
|
+
.json-key { color:var(--accent-strong); }
|
|
355
|
+
.json-string { color:#9ddc8a; }
|
|
356
|
+
.json-number { color:#8fc7ff; }
|
|
357
|
+
.json-literal { color:#d7a6ff; }
|
|
358
|
+
.json-punct { color:#8d8a85; }
|
|
359
|
+
@media (max-width:900px) { .artifact-layout { grid-template-columns:1fr; } .artifact-list { max-height:240px; } }
|
|
360
|
+
.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;
|
|
361
|
+
color:var(--success); background:rgba(69,192,122,.12); border:1px solid rgba(69,192,122,.32); border-radius:999px; text-decoration:none; }
|
|
362
|
+
.sent-tag:hover { color:#fff; }
|
|
363
|
+
.sent-tag.gh { color:#b9c7d6; background:rgba(139,164,191,.12); border-color:rgba(139,164,191,.35); }
|
|
364
|
+
.sent-tag.gh:hover { color:#fff; background:rgba(139,164,191,.2); }
|
|
365
|
+
.resume { display:flex; flex-direction:column; }
|
|
366
|
+
.resume-hero { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; padding:18px 20px;
|
|
367
|
+
background:linear-gradient(135deg, rgba(240,165,44,.08), rgba(255,255,255,.015) 55%);
|
|
368
|
+
border:1px solid var(--border); border-radius:var(--radius-lg); }
|
|
369
|
+
.resume-kicker { display:block; font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--accent); }
|
|
370
|
+
.resume-title { margin:5px 0 7px; font-size:22px; font-weight:700; letter-spacing:-.02em; word-break:break-word; }
|
|
371
|
+
.resume-meta { display:flex; flex-wrap:wrap; align-items:center; gap:12px; margin:0; font-size:12px; color:var(--muted); }
|
|
372
|
+
.resume-pr { display:inline-flex; align-items:center; gap:5px; color:var(--accent-strong); text-decoration:none; }
|
|
373
|
+
.resume-pr:hover { text-decoration:underline; }
|
|
374
|
+
.resume-hero-side { display:flex; flex:none; flex-direction:column; align-items:flex-end; gap:10px; }
|
|
375
|
+
.resume-status { display:inline-flex; align-items:center; padding:5px 12px; font-size:11px; font-weight:700; letter-spacing:.04em; border-radius:999px; }
|
|
376
|
+
.resume-status.blocking { color:var(--blocking); background:rgba(242,89,94,.12); border:1px solid rgba(242,89,94,.35); }
|
|
377
|
+
.resume-status.attention { color:var(--attention); background:rgba(237,161,58,.12); border:1px solid rgba(237,161,58,.35); }
|
|
378
|
+
.resume-status.clean { color:var(--success); background:rgba(69,192,122,.12); border:1px solid rgba(69,192,122,.35); }
|
|
379
|
+
.resume-kpis { margin:14px 0 18px; }
|
|
380
|
+
.resume-kpis .kpi-value { font-size:24px; }
|
|
381
|
+
.churn-add { color:var(--success); }
|
|
382
|
+
.churn-del { color:var(--blocking); }
|
|
383
|
+
.churn-sep { margin:0 3px; color:var(--muted-2); font-weight:400; }
|
|
384
|
+
.kpi-blocking .kpi-value { color:var(--blocking); }
|
|
385
|
+
.kpi-attention .kpi-value { color:var(--attention); }
|
|
386
|
+
.kpi-total .kpi-value { color:var(--fg); }
|
|
387
|
+
.resume-grid { display:grid; grid-template-columns:minmax(0,1.35fr) minmax(0,1fr); gap:16px; align-items:start; }
|
|
388
|
+
.resume-card { min-width:0; padding:16px 18px; background:var(--card); border:1px solid var(--border); border-radius:var(--radius-lg); }
|
|
389
|
+
.resume-card-head { display:flex; align-items:center; justify-content:space-between; gap:10px; }
|
|
390
|
+
.resume-card-title { margin:0 0 10px; font-size:12px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--muted); }
|
|
391
|
+
.resume-card-count { display:inline-flex; align-items:center; justify-content:center; min-width:22px; height:22px; margin-bottom:10px; padding:0 7px;
|
|
392
|
+
font-family:var(--font-mono); font-size:11px; color:var(--accent); background:var(--accent-soft); border:1px solid var(--accent-line); border-radius:999px; }
|
|
393
|
+
.resume-narrative .md-p:last-child { margin-bottom:0; }
|
|
394
|
+
.resume-domains { display:flex; flex-direction:column; gap:8px; margin:0; padding:0; list-style:none; }
|
|
395
|
+
.resume-domain { display:flex; flex-direction:column; gap:7px; padding:10px 12px; background:var(--surface-2);
|
|
396
|
+
border:1px solid var(--border); border-radius:10px; }
|
|
397
|
+
.resume-domain-head { display:flex; align-items:baseline; justify-content:space-between; gap:10px; }
|
|
398
|
+
.resume-domain-name { font-size:12.5px; font-weight:600; color:var(--fg); }
|
|
399
|
+
.resume-domain-files { flex:none; font-family:var(--font-mono); font-size:10.5px; color:var(--muted-2); }
|
|
400
|
+
.resume-domain-sev { display:flex; flex-wrap:wrap; gap:6px; }
|
|
401
|
+
.resume-domain-sev .sev-badge { padding:2px 7px; font-size:9.5px; }
|
|
402
|
+
.resume-domain-ok { font-size:11.5px; color:var(--success); }
|
|
403
|
+
@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; } }
|
|
404
|
+
.modal-layer { position:fixed; inset:0; z-index:50; display:grid; place-items:center; padding:24px; }
|
|
405
|
+
.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); }
|
|
406
|
+
.modal { position:relative; width:min(640px, 100%); max-height:min(78vh, 780px); display:flex; flex-direction:column; overflow:hidden;
|
|
407
|
+
background:var(--surface); border:1px solid var(--border-strong); border-radius:var(--radius-lg); box-shadow:0 24px 70px rgba(0,0,0,.5); }
|
|
408
|
+
.modal-head { display:flex; align-items:flex-start; justify-content:space-between; gap:14px; padding:18px 20px 14px; border-bottom:1px solid var(--border); }
|
|
409
|
+
.modal-title { margin:0; font-size:17px; font-weight:700; letter-spacing:-.01em; }
|
|
410
|
+
.modal-sub { margin:5px 0 0; font-size:12px; color:var(--muted); line-height:1.5; }
|
|
411
|
+
.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; }
|
|
412
|
+
.modal-close:hover { color:var(--fg); border-color:var(--border-strong); }
|
|
413
|
+
.modal-body { display:flex; flex-direction:column; gap:16px; padding:16px 20px; overflow-y:auto; }
|
|
414
|
+
.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); }
|
|
415
|
+
.submit-field { display:flex; flex-direction:column; gap:8px; }
|
|
416
|
+
.submit-label { font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--muted-2); }
|
|
417
|
+
.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); }
|
|
418
|
+
.submit-summary b { font-family:var(--font-mono); }
|
|
419
|
+
.submit-list { display:flex; flex-direction:column; gap:6px; max-height:240px; overflow-y:auto; padding-right:2px; }
|
|
420
|
+
.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; }
|
|
421
|
+
.submit-item.sent { opacity:.7; }
|
|
422
|
+
.submit-item .rule { flex:1; min-width:0; color:var(--muted); overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }
|
|
423
|
+
.submit-empty { color:var(--muted); font-size:12.5px; }
|
|
424
|
+
.submit-field .chips { flex-wrap:wrap; }
|
|
425
|
+
.submit-check { display:inline-flex; align-items:center; gap:8px; font-size:12.5px; color:var(--muted); cursor:pointer; }
|
|
426
|
+
.submit-check input { accent-color:var(--accent); }
|
|
427
|
+
.submit-actions { display:flex; align-items:center; gap:8px; }
|
|
428
|
+
.sev-badge.sev-blocking { color:var(--blocking); background:rgba(242,89,94,.12); }
|
|
429
|
+
.sev-badge.sev-attention { color:var(--attention); background:rgba(237,161,58,.12); }
|
|
430
|
+
.sev-badge.sev-suggestion { color:var(--suggestion); background:rgba(90,164,240,.12); }
|
|
431
|
+
.sev-badge.sev-nit { color:var(--nit); background:rgba(255,255,255,.06); }
|
|
432
|
+
@keyframes pulse { 0%,100% { opacity:1; transform:scale(1); } 50% { opacity:.4; transform:scale(.8); } }
|
|
433
|
+
@keyframes spin { to { transform:rotate(360deg); } }
|
|
434
|
+
@media (max-width:980px) { .kpis { grid-template-columns:repeat(2, minmax(0,1fr)); } }
|
|
435
|
+
@media (max-width:1100px) {
|
|
436
|
+
/* Painel largo reduz o espaço do conteúdo: compacta o topo para evitar scroll-x. */
|
|
437
|
+
.topbar { gap:12px; padding:12px 16px; }
|
|
438
|
+
.seg-btn { gap:6px; padding:7px 11px; font-size:12px; }
|
|
439
|
+
.auto-refresh { display:none; }
|
|
440
|
+
}
|
|
441
|
+
@media (max-width:720px) {
|
|
442
|
+
/* Em telas estreitas as abas viram só ícone (o título do botão explica cada uma). */
|
|
443
|
+
.seg-btn { padding:8px 10px; }
|
|
444
|
+
.seg-btn .seg-label { display:none; }
|
|
445
|
+
}
|
|
446
|
+
@media (max-width:820px) {
|
|
447
|
+
/* Mantém a rail fixa e encolhe o painel para preservar o conteúdo principal. */
|
|
448
|
+
.sidebar { width:calc(var(--rail-w) + clamp(220px, 35vw, var(--side-width))); }
|
|
449
|
+
.side-head { padding:14px 12px 10px; }
|
|
450
|
+
.side-body { padding:12px; }
|
|
451
|
+
.topbar { padding:12px 16px; }
|
|
452
|
+
.content { padding:18px 16px 56px; }
|
|
453
|
+
.auto-refresh { display:none; }
|
|
454
|
+
}
|
|
@@ -493,10 +493,14 @@ export type HoundsmanAst = {
|
|
|
493
493
|
/** Providers suportados pela camada de IA. */
|
|
494
494
|
export type AiProviderKind = "deepseek" | "openai" | "anthropic";
|
|
495
495
|
|
|
496
|
+
/** Opções do AI SDK por provider (repassadas sem interpretação). */
|
|
497
|
+
export type AiProviderOptions = Record<string, Record<string, unknown>>;
|
|
498
|
+
|
|
496
499
|
/** Features de IA do fluxo normal (todas fail-open). */
|
|
497
500
|
export type HoundsmanAiFeatures = {
|
|
498
501
|
context?: boolean;
|
|
499
502
|
brief?: boolean;
|
|
503
|
+
technical?: boolean;
|
|
500
504
|
classification?: boolean;
|
|
501
505
|
preAnalysis?: boolean;
|
|
502
506
|
verify?: boolean;
|
|
@@ -519,6 +523,7 @@ export type HoundsmanAi = {
|
|
|
519
523
|
maxSteps?: number;
|
|
520
524
|
maxOutputTokens?: number;
|
|
521
525
|
temperature?: number;
|
|
526
|
+
providerOptions?: AiProviderOptions;
|
|
522
527
|
timeoutMs?: number;
|
|
523
528
|
maxCallsPerReview?: number;
|
|
524
529
|
maxTokensPerReview?: number;
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import type { LanguageModel } from "ai";
|
|
6
7
|
import { z } from "zod";
|
|
7
8
|
import { AiClient, AiUsageBudget, AiUsageRecorder } from "../core/ai";
|
|
9
|
+
import { buildGenerateTextArgs } from "../core/ai/ai-sdk-model-provider";
|
|
10
|
+
import { temperatureApplies } from "../core/ai/provider-capabilities";
|
|
8
11
|
import { defineHoundsmanConfig } from "../core/config";
|
|
9
12
|
import { DEFAULT_AI_MODEL } from "../core/defaults";
|
|
10
13
|
import { ProjectConfig } from "../core/project-config";
|
|
@@ -82,6 +85,74 @@ describe("AiClient", () => {
|
|
|
82
85
|
});
|
|
83
86
|
});
|
|
84
87
|
|
|
88
|
+
describe("temperature e providerOptions", () => {
|
|
89
|
+
test("DeepSeek V4 ignora temperature; thinking disabled reabilita", () => {
|
|
90
|
+
expect(temperatureApplies("deepseek", "deepseek-v4-pro", undefined)).toBe(false);
|
|
91
|
+
expect(
|
|
92
|
+
temperatureApplies("deepseek", "deepseek-v4-pro", {
|
|
93
|
+
deepseek: { thinking: { type: "disabled" } },
|
|
94
|
+
}),
|
|
95
|
+
).toBe(true);
|
|
96
|
+
expect(
|
|
97
|
+
temperatureApplies("deepseek", "deepseek-v4-pro", {
|
|
98
|
+
deepseek: { thinking: { type: "enabled" } },
|
|
99
|
+
}),
|
|
100
|
+
).toBe(false);
|
|
101
|
+
expect(temperatureApplies("deepseek", "deepseek-chat", undefined)).toBe(true);
|
|
102
|
+
expect(temperatureApplies("openai", "gpt-4o", undefined)).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("adapter omite temperature ignorada e repassa providerOptions", () => {
|
|
106
|
+
const model = {} as LanguageModel;
|
|
107
|
+
const thinkingOff = buildGenerateTextArgs(
|
|
108
|
+
model,
|
|
109
|
+
{
|
|
110
|
+
prompt: "oi",
|
|
111
|
+
temperature: 0,
|
|
112
|
+
providerOptions: { deepseek: { thinking: { type: "disabled" } } },
|
|
113
|
+
},
|
|
114
|
+
"deepseek",
|
|
115
|
+
"deepseek-v4-pro",
|
|
116
|
+
);
|
|
117
|
+
expect(thinkingOff).toMatchObject({
|
|
118
|
+
temperature: 0,
|
|
119
|
+
providerOptions: { deepseek: { thinking: { type: "disabled" } } },
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const defaultThinking = buildGenerateTextArgs(
|
|
123
|
+
model,
|
|
124
|
+
{ prompt: "oi", temperature: 0 },
|
|
125
|
+
"deepseek",
|
|
126
|
+
"deepseek-v4-pro",
|
|
127
|
+
);
|
|
128
|
+
expect(defaultThinking).not.toHaveProperty("temperature");
|
|
129
|
+
|
|
130
|
+
const openai = buildGenerateTextArgs(
|
|
131
|
+
model,
|
|
132
|
+
{ prompt: "oi", temperature: 0 },
|
|
133
|
+
"openai",
|
|
134
|
+
"gpt-4o",
|
|
135
|
+
);
|
|
136
|
+
expect(openai).toMatchObject({ temperature: 0 });
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("AiClient injeta providerOptions da config", async () => {
|
|
140
|
+
const configured = new ProjectConfig({
|
|
141
|
+
...fakeHoundsmanConfig,
|
|
142
|
+
ai: { providerOptions: { deepseek: { thinking: { type: "disabled" } } } },
|
|
143
|
+
});
|
|
144
|
+
const provider = new FakeModelProvider(() => ({ ok: true }));
|
|
145
|
+
const client = new AiClient({ project: configured, provider });
|
|
146
|
+
await client.generateObject("x", {
|
|
147
|
+
prompt: "oi",
|
|
148
|
+
outputSchema: z.object({ ok: z.boolean() }),
|
|
149
|
+
});
|
|
150
|
+
expect(provider.requests[0]?.providerOptions).toEqual({
|
|
151
|
+
deepseek: { thinking: { type: "disabled" } },
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
85
156
|
describe("AiUsageBudget / AiUsageRecorder", () => {
|
|
86
157
|
test("acumula uso e bloqueia no teto", () => {
|
|
87
158
|
const budget = new AiUsageBudget({ maxCalls: 2 });
|
|
@@ -8,8 +8,11 @@ import { AiBriefEnricher } from "../core/ai/features/brief-enricher";
|
|
|
8
8
|
import { AiClassifier } from "../core/ai/features/classifier";
|
|
9
9
|
import { AiContextSummarizer } from "../core/ai/features/context-summarizer";
|
|
10
10
|
import { AiPreAnalyzer } from "../core/ai/features/pre-analyzer";
|
|
11
|
+
import { AiTechnicalSummarizer } from "../core/ai/features/technical-summarizer";
|
|
11
12
|
import { AiVerifier } from "../core/ai/features/verifier";
|
|
12
13
|
import { briefInstructions, briefPrompt } from "../core/ai/prompts/brief";
|
|
14
|
+
import { contextInstructions } from "../core/ai/prompts/context";
|
|
15
|
+
import { technicalInstructions } from "../core/ai/prompts/technical";
|
|
13
16
|
import { renderBrief } from "../core/brief";
|
|
14
17
|
import { ProjectConfig } from "../core/project-config";
|
|
15
18
|
import { FakeModelProvider } from "./fixtures/fake-model-provider";
|
|
@@ -153,6 +156,41 @@ describe("AiClassifier", () => {
|
|
|
153
156
|
});
|
|
154
157
|
});
|
|
155
158
|
|
|
159
|
+
describe("AiTechnicalSummarizer", () => {
|
|
160
|
+
test("devolve os destaques estruturais do modelo", async () => {
|
|
161
|
+
const provider = new FakeModelProvider(() => ({
|
|
162
|
+
overview: "Novo módulo bin-lookup",
|
|
163
|
+
highlights: [{ area: "Contratos", detail: "`BinLookupProvider`" }],
|
|
164
|
+
}));
|
|
165
|
+
const summarizer = new AiTechnicalSummarizer(new AiClient({ project, provider }));
|
|
166
|
+
const summary = await summarizer.summarize({
|
|
167
|
+
branch: "main",
|
|
168
|
+
classification: {
|
|
169
|
+
files: { "src/bin-lookup/service.ts": { status: "added" } },
|
|
170
|
+
churn: { "src/bin-lookup/service.ts": { additions: 3, deletions: 0 } },
|
|
171
|
+
},
|
|
172
|
+
headSha: "abc",
|
|
173
|
+
domains: ["Alpha"],
|
|
174
|
+
});
|
|
175
|
+
expect(summary?.source).toBe("ai");
|
|
176
|
+
expect(summary?.headSha).toBe("abc");
|
|
177
|
+
expect(summary?.highlights[0].area).toBe("Contratos");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("sem IA o resumo é nulo (fallback no chamador)", async () => {
|
|
181
|
+
const summarizer = new AiTechnicalSummarizer(
|
|
182
|
+
new AiClient({ project, provider: null }),
|
|
183
|
+
);
|
|
184
|
+
expect(summarizer.enabled).toBe(false);
|
|
185
|
+
expect(
|
|
186
|
+
await summarizer.summarize({
|
|
187
|
+
branch: "main",
|
|
188
|
+
classification: { files: {}, churn: {} },
|
|
189
|
+
}),
|
|
190
|
+
).toBeNull();
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
156
194
|
describe("AiVerifier", () => {
|
|
157
195
|
test("devolve confiança apenas para ids conhecidos", async () => {
|
|
158
196
|
const provider = new FakeModelProvider(() => ({
|
|
@@ -181,3 +219,12 @@ describe("AiPreAnalyzer", () => {
|
|
|
181
219
|
).toEqual([]);
|
|
182
220
|
});
|
|
183
221
|
});
|
|
222
|
+
|
|
223
|
+
describe("idioma dos resumos", () => {
|
|
224
|
+
test("prompts de contexto e pontos técnicos exigem pt-BR", () => {
|
|
225
|
+
for (const instructions of [contextInstructions(), technicalInstructions()]) {
|
|
226
|
+
expect(instructions).toContain("português do Brasil");
|
|
227
|
+
expect(instructions).toContain("pt-BR");
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
});
|