@greenarmor/ges 1.3.0 → 1.4.1

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/cli.js CHANGED
@@ -17,6 +17,7 @@ import { controlCommand } from "./commands/control.js";
17
17
  import { fixCommand } from "./commands/fix.js";
18
18
  import { hooksCommand } from "./commands/hooks.js";
19
19
  import { dashboardCommand } from "./commands/dashboard.js";
20
+ import { governanceCommand } from "./commands/governance.js";
20
21
  import { CLI_VERSION } from "./utils/version.js";
21
22
  const program = new Command();
22
23
  program
@@ -40,4 +41,5 @@ program.addCommand(controlCommand);
40
41
  program.addCommand(fixCommand);
41
42
  program.addCommand(hooksCommand);
42
43
  program.addCommand(dashboardCommand);
44
+ program.addCommand(governanceCommand);
43
45
  program.parse();
@@ -5,6 +5,7 @@ import { getPacksForProjectType, getAllPacks } from "@greenarmor/ges-policy-engi
5
5
  import { generateScoreFile, formatScoreOutput } from "@greenarmor/ges-scoring-engine";
6
6
  import { runAudit, runAuditIncremental, deduplicateFindings } from "@greenarmor/ges-audit-engine";
7
7
  import { showNextStepsMenu } from "../utils/next-steps.js";
8
+ import { banner, divider, blank, success, warn, info, severityBadge, BOLD, DIM, CYAN, RED, YELLOW, GRAY } from "../utils/ui.js";
8
9
  import * as fs from "node:fs";
9
10
  import * as path from "node:path";
10
11
  export const auditCommand = new Command("audit")
@@ -15,9 +16,10 @@ export const auditCommand = new Command("audit")
15
16
  .action(async (options) => {
16
17
  const root = ensureGESInitialized();
17
18
  const config = readJsonFile(path.join(root, ".ges", "config.json"));
18
- console.log("\n GESF Compliance Audit");
19
- console.log(" ────────────────────\n");
20
- console.log(" Scanning project files...");
19
+ if (!options.json) {
20
+ banner("GESF Compliance Audit", options.incremental ? "Incremental scan" : "Full project scan");
21
+ info("Scanning project files...");
22
+ }
21
23
  let rawFindings;
22
24
  let scannedFiles;
23
25
  if (options.incremental) {
@@ -27,16 +29,19 @@ export const auditCommand = new Command("audit")
27
29
  writeJsonFile(cachePath, result.newCache);
28
30
  rawFindings = result.findings;
29
31
  scannedFiles = result.scannedFiles;
30
- console.log(` Scanned ${scannedFiles} files (${result.changedFiles} changed)`);
32
+ if (!options.json)
33
+ success("Scan complete", `${scannedFiles} files (${result.changedFiles} changed)`);
31
34
  }
32
35
  else {
33
36
  const result = runAudit(root);
34
37
  rawFindings = result.findings;
35
38
  scannedFiles = result.scannedFiles;
36
- console.log(` Scanned ${scannedFiles} files`);
39
+ if (!options.json)
40
+ success("Scan complete", `${scannedFiles} files`);
37
41
  }
38
42
  const findings = deduplicateFindings(rawFindings);
39
- console.log("");
43
+ if (!options.json)
44
+ blank();
40
45
  const configFrameworks = (config?.frameworks || ["GDPR", "OWASP"]);
41
46
  const projectPacks = getPacksForProjectType(config?.project_type || "generic-web-application");
42
47
  const packIds = new Set(projectPacks.map(p => p.id));
@@ -76,7 +81,8 @@ export const auditCommand = new Command("audit")
76
81
  if (overrides.length > 0) {
77
82
  const naCount = overrides.filter(o => o.status === "not-applicable").length;
78
83
  const passCount = overrides.filter(o => o.status === "pass").length;
79
- console.log(` Control overrides: ${naCount} not-applicable, ${passCount} pre-verified\n`);
84
+ if (!options.json)
85
+ info("Control overrides", `${naCount} not-applicable, ${passCount} pre-verified`);
80
86
  }
81
87
  if (options.json) {
82
88
  console.log(JSON.stringify({ findings, score: scoreData }, null, 2));
@@ -84,34 +90,37 @@ export const auditCommand = new Command("audit")
84
90
  process.exit(1);
85
91
  return;
86
92
  }
87
- console.log(" ── Findings ─────────────────────\n");
88
- console.log(` Total findings: ${findings.length}`);
89
- console.log(` Critical: ${critical.length} High: ${high.length} Medium: ${medium.length} Low: ${low.length}\n`);
93
+ console.log(` ${BOLD("Findings")}`);
94
+ divider(40);
95
+ console.log(` ${DIM("Total")} ${findings.length}`);
96
+ console.log(` ${RED(`Critical ${critical.length}`)} ${RED(`High ${high.length}`)} ${YELLOW(`Medium ${medium.length}`)} ${CYAN(`Low ${low.length}`)}\n`);
90
97
  if (findings.length > 0) {
91
98
  const grouped = groupByCategory(findings);
92
99
  for (const [category, categoryFindings] of Object.entries(grouped)) {
93
- console.log(` [${category.toUpperCase()}]`);
100
+ console.log(` ${BOLD(category.toUpperCase())}`);
94
101
  for (const f of categoryFindings.slice(0, 10)) {
95
- const sev = f.severity === "critical" ? "CRIT" : f.severity === "high" ? "HIGH" : f.severity === "medium" ? "MED " : "LOW ";
96
- const loc = f.file !== "project" ? ` (${f.file}${f.line ? ":" + f.line : ""})` : "";
97
- console.log(` [${sev}] ${f.title}${loc}`);
102
+ const loc = f.file !== "project" ? ` ${DIM(`(${f.file}${f.line ? ":" + f.line : ""})`)}` : "";
103
+ console.log(` ${severityBadge(f.severity).padEnd(10)} ${f.title}${loc}`);
98
104
  if (f.evidence && f.file !== "project") {
99
- console.log(` ${f.evidence.slice(0, 100)}`);
105
+ console.log(` ${GRAY(f.evidence.slice(0, 100))}`);
100
106
  }
101
107
  }
102
108
  if (categoryFindings.length > 10) {
103
- console.log(` ... and ${categoryFindings.length - 10} more`);
109
+ console.log(` ${DIM(`... and ${categoryFindings.length - 10} more`)}`);
104
110
  }
105
111
  console.log("");
106
112
  }
107
113
  }
108
114
  else {
109
- console.log("No security or compliance issues found in source code.\n");
115
+ success("No security or compliance issues found in source code.");
116
+ blank();
110
117
  }
111
- console.log(" ── Compliance Score ──────────────");
118
+ console.log(` ${BOLD("Compliance Score")}`);
119
+ divider(40);
112
120
  console.log(formatScoreOutput(scoreData));
113
121
  if (critical.length > 0) {
114
- console.log(" !! Critical issues must be resolved before deployment. !!\n");
122
+ warn("Critical issues must be resolved before deployment.");
123
+ blank();
115
124
  }
116
125
  recordActivity(root, {
117
126
  source: "cli",
@@ -1,15 +1,15 @@
1
1
  import { Command } from "commander";
2
2
  import { findProjectRoot, readJsonFile } from "../utils/project.js";
3
3
  import { CLI_VERSION } from "../utils/version.js";
4
- import { GES_DIR } from "@greenarmor/ges-core";
4
+ import { GES_DIR, loadGovernanceRecords, verifyGovernanceRecord } from "@greenarmor/ges-core";
5
5
  import { showNextStepsMenu } from "../utils/next-steps.js";
6
+ import { banner, success, warn, error, blank, progressBar, BOLD, DIM, GREEN, RED, YELLOW } from "../utils/ui.js";
6
7
  import * as fs from "node:fs";
7
8
  import * as path from "node:path";
8
9
  export const doctorCommand = new Command("doctor")
9
10
  .description("Diagnose GESF configuration and health")
10
11
  .action(async () => {
11
- console.log("\n GESF Doctor - Diagnostic Check");
12
- console.log(" ─────────────────────────────\n");
12
+ banner("GESF Doctor", "Diagnostic health check");
13
13
  const checks = [];
14
14
  const root = findProjectRoot();
15
15
  if (root) {
@@ -51,13 +51,54 @@ export const doctorCommand = new Command("doctor")
51
51
  checks.push({ name: "Project", status: "OK", detail: `${config.project_name} (${config.project_type})` });
52
52
  checks.push({ name: "Frameworks", status: "OK", detail: config.frameworks.join(", ") });
53
53
  }
54
+ const govRecords = loadGovernanceRecords(root);
55
+ if (govRecords.length > 0) {
56
+ let approved = 0;
57
+ let blockingIssues = 0;
58
+ let expiredApprovals = 0;
59
+ let missingReviewCycles = 0;
60
+ for (const record of govRecords) {
61
+ const verification = verifyGovernanceRecord(record);
62
+ if (verification.completeness.has_approval && record.approval && record.approval.decision === "approved")
63
+ approved++;
64
+ if (verification.issues.length > 0)
65
+ blockingIssues++;
66
+ if (verification.approval_status === "expired")
67
+ expiredApprovals++;
68
+ if (!record.review_cycle)
69
+ missingReviewCycles++;
70
+ }
71
+ checks.push({
72
+ name: "Governance records",
73
+ status: blockingIssues > 0 || expiredApprovals > 0 ? "WARN" : "OK",
74
+ detail: `${govRecords.length} record(s), ${approved} approved, ${blockingIssues} with blocking issues`,
75
+ });
76
+ if (expiredApprovals > 0) {
77
+ checks.push({ name: "Governance approvals", status: "WARN", detail: `${expiredApprovals} expired approval(s)` });
78
+ }
79
+ if (missingReviewCycles > 0) {
80
+ checks.push({ name: "Governance review cycles", status: "WARN", detail: `${missingReviewCycles} record(s) without review cycle` });
81
+ }
82
+ }
54
83
  }
55
84
  checks.push({ name: "GESF Version", status: "OK", detail: CLI_VERSION });
85
+ const okCount = checks.filter(c => c.status === "OK").length;
86
+ const warnCount = checks.filter(c => c.status === "WARN" || c.status === "MISSING").length;
87
+ const failCount = checks.filter(c => c.status === "FAIL").length;
88
+ console.log(` ${BOLD("Health Score")} ${progressBar(okCount, checks.length, 24)}`);
89
+ console.log(` ${DIM("Checks")} ${GREEN(`${okCount} ok`)} ${YELLOW(`${warnCount} warn`)} ${RED(`${failCount} fail`)}`);
90
+ blank();
56
91
  for (const check of checks) {
57
- const icon = check.status === "OK" ? "✓" : check.status === "WARN" ? "!" : "✗";
58
- const line = ` [${icon}] ${check.name}`;
59
- console.log(line + (check.detail ? ` - ${check.detail}` : ""));
92
+ if (check.status === "OK") {
93
+ success(check.name, check.detail);
94
+ }
95
+ else if (check.status === "WARN" || check.status === "MISSING") {
96
+ warn(check.name, check.detail);
97
+ }
98
+ else {
99
+ error(check.name, check.detail);
100
+ }
60
101
  }
61
- console.log("");
102
+ blank();
62
103
  await showNextStepsMenu("doctor");
63
104
  });
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const governanceCommand: Command;