@greenarmor/ges-web-dashboard 1.2.3 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as http from "node:http";
2
- import type { ScoreFile, Control, FixHistoryEntry } from "@greenarmor/ges-core";
2
+ import type { ScoreFile, Control, FixHistoryEntry, ActivityLogEntry } from "@greenarmor/ges-core";
3
3
  import type { Finding } from "@greenarmor/ges-audit-engine";
4
4
  export interface DashboardOptions {
5
5
  port?: number;
@@ -77,6 +77,7 @@ export interface DashboardData {
77
77
  findings: Finding[];
78
78
  packs: PackSummary[];
79
79
  fixHistory: FixHistoryEntry[];
80
+ activityLog: ActivityLogEntry[];
80
81
  lastAudit: string;
81
82
  }
82
83
  export declare function collectDashboardData(projectPath: string): DashboardData;
package/dist/index.js CHANGED
@@ -4,7 +4,8 @@ import * as path from "node:path";
4
4
  import { runAudit, deduplicateFindings } from "@greenarmor/ges-audit-engine";
5
5
  import { getAllPacks, getPack } from "@greenarmor/ges-policy-engine";
6
6
  import { generateScoreFile } from "@greenarmor/ges-scoring-engine";
7
- import { loadFixHistory } from "@greenarmor/ges-core";
7
+ import { loadFixHistory, loadActivityLog, loadControlsFromDisk, loadControlOverrides, applyOverridesToControls } from "@greenarmor/ges-core";
8
+ import { getInstalledPackIds as getInstalledPackIdsFromDisk } from "@greenarmor/ges-core";
8
9
  import { renderDashboard } from "./template.js";
9
10
  function loadConfig(projectPath) {
10
11
  const configPath = path.join(projectPath, ".ges", "config.json");
@@ -31,21 +32,13 @@ function loadControlsForConfig(projectPath, config) {
31
32
  const fwLower = new Set(config.frameworks.map(f => f.toLowerCase()));
32
33
  const allPacks = getAllPacks();
33
34
  const packs = allPacks.filter(pack => fwLower.has(pack.id.toLowerCase()));
34
- const controls = packs.flatMap(p => p.controls);
35
- const overridesPath = path.join(projectPath, ".ges", "control-overrides.json");
36
- if (fs.existsSync(overridesPath)) {
37
- const overrides = JSON.parse(fs.readFileSync(overridesPath, "utf-8"));
38
- for (const override of overrides) {
39
- const control = controls.find((c) => c.id === override.control_id);
40
- if (control) {
41
- control.status = override.status;
42
- for (const check of control.checks) {
43
- check.status = override.status;
44
- }
45
- }
46
- }
47
- }
48
- return controls;
35
+ const inMemoryControls = packs.flatMap(p => p.controls);
36
+ const diskControls = loadControlsFromDisk(projectPath);
37
+ const seenIds = new Set(inMemoryControls.map((c) => c.id));
38
+ const extraFromDisk = diskControls.filter(c => !seenIds.has(c.id));
39
+ const controls = [...inMemoryControls, ...extraFromDisk];
40
+ const overrides = loadControlOverrides(projectPath);
41
+ return applyOverridesToControls(controls, overrides);
49
42
  }
50
43
  catch {
51
44
  return [];
@@ -139,32 +132,40 @@ function getInstalledPackIds(projectPath, config) {
139
132
  }
140
133
  }
141
134
  }
142
- const controlsDir = path.join(projectPath, "controls");
143
- try {
144
- const entries = fs.readdirSync(controlsDir, { withFileTypes: true });
145
- for (const entry of entries) {
146
- if (entry.isDirectory()) {
147
- const ctrlFile = path.join(controlsDir, entry.name, "controls.json");
148
- if (fs.existsSync(ctrlFile)) {
149
- ids.add(entry.name);
150
- }
151
- }
152
- }
153
- }
154
- catch {
155
- // controls dir may not exist
135
+ for (const id of getInstalledPackIdsFromDisk(projectPath)) {
136
+ ids.add(id);
156
137
  }
157
138
  return ids;
158
139
  }
140
+ function getFrameworksFromControls(controls) {
141
+ const fwSet = new Set();
142
+ for (const c of controls) {
143
+ if (c.framework)
144
+ fwSet.add(c.framework);
145
+ }
146
+ return [...fwSet];
147
+ }
159
148
  export function collectDashboardData(projectPath) {
160
149
  const config = loadConfig(projectPath);
161
150
  let score = loadScore(projectPath);
162
- const baseControls = config ? loadControlsForConfig(projectPath, config) : [];
151
+ let baseControls;
152
+ let frameworks;
153
+ if (config) {
154
+ baseControls = loadControlsForConfig(projectPath, config);
155
+ frameworks = config.frameworks;
156
+ }
157
+ else {
158
+ baseControls = loadControlsFromDisk(projectPath);
159
+ frameworks = [];
160
+ }
163
161
  const findings = loadFindings(projectPath);
164
162
  const controls = updateControlsFromFindings(baseControls, findings);
165
- if (config) {
163
+ if (config || controls.length > 0) {
166
164
  try {
167
- const freshScore = generateScoreFile(controls, config.frameworks, findings);
165
+ const scoreFrameworks = frameworks.length > 0
166
+ ? frameworks
167
+ : getFrameworksFromControls(controls);
168
+ const freshScore = generateScoreFile(controls, scoreFrameworks, findings);
168
169
  score = freshScore;
169
170
  }
170
171
  catch {
@@ -176,6 +177,7 @@ export function collectDashboardData(projectPath) {
176
177
  const installedPacks = getInstalledPackIds(projectPath, config || undefined);
177
178
  const packs = allPacks.map(p => buildPackSummary(p, controls, findings, installedPacks));
178
179
  const fixHistory = loadFixHistory(projectPath);
180
+ const activityLog = loadActivityLog(projectPath);
179
181
  const metadataPath = path.join(projectPath, ".ges", "metadata.json");
180
182
  let lastAudit = "";
181
183
  try {
@@ -185,16 +187,22 @@ export function collectDashboardData(projectPath) {
185
187
  catch {
186
188
  lastAudit = new Date().toISOString();
187
189
  }
190
+ const allFrameworks = new Set(frameworks);
191
+ for (const c of controls) {
192
+ if (c.framework)
193
+ allFrameworks.add(c.framework);
194
+ }
188
195
  return {
189
196
  projectName: config?.project_name || "Unknown Project",
190
197
  projectType: config?.project_type || "unknown",
191
- frameworks: config?.frameworks || [],
192
- gesfVersion: "1.2.3",
198
+ frameworks: [...allFrameworks],
199
+ gesfVersion: "1.2.5",
193
200
  score,
194
201
  controls,
195
202
  findings,
196
203
  packs,
197
204
  fixHistory,
205
+ activityLog,
198
206
  lastAudit,
199
207
  };
200
208
  }
@@ -203,7 +211,9 @@ export function collectPackDetail(projectPath, packId) {
203
211
  if (!pack)
204
212
  return null;
205
213
  const config = loadConfig(projectPath);
206
- const baseControls = config ? loadControlsForConfig(projectPath, config) : [];
214
+ const baseControls = config
215
+ ? loadControlsForConfig(projectPath, config)
216
+ : loadControlsFromDisk(projectPath);
207
217
  const findings = loadFindings(projectPath);
208
218
  const controls = updateControlsFromFindings(baseControls, findings);
209
219
  const packControlIds = new Set(pack.controls.map(c => c.id));
@@ -281,9 +291,9 @@ export function collectPackDetail(projectPath, packId) {
281
291
  }
282
292
  export function collectControlDetail(projectPath, controlId) {
283
293
  const config = loadConfig(projectPath);
284
- if (!config)
285
- return null;
286
- const baseControls = loadControlsForConfig(projectPath, config);
294
+ const baseControls = config
295
+ ? loadControlsForConfig(projectPath, config)
296
+ : loadControlsFromDisk(projectPath);
287
297
  const findings = loadFindings(projectPath);
288
298
  const controls = updateControlsFromFindings(baseControls, findings);
289
299
  const control = controls.find(c => c.id === controlId);
@@ -380,6 +390,16 @@ export function startDashboard(options) {
380
390
  }
381
391
  return;
382
392
  }
393
+ if (pathname === "/api/activity-log") {
394
+ try {
395
+ const data = collectDashboardData(options.projectPath);
396
+ jsonResponse(res, data.activityLog);
397
+ }
398
+ catch (err) {
399
+ jsonError(res, err instanceof Error ? err.message : String(err));
400
+ }
401
+ return;
402
+ }
383
403
  const packMatch = pathname.match(/^\/api\/packs\/([a-z0-9-]+)$/);
384
404
  if (packMatch) {
385
405
  try {
package/dist/template.js CHANGED
@@ -1,3 +1,29 @@
1
+ function matchPackForControl(controlId, packs) {
2
+ const idUpper = controlId.toUpperCase();
3
+ for (const p of packs) {
4
+ if (p.id === "gdpr" && idUpper.startsWith("GDPR-"))
5
+ return p;
6
+ if (p.id === "owasp" && idUpper.startsWith("OWASP-"))
7
+ return p;
8
+ if (p.id === "cis" && idUpper.startsWith("CIS-"))
9
+ return p;
10
+ if (p.id === "nist" && idUpper.startsWith("NIST-"))
11
+ return p;
12
+ if (p.id === "ai" && idUpper.startsWith("AI-"))
13
+ return p;
14
+ if (p.id === "blockchain" && idUpper.startsWith("BC-"))
15
+ return p;
16
+ if (p.id === "government" && idUpper.startsWith("GOV-"))
17
+ return p;
18
+ if (p.id === "iso27001" && idUpper.startsWith("ISO27K-"))
19
+ return p;
20
+ if (p.id === "iso27701" && idUpper.startsWith("ISO277-"))
21
+ return p;
22
+ if (p.id === "hipaa" && idUpper.startsWith("HIPAA-"))
23
+ return p;
24
+ }
25
+ return undefined;
26
+ }
1
27
  function gradeColor(grade) {
2
28
  switch (grade) {
3
29
  case "A": return "#22c55e";
@@ -106,6 +132,45 @@ export function renderDashboard(data) {
106
132
  ...p,
107
133
  _controls: undefined,
108
134
  })));
135
+ const complianceIssues = controls
136
+ .filter(c => c.status !== "pass" && c.status !== "not-applicable")
137
+ .map(c => {
138
+ const pack = matchPackForControl(c.id, packs);
139
+ const auditFindings = findings.filter(f => f.controlIds.includes(c.id));
140
+ return {
141
+ controlId: c.id,
142
+ controlName: c.name,
143
+ severity: c.severity,
144
+ status: c.status,
145
+ category: c.category,
146
+ framework: c.framework,
147
+ article: c.article,
148
+ description: c.description,
149
+ implementation_guidance: c.implementation_guidance,
150
+ packId: pack?.id || "",
151
+ packName: pack?.name || "Direct",
152
+ passedChecks: c.checks.filter(ch => ch.status === "pass").length,
153
+ totalChecks: c.checks.length,
154
+ auditFindings,
155
+ };
156
+ })
157
+ .sort((a, b) => {
158
+ const sevOrder = { critical: 0, high: 1, medium: 2, low: 3 };
159
+ return (sevOrder[a.severity] ?? 4) - (sevOrder[b.severity] ?? 4);
160
+ });
161
+ const issuesBySeverity = {
162
+ critical: complianceIssues.filter(i => i.severity === "critical").length,
163
+ high: complianceIssues.filter(i => i.severity === "high").length,
164
+ medium: complianceIssues.filter(i => i.severity === "medium").length,
165
+ low: complianceIssues.filter(i => i.severity === "low").length,
166
+ };
167
+ const issuesByPackId = {};
168
+ for (const issue of complianceIssues) {
169
+ const key = issue.packId || "direct";
170
+ if (!issuesByPackId[key])
171
+ issuesByPackId[key] = [];
172
+ issuesByPackId[key].push(issue);
173
+ }
109
174
  return `<!DOCTYPE html>
110
175
  <html lang="en">
111
176
  <head>
@@ -252,8 +317,8 @@ export function renderDashboard(data) {
252
317
 
253
318
  <div class="header">
254
319
  <div>
255
- <h1>GESF Compliance Dashboard</h1>
256
- <div class="subtitle">${escapeHtml(data.projectName)} | ${escapeHtml(data.projectType)} | GESF v${escapeHtml(data.gesfVersion)}</div>
320
+ <h1>${escapeHtml(data.projectName)}</h1>
321
+ <div class="subtitle">GESF v${escapeHtml(data.gesfVersion)}</div>
257
322
  </div>
258
323
  <div class="nav-tabs">
259
324
  <button class="nav-tab active" onclick="showPage('overview', this)">Overview</button>
@@ -261,6 +326,7 @@ export function renderDashboard(data) {
261
326
  <button class="nav-tab" onclick="showPage('fixes', this)">Fixes Detail</button>
262
327
  <button class="nav-tab" onclick="showPage('findings', this)">Findings</button>
263
328
  <button class="nav-tab" onclick="showPage('traceability', this)">Traceability</button>
329
+ <button class="nav-tab" onclick="showPage('activity', this)">Activity Log</button>
264
330
  </div>
265
331
  </div>
266
332
 
@@ -405,100 +471,97 @@ export function renderDashboard(data) {
405
471
  <div id="page-fixes" class="page">
406
472
  <div class="tab-bar" style="margin-bottom:0;">
407
473
  <button class="tab-btn active" onclick="showFixesTab('history', this)">Fix History (${data.fixHistory.length})</button>
408
- <button class="tab-btn" onclick="showFixesTab('pending', this)">Pending Fixes (${findings.length})</button>
474
+ <button class="tab-btn" onclick="showFixesTab('pending', this)">Pending Fixes (${complianceIssues.length})</button>
409
475
  </div>
410
476
 
411
477
  <div id="fixes-tab-history" class="tab-panel active">
412
- ${renderFixHistorySection(data.fixHistory)}
478
+ ${renderFixHistorySection(data.fixHistory, complianceIssues)}
413
479
  </div>
414
480
 
415
481
  <div id="fixes-tab-pending" class="tab-panel">
416
- ${renderDetailedFixesList(findings, controls, packs)}
482
+ ${renderComplianceFixCards(complianceIssues, "fix")}
417
483
  </div>
418
484
  </div>
419
485
 
420
486
  <div id="page-findings" class="page">
421
487
  <div id="findings-main">
422
- <h2 style="font-size:20px;font-weight:700;margin-bottom:16px;">Security Findings Report</h2>
488
+ <h2 style="font-size:20px;font-weight:700;margin-bottom:8px;">Compliance Findings &amp; Issues</h2>
489
+ <p style="color:#6b7280;font-size:14px;margin-bottom:20px;">Every control that is not passing is a compliance finding. Code-level audit evidence is shown where available.</p>
490
+
491
+ <div class="grid grid-4" style="margin-bottom:20px;">
492
+ <div class="card stat"><div class="num" style="color:${complianceIssues.length > 0 ? '#ef4444' : '#22c55e'};">${complianceIssues.length}</div><div class="label">Total Issues</div></div>
493
+ <div class="card stat"><div class="num" style="color:#ef4444;">${issuesBySeverity.critical}</div><div class="label">Critical</div></div>
494
+ <div class="card stat"><div class="num" style="color:#f97316;">${issuesBySeverity.high}</div><div class="label">High</div></div>
495
+ <div class="card stat"><div class="num">${findings.length}</div><div class="label">Audit Evidence</div></div>
496
+ </div>
497
+
423
498
  <div class="tab-bar">
424
- <button class="tab-btn active" onclick="showFindingsTab('all', this)">All (${findings.length})</button>
425
- <button class="tab-btn" onclick="showFindingsTab('critical', this)">Critical (${findingsBySeverity.critical})</button>
426
- <button class="tab-btn" onclick="showFindingsTab('high', this)">High (${findingsBySeverity.high})</button>
427
- <button class="tab-btn" onclick="showFindingsTab('medium', this)">Medium (${findingsBySeverity.medium})</button>
428
- <button class="tab-btn" onclick="showFindingsTab('low', this)">Low (${findingsBySeverity.low})</button>
499
+ <button class="tab-btn active" onclick="showFindingsTab('all', this)">All Issues (${complianceIssues.length})</button>
500
+ <button class="tab-btn" onclick="showFindingsTab('critical', this)">Critical (${issuesBySeverity.critical})</button>
501
+ <button class="tab-btn" onclick="showFindingsTab('high', this)">High (${issuesBySeverity.high})</button>
502
+ <button class="tab-btn" onclick="showFindingsTab('medium', this)">Medium (${issuesBySeverity.medium})</button>
503
+ <button class="tab-btn" onclick="showFindingsTab('low', this)">Low (${issuesBySeverity.low})</button>
429
504
  <button class="tab-btn" onclick="showFindingsTab('bypack', this)">By Pack</button>
505
+ ${findings.length > 0 ? `<button class="tab-btn" onclick="showFindingsTab('evidence', this)">Audit Evidence (${findings.length})</button>` : ''}
430
506
  </div>
431
507
 
432
508
  <div id="findings-tab-all" class="tab-panel active">
433
- ${renderFindingsTable(findings)}
509
+ ${renderComplianceIssuesTable(complianceIssues)}
434
510
  </div>
435
- <div id="findings-tab-critical" class="tab-panel">${renderFindingsTable(findings.filter(f => f.severity === "critical"))}</div>
436
- <div id="findings-tab-high" class="tab-panel">${renderFindingsTable(findings.filter(f => f.severity === "high"))}</div>
437
- <div id="findings-tab-medium" class="tab-panel">${renderFindingsTable(findings.filter(f => f.severity === "medium"))}</div>
438
- <div id="findings-tab-low" class="tab-panel">${renderFindingsTable(findings.filter(f => f.severity === "low"))}</div>
511
+ <div id="findings-tab-critical" class="tab-panel">${renderComplianceIssuesTable(complianceIssues.filter(i => i.severity === "critical"))}</div>
512
+ <div id="findings-tab-high" class="tab-panel">${renderComplianceIssuesTable(complianceIssues.filter(i => i.severity === "high"))}</div>
513
+ <div id="findings-tab-medium" class="tab-panel">${renderComplianceIssuesTable(complianceIssues.filter(i => i.severity === "medium"))}</div>
514
+ <div id="findings-tab-low" class="tab-panel">${renderComplianceIssuesTable(complianceIssues.filter(i => i.severity === "low"))}</div>
439
515
  <div id="findings-tab-bypack" class="tab-panel">
440
- ${packs.filter(p => (findingsByPackId[p.id] || []).length > 0).length > 0 ? packs.filter(p => (findingsByPackId[p.id] || []).length > 0).map(p => `
516
+ ${packs.filter(p => (issuesByPackId[p.id] || []).length > 0).length > 0 ? packs.filter(p => (issuesByPackId[p.id] || []).length > 0).map(p => `
441
517
  <div class="card" style="margin-bottom:16px;">
442
518
  <div class="card-title" style="cursor:pointer;" onclick="loadPackDetail('${p.id}')">
443
- ${escapeHtml(p.name)} &mdash; ${(findingsByPackId[p.id] || []).length} findings
519
+ ${escapeHtml(p.name)} &mdash; ${(issuesByPackId[p.id] || []).length} issues
444
520
  <span style="float:right;color:#0f766e;font-weight:400;font-size:11px;">View pack details &rarr;</span>
445
521
  </div>
446
- ${renderFindingsTable(findingsByPackId[p.id] || [])}
522
+ ${renderComplianceIssuesTable(issuesByPackId[p.id] || [])}
447
523
  </div>
448
- `).join('') : '<div class="empty-state"><div class="msg">No findings mapped to policy packs</div></div>'}
524
+ `).join('') : '<div class="empty-state"><div class="msg">No compliance issues mapped to policy packs</div></div>'}
449
525
  </div>
526
+ ${findings.length > 0 ? `<div id="findings-tab-evidence" class="tab-panel">${renderFindingsTable(findings)}</div>` : ''}
450
527
  </div>
451
528
  <div id="finding-detail" style="display:none;"></div>
452
529
  </div>
453
530
 
454
531
  <div id="page-traceability" class="page">
455
- <h2 style="font-size:20px;font-weight:700;margin-bottom:8px;">Fix Traceability Matrix</h2>
456
- <p style="color:#6b7280;font-size:14px;margin-bottom:20px;">Finding &rarr; Fix &rarr; Control &rarr; Policy Pack traceability for every security issue.</p>
532
+ <h2 style="font-size:20px;font-weight:700;margin-bottom:8px;">Compliance Traceability Matrix</h2>
533
+ <p style="color:#6b7280;font-size:14px;margin-bottom:20px;">Full traceability: Control &rarr; Framework &rarr; Policy Pack &rarr; Severity &rarr; Fix Guidance for every compliance issue.</p>
457
534
  <div class="tab-bar">
458
- <button class="tab-btn active" onclick="showTraceTab('matrix', this)">Matrix</button>
535
+ <button class="tab-btn active" onclick="showTraceTab('matrix', this)">Matrix (${complianceIssues.length})</button>
459
536
  <button class="tab-btn" onclick="showTraceTab('fixes', this)">Prioritized Fixes</button>
460
537
  <button class="tab-btn" onclick="showTraceTab('controls', this)">Control Coverage</button>
461
538
  </div>
462
539
 
463
540
  <div id="trace-tab-matrix" class="tab-panel active">
464
- ${findings.length > 0 ? `<div class="card">
541
+ ${complianceIssues.length > 0 ? `<div class="card">
465
542
  <table>
466
- <thead><tr><th>Finding</th><th>Severity</th><th>File</th><th>Linked Controls</th><th>Policy Pack</th><th>Fix Guidance</th></tr></thead>
543
+ <thead><tr><th>Control</th><th>Severity</th><th>Framework</th><th>Policy Pack</th><th>Status</th><th>Checks</th><th>Audit</th><th>Fix Guidance</th></tr></thead>
467
544
  <tbody>
468
- ${findings.slice(0, 50).map(f => {
469
- const linkedControls = controls.filter(c => f.controlIds.includes(c.id));
470
- const linkedPackIds = new Set();
471
- for (const ctrl of linkedControls) {
472
- const pk = packs.find(pp => {
473
- const pCtrls = getAllControlsForPack(pp.id, controls);
474
- return pCtrls.some(c2 => c2.id === ctrl.id);
475
- });
476
- if (pk)
477
- linkedPackIds.add(pk.id);
478
- }
479
- return `<tr>
480
- <td>
481
- <div style="font-weight:600;font-size:13px;">${escapeHtml(f.title)}</div>
482
- <div style="font-size:11px;color:#6b7280;">${escapeHtml(f.ruleId)}</div>
483
- </td>
484
- <td><span class="badge badge-sev" style="background:${severityColor(f.severity)}">${f.severity.toUpperCase()}</span></td>
485
- <td style="font-family:monospace;font-size:11px;">${escapeHtml(f.file)}${f.line ? ':' + f.line : ''}</td>
486
- <td>${linkedControls.length > 0 ? linkedControls.map(c => `<div style="margin-bottom:2px;"><span class="link" onclick="showControlDetail('${escapeHtml(c.id)}')">${escapeHtml(c.id)}</span> <span style="color:#6b7280;font-size:11px;">${escapeHtml(c.name)}</span></div>`).join('') : '<span style="color:#9ca3af;">No linked controls</span>'}</td>
487
- <td>${linkedPackIds.size > 0 ? [...linkedPackIds].map(pid => {
488
- const pk = packs.find(pp => pp.id === pid);
489
- return pk ? `<span class="tag" style="cursor:pointer;" onclick="loadPackDetail('${pk.id}')">${escapeHtml(pk.name)}</span>` : '';
490
- }).join(' ') : '<span style="color:#9ca3af;">-</span>'}</td>
491
- <td style="max-width:300px;font-size:12px;color:#374151;">${escapeHtml(f.fix)}</td>
492
- </tr>`;
493
- }).join('')}
545
+ ${complianceIssues.map(issue => `<tr>
546
+ <td>
547
+ <span class="link" onclick="showControlDetail('${escapeHtml(issue.controlId)}')">${escapeHtml(issue.controlId)}</span>
548
+ <div style="font-size:12px;color:#4b5563;">${escapeHtml(issue.controlName)}</div>
549
+ </td>
550
+ <td><span class="badge badge-sev" style="background:${severityColor(issue.severity)}">${issue.severity.toUpperCase()}</span></td>
551
+ <td style="font-size:12px;">${escapeHtml(issue.framework)}${issue.article ? '<br><span style="color:#6b7280;">' + escapeHtml(issue.article) + '</span>' : ''}</td>
552
+ <td>${issue.packId ? `<span class="tag" style="cursor:pointer;" onclick="loadPackDetail('${issue.packId}')">${escapeHtml(issue.packName)}</span>` : '<span style="color:#9ca3af;">-</span>'}</td>
553
+ <td><span class="badge badge-status" style="background:${statusColor(issue.status)}">${statusLabel(issue.status)}</span></td>
554
+ <td style="font-size:12px;">${issue.passedChecks}/${issue.totalChecks}</td>
555
+ <td>${issue.auditFindings.length > 0 ? `<span style="color:#ef4444;font-weight:600;">${issue.auditFindings.length}</span>` : '<span style="color:#9ca3af;">0</span>'}</td>
556
+ <td style="max-width:280px;font-size:12px;color:#374151;">${escapeHtml(issue.implementation_guidance.substring(0, 150))}${issue.implementation_guidance.length > 150 ? '...' : ''}</td>
557
+ </tr>`).join('')}
494
558
  </tbody>
495
559
  </table>
496
- ${findings.length > 50 ? `<div style="text-align:center;padding:8px;color:#9ca3af;font-size:12px;">Showing 50 of ${findings.length} findings</div>` : ''}
497
- </div>` : '<div class="card"><div class="empty-state"><div class="icon">&#10003;</div><div class="msg" style="color:#22c55e;">No findings to trace</div><div class="sub">All clear</div></div></div>'}
560
+ </div>` : '<div class="card"><div class="empty-state"><div class="icon">&#10003;</div><div class="msg" style="color:#22c55e;">No issues to trace</div><div class="sub">All controls passing</div></div></div>'}
498
561
  </div>
499
562
 
500
563
  <div id="trace-tab-fixes" class="tab-panel">
501
- ${renderDetailedFixesList(findings, controls, packs)}
564
+ ${renderComplianceFixCards(complianceIssues, "trace")}
502
565
  </div>
503
566
 
504
567
  <div id="trace-tab-controls" class="tab-panel">
@@ -527,12 +590,16 @@ export function renderDashboard(data) {
527
590
  </div>
528
591
  </div>
529
592
 
593
+ <div id="page-activity" class="page">
594
+ ${renderActivityLogSection(data.activityLog || [])}
595
+ </div>
596
+
530
597
  <div id="control-detail-modal" style="display:none;"></div>
531
598
 
532
599
  </div>
533
600
 
534
601
  <div class="footer">
535
- Generated by GESF v${escapeHtml(data.gesfVersion)} | Last audit: ${escapeHtml(new Date(data.lastAudit).toLocaleString())} | <a href="/api/data">JSON API</a> | <a href="/api/packs">Packs API</a> | <a href="/api/fix-history">Fix History API</a>
602
+ Generated by GESF v${escapeHtml(data.gesfVersion)} | Last audit: ${escapeHtml(new Date(data.lastAudit).toLocaleString())} | <a href="/api/data">JSON API</a> | <a href="/api/packs">Packs API</a> | <a href="/api/fix-history">Fix History API</a> | <a href="/api/activity-log">Activity Log API</a>
536
603
  </div>
537
604
 
538
605
  <script>
@@ -553,7 +620,7 @@ export function renderDashboard(data) {
553
620
  }
554
621
  };
555
622
 
556
- var navTabMap = { overview: 0, packs: 1, fixes: 2, findings: 3, traceability: 4 };
623
+ var navTabMap = { overview: 0, packs: 1, fixes: 2, findings: 3, traceability: 4, activity: 5 };
557
624
 
558
625
  window.navigateToPage = function(page) {
559
626
  var tabs = document.querySelectorAll('.nav-tab');
@@ -597,6 +664,11 @@ export function renderDashboard(data) {
597
664
  if (btn) btn.classList.add('active');
598
665
  };
599
666
 
667
+ window.goToPendingFixes = function() {
668
+ var btns = document.querySelectorAll('#page-fixes .tab-btn');
669
+ showFixesTab('pending', btns.length > 1 ? btns[1] : (btns[0] || null));
670
+ };
671
+
600
672
  window.showTraceTab = function(tab, btn) {
601
673
  var panels = document.querySelectorAll('#page-traceability .tab-panel');
602
674
  for (var i = 0; i < panels.length; i++) panels[i].classList.remove('active');
@@ -1040,7 +1112,7 @@ function renderDetailedFixesList(findings, controls, packs) {
1040
1112
  }
1041
1113
  return html;
1042
1114
  }
1043
- function renderFixHistorySection(entries) {
1115
+ function renderFixHistorySection(entries, complianceIssues = []) {
1044
1116
  if (entries.length === 0) {
1045
1117
  return `<div class="card">
1046
1118
  <h2 style="font-size:20px;font-weight:700;margin-bottom:8px;">Compliance Fix History</h2>
@@ -1049,6 +1121,7 @@ function renderFixHistorySection(entries) {
1049
1121
  <div class="icon">&#128203;</div>
1050
1122
  <div class="msg">No fixes recorded yet</div>
1051
1123
  <div class="sub">Run <code style="background:#f3f4f6;padding:2px 6px;border-radius:4px;font-size:12px;">ges fix</code> or use the MCP <code style="background:#f3f4f6;padding:2px 6px;border-radius:4px;font-size:12px;">auto_fix</code> tool to apply fixes. Each fix will be recorded here.</div>
1124
+ ${complianceIssues.length > 0 ? `<div style="margin-top:16px;"><span class="badge badge-status" style="background:#f97316;font-size:12px;padding:4px 12px;">${complianceIssues.length} pending fixes</span> <span class="link" style="font-size:13px;" onclick="goToPendingFixes()">View pending fixes &rarr;</span></div>` : ''}
1052
1125
  </div>
1053
1126
  </div>`;
1054
1127
  }
@@ -1193,6 +1266,241 @@ function renderFixHistorySection(entries) {
1193
1266
  html += `</div>`;
1194
1267
  return html;
1195
1268
  }
1269
+ function renderComplianceIssuesTable(issues) {
1270
+ if (issues.length === 0) {
1271
+ return '<div class="empty-state"><div class="icon">&#10003;</div><div class="msg" style="color:#22c55e;">No compliance issues in this category</div></div>';
1272
+ }
1273
+ return `<table>
1274
+ <thead><tr><th>Severity</th><th>Control</th><th>Framework</th><th>Policy Pack</th><th>Status</th><th>Checks</th><th>Audit</th><th>Fix Guidance</th></tr></thead>
1275
+ <tbody>
1276
+ ${issues.map(issue => `<tr>
1277
+ <td><span class="badge badge-sev" style="background:${severityColor(issue.severity)}">${issue.severity.toUpperCase()}</span></td>
1278
+ <td>
1279
+ <span class="link" onclick="showControlDetail('${escapeHtml(issue.controlId)}')">${escapeHtml(issue.controlId)}</span>
1280
+ <div style="font-size:12px;color:#4b5563;">${escapeHtml(issue.controlName)}</div>
1281
+ </td>
1282
+ <td style="font-size:12px;">${escapeHtml(issue.framework)}${issue.article ? '<br><span style="color:#6b7280;">' + escapeHtml(issue.article) + '</span>' : ''}</td>
1283
+ <td>${issue.packId ? `<span class="tag" style="cursor:pointer;" onclick="loadPackDetail('${issue.packId}')">${escapeHtml(issue.packName)}</span>` : '<span style="color:#9ca3af;">Direct</span>'}</td>
1284
+ <td><span class="badge badge-status" style="background:${statusColor(issue.status)}">${statusLabel(issue.status)}</span></td>
1285
+ <td style="font-size:12px;">${issue.passedChecks}/${issue.totalChecks}</td>
1286
+ <td>${issue.auditFindings.length > 0 ? `<span style="color:#ef4444;font-weight:600;">${issue.auditFindings.length}</span>` : '<span style="color:#9ca3af;">0</span>'}</td>
1287
+ <td style="max-width:280px;font-size:12px;color:#4b5563;">${escapeHtml(issue.implementation_guidance.substring(0, 200))}${issue.implementation_guidance.length > 200 ? '...' : ''}</td>
1288
+ </tr>`).join('')}
1289
+ </tbody>
1290
+ </table>`;
1291
+ }
1292
+ function renderComplianceFixCards(issues, idPrefix) {
1293
+ if (issues.length === 0) {
1294
+ return '<div class="card"><div class="empty-state"><div class="icon">&#10003;</div><div class="msg" style="color:#22c55e;">All controls passing</div><div class="sub">No fixes needed</div></div></div>';
1295
+ }
1296
+ const totalAuditFindings = issues.reduce((sum, i) => sum + i.auditFindings.length, 0);
1297
+ const criticalCount = issues.filter(i => i.severity === "critical").length;
1298
+ const highCount = issues.filter(i => i.severity === "high").length;
1299
+ let html = '';
1300
+ html += `<div style="margin-bottom:20px;">`;
1301
+ html += `<div class="grid grid-4" style="margin-bottom:20px;">`;
1302
+ html += `<div class="card stat"><div class="num" style="color:#ef4444;">${issues.length}</div><div class="label">Controls to Fix</div></div>`;
1303
+ html += `<div class="card stat"><div class="num" style="color:#ef4444;">${criticalCount}</div><div class="label">Critical</div></div>`;
1304
+ html += `<div class="card stat"><div class="num" style="color:#f97316;">${highCount}</div><div class="label">High</div></div>`;
1305
+ html += `<div class="card stat"><div class="num">${totalAuditFindings}</div><div class="label">Audit Findings</div></div>`;
1306
+ html += `</div>`;
1307
+ html += `</div>`;
1308
+ for (let i = 0; i < issues.length; i++) {
1309
+ const issue = issues[i];
1310
+ const fixId = `${idPrefix}-${i}`;
1311
+ html += `<div class="fix-detail-card">`;
1312
+ html += `<div class="fix-detail-header ${issue.severity}" onclick="toggleFix('${fixId}')">`;
1313
+ html += `<div class="fix-detail-num" style="color:${severityColor(issue.severity)};">${i + 1}</div>`;
1314
+ html += `<div class="fix-detail-info">`;
1315
+ html += `<div class="fix-detail-title">${escapeHtml(issue.controlName)}</div>`;
1316
+ html += `<div class="fix-detail-meta">${escapeHtml(issue.controlId)} | ${escapeHtml(issue.category)} | ${escapeHtml(issue.framework)}${issue.article ? ' | ' + escapeHtml(issue.article) : ''} | Pack: ${escapeHtml(issue.packName)}</div>`;
1317
+ html += `</div>`;
1318
+ html += `<div class="fix-detail-badges">`;
1319
+ html += `<span class="badge badge-sev" style="background:${severityColor(issue.severity)}">${issue.severity.toUpperCase()}</span>`;
1320
+ html += `<span class="badge badge-status" style="background:${statusColor(issue.status)}">${statusLabel(issue.status)}</span>`;
1321
+ html += `<span style="font-size:12px;color:#6b7280;">${issue.passedChecks}/${issue.totalChecks} checks</span>`;
1322
+ if (issue.auditFindings.length > 0) {
1323
+ html += `<span style="font-size:12px;color:#ef4444;font-weight:600;">${issue.auditFindings.length} evidence</span>`;
1324
+ }
1325
+ html += `<span class="fix-toggle" id="${fixId}-toggle">Expand</span>`;
1326
+ html += `</div></div>`;
1327
+ html += `<div class="fix-detail-body" id="${fixId}">`;
1328
+ html += `<div class="fix-section"><div class="fix-section-title">Description</div>`;
1329
+ html += `<div style="font-size:13px;color:#4b5563;line-height:1.6;">${escapeHtml(issue.description)}</div>`;
1330
+ html += `</div>`;
1331
+ if (issue.auditFindings.length > 0) {
1332
+ html += `<div class="fix-section"><div class="fix-section-title">Audit Evidence (${issue.auditFindings.length})</div>`;
1333
+ for (const f of issue.auditFindings) {
1334
+ html += `<div class="fix-finding-item ${f.severity}">`;
1335
+ html += `<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px;">`;
1336
+ html += `<span class="badge badge-sev" style="background:${severityColor(f.severity)};font-size:10px;">${f.severity.toUpperCase()}</span>`;
1337
+ html += `<strong style="font-size:13px;">${escapeHtml(f.title)}</strong></div>`;
1338
+ html += `<div style="font-size:12px;color:#6b7280;"><span style="font-family:monospace;font-weight:600;">${escapeHtml(f.ruleId)}</span> &mdash; <span style="font-family:monospace;">${escapeHtml(f.file)}${f.line ? ':' + f.line : ''}</span></div>`;
1339
+ if (f.description)
1340
+ html += `<div style="font-size:12px;color:#4b5563;margin-top:4px;">${escapeHtml(f.description)}</div>`;
1341
+ if (f.evidence)
1342
+ html += `<div class="fix-evidence">${escapeHtml(f.evidence)}</div>`;
1343
+ html += `</div>`;
1344
+ }
1345
+ html += `</div>`;
1346
+ }
1347
+ html += `<div class="fix-section"><div class="fix-section-title">Fix Guidance</div>`;
1348
+ html += `<div class="fix-guidance-box"><strong>How to fix:</strong> ${escapeHtml(issue.implementation_guidance)}</div>`;
1349
+ for (const f of issue.auditFindings) {
1350
+ if (f.fix) {
1351
+ html += `<div class="fix-guidance-box" style="margin-top:8px;background:#eff6ff;border-color:#bfdbfe;"><strong>Fix for ${escapeHtml(f.ruleId)}:</strong> ${escapeHtml(f.fix)}</div>`;
1352
+ }
1353
+ }
1354
+ html += `</div>`;
1355
+ html += `<div class="fix-section"><div class="fix-section-title">Traceability</div>`;
1356
+ html += `<table><tbody>`;
1357
+ html += `<tr><td style="font-weight:600;width:160px;">Control</td><td><span class="link" onclick="showControlDetail('${escapeHtml(issue.controlId)}')">${escapeHtml(issue.controlId)}</span> &mdash; ${escapeHtml(issue.controlName)}</td></tr>`;
1358
+ html += `<tr><td style="font-weight:600;">Category</td><td>${escapeHtml(issue.category)}</td></tr>`;
1359
+ html += `<tr><td style="font-weight:600;">Framework</td><td>${escapeHtml(issue.framework)}${issue.article ? ' / ' + escapeHtml(issue.article) : ''}</td></tr>`;
1360
+ html += `<tr><td style="font-weight:600;">Policy Pack</td><td>${issue.packId ? `<span class="tag" style="cursor:pointer;" onclick="loadPackDetail('${issue.packId}')">${escapeHtml(issue.packName)}</span>` : 'Direct'}</td></tr>`;
1361
+ html += `<tr><td style="font-weight:600;">Severity</td><td><span class="badge badge-sev" style="background:${severityColor(issue.severity)}">${issue.severity.toUpperCase()}</span></td></tr>`;
1362
+ html += `<tr><td style="font-weight:600;">Status</td><td><span class="badge badge-status" style="background:${statusColor(issue.status)}">${statusLabel(issue.status)}</span></td></tr>`;
1363
+ html += `<tr><td style="font-weight:600;">Checks</td><td>${issue.passedChecks}/${issue.totalChecks} passed</td></tr>`;
1364
+ html += `<tr><td style="font-weight:600;">Audit Evidence</td><td>${issue.auditFindings.length} finding(s)</td></tr>`;
1365
+ html += `</tbody></table>`;
1366
+ html += `</div>`;
1367
+ html += `</div></div>`;
1368
+ }
1369
+ return html;
1370
+ }
1371
+ function renderActivityLogSection(entries) {
1372
+ if (!entries || entries.length === 0) {
1373
+ return `<div class="card">
1374
+ <h2 style="font-size:20px;font-weight:700;margin-bottom:8px;">Activity Log</h2>
1375
+ <p style="color:#6b7280;font-size:14px;margin-bottom:16px;">Every GESF operation performed via CLI or MCP is recorded here &mdash; the single source of truth for what GESF did to your project.</p>
1376
+ <div class="empty-state">
1377
+ <div class="icon">&#128203;</div>
1378
+ <div class="msg">No activity recorded yet</div>
1379
+ <div class="sub">Operations will appear here as you use GESF commands (<code style="background:#f3f4f6;padding:2px 6px;border-radius:4px;font-size:12px;">ges init</code>, <code style="background:#f3f4f6;padding:2px 6px;border-radius:4px;font-size:12px;">ges audit</code>, <code style="background:#f3f4f6;padding:2px 6px;border-radius:4px;font-size:12px;">ges fix</code>, MCP tools, etc.)</div>
1380
+ </div>
1381
+ </div>`;
1382
+ }
1383
+ const sorted = [...entries].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
1384
+ const bySource = { cli: entries.filter(e => e.source === "cli").length, mcp: entries.filter(e => e.source === "mcp").length };
1385
+ const byStatus = {
1386
+ success: entries.filter(e => e.status === "success").length,
1387
+ partial: entries.filter(e => e.status === "partial").length,
1388
+ failed: entries.filter(e => e.status === "failed").length,
1389
+ info: entries.filter(e => e.status === "info").length,
1390
+ };
1391
+ const byAction = {};
1392
+ for (const e of entries) {
1393
+ byAction[e.action] = (byAction[e.action] || 0) + 1;
1394
+ }
1395
+ const actionLabels = {
1396
+ init: "Project Init",
1397
+ audit: "Audit Run",
1398
+ fix: "Auto-Fix",
1399
+ policy_install: "Pack Installed",
1400
+ policy_remove: "Pack Removed",
1401
+ control_override: "Control Override",
1402
+ implement_control: "Control Implemented",
1403
+ score: "Score Generated",
1404
+ scan: "Scanners Run",
1405
+ validate: "Validation",
1406
+ generate: "Docs Generated",
1407
+ hooks_install: "Hooks Installed",
1408
+ hooks_uninstall: "Hooks Removed",
1409
+ dashboard_start: "Dashboard Started",
1410
+ badge_generate: "Badge Generated",
1411
+ };
1412
+ const actionColors = {
1413
+ init: "#0f766e",
1414
+ audit: "#3b82f6",
1415
+ fix: "#22c55e",
1416
+ policy_install: "#8b5cf6",
1417
+ policy_remove: "#ef4444",
1418
+ control_override: "#eab308",
1419
+ implement_control: "#22c55e",
1420
+ score: "#3b82f6",
1421
+ scan: "#f97316",
1422
+ validate: "#6b7280",
1423
+ generate: "#0f766e",
1424
+ hooks_install: "#6b7280",
1425
+ hooks_uninstall: "#6b7280",
1426
+ dashboard_start: "#8b5cf6",
1427
+ badge_generate: "#0f766e",
1428
+ };
1429
+ const statusColors = {
1430
+ success: "#22c55e",
1431
+ partial: "#eab308",
1432
+ failed: "#ef4444",
1433
+ info: "#3b82f6",
1434
+ };
1435
+ let html = '';
1436
+ html += `<h2 style="font-size:20px;font-weight:700;margin-bottom:8px;">Activity Log</h2>`;
1437
+ html += `<p style="color:#6b7280;font-size:14px;margin-bottom:16px;">Every GESF operation performed via CLI or MCP is recorded here &mdash; the single source of truth for what GESF did to your project.</p>`;
1438
+ html += `<div class="grid grid-4" style="margin-bottom:20px;">`;
1439
+ html += `<div class="card stat"><div class="num">${entries.length}</div><div class="label">Total Operations</div></div>`;
1440
+ html += `<div class="card stat"><div class="num" style="color:#22c55e;">${byStatus.success}</div><div class="label">Successful</div></div>`;
1441
+ html += `<div class="card stat"><div class="num" style="color:#ef4444;">${byStatus.failed + byStatus.partial}</div><div class="label">Failed/Partial</div></div>`;
1442
+ html += `<div class="card stat"><div class="num" style="color:#0f766e;">${bySource.cli}</div><div class="label">CLI Source</div></div>`;
1443
+ html += `</div>`;
1444
+ html += `<div class="grid grid-2" style="margin-bottom:20px;">`;
1445
+ html += `<div class="card"><div class="card-title">Operations by Type</div>`;
1446
+ for (const [action, count] of Object.entries(byAction).sort((a, b) => b[1] - a[1])) {
1447
+ const label = actionLabels[action] || action;
1448
+ const color = actionColors[action] || "#6b7280";
1449
+ html += `<div class="framework-row"><div class="framework-name" style="min-width:160px;font-size:13px;"><span class="badge" style="background:${color};font-size:10px;margin-right:6px;">${label}</span></div><div style="flex:1;"></div><div class="pct-text">${count}</div></div>`;
1450
+ }
1451
+ html += `</div>`;
1452
+ html += `<div class="card"><div class="card-title">Sources & Status</div>`;
1453
+ html += `<div style="display:flex;flex-wrap:wrap;gap:16px;margin-top:8px;">`;
1454
+ html += `<div class="stat"><div class="num" style="font-size:20px;">${bySource.cli}</div><div class="label">CLI</div></div>`;
1455
+ html += `<div class="stat"><div class="num" style="font-size:20px;">${bySource.mcp}</div><div class="label">MCP</div></div>`;
1456
+ html += `<div class="stat"><div class="num" style="font-size:20px;color:#22c55e;">${byStatus.success}</div><div class="label">Success</div></div>`;
1457
+ html += `<div class="stat"><div class="num" style="font-size:20px;color:#ef4444;">${byStatus.failed}</div><div class="label">Failed</div></div>`;
1458
+ html += `</div></div>`;
1459
+ html += `</div>`;
1460
+ html += `<div class="card"><div class="card-title">Timeline (newest first)</div>`;
1461
+ html += `<table><thead><tr><th>Time</th><th>Source</th><th>Action</th><th>Status</th><th>Description</th><th>Impact</th></tr></thead><tbody>`;
1462
+ for (const entry of sorted) {
1463
+ const time = new Date(entry.timestamp).toLocaleString();
1464
+ const sourceBadge = entry.source === "mcp"
1465
+ ? '<span class="badge" style="background:#7c3aed;font-size:10px;">MCP</span>'
1466
+ : '<span class="badge" style="background:#0f766e;font-size:10px;">CLI</span>';
1467
+ const actionLabel = actionLabels[entry.action] || entry.action;
1468
+ const actionColor = actionColors[entry.action] || "#6b7280";
1469
+ const actionBadge = `<span class="badge" style="background:${actionColor};font-size:10px;">${escapeHtml(actionLabel)}</span>`;
1470
+ const statusBadge = `<span class="badge badge-status" style="background:${statusColors[entry.status] || '#6b7280'};font-size:10px;">${entry.status.toUpperCase()}</span>`;
1471
+ const impactParts = [];
1472
+ if (entry.details.findings_count !== undefined)
1473
+ impactParts.push(`${entry.details.findings_count} findings`);
1474
+ if (entry.details.fixes_applied !== undefined)
1475
+ impactParts.push(`${entry.details.fixes_applied} fixes`);
1476
+ if (entry.details.packs_affected && entry.details.packs_affected.length > 0)
1477
+ impactParts.push(`Packs: ${entry.details.packs_affected.join(", ")}`);
1478
+ if (entry.details.controls_affected && entry.details.controls_affected.length > 0)
1479
+ impactParts.push(`Controls: ${entry.details.controls_affected.length}`);
1480
+ if (entry.details.files_created && entry.details.files_created.length > 0)
1481
+ impactParts.push(`${entry.details.files_created.length} files created`);
1482
+ if (entry.details.frameworks_added && entry.details.frameworks_added.length > 0)
1483
+ impactParts.push(`Added: ${entry.details.frameworks_added.join(", ")}`);
1484
+ if (entry.details.score !== undefined)
1485
+ impactParts.push(`Score: ${entry.details.score}%`);
1486
+ const impactHtml = impactParts.length > 0
1487
+ ? impactParts.map(p => `<div style="font-size:11px;color:#6b7280;margin-bottom:2px;">${escapeHtml(p)}</div>`).join('')
1488
+ : '<span style="color:#9ca3af;">-</span>';
1489
+ html += `<tr>
1490
+ <td style="font-size:11px;white-space:nowrap;">${time}</td>
1491
+ <td>${sourceBadge}</td>
1492
+ <td>${actionBadge}</td>
1493
+ <td>${statusBadge}</td>
1494
+ <td>
1495
+ <div style="font-weight:600;font-size:13px;">${escapeHtml(entry.title)}</div>
1496
+ <div style="font-size:12px;color:#6b7280;">${escapeHtml(entry.description)}</div>
1497
+ </td>
1498
+ <td style="max-width:200px;">${impactHtml}</td>
1499
+ </tr>`;
1500
+ }
1501
+ html += `</tbody></table></div>`;
1502
+ return html;
1503
+ }
1196
1504
  function escapeHtml(str) {
1197
1505
  return str
1198
1506
  .replace(/&/g, "&amp;")
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "dependencies": {
3
- "@greenarmor/ges-audit-engine": "1.2.3",
4
- "@greenarmor/ges-core": "1.2.3",
5
- "@greenarmor/ges-policy-engine": "1.2.3",
6
- "@greenarmor/ges-scoring-engine": "1.2.3"
3
+ "@greenarmor/ges-audit-engine": "1.2.5",
4
+ "@greenarmor/ges-core": "1.2.5",
5
+ "@greenarmor/ges-policy-engine": "1.2.5",
6
+ "@greenarmor/ges-scoring-engine": "1.2.5"
7
7
  },
8
8
  "description": "GESF Web Dashboard - Visual compliance dashboard for teams",
9
9
  "devDependencies": {
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "type": "module",
42
42
  "types": "./dist/index.d.ts",
43
- "version": "1.2.3",
43
+ "version": "1.2.5",
44
44
  "scripts": {
45
45
  "build": "tsc",
46
46
  "clean": "rm -rf dist tsconfig.tsbuildinfo",