@greenarmor/ges 1.5.2 → 1.5.4
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/commands/assign.js +6 -6
- package/dist/commands/governance.js +189 -36
- package/dist/commands/hooks.js +44 -0
- package/dist/commands/mcp.js +29 -0
- package/dist/commands/policy.js +83 -2
- package/package.json +14 -14
package/dist/commands/assign.js
CHANGED
|
@@ -151,15 +151,15 @@ async function assignFinding(root, options) {
|
|
|
151
151
|
errorOut("Governance record not found.");
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
|
-
const assignee = options.assignee || await input({ message: "Assignee name:" });
|
|
154
|
+
const assignee = options.assignee || await input({ message: "Assignee name (who will fix this):" });
|
|
155
155
|
if (!assignee.trim()) {
|
|
156
|
-
errorOut("Assignee name is required.");
|
|
156
|
+
errorOut("Assignee name is required.", "Please provide the name of the person responsible for this fix.");
|
|
157
157
|
return;
|
|
158
158
|
}
|
|
159
|
-
const assigneeRole = options.assigneeRole || await input({ message: "Assignee role (optional):" });
|
|
160
|
-
const notes = options.notes || await input({ message: "Notes (optional):" });
|
|
161
|
-
const actorName = options.actor || await input({ message: "Your name (for audit trail):" });
|
|
162
|
-
const actorRole = options.actorRole || await input({ message: "Your role (optional):" });
|
|
159
|
+
const assigneeRole = options.assigneeRole || await input({ message: "Assignee role (optional, e.g., 'Security Engineer'):", default: "" });
|
|
160
|
+
const notes = options.notes || await input({ message: "Notes (optional, e.g., 'Urgent — fix before release'):", default: "" });
|
|
161
|
+
const actorName = options.actor || await input({ message: "Your name (for audit trail):", default: "cli-user" });
|
|
162
|
+
const actorRole = options.actorRole || await input({ message: "Your role (optional):", default: "" });
|
|
163
163
|
const assignment = createFixAssignment({
|
|
164
164
|
finding_key: fkey,
|
|
165
165
|
finding_rule_id: selectedFinding.ruleId,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { ensureGESInitialized } from "../utils/project.js";
|
|
3
3
|
import { input, select } from "../utils/prompts.js";
|
|
4
|
-
import { banner, divider, blank, success, info, kv, statusBadge, severityBadge, BOLD, DIM, GREEN, RED, YELLOW, GRAY, } from "../utils/ui.js";
|
|
4
|
+
import { banner, divider, blank, success, error, warn, info, kv, label, statusBadge, severityBadge, BOLD, DIM, GREEN, RED, YELLOW, GRAY, } from "../utils/ui.js";
|
|
5
5
|
import { loadGovernanceRecords, createGovernanceRecord, addGovernanceRecord, findGovernanceRecord, setGovernanceApproval, addGovernanceEvidence, createEvidenceRef, verifyGovernanceRecord, deleteGovernanceRecord, setGovernanceRiskAssessment, setGovernancePolicyBasis, setGovernanceReviewCycle, setGovernanceDataInventory, setGovernanceComplianceLinks, setGovernanceCommittee, } from "@greenarmor/ges-core";
|
|
6
6
|
import { recordActivity } from "@greenarmor/ges-core";
|
|
7
7
|
const STATUS_BADGE = {
|
|
@@ -32,8 +32,157 @@ function printRecordSummary(record) {
|
|
|
32
32
|
}
|
|
33
33
|
console.log(` ${DIM("Ev")} ${record.evidence.length} reference(s)`);
|
|
34
34
|
}
|
|
35
|
+
async function showGovernanceNextAction(root, records, lastShownId) {
|
|
36
|
+
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true)
|
|
37
|
+
return;
|
|
38
|
+
const choices = [];
|
|
39
|
+
if (lastShownId) {
|
|
40
|
+
choices.push({ name: `Verify this record ${DIM("— check provenance completeness")}`, value: `ges governance verify ${lastShownId}` });
|
|
41
|
+
if (records.length > 1) {
|
|
42
|
+
choices.push({ name: `Show another record ${DIM("— pick from list")}`, value: `__pick_show__` });
|
|
43
|
+
}
|
|
44
|
+
choices.push({ name: `Record an approval ${DIM("— add approval decision")}`, value: `ges governance approve ${lastShownId}` });
|
|
45
|
+
choices.push({ name: `Add evidence reference ${DIM("— link to Jira/Confluence/etc")}`, value: `ges governance evidence ${lastShownId}` });
|
|
46
|
+
choices.push({ name: `Link risk assessment ${DIM("— assessor, methodology, score")}`, value: `ges governance risk-assessment ${lastShownId}` });
|
|
47
|
+
choices.push({ name: `Document policy basis ${DIM("— which policy applies")}`, value: `ges governance policy-basis ${lastShownId}` });
|
|
48
|
+
}
|
|
49
|
+
else if (records.length > 0) {
|
|
50
|
+
choices.push({ name: `Show a record's full provenance chain ${DIM("— all dimensions in detail")}`, value: `__pick_show__` });
|
|
51
|
+
choices.push({ name: `Verify a record's completeness ${DIM("— check all 8 dimensions")}`, value: `__pick_verify__` });
|
|
52
|
+
}
|
|
53
|
+
choices.push({ name: `Create a new governance record ${DIM("— start a new approval chain")}`, value: `ges governance add` });
|
|
54
|
+
if (records.length === 0) {
|
|
55
|
+
choices.length = 0;
|
|
56
|
+
choices.push({ name: `Create a new governance record ${DIM("— start a new approval chain")}`, value: `ges governance add` });
|
|
57
|
+
}
|
|
58
|
+
choices.push({ name: `${YELLOW("Exit")} ${DIM("— return to terminal")}`, value: `exit` });
|
|
59
|
+
divider();
|
|
60
|
+
label("What would you like to do next?");
|
|
61
|
+
const answer = await select({
|
|
62
|
+
message: "Choose your next action:",
|
|
63
|
+
choices,
|
|
64
|
+
});
|
|
65
|
+
if (answer === "exit") {
|
|
66
|
+
blank();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
let cmd = answer;
|
|
70
|
+
if (answer === "__pick_show__" || answer === "__pick_verify__") {
|
|
71
|
+
let recordChoice;
|
|
72
|
+
if (records.length === 1) {
|
|
73
|
+
recordChoice = records[0].id;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
recordChoice = await select({
|
|
77
|
+
message: "Select a record:",
|
|
78
|
+
choices: records.map(r => ({
|
|
79
|
+
name: `${r.system_name} ${GRAY(`(${r.status}, ${r.risk_level})`)}`,
|
|
80
|
+
value: r.id,
|
|
81
|
+
})),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const sub = answer === "__pick_show__" ? "show" : "verify";
|
|
85
|
+
cmd = `ges governance ${sub} ${recordChoice}`;
|
|
86
|
+
}
|
|
87
|
+
blank();
|
|
88
|
+
info("Running", GREEN(cmd));
|
|
89
|
+
divider();
|
|
90
|
+
blank();
|
|
91
|
+
const { execSync } = await import("node:child_process");
|
|
92
|
+
try {
|
|
93
|
+
execSync(cmd, { stdio: "inherit" });
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
35
99
|
export const governanceCommand = new Command("governance")
|
|
36
100
|
.description("Manage governance approval provenance chains")
|
|
101
|
+
.action(async () => {
|
|
102
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
103
|
+
governanceCommand.outputHelp();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
banner("GESF Governance", "Provenance Chain Management");
|
|
107
|
+
let root;
|
|
108
|
+
try {
|
|
109
|
+
root = ensureGESInitialized();
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
error("GESF is not initialized.", "Run `ges init` first.");
|
|
113
|
+
blank();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const records = loadGovernanceRecords(root);
|
|
117
|
+
if (records.length > 0) {
|
|
118
|
+
console.log(` ${BOLD("Existing Records")} ${GRAY(`(${records.length})`)}`);
|
|
119
|
+
records.forEach(r => printRecordSummary(r));
|
|
120
|
+
console.log("");
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
warn("No governance records yet.", "Create one to start building a provenance chain.");
|
|
124
|
+
blank();
|
|
125
|
+
}
|
|
126
|
+
const action = await select({
|
|
127
|
+
message: "What would you like to do?",
|
|
128
|
+
choices: [
|
|
129
|
+
{ name: `Create a new governance record ${DIM("— start a new approval chain")}`, value: "add" },
|
|
130
|
+
...(records.length > 0 ? [
|
|
131
|
+
{ name: `List all records ${DIM(`(${records.length} existing)`)}`, value: "list" },
|
|
132
|
+
{ name: `Show a record's full provenance chain ${DIM("— all dimensions in detail")}`, value: "show" },
|
|
133
|
+
{ name: `Verify a record's completeness ${DIM("— check all 8 dimensions")}`, value: "verify" },
|
|
134
|
+
{ name: `Record an approval decision ${DIM("— who approved, under what authority")}`, value: "approve" },
|
|
135
|
+
{ name: `Add an evidence reference ${DIM("— link to Jira, Confluence, etc.")}`, value: "evidence" },
|
|
136
|
+
{ name: `Link a risk assessment ${DIM("— assessor, methodology, score")}`, value: "risk-assessment" },
|
|
137
|
+
{ name: `Document the policy basis ${DIM("— which policy/standard applies")}`, value: "policy-basis" },
|
|
138
|
+
{ name: `Set up a review cycle ${DIM("— when to re-review")}`, value: "review-cycle" },
|
|
139
|
+
{ name: `Document data inventory ${DIM("— what personal data is processed")}`, value: "data-inventory" },
|
|
140
|
+
{ name: `Record committee approval ${DIM("— formal committee sign-off")}`, value: "committee" },
|
|
141
|
+
{ name: `Map compliance frameworks ${DIM("— GDPR, OWASP, etc.")}`, value: "compliance-links" },
|
|
142
|
+
{ name: `Delete a record ${DIM("— permanently remove")}`, value: "delete" },
|
|
143
|
+
] : []),
|
|
144
|
+
{ name: `${YELLOW("Exit")} ${DIM("— return to terminal")}`, value: "exit" },
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
if (action === "exit") {
|
|
148
|
+
blank();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
let cmd = `ges governance ${action}`;
|
|
152
|
+
if (["show", "verify", "approve", "evidence", "risk-assessment", "policy-basis", "review-cycle", "data-inventory", "committee", "compliance-links", "delete"].includes(action)) {
|
|
153
|
+
if (records.length === 0) {
|
|
154
|
+
error("No records to work with.", "Create one first with: ges governance add");
|
|
155
|
+
blank();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (records.length === 1) {
|
|
159
|
+
cmd += ` ${records[0].id}`;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const recordChoice = await select({
|
|
163
|
+
message: "Select a record:",
|
|
164
|
+
choices: [
|
|
165
|
+
...records.map(r => ({
|
|
166
|
+
name: `${r.system_name} ${GRAY(`(${r.status}, ${r.risk_level})`)}`,
|
|
167
|
+
value: r.id,
|
|
168
|
+
})),
|
|
169
|
+
],
|
|
170
|
+
});
|
|
171
|
+
cmd += ` ${recordChoice}`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
blank();
|
|
175
|
+
info("Running", GREEN(cmd));
|
|
176
|
+
divider();
|
|
177
|
+
blank();
|
|
178
|
+
const { execSync } = await import("node:child_process");
|
|
179
|
+
try {
|
|
180
|
+
execSync(cmd, { stdio: "inherit" });
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
})
|
|
37
186
|
.addCommand(new Command("add")
|
|
38
187
|
.description("Create a new governance record")
|
|
39
188
|
.option("-n, --name <name>", "System name")
|
|
@@ -119,10 +268,10 @@ export const governanceCommand = new Command("governance")
|
|
|
119
268
|
console.error(` Error: Governance record "${id}" not found.`);
|
|
120
269
|
process.exit(1);
|
|
121
270
|
}
|
|
122
|
-
const approverName = options.approver || await input({ message: "Approver name:", default: "" });
|
|
123
|
-
const approverRole = options.role || await input({ message: "Approver role:", default: "" });
|
|
124
|
-
const approverEmail = options.email || await input({ message: "Approver email:", default: "" });
|
|
125
|
-
const authority = options.authority || await input({ message: "Approval authority:", default: "" });
|
|
271
|
+
const approverName = options.approver || await input({ message: "Approver full name:", default: "" });
|
|
272
|
+
const approverRole = options.role || await input({ message: "Approver role/title (e.g., 'CISO', 'CTO'):", default: "" });
|
|
273
|
+
const approverEmail = options.email || await input({ message: "Approver email (optional):", default: "" });
|
|
274
|
+
const authority = options.authority || await input({ message: "Approval authority (e.g., 'AI Ethics Board', 'Security Committee'):", default: "" });
|
|
126
275
|
const decision = (options.decision || await select({
|
|
127
276
|
message: "Decision:",
|
|
128
277
|
choices: [
|
|
@@ -132,9 +281,9 @@ export const governanceCommand = new Command("governance")
|
|
|
132
281
|
],
|
|
133
282
|
}));
|
|
134
283
|
const validFrom = options.validFrom || new Date().toISOString().split("T")[0];
|
|
135
|
-
const validUntil = options.validUntil || await input({ message: "Valid until
|
|
136
|
-
const conditionsStr = options.conditions || await input({ message: "Conditions (comma-separated):", default: "" });
|
|
137
|
-
const rationale = options.rationale || await input({ message: "Rationale:", default: "" });
|
|
284
|
+
const validUntil = options.validUntil || await input({ message: "Valid until YYYY-MM-DD (or press Enter for indefinite):", default: "" });
|
|
285
|
+
const conditionsStr = options.conditions || await input({ message: "Conditions (comma-separated, or press Enter to skip):", default: "" });
|
|
286
|
+
const rationale = options.rationale || await input({ message: "Rationale (why was this decision made?):", default: "" });
|
|
138
287
|
const updated = setGovernanceApproval(root, record.id, {
|
|
139
288
|
approver_name: approverName,
|
|
140
289
|
approver_role: approverRole,
|
|
@@ -182,7 +331,7 @@ export const governanceCommand = new Command("governance")
|
|
|
182
331
|
console.error(` Error: Governance record "${id}" not found.`);
|
|
183
332
|
process.exit(1);
|
|
184
333
|
}
|
|
185
|
-
const title = options.title || await input({ message: "Evidence title:", default: "" });
|
|
334
|
+
const title = options.title || await input({ message: "Evidence title (e.g., 'DPIA Report Q4 2026'):", default: "" });
|
|
186
335
|
const sourceSystem = (options.source || await select({
|
|
187
336
|
message: "Source system:",
|
|
188
337
|
choices: [
|
|
@@ -198,7 +347,7 @@ export const governanceCommand = new Command("governance")
|
|
|
198
347
|
{ name: "Other", value: "other" },
|
|
199
348
|
],
|
|
200
349
|
}));
|
|
201
|
-
const reference = options.reference || await input({ message: "Reference (ticket ID, URL,
|
|
350
|
+
const reference = options.reference || await input({ message: "Reference (ticket ID, URL, or document path):", default: "" });
|
|
202
351
|
const evidenceType = await select({
|
|
203
352
|
message: "Evidence type:",
|
|
204
353
|
choices: [
|
|
@@ -214,7 +363,7 @@ export const governanceCommand = new Command("governance")
|
|
|
214
363
|
{ name: "Other", value: "other" },
|
|
215
364
|
],
|
|
216
365
|
});
|
|
217
|
-
const locationDesc = await input({ message: "Location description:", default: "" });
|
|
366
|
+
const locationDesc = await input({ message: "Location description (where to find it, optional):", default: "" });
|
|
218
367
|
const evidence = createEvidenceRef({
|
|
219
368
|
type: evidenceType,
|
|
220
369
|
title,
|
|
@@ -247,12 +396,13 @@ export const governanceCommand = new Command("governance")
|
|
|
247
396
|
}))
|
|
248
397
|
.addCommand(new Command("list")
|
|
249
398
|
.description("List all governance records")
|
|
250
|
-
.action(() => {
|
|
399
|
+
.action(async () => {
|
|
251
400
|
const root = ensureGESInitialized();
|
|
252
401
|
const records = loadGovernanceRecords(root);
|
|
253
402
|
if (records.length === 0) {
|
|
254
403
|
info("No governance records found.");
|
|
255
404
|
console.log(` ${DIM("Create one with:")} ${GREEN("ges governance add")}\n`);
|
|
405
|
+
await showGovernanceNextAction(root, records);
|
|
256
406
|
return;
|
|
257
407
|
}
|
|
258
408
|
blank();
|
|
@@ -262,11 +412,12 @@ export const governanceCommand = new Command("governance")
|
|
|
262
412
|
printRecordSummary(r);
|
|
263
413
|
console.log();
|
|
264
414
|
});
|
|
415
|
+
await showGovernanceNextAction(root, records);
|
|
265
416
|
}))
|
|
266
417
|
.addCommand(new Command("show")
|
|
267
418
|
.description("Show full provenance chain for a governance record")
|
|
268
419
|
.argument("<id>", "Record ID or system name")
|
|
269
|
-
.action((id) => {
|
|
420
|
+
.action(async (id) => {
|
|
270
421
|
const root = ensureGESInitialized();
|
|
271
422
|
const record = findGovernanceRecord(root, id);
|
|
272
423
|
if (!record) {
|
|
@@ -409,11 +560,12 @@ export const governanceCommand = new Command("governance")
|
|
|
409
560
|
}
|
|
410
561
|
console.log(`\n Created: ${record.created_at} by ${record.created_by}`);
|
|
411
562
|
console.log(` Updated: ${record.updated_at} by ${record.updated_by} (v${record.record_version})\n`);
|
|
563
|
+
await showGovernanceNextAction(root, [record], record.id);
|
|
412
564
|
}))
|
|
413
565
|
.addCommand(new Command("verify")
|
|
414
566
|
.description("Verify the provenance chain completeness of a governance record")
|
|
415
567
|
.argument("<id>", "Record ID or system name")
|
|
416
|
-
.action((id) => {
|
|
568
|
+
.action(async (id) => {
|
|
417
569
|
const root = ensureGESInitialized();
|
|
418
570
|
const record = findGovernanceRecord(root, id);
|
|
419
571
|
if (!record) {
|
|
@@ -458,6 +610,7 @@ export const governanceCommand = new Command("governance")
|
|
|
458
610
|
result.warnings.forEach(w => console.log(` ${YELLOW("△")} ${w}`));
|
|
459
611
|
}
|
|
460
612
|
console.log();
|
|
613
|
+
await showGovernanceNextAction(root, [record], record.id);
|
|
461
614
|
}))
|
|
462
615
|
.addCommand(new Command("delete")
|
|
463
616
|
.description("Delete a governance record")
|
|
@@ -507,12 +660,12 @@ export const governanceCommand = new Command("governance")
|
|
|
507
660
|
console.error(` Error: Governance record "${id}" not found.`);
|
|
508
661
|
process.exit(1);
|
|
509
662
|
}
|
|
510
|
-
const assessor = options.assessor || await input({ message: "
|
|
511
|
-
const methodology = options.methodology || await input({ message: "Methodology:", default: "" });
|
|
512
|
-
const score = options.score || await input({ message: "Risk score:", default: "" });
|
|
513
|
-
const residual = options.residual || await input({ message: "Residual risk level:", default: "" });
|
|
514
|
-
const risksStr = await input({ message: "Identified risks (comma-separated):", default: "" });
|
|
515
|
-
const mitigationsStr = await input({ message: "Mitigation measures (comma-separated):", default: "" });
|
|
663
|
+
const assessor = options.assessor || await input({ message: "Risk assessor name:", default: "" });
|
|
664
|
+
const methodology = options.methodology || await input({ message: "Methodology (e.g., 'NIST RMF', 'ISO 27005'):", default: "" });
|
|
665
|
+
const score = options.score || await input({ message: "Risk score (e.g., '7.5/10', 'High'):", default: "" });
|
|
666
|
+
const residual = options.residual || await input({ message: "Residual risk level (low/medium/high):", default: "" });
|
|
667
|
+
const risksStr = await input({ message: "Identified risks (comma-separated, or Enter to skip):", default: "" });
|
|
668
|
+
const mitigationsStr = await input({ message: "Mitigation measures (comma-separated, or Enter to skip):", default: "" });
|
|
516
669
|
const updated = setGovernanceRiskAssessment(root, record.id, {
|
|
517
670
|
id: `risk-${Date.now()}`,
|
|
518
671
|
assessor,
|
|
@@ -552,11 +705,11 @@ export const governanceCommand = new Command("governance")
|
|
|
552
705
|
console.error(` Error: Governance record "${id}" not found.`);
|
|
553
706
|
process.exit(1);
|
|
554
707
|
}
|
|
555
|
-
const policyId = options.policyId || await input({ message: "Policy ID:", default: "" });
|
|
556
|
-
const policyName = options.policyName || await input({ message: "Policy name:", default: "" });
|
|
708
|
+
const policyId = options.policyId || await input({ message: "Policy ID (e.g., 'POL-SEC-001'):", default: "" });
|
|
709
|
+
const policyName = options.policyName || await input({ message: "Policy name (e.g., 'Information Security Policy'):", default: "" });
|
|
557
710
|
const version = options.pv || await input({ message: "Policy version:", default: "1.0" });
|
|
558
|
-
const standard = options.standard || await input({ message: "Standard:", default: "" });
|
|
559
|
-
const clausesStr = await input({ message: "Applicable clauses (comma-separated):", default: "" });
|
|
711
|
+
const standard = options.standard || await input({ message: "Standard (e.g., 'GDPR', 'ISO 27001'):", default: "" });
|
|
712
|
+
const clausesStr = await input({ message: "Applicable clauses (comma-separated, or Enter to skip):", default: "" });
|
|
560
713
|
const updated = setGovernancePolicyBasis(root, record.id, {
|
|
561
714
|
policy_id: policyId,
|
|
562
715
|
policy_name: policyName,
|
|
@@ -633,11 +786,11 @@ export const governanceCommand = new Command("governance")
|
|
|
633
786
|
console.error(` Error: Governance record "${id}" not found.`);
|
|
634
787
|
process.exit(1);
|
|
635
788
|
}
|
|
636
|
-
const categoriesStr = options.categories || await input({ message: "Personal data categories (
|
|
637
|
-
const purposesStr = options.purposes || await input({ message: "Processing purposes (
|
|
638
|
-
const subjectsStr = await input({ message: "Data subjects (
|
|
639
|
-
const transfersStr = await input({ message: "Cross-border transfers (
|
|
640
|
-
const retention = options.retention || await input({ message: "Retention period:", default: "" });
|
|
789
|
+
const categoriesStr = options.categories || await input({ message: "Personal data categories (e.g., 'names,emails,IP addresses'):", default: "" });
|
|
790
|
+
const purposesStr = options.purposes || await input({ message: "Processing purposes (e.g., 'user auth,analytics'):", default: "" });
|
|
791
|
+
const subjectsStr = await input({ message: "Data subjects (e.g., 'customers,employees', or Enter to skip):", default: "" });
|
|
792
|
+
const transfersStr = await input({ message: "Cross-border transfers (e.g., 'US,EU', or Enter to skip):", default: "" });
|
|
793
|
+
const retention = options.retention || await input({ message: "Retention period (e.g., '2 years', '90 days'):", default: "" });
|
|
641
794
|
const updated = setGovernanceDataInventory(root, record.id, {
|
|
642
795
|
personal_data_categories: categoriesStr ? categoriesStr.split(",").map((s) => s.trim()).filter(Boolean) : [],
|
|
643
796
|
processing_purposes: purposesStr ? purposesStr.split(",").map((s) => s.trim()).filter(Boolean) : [],
|
|
@@ -669,11 +822,11 @@ export const governanceCommand = new Command("governance")
|
|
|
669
822
|
console.error(` Error: Governance record "${id}" not found.`);
|
|
670
823
|
process.exit(1);
|
|
671
824
|
}
|
|
672
|
-
const committeeName = options.committee || await input({ message: "Committee name:", default: "" });
|
|
673
|
-
const meetingRef = options.meetingRef || await input({ message: "Meeting reference:", default: "" });
|
|
825
|
+
const committeeName = options.committee || await input({ message: "Committee name (e.g., 'AI Ethics Board'):", default: "" });
|
|
826
|
+
const meetingRef = options.meetingRef || await input({ message: "Meeting reference (e.g., 'MIN-2026-001'):", default: "" });
|
|
674
827
|
const meetingDate = options.meetingDate || await input({ message: "Meeting date (YYYY-MM-DD):", default: "" });
|
|
675
|
-
const attendeesStr = await input({ message: "Attendees (comma-separated):", default: "" });
|
|
676
|
-
const summary = await input({ message: "Decision summary:", default: "" });
|
|
828
|
+
const attendeesStr = await input({ message: "Attendees (comma-separated names, or Enter to skip):", default: "" });
|
|
829
|
+
const summary = await input({ message: "Decision summary (what was decided):", default: "" });
|
|
677
830
|
const updated = setGovernanceCommittee(root, record.id, {
|
|
678
831
|
committee_name: committeeName,
|
|
679
832
|
meeting_date: meetingDate,
|
|
@@ -707,9 +860,9 @@ export const governanceCommand = new Command("governance")
|
|
|
707
860
|
console.error(` Error: Governance record "${id}" not found.`);
|
|
708
861
|
process.exit(1);
|
|
709
862
|
}
|
|
710
|
-
const frameworksStr = options.frameworks || await input({ message: "Frameworks (
|
|
711
|
-
const controlsStr = options.controls || await input({ message: "Controls satisfied (
|
|
712
|
-
const packsStr = await input({ message: "Control pack IDs (comma-separated):", default: "" });
|
|
863
|
+
const frameworksStr = options.frameworks || await input({ message: "Frameworks (e.g., 'GDPR,OWASP'):", default: "" });
|
|
864
|
+
const controlsStr = options.controls || await input({ message: "Controls satisfied (e.g., 'GDPR-ART32-002', or Enter to skip):", default: "" });
|
|
865
|
+
const packsStr = await input({ message: "Control pack IDs (comma-separated, or Enter to skip):", default: "" });
|
|
713
866
|
const updated = setGovernanceComplianceLinks(root, record.id, {
|
|
714
867
|
frameworks: frameworksStr ? frameworksStr.split(",").map((s) => s.trim()).filter(Boolean) : [],
|
|
715
868
|
controls_satisfied: controlsStr ? controlsStr.split(",").map((s) => s.trim()).filter(Boolean) : [],
|
package/dist/commands/hooks.js
CHANGED
|
@@ -2,8 +2,52 @@ import { Command } from "commander";
|
|
|
2
2
|
import { ensureGESInitialized } from "../utils/project.js";
|
|
3
3
|
import { installHooks, uninstallHooks } from "@greenarmor/ges-git-hooks";
|
|
4
4
|
import { recordActivity } from "@greenarmor/ges-core";
|
|
5
|
+
import { banner, blank, DIM, YELLOW } from "../utils/ui.js";
|
|
6
|
+
import { select } from "../utils/prompts.js";
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
5
9
|
export const hooksCommand = new Command("hooks")
|
|
6
10
|
.description("Manage GESF git hooks (pre-commit compliance enforcement)")
|
|
11
|
+
.action(async () => {
|
|
12
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
13
|
+
hooksCommand.outputHelp();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
banner("Git Hooks", "Pre-commit compliance enforcement");
|
|
17
|
+
let root;
|
|
18
|
+
try {
|
|
19
|
+
root = ensureGESInitialized();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
const { error } = await import("../utils/ui.js");
|
|
23
|
+
error("GESF is not initialized.", "Run `ges init` first.");
|
|
24
|
+
blank();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const hookPath = path.join(root, ".git", "hooks", "pre-commit");
|
|
28
|
+
const isInstalled = fs.existsSync(hookPath);
|
|
29
|
+
const action = await select({
|
|
30
|
+
message: "What would you like to do?",
|
|
31
|
+
choices: [
|
|
32
|
+
...(isInstalled
|
|
33
|
+
? [{ name: `Uninstall pre-commit hook ${DIM("— remove compliance gate")}`, value: "uninstall" }]
|
|
34
|
+
: [{ name: `Install pre-commit hook ${DIM("— blocks commits with critical findings")}`, value: "install" }]),
|
|
35
|
+
{ name: `${YELLOW("Exit")} ${DIM("— return to terminal")}`, value: "exit" },
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
if (action === "exit") {
|
|
39
|
+
blank();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
blank();
|
|
43
|
+
const { execSync } = await import("node:child_process");
|
|
44
|
+
try {
|
|
45
|
+
execSync(`ges hooks ${action}`, { stdio: "inherit" });
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
})
|
|
7
51
|
.addCommand(new Command("install")
|
|
8
52
|
.description("Install the pre-commit hook that runs ges audit before each commit")
|
|
9
53
|
.action(async () => {
|
package/dist/commands/mcp.js
CHANGED
|
@@ -2,8 +2,37 @@ import { Command } from "commander";
|
|
|
2
2
|
import * as readline from "node:readline";
|
|
3
3
|
import { handleRequest } from "@greenarmor/ges-mcp-server";
|
|
4
4
|
import { mcpSetupCommand } from "./mcp-setup.js";
|
|
5
|
+
import { banner, blank, DIM, YELLOW } from "../utils/ui.js";
|
|
6
|
+
import { select } from "../utils/prompts.js";
|
|
5
7
|
export const mcpCommand = new Command("mcp")
|
|
6
8
|
.description("MCP AI Compliance Assistant")
|
|
9
|
+
.action(async () => {
|
|
10
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
11
|
+
mcpCommand.outputHelp();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
banner("GESF MCP Server", "AI Compliance Assistant Integration");
|
|
15
|
+
const action = await select({
|
|
16
|
+
message: "What would you like to do?",
|
|
17
|
+
choices: [
|
|
18
|
+
{ name: `Setup MCP clients ${DIM("— configure Claude, Cursor, VS Code, etc.")}`, value: "setup" },
|
|
19
|
+
{ name: `Start MCP server ${DIM("— JSON-RPC over stdio (for advanced use)")}`, value: "start" },
|
|
20
|
+
{ name: `${YELLOW("Exit")} ${DIM("— return to terminal")}`, value: "exit" },
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
if (action === "exit") {
|
|
24
|
+
blank();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
blank();
|
|
28
|
+
const { execSync } = await import("node:child_process");
|
|
29
|
+
try {
|
|
30
|
+
execSync(`ges mcp ${action}`, { stdio: "inherit" });
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
})
|
|
7
36
|
.addCommand(new Command("start")
|
|
8
37
|
.description("Start the GESF MCP server (JSON-RPC over stdio)")
|
|
9
38
|
.action(() => {
|
package/dist/commands/policy.js
CHANGED
|
@@ -3,11 +3,92 @@ import { getAllPacks, listPackIds } from "@greenarmor/ges-policy-engine";
|
|
|
3
3
|
import { ensureGESInitialized, writeFileSync } from "../utils/project.js";
|
|
4
4
|
import { addFrameworkToConfig, removeFrameworkFromConfig, recordActivity } from "@greenarmor/ges-core";
|
|
5
5
|
import { showNextStepsMenu } from "../utils/next-steps.js";
|
|
6
|
-
import { banner, blank, success, error, BOLD, CYAN, DIM, GRAY } from "../utils/ui.js";
|
|
6
|
+
import { banner, blank, success, error, BOLD, CYAN, DIM, GRAY, YELLOW } from "../utils/ui.js";
|
|
7
|
+
import { select } from "../utils/prompts.js";
|
|
7
8
|
import * as fs from "node:fs";
|
|
8
9
|
import * as path from "node:path";
|
|
9
10
|
const policyCmd = new Command("policy")
|
|
10
|
-
.description("Manage policy packs")
|
|
11
|
+
.description("Manage policy packs")
|
|
12
|
+
.action(async () => {
|
|
13
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
14
|
+
policyCmd.outputHelp();
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
banner("Policy Packs", "Compliance control management");
|
|
18
|
+
let root;
|
|
19
|
+
try {
|
|
20
|
+
root = ensureGESInitialized();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
error("GESF is not initialized.", "Run `ges init` first.");
|
|
24
|
+
blank();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const installedPacks = fs.existsSync(path.join(root, "controls"))
|
|
28
|
+
? fs.readdirSync(path.join(root, "controls")).filter(d => {
|
|
29
|
+
try {
|
|
30
|
+
return fs.statSync(path.join(root, "controls", d)).isDirectory();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
: [];
|
|
37
|
+
if (installedPacks.length > 0) {
|
|
38
|
+
console.log(` ${BOLD("Installed Packs")} ${GRAY(`(${installedPacks.length})`)}`);
|
|
39
|
+
installedPacks.forEach(p => console.log(` ${GRAY("•")} ${p}`));
|
|
40
|
+
console.log("");
|
|
41
|
+
}
|
|
42
|
+
const action = await select({
|
|
43
|
+
message: "What would you like to do?",
|
|
44
|
+
choices: [
|
|
45
|
+
{ name: `List all available packs ${DIM("— see what can be installed")}`, value: "list" },
|
|
46
|
+
{ name: `Install a pack ${DIM("— add compliance controls")}`, value: "install" },
|
|
47
|
+
...(installedPacks.length > 0 ? [
|
|
48
|
+
{ name: `Remove a pack ${DIM(`(${installedPacks.length} installed)`)}`, value: "remove" },
|
|
49
|
+
] : []),
|
|
50
|
+
{ name: `${YELLOW("Exit")} ${DIM("— return to terminal")}`, value: "exit" },
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
if (action === "exit") {
|
|
54
|
+
blank();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
let cmd = "ges policy";
|
|
58
|
+
if (action === "list") {
|
|
59
|
+
cmd += " list";
|
|
60
|
+
}
|
|
61
|
+
else if (action === "install") {
|
|
62
|
+
const packs = getAllPacks();
|
|
63
|
+
const packChoice = await select({
|
|
64
|
+
message: "Select a pack to install:",
|
|
65
|
+
choices: [
|
|
66
|
+
...packs.map(p => ({
|
|
67
|
+
name: `${p.id.padEnd(16)} ${DIM(p.name)} ${GRAY(`(${p.controls.length} controls)`)}`,
|
|
68
|
+
value: p.id,
|
|
69
|
+
})),
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
cmd += ` install ${packChoice}`;
|
|
73
|
+
}
|
|
74
|
+
else if (action === "remove") {
|
|
75
|
+
const packChoice = await select({
|
|
76
|
+
message: "Select a pack to remove:",
|
|
77
|
+
choices: [
|
|
78
|
+
...installedPacks.map(p => ({ name: p, value: p })),
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
cmd += ` remove ${packChoice}`;
|
|
82
|
+
}
|
|
83
|
+
blank();
|
|
84
|
+
const { execSync } = await import("node:child_process");
|
|
85
|
+
try {
|
|
86
|
+
execSync(cmd, { stdio: "inherit" });
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
11
92
|
policyCmd
|
|
12
93
|
.command("list")
|
|
13
94
|
.description("List available policy packs")
|
package/package.json
CHANGED
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
"ges": "./dist/cli.js"
|
|
4
4
|
},
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@greenarmor/ges-audit-engine": "1.5.
|
|
7
|
-
"@greenarmor/ges-cicd-generator": "1.5.
|
|
8
|
-
"@greenarmor/ges-compliance-engine": "1.5.
|
|
9
|
-
"@greenarmor/ges-core": "1.5.
|
|
10
|
-
"@greenarmor/ges-doc-generator": "1.5.
|
|
11
|
-
"@greenarmor/ges-git-hooks": "1.5.
|
|
12
|
-
"@greenarmor/ges-mcp-server": "1.5.
|
|
13
|
-
"@greenarmor/ges-policy-engine": "1.5.
|
|
14
|
-
"@greenarmor/ges-report-generator": "1.5.
|
|
15
|
-
"@greenarmor/ges-rules-engine": "1.5.
|
|
16
|
-
"@greenarmor/ges-scanner-integration": "1.5.
|
|
17
|
-
"@greenarmor/ges-scoring-engine": "1.5.
|
|
18
|
-
"@greenarmor/ges-web-dashboard": "1.5.
|
|
6
|
+
"@greenarmor/ges-audit-engine": "1.5.4",
|
|
7
|
+
"@greenarmor/ges-cicd-generator": "1.5.4",
|
|
8
|
+
"@greenarmor/ges-compliance-engine": "1.5.4",
|
|
9
|
+
"@greenarmor/ges-core": "1.5.4",
|
|
10
|
+
"@greenarmor/ges-doc-generator": "1.5.4",
|
|
11
|
+
"@greenarmor/ges-git-hooks": "1.5.4",
|
|
12
|
+
"@greenarmor/ges-mcp-server": "1.5.4",
|
|
13
|
+
"@greenarmor/ges-policy-engine": "1.5.4",
|
|
14
|
+
"@greenarmor/ges-report-generator": "1.5.4",
|
|
15
|
+
"@greenarmor/ges-rules-engine": "1.5.4",
|
|
16
|
+
"@greenarmor/ges-scanner-integration": "1.5.4",
|
|
17
|
+
"@greenarmor/ges-scoring-engine": "1.5.4",
|
|
18
|
+
"@greenarmor/ges-web-dashboard": "1.5.4",
|
|
19
19
|
"chalk": "^5.6.2",
|
|
20
20
|
"commander": "^13.0.0"
|
|
21
21
|
},
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"type": "module",
|
|
62
62
|
"types": "./dist/index.d.ts",
|
|
63
|
-
"version": "1.5.
|
|
63
|
+
"version": "1.5.4",
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "tsc",
|
|
66
66
|
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|