total-recall-brain 3.41.2 → 3.41.3

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 (69) hide show
  1. package/bin/total-recall.mjs +9 -3
  2. package/frontend/dist/assets/{index-BG9NkVUy.css → index-BIQmqDKP.css} +1 -1
  3. package/frontend/dist/assets/{index-BtuiZqwP.js → index-DFenQQEQ.js} +74 -79
  4. package/frontend/dist/index.html +2 -2
  5. package/frontend/dist/sw.js +8 -5
  6. package/frontend/src/api/auth.ts +0 -3
  7. package/frontend/src/components/plugins/PluginPanel.tsx +7 -3
  8. package/frontend/src/components/plugins/card/CardEngine.tsx +1 -1
  9. package/frontend/src/pages/LoginPage.tsx +1 -41
  10. package/frontend/src/pages/PluginsPage.tsx +14 -7
  11. package/package.json +2 -2
  12. package/scaffold/.agent/skills/plugin/scripts/record.mjs +6 -3
  13. package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +197 -31
  14. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +4 -4
  15. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
  16. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +8 -1
  17. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +56 -8
  18. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +1068 -204
  19. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/panel.js +4 -2
  20. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +32 -0
  21. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +436 -274
  22. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +4 -4
  23. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
  24. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +16 -2
  25. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +461 -242
  26. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +4 -4
  27. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  28. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -7
  29. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +17 -2
  30. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +426 -300
  31. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +4 -4
  32. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +32 -0
  33. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +350 -283
  34. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +4 -4
  35. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +31 -19
  36. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +4 -4
  37. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +4 -4
  38. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +16 -2
  39. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +380 -298
  40. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -1
  41. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +6 -6
  42. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
  43. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  44. package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +17 -3
  45. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +38 -15
  46. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +60 -34
  47. package/src/cli/command.mjs +23 -18
  48. package/src/cli/daemon.mjs +23 -1
  49. package/src/cli/plugin/copies.mjs +8 -5
  50. package/src/cli/plugin/index.mjs +25 -2
  51. package/src/cli/rebuild.mjs +22 -0
  52. package/src/cli/reset-password.mjs +3 -2
  53. package/src/cli/skill.mjs +8 -2
  54. package/src/cli/status.mjs +25 -7
  55. package/src/core/automatic-brain-updates.mjs +9 -3
  56. package/src/core/brain-updates.mjs +63 -5
  57. package/src/core/global-brain-updates.mjs +21 -1
  58. package/src/core/mesh-brain-updates.mjs +5 -2
  59. package/src/core/meta-harness.mjs +16 -41
  60. package/src/core/plugin-runner.mjs +6 -1
  61. package/src/core/plugin-store.mjs +67 -1
  62. package/src/core/plugin-updates.mjs +1 -1
  63. package/src/core/portable-skills-maintenance.mjs +19 -0
  64. package/src/core/required-plugins.mjs +7 -0
  65. package/src/core/skills-registry.mjs +33 -3
  66. package/src/server/auth.mjs +41 -1
  67. package/src/server/memory-app.mjs +2 -1
  68. package/src/server/routes/auth.mjs +25 -1
  69. package/src/server/routes/plugins.mjs +17 -4
@@ -1,365 +1,584 @@
1
1
  /**
2
- * Code Quality Gates — Deterministic Code Verification & Fail-Closed Gates
3
- * Purpose-built domain interface: queries latest gate report on mount, displays real
4
- * deterministic gate findings and stale file tracking, and exposes authentic execution verbs.
2
+ * Nitpicker — Fail-Closed Code Quality Gate Console
5
3
  *
6
- * Zero emojis, authentic commercial brand identity, touch-first ergonomics (>=44px).
4
+ * Bespoke operator interface for deterministic repository verification.
5
+ * Displays real-time gate matrix, structured code violations, tier controllers,
6
+ * and worktree drift indicators with zero template wrappers or terminal dumps.
7
7
  */
8
8
 
9
+ const DASH = '\u2014';
10
+
11
+ function escapeHtml(str) {
12
+ if (str === null || str === undefined) return '';
13
+ return String(str).replace(/[&<>"']/g, c => ({
14
+ '&': '&amp;',
15
+ '<': '&lt;',
16
+ '>': '&gt;',
17
+ '"': '&quot;',
18
+ "'": '&#39;'
19
+ }[c]));
20
+ }
21
+
9
22
  export class CodeQualityCard extends HTMLElement {
10
23
  constructor() {
11
24
  super();
25
+ this.report = null;
12
26
  this.executing = false;
13
- this.telemetry = {
14
- findings: '0 Findings',
15
- tier: 'Fast',
16
- status: 'Clean'
17
- };
27
+ this.activeFilter = 'all';
28
+ this.selectedTier = 'fast';
29
+ this.screens = [{ id: 'overview', label: 'Gate Matrix' }];
30
+ this.screen = 'overview';
18
31
  }
19
32
 
20
33
  connectedCallback() {
21
- if (!this.shadowRoot) this.attachShadow({ mode: 'open' });
34
+ if (!this.shadowRoot) {
35
+ this.attachShadow({ mode: 'open' });
36
+ }
22
37
  this.render();
23
- this.refreshStatus();
38
+ this.refreshReport();
24
39
  }
25
40
 
26
- async refreshStatus() {
41
+ async refreshReport() {
42
+ if (!this.host) return;
27
43
  try {
28
- const res = await this.host?.run('report', []);
29
- let text = '';
30
- if (typeof res === 'object') {
31
- text = res.output || JSON.stringify(res, null, 2);
32
- } else {
33
- text = String(res || '');
44
+ const res = await this.host.run('report', ['--json']);
45
+ if (res && typeof res === 'object') {
46
+ this.report = res;
47
+ } else if (typeof res === 'string' && res.trim().startsWith('{')) {
48
+ this.report = JSON.parse(res);
34
49
  }
35
-
36
- const isClean = text.includes('0 finding') || text.includes('No findings');
37
- const isStale = text.includes('STALE');
38
-
39
- this.telemetry = {
40
- findings: isClean ? '0 Findings' : 'Findings Detected',
41
- tier: text.includes('tier=full') ? 'Full Tier' : 'Fast Tier',
42
- status: isClean ? (isStale ? 'Stale' : 'Pass') : 'Needs Review'
43
- };
44
- this.updateView();
50
+ this.render();
45
51
  } catch {
46
- this.telemetry = {
47
- findings: '0 Findings',
48
- tier: 'Gate Deck',
49
- status: 'Ready'
50
- };
51
- this.updateView();
52
+ this.report = null;
53
+ this.render();
52
54
  }
53
55
  }
54
56
 
55
- updateView() {
56
- const root = this.shadowRoot;
57
- if (!root) return;
58
-
59
- const findingsEl = root.querySelector('#stat-findings');
60
- const tierEl = root.querySelector('#stat-tier');
61
- const statusEl = root.querySelector('#stat-status');
62
- if (findingsEl) findingsEl.textContent = this.telemetry.findings;
63
- if (tierEl) tierEl.textContent = this.telemetry.tier;
64
- if (statusEl) statusEl.textContent = this.telemetry.status;
65
- }
66
-
67
- async execute(action, args = []) {
68
- const out = this.shadowRoot?.querySelector('#card-out');
69
- const actionLabel = action.toUpperCase();
70
- if (out) out.textContent = `[CODE QUALITY] Running ${actionLabel}...`;
57
+ async runTier(tier) {
58
+ if (!this.host || this.executing) return;
71
59
  this.executing = true;
60
+ this.selectedTier = tier;
61
+ this.render();
72
62
 
73
63
  try {
74
- const res = await this.host?.run(action, args);
75
- let text = '';
76
- if (typeof res === 'object') {
77
- text = res.output || JSON.stringify(res, null, 2);
78
- } else {
79
- text = String(res || 'Execution completed without output.');
80
- }
81
- if (out) out.textContent = text.trim();
82
- if (['gate', 'snapshot', 'report'].includes(action)) {
83
- this.refreshStatus();
84
- }
85
- } catch (err) {
86
- if (out) out.textContent = `[QUALITY FAILURE] ${err.message}`;
64
+ await this.host.run('check', ['--tier', tier, '--json']);
65
+ await this.refreshReport();
66
+ } catch {
67
+ await this.refreshReport();
87
68
  } finally {
88
69
  this.executing = false;
70
+ this.render();
89
71
  }
90
72
  }
91
73
 
74
+ setFilter(filter) {
75
+ this.activeFilter = filter;
76
+ this.render();
77
+ }
78
+
92
79
  render() {
93
80
  const root = this.shadowRoot;
94
81
  if (!root) return;
95
82
 
83
+ const rep = this.report;
84
+ const isClean = rep?.clean === true;
85
+ const checks = Array.isArray(rep?.checks) ? rep.checks : [];
86
+ const timestamp = rep?.timestamp ? new Date(rep.timestamp).toLocaleTimeString() : DASH;
87
+ const tierName = rep?.tier || this.selectedTier;
88
+
89
+ // Aggregate findings
90
+ const allFindings = [];
91
+ for (const c of checks) {
92
+ if (Array.isArray(c.findings)) {
93
+ for (const f of c.findings) {
94
+ allFindings.push({ ...f, checkName: c.name || c.id });
95
+ }
96
+ }
97
+ }
98
+
99
+ const errorCount = allFindings.filter(f => f.severity === 'error').length;
100
+ const warnCount = allFindings.filter(f => f.severity === 'warning').length;
101
+
102
+ const filteredFindings = allFindings.filter(f => {
103
+ if (this.activeFilter === 'errors') return f.severity === 'error';
104
+ if (this.activeFilter === 'warnings') return f.severity === 'warning';
105
+ return true;
106
+ });
107
+
96
108
  root.innerHTML = `
97
109
  <style>
98
110
  :host {
111
+ --face-bg: #0f172a;
112
+ --face-surface: #1e293b;
113
+ --face-line: #334155;
114
+ --face-text: #f8fafc;
115
+ --face-muted: #94a3b8;
116
+ --face-brand: #eab308;
117
+ --face-brand-hover: #facc15;
118
+ --face-accent: #38bdf8;
119
+ --face-ok: #10b981;
120
+ --face-danger: #ef4444;
121
+ --face-warn: #f59e0b;
122
+ --face-dark: #0f172a;
123
+
99
124
  display: block;
100
- font-family: 'Space Grotesk', -apple-system, sans-serif;
101
- background: #03171e;
102
- color: #ecfeff;
103
- border-radius: 12px;
104
- border: 1px solid #0e4a5e;
105
- padding: 14px;
125
+ font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
126
+ color: var(--text-primary, var(--face-text));
127
+ background: var(--bg-secondary, var(--face-bg));
128
+ border: 1px solid var(--border, var(--face-line));
129
+ border-radius: 8px;
130
+ padding: 16px;
106
131
  box-sizing: border-box;
107
- user-select: none;
132
+ overflow: hidden;
108
133
  }
109
134
 
110
- * { box-sizing: border-box; }
135
+ @media (prefers-reduced-motion: reduce) {
136
+ *, ::before, ::after {
137
+ animation-duration: 0.01ms !important;
138
+ animation-iteration-count: 1 !important;
139
+ transition-duration: 0.01ms !important;
140
+ }
141
+ }
111
142
 
112
- /* Telemetry Metric Cards */
113
- .telemetry-row {
114
- display: grid;
115
- grid-template-columns: repeat(3, 1fr);
116
- gap: 8px;
117
- margin-bottom: 12px;
143
+ /* Header Bar */
144
+ .header {
145
+ display: flex;
146
+ align-items: center;
147
+ justify-content: space-between;
148
+ flex-wrap: wrap;
149
+ gap: 12px;
150
+ padding-bottom: 14px;
151
+ border-bottom: 1px solid var(--border, var(--face-line));
152
+ margin-bottom: 16px;
118
153
  }
119
154
 
120
- .metric-card {
121
- background: rgba(8, 47, 61, 0.6);
122
- border: 1px solid rgba(6, 182, 212, 0.25);
123
- border-radius: 8px;
124
- padding: 8px 10px;
155
+ .brand-group {
125
156
  display: flex;
126
- flex-direction: column;
127
- gap: 2px;
157
+ align-items: center;
158
+ gap: 10px;
128
159
  }
129
160
 
130
- .metric-label {
131
- font-family: 'JetBrains Mono', monospace;
132
- font-size: 9px;
133
- font-weight: 700;
134
- color: #67e8f9;
161
+ .wordmark-badge {
162
+ font-family: var(--font-mono, 'JetBrains Mono', monospace);
163
+ font-size: 13px;
164
+ font-weight: 800;
135
165
  letter-spacing: 0.05em;
136
- text-transform: uppercase;
166
+ color: var(--card-accent, var(--face-accent));
167
+ background: rgba(56, 189, 248, 0.12);
168
+ padding: 6px 10px;
169
+ border-radius: 6px;
170
+ border: 1px solid rgba(56, 189, 248, 0.3);
137
171
  }
138
172
 
139
- .metric-value {
140
- font-size: 13px;
173
+ .title-meta {
174
+ display: flex;
175
+ flex-direction: column;
176
+ }
177
+
178
+ .plugin-title {
179
+ font-size: 14px;
141
180
  font-weight: 700;
142
- color: #ffffff;
143
- font-family: 'JetBrains Mono', monospace;
144
- overflow: hidden;
145
- text-overflow: ellipsis;
146
- white-space: nowrap;
181
+ color: var(--text-primary, var(--face-text));
182
+ margin: 0;
147
183
  }
148
184
 
149
- /* Gate Status Deck */
150
- .gate-deck {
151
- background: rgba(0, 0, 0, 0.35);
152
- border: 1px solid rgba(6, 182, 212, 0.2);
153
- border-radius: 8px;
154
- padding: 8px 10px;
155
- margin-bottom: 10px;
185
+ .plugin-subtitle {
186
+ font-size: 11px;
187
+ color: var(--text-secondary, var(--face-muted));
188
+ margin: 0;
156
189
  }
157
190
 
158
- .gate-header {
159
- font-family: 'JetBrains Mono', monospace;
160
- font-size: 9px;
161
- color: #67e8f9;
191
+ .verdict-chip {
192
+ display: flex;
193
+ align-items: center;
194
+ gap: 6px;
195
+ font-family: var(--font-mono, 'JetBrains Mono', monospace);
196
+ font-size: 11px;
197
+ font-weight: 700;
198
+ padding: 5px 12px;
199
+ border-radius: 6px;
162
200
  text-transform: uppercase;
163
- letter-spacing: 0.05em;
164
- margin-bottom: 6px;
165
201
  }
166
202
 
167
- .gate-chips {
203
+ .verdict-clean {
204
+ color: var(--face-ok);
205
+ background: rgba(16, 185, 129, 0.15);
206
+ border: 1px solid rgba(16, 185, 129, 0.4);
207
+ }
208
+
209
+ .verdict-failed {
210
+ color: var(--face-danger);
211
+ background: rgba(239, 68, 68, 0.15);
212
+ border: 1px solid rgba(239, 68, 68, 0.4);
213
+ }
214
+
215
+ .verdict-pending {
216
+ color: var(--text-secondary, var(--face-muted));
217
+ background: rgba(148, 163, 184, 0.1);
218
+ border: 1px solid var(--border, var(--face-line));
219
+ }
220
+
221
+ /* Tier & Execution Bar */
222
+ .controls-deck {
168
223
  display: flex;
224
+ align-items: center;
225
+ justify-content: space-between;
169
226
  flex-wrap: wrap;
170
- gap: 6px;
227
+ gap: 10px;
228
+ margin-bottom: 16px;
229
+ background: rgba(30, 41, 59, 0.5);
230
+ border: 1px solid var(--border, var(--face-line));
231
+ border-radius: 6px;
232
+ padding: 8px 12px;
171
233
  }
172
234
 
173
- .gate-chip {
235
+ .tier-toggles {
174
236
  display: flex;
175
237
  align-items: center;
176
238
  gap: 6px;
177
- padding: 3px 8px;
178
- border-radius: 6px;
239
+ }
240
+
241
+ .btn-tier {
242
+ min-height: 44px;
243
+ min-width: 44px;
244
+ padding: 8px 14px;
245
+ font-family: var(--font-mono, 'JetBrains Mono', monospace);
179
246
  font-size: 11px;
180
- font-family: 'JetBrains Mono', monospace;
181
- background: rgba(6, 182, 212, 0.12);
182
- border: 1px solid rgba(6, 182, 212, 0.3);
183
- color: #cffafe;
247
+ font-weight: 700;
248
+ color: var(--text-secondary, var(--face-muted));
249
+ background: transparent;
250
+ border: 1px solid var(--border, var(--face-line));
251
+ border-radius: 5px;
252
+ cursor: pointer;
253
+ transition: all 0.2s ease;
184
254
  }
185
255
 
186
- .gate-dot {
187
- width: 6px;
188
- height: 6px;
189
- border-radius: 50%;
190
- background: #06b6d4;
191
- box-shadow: 0 0 6px rgba(6, 182, 212, 0.8);
256
+ .btn-tier:hover {
257
+ color: var(--text-primary, var(--face-text));
258
+ border-color: var(--card-accent, var(--face-accent));
192
259
  }
193
260
 
194
- /* Action Buttons Grid (>=44px touch targets) */
195
- .actions-grid {
196
- display: grid;
197
- grid-template-columns: repeat(3, 1fr);
198
- gap: 6px;
199
- margin-bottom: 10px;
261
+ .btn-tier[data-active="true"] {
262
+ color: var(--face-dark);
263
+ background: var(--card-accent, var(--face-accent));
264
+ border-color: var(--card-accent, var(--face-accent));
265
+ }
266
+
267
+ .action-group {
268
+ display: flex;
269
+ align-items: center;
270
+ gap: 8px;
200
271
  }
201
272
 
202
- .btn-action {
273
+ .btn-run {
203
274
  min-height: 44px;
204
- min-width: 44px;
205
- background: rgba(255, 255, 255, 0.04);
206
- border: 1px solid rgba(6, 182, 212, 0.25);
207
- border-radius: 8px;
208
- color: #ecfeff;
209
- font-family: 'Space Grotesk', sans-serif;
210
- font-size: 11px;
275
+ padding: 8px 16px;
276
+ font-family: var(--font-sans, sans-serif);
277
+ font-size: 12px;
211
278
  font-weight: 700;
212
- letter-spacing: 0.02em;
279
+ color: var(--face-dark);
280
+ background: var(--face-brand);
281
+ border: none;
282
+ border-radius: 5px;
213
283
  cursor: pointer;
214
- touch-action: manipulation;
215
284
  display: flex;
216
285
  align-items: center;
217
- justify-content: center;
218
- text-align: center;
219
- padding: 6px;
220
- transition: all 0.15s ease;
286
+ gap: 6px;
287
+ transition: background 0.2s ease;
221
288
  }
222
289
 
223
- .btn-action:hover {
224
- background: #0e7490;
225
- border-color: #06b6d4;
226
- color: #ffffff;
290
+ .btn-run:hover {
291
+ background: var(--face-brand-hover);
227
292
  }
228
293
 
229
- .btn-action:active {
230
- transform: scale(0.98);
294
+ .btn-run:disabled {
295
+ opacity: 0.6;
296
+ cursor: not-allowed;
231
297
  }
232
298
 
233
- .btn-action.btn-primary {
234
- background: rgba(6, 182, 212, 0.2);
235
- border-color: #06b6d4;
236
- color: #22d3ee;
299
+ /* Gate Matrix Grid */
300
+ .section-title {
301
+ font-size: 12px;
302
+ font-weight: 700;
303
+ letter-spacing: 0.05em;
304
+ text-transform: uppercase;
305
+ color: var(--text-secondary, var(--face-muted));
306
+ margin: 0 0 10px 0;
237
307
  }
238
308
 
239
- .btn-action.btn-primary:hover {
240
- background: #06b6d4;
241
- color: #082f49;
309
+ .gate-grid {
310
+ display: grid;
311
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
312
+ gap: 10px;
313
+ margin-bottom: 18px;
242
314
  }
243
315
 
244
- /* Terminal Console */
245
- .terminal-container {
246
- background: #010b0e;
247
- border: 1px solid rgba(6, 182, 212, 0.2);
248
- border-radius: 8px;
249
- padding: 8px 10px;
316
+ .gate-card {
317
+ background: rgba(30, 41, 59, 0.4);
318
+ border: 1px solid var(--border, var(--face-line));
319
+ border-radius: 6px;
320
+ padding: 10px;
321
+ display: flex;
322
+ flex-direction: column;
323
+ gap: 6px;
250
324
  }
251
325
 
252
- .terminal-header {
326
+ .gate-head {
253
327
  display: flex;
254
- justify-content: space-between;
255
328
  align-items: center;
256
- margin-bottom: 4px;
257
- padding-bottom: 4px;
258
- border-bottom: 1px solid rgba(6, 182, 212, 0.1);
329
+ justify-content: space-between;
330
+ }
331
+
332
+ .gate-name {
333
+ font-family: var(--font-mono, 'JetBrains Mono', monospace);
334
+ font-size: 11px;
335
+ font-weight: 700;
336
+ color: var(--text-primary, var(--face-text));
259
337
  }
260
338
 
261
- .terminal-label {
262
- font-family: 'JetBrains Mono', monospace;
339
+ .gate-badge {
263
340
  font-size: 9px;
264
- color: #67e8f9;
341
+ font-weight: 700;
342
+ padding: 2px 6px;
343
+ border-radius: 4px;
265
344
  text-transform: uppercase;
266
- letter-spacing: 0.05em;
267
345
  }
268
346
 
269
- .terminal-copy {
270
- background: none;
271
- border: none;
272
- color: #22d3ee;
273
- font-family: 'JetBrains Mono', monospace;
347
+ .badge-passed {
348
+ color: var(--face-ok);
349
+ background: rgba(16, 185, 129, 0.15);
350
+ }
351
+
352
+ .badge-failed {
353
+ color: var(--face-danger);
354
+ background: rgba(239, 68, 68, 0.15);
355
+ }
356
+
357
+ .badge-skipped {
358
+ color: var(--text-secondary, var(--face-muted));
359
+ background: rgba(148, 163, 184, 0.1);
360
+ }
361
+
362
+ .gate-meta {
363
+ display: flex;
364
+ justify-content: space-between;
274
365
  font-size: 10px;
366
+ color: var(--text-secondary, var(--face-muted));
367
+ }
368
+
369
+ /* Violation Browser */
370
+ .filter-tabs {
371
+ display: flex;
372
+ align-items: center;
373
+ gap: 8px;
374
+ margin-bottom: 10px;
375
+ }
376
+
377
+ .filter-btn {
378
+ min-height: 44px;
379
+ padding: 6px 12px;
380
+ font-size: 11px;
381
+ font-weight: 600;
382
+ color: var(--text-secondary, var(--face-muted));
383
+ background: transparent;
384
+ border: 1px solid var(--border, var(--face-line));
385
+ border-radius: 5px;
275
386
  cursor: pointer;
276
- padding: 2px 4px;
387
+ }
388
+
389
+ .filter-btn[data-active="true"] {
390
+ color: var(--text-primary, var(--face-text));
391
+ background: rgba(56, 189, 248, 0.15);
392
+ border-color: var(--card-accent, var(--face-accent));
393
+ }
394
+
395
+ .violations-box {
396
+ border: 1px solid var(--border, var(--face-line));
397
+ border-radius: 6px;
398
+ background: rgba(15, 23, 42, 0.6);
399
+ max-height: 240px;
400
+ overflow-y: auto;
401
+ }
402
+
403
+ .violation-row {
404
+ padding: 8px 12px;
405
+ border-bottom: 1px solid rgba(51, 65, 85, 0.5);
406
+ display: flex;
407
+ flex-direction: column;
408
+ gap: 4px;
409
+ }
410
+
411
+ .violation-row:last-child {
412
+ border-bottom: none;
413
+ }
414
+
415
+ .violation-header {
416
+ display: flex;
417
+ align-items: center;
418
+ gap: 8px;
419
+ font-size: 11px;
420
+ }
421
+
422
+ .severity-tag {
423
+ font-family: var(--font-mono, 'JetBrains Mono', monospace);
424
+ font-size: 9px;
425
+ font-weight: 700;
426
+ padding: 2px 6px;
277
427
  border-radius: 4px;
428
+ text-transform: uppercase;
429
+ }
430
+
431
+ .severity-error {
432
+ color: var(--face-danger);
433
+ background: rgba(239, 68, 68, 0.18);
434
+ }
435
+
436
+ .severity-warning {
437
+ color: var(--face-warn);
438
+ background: rgba(245, 158, 11, 0.18);
278
439
  }
279
440
 
280
- .terminal-copy:hover {
281
- background: rgba(6, 182, 212, 0.2);
441
+ .violation-loc {
442
+ font-family: var(--font-mono, 'JetBrains Mono', monospace);
443
+ color: var(--card-accent, var(--face-accent));
282
444
  }
283
445
 
284
- .terminal-readout {
285
- font-family: 'JetBrains Mono', monospace;
446
+ .violation-msg {
286
447
  font-size: 11px;
448
+ color: var(--text-primary, var(--face-text));
287
449
  line-height: 1.4;
288
- color: #22d3ee;
289
- min-height: 48px;
290
- max-height: 120px;
291
- overflow-y: auto;
292
- white-space: pre-wrap;
293
- word-break: break-all;
450
+ }
451
+
452
+ .empty-violations {
453
+ padding: 24px;
454
+ text-align: center;
455
+ font-size: 12px;
456
+ color: var(--face-ok);
457
+ }
458
+
459
+ /* Footer Telemetry */
460
+ .footer-bar {
461
+ display: flex;
462
+ justify-content: space-between;
463
+ align-items: center;
464
+ padding-top: 12px;
465
+ margin-top: 14px;
466
+ border-top: 1px solid var(--border, var(--face-line));
467
+ font-size: 10px;
468
+ color: var(--text-secondary, var(--face-muted));
294
469
  }
295
470
  </style>
296
471
 
297
- <!-- Live Telemetry -->
298
- <div class="telemetry-row">
299
- <div class="metric-card">
300
- <span class="metric-label">Gate Findings</span>
301
- <span class="metric-value" id="stat-findings">${this.telemetry.findings}</span>
302
- </div>
303
- <div class="metric-card">
304
- <span class="metric-label">Execution Tier</span>
305
- <span class="metric-value" id="stat-tier">${this.telemetry.tier}</span>
472
+ <div class="header">
473
+ <div class="brand-group">
474
+ <div class="wordmark-badge">[NIT//PICKER]</div>
475
+ <div class="title-meta">
476
+ <h3 class="plugin-title">Code Quality Gates</h3>
477
+ <p class="plugin-subtitle">Fail-closed deterministic repository verification</p>
478
+ </div>
306
479
  </div>
307
- <div class="metric-card">
308
- <span class="metric-label">Gate State</span>
309
- <span class="metric-value" id="stat-status">${this.telemetry.status}</span>
480
+ <div class="verdict-chip ${rep ? (isClean ? 'verdict-clean' : 'verdict-failed') : 'verdict-pending'}">
481
+ ${this.executing ? 'Checking...' : rep ? (isClean ? 'Clean · Pass' : `${errorCount} Errors · Failed`) : 'Pending Report'}
310
482
  </div>
311
483
  </div>
312
484
 
313
- <!-- Configured Gate Deck -->
314
- <div class="gate-deck">
315
- <div class="gate-header">Deterministic Verification Gates</div>
316
- <div class="gate-chips">
317
- <div class="gate-chip"><span class="gate-dot"></span>Syntax &amp; Types</div>
318
- <div class="gate-chip"><span class="gate-dot"></span>Git Hygiene</div>
319
- <div class="gate-chip"><span class="gate-dot"></span>Security Deps</div>
320
- <div class="gate-chip"><span class="gate-dot"></span>SSSS Registry</div>
485
+ <div class="controls-deck">
486
+ <div class="tier-toggles">
487
+ <button class="btn-tier" data-active="${tierName === 'fast'}" type="button" id="tier-fast">Fast</button>
488
+ <button class="btn-tier" data-active="${tierName === 'full'}" type="button" id="tier-full">Full</button>
489
+ <button class="btn-tier" data-active="${tierName === 'remote'}" type="button" id="tier-remote">Remote</button>
490
+ </div>
491
+ <div class="action-group">
492
+ <button class="btn-run" type="button" id="btn-run" ${this.executing ? 'disabled' : ''}>
493
+ ${this.executing ? 'Verifying...' : `Run ${tierName.toUpperCase()} Gate`}
494
+ </button>
321
495
  </div>
322
496
  </div>
323
497
 
324
- <!-- Action Buttons Grid (>=44px touch targets) -->
325
- <div class="actions-grid">
326
- <button class="btn-action btn-primary" data-cmd="report">REPORT</button>
327
- <button class="btn-action" data-cmd="snapshot">SNAPSHOT</button>
328
- <button class="btn-action" data-cmd="gate">RUN GATES</button>
329
- <button class="btn-action" data-cmd="detect">DETECT GATES</button>
330
- <button class="btn-action" data-cmd="config">CONFIG</button>
331
- <button class="btn-action" data-cmd="init">INIT</button>
498
+ <div class="section-title">Configured Gate Matrix</div>
499
+ <div class="gate-grid">
500
+ ${checks.length > 0 ? checks.map(c => `
501
+ <div class="gate-card">
502
+ <div class="gate-head">
503
+ <span class="gate-name">${escapeHtml(c.name || c.id)}</span>
504
+ <span class="gate-badge badge-${escapeHtml(c.status || 'passed')}">${escapeHtml(c.status || 'passed')}</span>
505
+ </div>
506
+ <div class="gate-meta">
507
+ <span>Exit ${c.exit_code !== undefined ? c.exit_code : 0}</span>
508
+ <span>${c.duration_ms ? `${c.duration_ms}ms` : DASH}</span>
509
+ </div>
510
+ </div>
511
+ `).join('') : `
512
+ <div class="gate-card">
513
+ <div class="gate-head">
514
+ <span class="gate-name">Syntax & Types</span>
515
+ <span class="gate-badge badge-skipped">Pending</span>
516
+ </div>
517
+ <div class="gate-meta"><span>Exit ${DASH}</span><span>${DASH}</span></div>
518
+ </div>
519
+ <div class="gate-card">
520
+ <div class="gate-head">
521
+ <span class="gate-name">Unit Tests</span>
522
+ <span class="gate-badge badge-skipped">Pending</span>
523
+ </div>
524
+ <div class="gate-meta"><span>Exit ${DASH}</span><span>${DASH}</span></div>
525
+ </div>
526
+ <div class="gate-card">
527
+ <div class="gate-head">
528
+ <span class="gate-name">Lint & Imports</span>
529
+ <span class="gate-badge badge-skipped">Pending</span>
530
+ </div>
531
+ <div class="gate-meta"><span>Exit ${DASH}</span><span>${DASH}</span></div>
532
+ </div>
533
+ `}
332
534
  </div>
333
535
 
334
- <!-- Live Terminal Output -->
335
- <div class="terminal-container">
336
- <div class="terminal-header">
337
- <span class="terminal-label">Verification Console</span>
338
- <button class="terminal-copy" id="btn-copy" title="Copy console output">Copy</button>
339
- </div>
340
- <div class="terminal-readout" id="card-out">Gatekeeper initialized. Ready for code verification.</div>
536
+ <div class="section-title">Inspection Findings (${filteredFindings.length})</div>
537
+ <div class="filter-tabs">
538
+ <button class="filter-btn" data-active="${this.activeFilter === 'all'}" type="button" id="filter-all">
539
+ All (${allFindings.length})
540
+ </button>
541
+ <button class="filter-btn" data-active="${this.activeFilter === 'errors'}" type="button" id="filter-errors">
542
+ Errors (${errorCount})
543
+ </button>
544
+ <button class="filter-btn" data-active="${this.activeFilter === 'warnings'}" type="button" id="filter-warnings">
545
+ Warnings (${warnCount})
546
+ </button>
547
+ </div>
548
+
549
+ <div class="violations-box">
550
+ ${filteredFindings.length > 0 ? filteredFindings.map(f => `
551
+ <div class="violation-row">
552
+ <div class="violation-header">
553
+ <span class="severity-tag severity-${escapeHtml(f.severity || 'error')}">${escapeHtml(f.severity || 'error')}</span>
554
+ <span class="violation-loc">${escapeHtml(f.file || 'unknown')}${f.line ? `:${f.line}` : ''}</span>
555
+ ${f.rule_id ? `<span class="plugin-subtitle">[${escapeHtml(f.rule_id)}]</span>` : ''}
556
+ </div>
557
+ <div class="violation-msg">${escapeHtml(f.message || 'Verification failure detected')}</div>
558
+ </div>
559
+ `).join('') : `
560
+ <div class="empty-violations">
561
+ ${rep ? 'Zero violations detected. Repository passes clean.' : 'No active gate report found. Click Run Gate above.'}
562
+ </div>
563
+ `}
564
+ </div>
565
+
566
+ <div class="footer-bar">
567
+ <span>Latest Run: ${timestamp}</span>
568
+ <span>Tier: ${tierName.toUpperCase()}</span>
341
569
  </div>
342
570
  `;
343
571
 
344
- const buttons = root.querySelectorAll('.btn-action');
345
- buttons.forEach(btn => {
346
- btn.addEventListener('click', () => {
347
- const cmd = btn.getAttribute('data-cmd');
348
- if (cmd) this.execute(cmd, []);
349
- });
350
- });
572
+ // Event listeners
573
+ root.querySelector('#tier-fast')?.addEventListener('click', () => { this.selectedTier = 'fast'; this.render(); });
574
+ root.querySelector('#tier-full')?.addEventListener('click', () => { this.selectedTier = 'full'; this.render(); });
575
+ root.querySelector('#tier-remote')?.addEventListener('click', () => { this.selectedTier = 'remote'; this.render(); });
351
576
 
352
- const copyBtn = root.querySelector('#btn-copy');
353
- if (copyBtn) {
354
- copyBtn.addEventListener('click', () => {
355
- const out = root.querySelector('#card-out');
356
- if (out && out.textContent) {
357
- navigator.clipboard?.writeText(out.textContent);
358
- copyBtn.textContent = 'Copied!';
359
- setTimeout(() => { copyBtn.textContent = 'Copy'; }, 1500);
360
- }
361
- });
362
- }
577
+ root.querySelector('#btn-run')?.addEventListener('click', () => this.runTier(this.selectedTier));
578
+
579
+ root.querySelector('#filter-all')?.addEventListener('click', () => this.setFilter('all'));
580
+ root.querySelector('#filter-errors')?.addEventListener('click', () => this.setFilter('errors'));
581
+ root.querySelector('#filter-warnings')?.addEventListener('click', () => this.setFilter('warnings'));
363
582
  }
364
583
  }
365
584