@aigrc/cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aigrc.js +879 -211
- package/dist/aigrc.js.map +1 -1
- package/dist/index.js +507 -94
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/aigrc.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
2
8
|
|
|
3
9
|
// src/bin/aigrc.ts
|
|
4
10
|
import { program } from "commander";
|
|
@@ -714,59 +720,414 @@ function sanitizeFileName2(name) {
|
|
|
714
720
|
|
|
715
721
|
// src/commands/validate.ts
|
|
716
722
|
import { Command as Command4 } from "commander";
|
|
717
|
-
import
|
|
723
|
+
import chalk6 from "chalk";
|
|
718
724
|
import ora3 from "ora";
|
|
719
|
-
import
|
|
725
|
+
import path5 from "path";
|
|
720
726
|
import fs3 from "fs/promises";
|
|
727
|
+
import YAML from "yaml";
|
|
721
728
|
import {
|
|
722
729
|
loadAssetCard,
|
|
723
730
|
classifyRisk,
|
|
724
731
|
validateAssetCard
|
|
725
732
|
} from "@aigrc/core";
|
|
733
|
+
|
|
734
|
+
// src/utils/exit-codes.ts
|
|
735
|
+
function exit(code) {
|
|
736
|
+
process.exit(code);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// src/formatters/sarif.ts
|
|
740
|
+
function formatSarif(results, version = "0.1.0") {
|
|
741
|
+
const sarifResults = [];
|
|
742
|
+
const artifacts = [];
|
|
743
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
744
|
+
for (const result of results) {
|
|
745
|
+
if (!seenPaths.has(result.path)) {
|
|
746
|
+
seenPaths.add(result.path);
|
|
747
|
+
artifacts.push({
|
|
748
|
+
location: {
|
|
749
|
+
uri: result.path
|
|
750
|
+
},
|
|
751
|
+
mimeType: "application/x-yaml"
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
if (!result.validation.valid) {
|
|
755
|
+
for (const error of result.validation.errors) {
|
|
756
|
+
sarifResults.push({
|
|
757
|
+
ruleId: "AIGRC001",
|
|
758
|
+
level: "error",
|
|
759
|
+
message: {
|
|
760
|
+
text: error
|
|
761
|
+
},
|
|
762
|
+
locations: [
|
|
763
|
+
{
|
|
764
|
+
physicalLocation: {
|
|
765
|
+
artifactLocation: {
|
|
766
|
+
uri: result.path
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
],
|
|
771
|
+
properties: {
|
|
772
|
+
cardId: result.card?.id,
|
|
773
|
+
cardName: result.card?.name
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
} else {
|
|
778
|
+
sarifResults.push({
|
|
779
|
+
ruleId: "AIGRC001",
|
|
780
|
+
level: "none",
|
|
781
|
+
message: {
|
|
782
|
+
text: "Asset card validation passed"
|
|
783
|
+
},
|
|
784
|
+
locations: [
|
|
785
|
+
{
|
|
786
|
+
physicalLocation: {
|
|
787
|
+
artifactLocation: {
|
|
788
|
+
uri: result.path
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
],
|
|
793
|
+
properties: {
|
|
794
|
+
cardId: result.card?.id,
|
|
795
|
+
cardName: result.card?.name,
|
|
796
|
+
riskLevel: result.card?.classification?.riskLevel
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
const sarif = {
|
|
802
|
+
version: "2.1.0",
|
|
803
|
+
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
|
804
|
+
runs: [
|
|
805
|
+
{
|
|
806
|
+
tool: {
|
|
807
|
+
driver: {
|
|
808
|
+
name: "AIGRC",
|
|
809
|
+
version,
|
|
810
|
+
informationUri: "https://github.com/aigrc/aigrc",
|
|
811
|
+
rules: [
|
|
812
|
+
{
|
|
813
|
+
id: "AIGRC001",
|
|
814
|
+
name: "AssetCardValidation",
|
|
815
|
+
shortDescription: {
|
|
816
|
+
text: "Asset card must be valid according to AIGRC schema"
|
|
817
|
+
},
|
|
818
|
+
fullDescription: {
|
|
819
|
+
text: "Validates asset cards against AIGRC compliance requirements including risk classification, ownership, and regulatory framework mappings."
|
|
820
|
+
},
|
|
821
|
+
help: {
|
|
822
|
+
text: "Ensure your asset card includes all required fields and follows the AIGRC schema. Run 'aigrc validate --help' for more information."
|
|
823
|
+
},
|
|
824
|
+
defaultConfiguration: {
|
|
825
|
+
level: "error"
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
]
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
results: sarifResults,
|
|
832
|
+
artifacts
|
|
833
|
+
}
|
|
834
|
+
]
|
|
835
|
+
};
|
|
836
|
+
return sarif;
|
|
837
|
+
}
|
|
838
|
+
function sarifToJson(sarif, pretty = true) {
|
|
839
|
+
return JSON.stringify(sarif, null, pretty ? 2 : 0);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// src/formatters/text.ts
|
|
843
|
+
import chalk5 from "chalk";
|
|
844
|
+
import path4 from "path";
|
|
845
|
+
function printValidationSummary(results) {
|
|
846
|
+
console.log(chalk5.bold("Validation Summary"));
|
|
847
|
+
console.log(chalk5.dim("\u2500".repeat(50)));
|
|
848
|
+
console.log();
|
|
849
|
+
for (const result of results) {
|
|
850
|
+
const fileName = path4.basename(result.path);
|
|
851
|
+
if (!result.validation.valid) {
|
|
852
|
+
console.log(chalk5.red(`\u2717 ${fileName}`));
|
|
853
|
+
for (const error of result.validation.errors) {
|
|
854
|
+
console.log(chalk5.red(` Error: ${error}`));
|
|
855
|
+
}
|
|
856
|
+
if (result.fixed && result.fixedFields && result.fixedFields.length > 0) {
|
|
857
|
+
console.log(chalk5.yellow(` Fixed: ${result.fixedFields.join(", ")}`));
|
|
858
|
+
}
|
|
859
|
+
} else {
|
|
860
|
+
console.log(chalk5.green(`\u2713 ${fileName}`));
|
|
861
|
+
if (result.fixed && result.fixedFields && result.fixedFields.length > 0) {
|
|
862
|
+
console.log(chalk5.yellow(` Fixed: ${result.fixedFields.join(", ")}`));
|
|
863
|
+
}
|
|
864
|
+
if (result.card && result.classification) {
|
|
865
|
+
const tierColor = getRiskLevelColor(result.classification.riskLevel);
|
|
866
|
+
console.log(
|
|
867
|
+
chalk5.dim(" Risk Level: ") + tierColor(result.classification.riskLevel)
|
|
868
|
+
);
|
|
869
|
+
if (result.classification.euAiActCategory) {
|
|
870
|
+
console.log(
|
|
871
|
+
chalk5.dim(" EU AI Act: ") + chalk5.yellow(result.classification.euAiActCategory)
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
console.log();
|
|
877
|
+
}
|
|
878
|
+
const valid = results.filter((r) => r.validation.valid).length;
|
|
879
|
+
const invalid = results.filter((r) => !r.validation.valid).length;
|
|
880
|
+
const fixed = results.filter((r) => r.fixed).length;
|
|
881
|
+
console.log(chalk5.dim("\u2500".repeat(50)));
|
|
882
|
+
console.log(
|
|
883
|
+
`Total: ${results.length} | ` + chalk5.green(`Valid: ${valid}`) + ` | ` + chalk5.red(`Invalid: ${invalid}`) + (fixed > 0 ? ` | ${chalk5.yellow(`Fixed: ${fixed}`)}` : "")
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
function getRiskLevelColor(level) {
|
|
887
|
+
switch (level) {
|
|
888
|
+
case "minimal":
|
|
889
|
+
return chalk5.green;
|
|
890
|
+
case "limited":
|
|
891
|
+
return chalk5.yellow;
|
|
892
|
+
case "high":
|
|
893
|
+
return chalk5.red;
|
|
894
|
+
case "unacceptable":
|
|
895
|
+
return chalk5.magenta;
|
|
896
|
+
default:
|
|
897
|
+
return chalk5.white;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// src/fixers/auto-fix.ts
|
|
902
|
+
function autoFixAssetCard(card) {
|
|
903
|
+
const fixedFields = [];
|
|
904
|
+
const fixedCard = JSON.parse(JSON.stringify(card));
|
|
905
|
+
if (!fixedCard.metadata?.createdAt) {
|
|
906
|
+
if (!fixedCard.metadata) {
|
|
907
|
+
fixedCard.metadata = {
|
|
908
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
909
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
910
|
+
version: "1.0.0"
|
|
911
|
+
};
|
|
912
|
+
} else {
|
|
913
|
+
fixedCard.metadata.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
914
|
+
}
|
|
915
|
+
fixedFields.push("metadata.createdAt");
|
|
916
|
+
} else if (!isValidISODate(fixedCard.metadata.createdAt)) {
|
|
917
|
+
fixedCard.metadata.createdAt = normalizeDate(fixedCard.metadata.createdAt);
|
|
918
|
+
fixedFields.push("metadata.createdAt (format)");
|
|
919
|
+
}
|
|
920
|
+
if (!fixedCard.metadata?.updatedAt) {
|
|
921
|
+
if (!fixedCard.metadata) {
|
|
922
|
+
fixedCard.metadata = {
|
|
923
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
924
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
925
|
+
version: "1.0.0"
|
|
926
|
+
};
|
|
927
|
+
} else {
|
|
928
|
+
fixedCard.metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
929
|
+
}
|
|
930
|
+
fixedFields.push("metadata.updatedAt");
|
|
931
|
+
} else if (!isValidISODate(fixedCard.metadata.updatedAt)) {
|
|
932
|
+
fixedCard.metadata.updatedAt = normalizeDate(fixedCard.metadata.updatedAt);
|
|
933
|
+
fixedFields.push("metadata.updatedAt (format)");
|
|
934
|
+
}
|
|
935
|
+
if (!fixedCard.metadata?.version) {
|
|
936
|
+
if (!fixedCard.metadata) {
|
|
937
|
+
fixedCard.metadata = {
|
|
938
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
939
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
940
|
+
version: "1.0.0"
|
|
941
|
+
};
|
|
942
|
+
} else {
|
|
943
|
+
fixedCard.metadata.version = "1.0.0";
|
|
944
|
+
}
|
|
945
|
+
fixedFields.push("metadata.version");
|
|
946
|
+
}
|
|
947
|
+
if (fixedCard.classification?.riskLevel) {
|
|
948
|
+
const normalized = normalizeRiskLevel(fixedCard.classification.riskLevel);
|
|
949
|
+
if (normalized !== fixedCard.classification.riskLevel) {
|
|
950
|
+
fixedCard.classification.riskLevel = normalized;
|
|
951
|
+
fixedFields.push("classification.riskLevel");
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if (fixedCard.classification?.riskFactors?.piiProcessing !== void 0) {
|
|
955
|
+
const pii = fixedCard.classification.riskFactors.piiProcessing;
|
|
956
|
+
if (typeof pii === "boolean") {
|
|
957
|
+
fixedCard.classification.riskFactors.piiProcessing = pii ? "yes" : "no";
|
|
958
|
+
fixedFields.push("classification.riskFactors.piiProcessing");
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (!fixedCard.id) {
|
|
962
|
+
fixedCard.id = generateId(fixedCard.name);
|
|
963
|
+
fixedFields.push("id");
|
|
964
|
+
}
|
|
965
|
+
if (!fixedCard.name) {
|
|
966
|
+
fixedCard.name = "Unnamed Asset";
|
|
967
|
+
fixedFields.push("name");
|
|
968
|
+
}
|
|
969
|
+
if (!fixedCard.description) {
|
|
970
|
+
fixedCard.description = "No description provided";
|
|
971
|
+
fixedFields.push("description");
|
|
972
|
+
}
|
|
973
|
+
if (!fixedCard.classification) {
|
|
974
|
+
fixedCard.classification = {
|
|
975
|
+
riskLevel: "minimal",
|
|
976
|
+
riskFactors: {
|
|
977
|
+
autonomousDecisions: false,
|
|
978
|
+
customerFacing: false,
|
|
979
|
+
toolExecution: false,
|
|
980
|
+
externalDataAccess: false,
|
|
981
|
+
piiProcessing: "unknown",
|
|
982
|
+
highStakesDecisions: false
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
fixedFields.push("classification");
|
|
986
|
+
}
|
|
987
|
+
if (!fixedCard.classification.riskFactors) {
|
|
988
|
+
fixedCard.classification.riskFactors = {
|
|
989
|
+
autonomousDecisions: false,
|
|
990
|
+
customerFacing: false,
|
|
991
|
+
toolExecution: false,
|
|
992
|
+
externalDataAccess: false,
|
|
993
|
+
piiProcessing: "unknown",
|
|
994
|
+
highStakesDecisions: false
|
|
995
|
+
};
|
|
996
|
+
fixedFields.push("classification.riskFactors");
|
|
997
|
+
}
|
|
998
|
+
return {
|
|
999
|
+
fixed: fixedFields.length > 0,
|
|
1000
|
+
fixedFields,
|
|
1001
|
+
card: fixedCard
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
function isValidISODate(dateStr) {
|
|
1005
|
+
const iso8601Regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/;
|
|
1006
|
+
if (!iso8601Regex.test(dateStr)) {
|
|
1007
|
+
return false;
|
|
1008
|
+
}
|
|
1009
|
+
const date = new Date(dateStr);
|
|
1010
|
+
return !isNaN(date.getTime());
|
|
1011
|
+
}
|
|
1012
|
+
function normalizeDate(dateStr) {
|
|
1013
|
+
const date = new Date(dateStr);
|
|
1014
|
+
if (isNaN(date.getTime())) {
|
|
1015
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1016
|
+
}
|
|
1017
|
+
return date.toISOString();
|
|
1018
|
+
}
|
|
1019
|
+
function normalizeRiskLevel(level) {
|
|
1020
|
+
const normalized = level.toLowerCase().trim();
|
|
1021
|
+
const validLevels = ["minimal", "limited", "high", "unacceptable"];
|
|
1022
|
+
if (validLevels.includes(normalized)) {
|
|
1023
|
+
return normalized;
|
|
1024
|
+
}
|
|
1025
|
+
const mappings = {
|
|
1026
|
+
low: "minimal",
|
|
1027
|
+
medium: "limited",
|
|
1028
|
+
critical: "unacceptable",
|
|
1029
|
+
extreme: "unacceptable",
|
|
1030
|
+
none: "minimal"
|
|
1031
|
+
};
|
|
1032
|
+
return mappings[normalized] || "minimal";
|
|
1033
|
+
}
|
|
1034
|
+
function generateId(name) {
|
|
1035
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").substring(0, 50);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// src/commands/validate.ts
|
|
726
1039
|
var DEFAULT_CARDS_DIR3 = ".aigrc/cards";
|
|
727
|
-
var validateCommand = new Command4("validate").description("Validate asset cards against compliance requirements").argument("[path]", "Path to asset card or cards directory").option("-s, --strict", "Fail on warnings as well as errors").option("-o, --output <format>", "Output format (text, json)", "text").option("-a, --all", "Validate all cards in the cards directory").action(async (cardPath, options) => {
|
|
1040
|
+
var validateCommand = new Command4("validate").description("Validate asset cards against compliance requirements").argument("[path]", "Path to asset card or cards directory").option("-s, --strict", "Fail on warnings as well as errors").option("-o, --output <format>", "Output format (text, json, sarif)", "text").option("-a, --all", "Validate all cards in the cards directory").option("--fix", "Automatically fix common issues").option("--dry-run", "Preview fixes without saving (requires --fix)").option("--output-file <path>", "Write output to file instead of stdout").action(async (cardPath, options) => {
|
|
728
1041
|
await runValidate(cardPath, options);
|
|
729
1042
|
});
|
|
730
1043
|
async function runValidate(cardPath, options) {
|
|
731
|
-
if (options.
|
|
1044
|
+
if (options.dryRun && !options.fix) {
|
|
1045
|
+
if (options.output === "text") {
|
|
1046
|
+
console.error(chalk6.red("Error: --dry-run requires --fix"));
|
|
1047
|
+
} else {
|
|
1048
|
+
console.error(JSON.stringify({ error: "--dry-run requires --fix" }));
|
|
1049
|
+
}
|
|
1050
|
+
exit(2 /* INVALID_ARGUMENTS */);
|
|
1051
|
+
}
|
|
1052
|
+
if (options.output === "text" && !options.outputFile) {
|
|
732
1053
|
printHeader();
|
|
733
1054
|
}
|
|
734
1055
|
const cardsToValidate = [];
|
|
735
1056
|
if (options.all || !cardPath) {
|
|
736
|
-
const cardsDir =
|
|
1057
|
+
const cardsDir = path5.join(process.cwd(), DEFAULT_CARDS_DIR3);
|
|
737
1058
|
try {
|
|
738
1059
|
const files = await fs3.readdir(cardsDir);
|
|
739
1060
|
const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
740
1061
|
for (const file of yamlFiles) {
|
|
741
|
-
cardsToValidate.push(
|
|
1062
|
+
cardsToValidate.push(path5.join(cardsDir, file));
|
|
742
1063
|
}
|
|
743
|
-
} catch {
|
|
744
|
-
if (options.output === "text") {
|
|
745
|
-
console.log(
|
|
746
|
-
console.log(
|
|
747
|
-
console.log(
|
|
1064
|
+
} catch (error) {
|
|
1065
|
+
if (options.output === "text" && !options.outputFile) {
|
|
1066
|
+
console.log(chalk6.yellow("No cards directory found."));
|
|
1067
|
+
console.log(chalk6.dim(`Expected: ${cardsDir}`));
|
|
1068
|
+
console.log(chalk6.dim("Run `aigrc init` to initialize AIGRC."));
|
|
748
1069
|
} else {
|
|
749
|
-
|
|
1070
|
+
const output = JSON.stringify({ error: "No cards directory found" });
|
|
1071
|
+
if (options.outputFile) {
|
|
1072
|
+
await fs3.writeFile(options.outputFile, output);
|
|
1073
|
+
} else {
|
|
1074
|
+
console.log(output);
|
|
1075
|
+
}
|
|
750
1076
|
}
|
|
751
|
-
|
|
1077
|
+
exit(4 /* FILE_NOT_FOUND */);
|
|
752
1078
|
}
|
|
753
1079
|
} else {
|
|
754
|
-
|
|
1080
|
+
const resolvedPath = path5.resolve(process.cwd(), cardPath);
|
|
1081
|
+
try {
|
|
1082
|
+
await fs3.access(resolvedPath);
|
|
1083
|
+
cardsToValidate.push(resolvedPath);
|
|
1084
|
+
} catch {
|
|
1085
|
+
if (options.output === "text" && !options.outputFile) {
|
|
1086
|
+
console.log(chalk6.red(`Error: File not found: ${cardPath}`));
|
|
1087
|
+
} else {
|
|
1088
|
+
const output = JSON.stringify({ error: `File not found: ${cardPath}` });
|
|
1089
|
+
if (options.outputFile) {
|
|
1090
|
+
await fs3.writeFile(options.outputFile, output);
|
|
1091
|
+
} else {
|
|
1092
|
+
console.log(output);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
exit(4 /* FILE_NOT_FOUND */);
|
|
1096
|
+
}
|
|
755
1097
|
}
|
|
756
1098
|
if (cardsToValidate.length === 0) {
|
|
757
|
-
if (options.output === "text") {
|
|
758
|
-
console.log(
|
|
1099
|
+
if (options.output === "text" && !options.outputFile) {
|
|
1100
|
+
console.log(chalk6.yellow("No asset cards found to validate."));
|
|
759
1101
|
} else {
|
|
760
|
-
|
|
1102
|
+
const output = JSON.stringify({ error: "No asset cards found" });
|
|
1103
|
+
if (options.outputFile) {
|
|
1104
|
+
await fs3.writeFile(options.outputFile, output);
|
|
1105
|
+
} else {
|
|
1106
|
+
console.log(output);
|
|
1107
|
+
}
|
|
761
1108
|
}
|
|
762
|
-
|
|
1109
|
+
exit(4 /* FILE_NOT_FOUND */);
|
|
763
1110
|
}
|
|
764
1111
|
const results = [];
|
|
765
1112
|
let hasErrors = false;
|
|
766
1113
|
for (const cardFile of cardsToValidate) {
|
|
767
|
-
const spinner = options.output === "text" ? ora3(`Validating ${
|
|
1114
|
+
const spinner = options.output === "text" && !options.outputFile ? ora3(`Validating ${path5.basename(cardFile)}...`).start() : void 0;
|
|
768
1115
|
try {
|
|
769
|
-
|
|
1116
|
+
let card = loadAssetCard(cardFile);
|
|
1117
|
+
let fixed = false;
|
|
1118
|
+
let fixedFields = [];
|
|
1119
|
+
if (options.fix) {
|
|
1120
|
+
const fixResult = autoFixAssetCard(card);
|
|
1121
|
+
if (fixResult.fixed) {
|
|
1122
|
+
fixed = true;
|
|
1123
|
+
fixedFields = fixResult.fixedFields;
|
|
1124
|
+
card = fixResult.card;
|
|
1125
|
+
if (!options.dryRun) {
|
|
1126
|
+
const yamlContent = YAML.stringify(card);
|
|
1127
|
+
await fs3.writeFile(cardFile, yamlContent, "utf-8");
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
770
1131
|
const validation = validateAssetCard(card);
|
|
771
1132
|
const classification = classifyRisk(card.classification.riskFactors);
|
|
772
1133
|
results.push({
|
|
@@ -776,13 +1137,17 @@ async function runValidate(cardPath, options) {
|
|
|
776
1137
|
valid: validation.valid,
|
|
777
1138
|
errors: validation.errors ?? []
|
|
778
1139
|
},
|
|
779
|
-
classification
|
|
1140
|
+
classification,
|
|
1141
|
+
fixed,
|
|
1142
|
+
fixedFields
|
|
780
1143
|
});
|
|
781
1144
|
if (!validation.valid) {
|
|
782
1145
|
hasErrors = true;
|
|
783
|
-
spinner?.fail(`${
|
|
1146
|
+
spinner?.fail(`${path5.basename(cardFile)}: Invalid`);
|
|
1147
|
+
} else if (fixed) {
|
|
1148
|
+
spinner?.succeed(`${path5.basename(cardFile)}: Valid (fixed ${fixedFields.length} issues)`);
|
|
784
1149
|
} else {
|
|
785
|
-
spinner?.succeed(`${
|
|
1150
|
+
spinner?.succeed(`${path5.basename(cardFile)}: Valid`);
|
|
786
1151
|
}
|
|
787
1152
|
} catch (error) {
|
|
788
1153
|
hasErrors = true;
|
|
@@ -794,76 +1159,93 @@ async function runValidate(cardPath, options) {
|
|
|
794
1159
|
errors: [errorMessage]
|
|
795
1160
|
}
|
|
796
1161
|
});
|
|
797
|
-
spinner?.fail(`${
|
|
1162
|
+
spinner?.fail(`${path5.basename(cardFile)}: Parse error`);
|
|
798
1163
|
}
|
|
799
1164
|
}
|
|
800
|
-
|
|
801
|
-
console.log(JSON.stringify({ results }, null, 2));
|
|
802
|
-
} else {
|
|
803
|
-
console.log();
|
|
804
|
-
printValidationSummary(results);
|
|
805
|
-
}
|
|
1165
|
+
await outputResults(results, options);
|
|
806
1166
|
if (hasErrors) {
|
|
807
|
-
|
|
1167
|
+
exit(3 /* VALIDATION_ERRORS */);
|
|
808
1168
|
}
|
|
1169
|
+
exit(0 /* SUCCESS */);
|
|
809
1170
|
}
|
|
810
|
-
function
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1171
|
+
async function outputResults(results, options) {
|
|
1172
|
+
let output;
|
|
1173
|
+
switch (options.output) {
|
|
1174
|
+
case "sarif": {
|
|
1175
|
+
const sarif = formatSarif(results);
|
|
1176
|
+
output = sarifToJson(sarif);
|
|
1177
|
+
break;
|
|
1178
|
+
}
|
|
1179
|
+
case "json": {
|
|
1180
|
+
output = JSON.stringify({ results }, null, 2);
|
|
1181
|
+
break;
|
|
1182
|
+
}
|
|
1183
|
+
case "text":
|
|
1184
|
+
default: {
|
|
1185
|
+
if (options.outputFile) {
|
|
1186
|
+
output = formatTextOutput(results);
|
|
1187
|
+
} else {
|
|
1188
|
+
console.log();
|
|
1189
|
+
printValidationSummary(results);
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
if (options.outputFile) {
|
|
1195
|
+
await fs3.writeFile(options.outputFile, output, "utf-8");
|
|
1196
|
+
if (options.output === "text") {
|
|
1197
|
+
console.log(chalk6.green(`\u2713 Output written to ${options.outputFile}`));
|
|
1198
|
+
}
|
|
1199
|
+
} else {
|
|
1200
|
+
console.log(output);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
function formatTextOutput(results) {
|
|
1204
|
+
const lines = [];
|
|
1205
|
+
lines.push("Validation Summary");
|
|
1206
|
+
lines.push("\u2500".repeat(50));
|
|
1207
|
+
lines.push("");
|
|
814
1208
|
for (const result of results) {
|
|
815
|
-
const fileName =
|
|
1209
|
+
const fileName = path5.basename(result.path);
|
|
816
1210
|
if (!result.validation.valid) {
|
|
817
|
-
|
|
1211
|
+
lines.push(`\u2717 ${fileName}`);
|
|
818
1212
|
for (const error of result.validation.errors) {
|
|
819
|
-
|
|
1213
|
+
lines.push(` Error: ${error}`);
|
|
820
1214
|
}
|
|
821
1215
|
} else {
|
|
822
|
-
|
|
1216
|
+
lines.push(`\u2713 ${fileName}`);
|
|
1217
|
+
if (result.fixed && result.fixedFields && result.fixedFields.length > 0) {
|
|
1218
|
+
lines.push(` Fixed: ${result.fixedFields.join(", ")}`);
|
|
1219
|
+
}
|
|
823
1220
|
if (result.card && result.classification) {
|
|
824
|
-
|
|
825
|
-
console.log(
|
|
826
|
-
chalk5.dim(" Risk Level: ") + tierColor(result.classification.riskLevel)
|
|
827
|
-
);
|
|
1221
|
+
lines.push(` Risk Level: ${result.classification.riskLevel}`);
|
|
828
1222
|
if (result.classification.euAiActCategory) {
|
|
829
|
-
|
|
830
|
-
chalk5.dim(" EU AI Act: ") + chalk5.yellow(result.classification.euAiActCategory)
|
|
831
|
-
);
|
|
1223
|
+
lines.push(` EU AI Act: ${result.classification.euAiActCategory}`);
|
|
832
1224
|
}
|
|
833
1225
|
}
|
|
834
1226
|
}
|
|
835
|
-
|
|
1227
|
+
lines.push("");
|
|
836
1228
|
}
|
|
837
1229
|
const valid = results.filter((r) => r.validation.valid).length;
|
|
838
1230
|
const invalid = results.filter((r) => !r.validation.valid).length;
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1231
|
+
const fixed = results.filter((r) => r.fixed).length;
|
|
1232
|
+
lines.push("\u2500".repeat(50));
|
|
1233
|
+
lines.push(
|
|
1234
|
+
`Total: ${results.length} | Valid: ${valid} | Invalid: ${invalid}` + (fixed > 0 ? ` | Fixed: ${fixed}` : "")
|
|
842
1235
|
);
|
|
843
|
-
|
|
844
|
-
function getRiskLevelColor(level) {
|
|
845
|
-
switch (level) {
|
|
846
|
-
case "minimal":
|
|
847
|
-
return chalk5.green;
|
|
848
|
-
case "limited":
|
|
849
|
-
return chalk5.yellow;
|
|
850
|
-
case "high":
|
|
851
|
-
return chalk5.red;
|
|
852
|
-
case "unacceptable":
|
|
853
|
-
return chalk5.magenta;
|
|
854
|
-
default:
|
|
855
|
-
return chalk5.white;
|
|
856
|
-
}
|
|
1236
|
+
return lines.join("\n");
|
|
857
1237
|
}
|
|
858
1238
|
|
|
859
1239
|
// src/commands/status.ts
|
|
860
1240
|
import { Command as Command5 } from "commander";
|
|
861
|
-
import
|
|
862
|
-
import
|
|
1241
|
+
import chalk7 from "chalk";
|
|
1242
|
+
import path6 from "path";
|
|
863
1243
|
import fs4 from "fs/promises";
|
|
864
1244
|
import {
|
|
865
1245
|
loadAssetCard as loadAssetCard2,
|
|
866
|
-
classifyRisk as classifyRisk2
|
|
1246
|
+
classifyRisk as classifyRisk2,
|
|
1247
|
+
extractGoldenThreadComponents,
|
|
1248
|
+
verifyGoldenThreadHashSync
|
|
867
1249
|
} from "@aigrc/core";
|
|
868
1250
|
var DEFAULT_CARDS_DIR4 = ".aigrc/cards";
|
|
869
1251
|
var DEFAULT_CONFIG_FILE2 = ".aigrc.yaml";
|
|
@@ -875,14 +1257,14 @@ async function runStatus(options) {
|
|
|
875
1257
|
if (options.output === "text") {
|
|
876
1258
|
printHeader();
|
|
877
1259
|
}
|
|
878
|
-
const configPath =
|
|
879
|
-
const cardsDir =
|
|
1260
|
+
const configPath = path6.join(cwd, DEFAULT_CONFIG_FILE2);
|
|
1261
|
+
const cardsDir = path6.join(cwd, DEFAULT_CARDS_DIR4);
|
|
880
1262
|
const configExists = await fileExists2(configPath);
|
|
881
1263
|
const cardsDirExists = await directoryExists(cardsDir);
|
|
882
1264
|
if (!configExists && !cardsDirExists) {
|
|
883
1265
|
if (options.output === "text") {
|
|
884
|
-
console.log(
|
|
885
|
-
console.log(
|
|
1266
|
+
console.log(chalk7.yellow("AIGRC is not initialized in this directory."));
|
|
1267
|
+
console.log(chalk7.dim("\nRun `aigrc init` to get started."));
|
|
886
1268
|
} else {
|
|
887
1269
|
console.log(JSON.stringify({ initialized: false }));
|
|
888
1270
|
}
|
|
@@ -895,7 +1277,7 @@ async function runStatus(options) {
|
|
|
895
1277
|
const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
896
1278
|
for (const file of yamlFiles) {
|
|
897
1279
|
try {
|
|
898
|
-
const filePath =
|
|
1280
|
+
const filePath = path6.join(cardsDir, file);
|
|
899
1281
|
const card = loadAssetCard2(filePath);
|
|
900
1282
|
const classification = classifyRisk2(card.classification.riskFactors);
|
|
901
1283
|
cards.push({ path: filePath, card, classification });
|
|
@@ -926,19 +1308,21 @@ async function runStatus(options) {
|
|
|
926
1308
|
);
|
|
927
1309
|
return;
|
|
928
1310
|
}
|
|
929
|
-
console.log(
|
|
930
|
-
console.log(
|
|
1311
|
+
console.log(chalk7.bold("AIGRC Status"));
|
|
1312
|
+
console.log(chalk7.dim("\u2500".repeat(50)));
|
|
1313
|
+
console.log();
|
|
1314
|
+
console.log(chalk7.dim("Config:"), configExists ? chalk7.green("\u2713") : chalk7.red("\u2717"), configPath);
|
|
1315
|
+
console.log(chalk7.dim("Cards:"), cardsDirExists ? chalk7.green("\u2713") : chalk7.red("\u2717"), cardsDir);
|
|
931
1316
|
console.log();
|
|
932
|
-
|
|
933
|
-
console.log(chalk6.dim("Cards:"), cardsDirExists ? chalk6.green("\u2713") : chalk6.red("\u2717"), cardsDir);
|
|
1317
|
+
printGoldenThreadStatus(cards);
|
|
934
1318
|
console.log();
|
|
935
1319
|
if (cards.length === 0) {
|
|
936
|
-
console.log(
|
|
937
|
-
console.log(
|
|
1320
|
+
console.log(chalk7.yellow("No asset cards registered."));
|
|
1321
|
+
console.log(chalk7.dim("\nRun `aigrc init` or `aigrc register` to create an asset card."));
|
|
938
1322
|
return;
|
|
939
1323
|
}
|
|
940
|
-
console.log(
|
|
941
|
-
console.log(
|
|
1324
|
+
console.log(chalk7.bold(`Registered Assets (${cards.length})`));
|
|
1325
|
+
console.log(chalk7.dim("\u2500".repeat(50)));
|
|
942
1326
|
console.log();
|
|
943
1327
|
const byLevel = groupByRiskLevel(cards);
|
|
944
1328
|
for (const level of ["unacceptable", "high", "limited", "minimal"]) {
|
|
@@ -948,18 +1332,18 @@ async function runStatus(options) {
|
|
|
948
1332
|
console.log(levelColor(`${level.toUpperCase()} (${levelCards.length})`));
|
|
949
1333
|
console.log();
|
|
950
1334
|
for (const { card, classification } of levelCards) {
|
|
951
|
-
console.log(` ${
|
|
952
|
-
console.log(
|
|
953
|
-
console.log(
|
|
1335
|
+
console.log(` ${chalk7.bold(card.name)}`);
|
|
1336
|
+
console.log(chalk7.dim(` ID: ${card.id}`));
|
|
1337
|
+
console.log(chalk7.dim(` Risk Level: ${classification.riskLevel}`));
|
|
954
1338
|
if (classification.euAiActCategory) {
|
|
955
|
-
console.log(
|
|
1339
|
+
console.log(chalk7.dim(` EU AI Act: `) + chalk7.yellow(classification.euAiActCategory));
|
|
956
1340
|
}
|
|
957
1341
|
if (card.ownership?.owner) {
|
|
958
|
-
console.log(
|
|
1342
|
+
console.log(chalk7.dim(` Owner: ${card.ownership.owner.name} <${card.ownership.owner.email}>`));
|
|
959
1343
|
}
|
|
960
1344
|
const activeRisks = getActiveRiskFactors(card);
|
|
961
1345
|
if (activeRisks.length > 0) {
|
|
962
|
-
console.log(
|
|
1346
|
+
console.log(chalk7.dim(` Risks: `) + activeRisks.join(", "));
|
|
963
1347
|
}
|
|
964
1348
|
console.log();
|
|
965
1349
|
}
|
|
@@ -978,17 +1362,17 @@ function groupByRiskLevel(cards) {
|
|
|
978
1362
|
return byLevel;
|
|
979
1363
|
}
|
|
980
1364
|
function printStatusSummary(cards) {
|
|
981
|
-
console.log(
|
|
1365
|
+
console.log(chalk7.dim("\u2500".repeat(50)));
|
|
982
1366
|
const minimal = cards.filter((c) => c.classification.riskLevel === "minimal").length;
|
|
983
1367
|
const limited = cards.filter((c) => c.classification.riskLevel === "limited").length;
|
|
984
1368
|
const high = cards.filter((c) => c.classification.riskLevel === "high").length;
|
|
985
1369
|
const unacceptable = cards.filter((c) => c.classification.riskLevel === "unacceptable").length;
|
|
986
1370
|
console.log(
|
|
987
|
-
`Total: ${cards.length} | ` +
|
|
1371
|
+
`Total: ${cards.length} | ` + chalk7.green(`Minimal: ${minimal}`) + ` | ` + chalk7.yellow(`Limited: ${limited}`) + ` | ` + chalk7.red(`High: ${high}`) + ` | ` + chalk7.magenta(`Unacceptable: ${unacceptable}`)
|
|
988
1372
|
);
|
|
989
1373
|
if (high > 0 || unacceptable > 0) {
|
|
990
1374
|
console.log();
|
|
991
|
-
console.log(
|
|
1375
|
+
console.log(chalk7.yellow("\u26A0 High-risk assets detected. Review compliance requirements."));
|
|
992
1376
|
}
|
|
993
1377
|
}
|
|
994
1378
|
function getActiveRiskFactors(card) {
|
|
@@ -1006,15 +1390,15 @@ function getActiveRiskFactors(card) {
|
|
|
1006
1390
|
function getRiskLevelColor2(level) {
|
|
1007
1391
|
switch (level) {
|
|
1008
1392
|
case "minimal":
|
|
1009
|
-
return
|
|
1393
|
+
return chalk7.green;
|
|
1010
1394
|
case "limited":
|
|
1011
|
-
return
|
|
1395
|
+
return chalk7.yellow;
|
|
1012
1396
|
case "high":
|
|
1013
|
-
return
|
|
1397
|
+
return chalk7.red;
|
|
1014
1398
|
case "unacceptable":
|
|
1015
|
-
return
|
|
1399
|
+
return chalk7.magenta;
|
|
1016
1400
|
default:
|
|
1017
|
-
return
|
|
1401
|
+
return chalk7.white;
|
|
1018
1402
|
}
|
|
1019
1403
|
}
|
|
1020
1404
|
async function fileExists2(filePath) {
|
|
@@ -1033,14 +1417,296 @@ async function directoryExists(dirPath) {
|
|
|
1033
1417
|
return false;
|
|
1034
1418
|
}
|
|
1035
1419
|
}
|
|
1420
|
+
function printGoldenThreadStatus(cards) {
|
|
1421
|
+
const cardsWithGoldenThread = cards.filter((c) => {
|
|
1422
|
+
const components = extractGoldenThreadComponents(c.card);
|
|
1423
|
+
return components && c.card.golden_thread?.hash;
|
|
1424
|
+
});
|
|
1425
|
+
if (cardsWithGoldenThread.length === 0) {
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
console.log(chalk7.bold("Golden Thread"));
|
|
1429
|
+
console.log(chalk7.dim("\u2500".repeat(50)));
|
|
1430
|
+
console.log();
|
|
1431
|
+
for (const { card } of cardsWithGoldenThread) {
|
|
1432
|
+
const components = extractGoldenThreadComponents(card);
|
|
1433
|
+
if (!components || !card.golden_thread?.hash) continue;
|
|
1434
|
+
console.log(chalk7.bold(card.name));
|
|
1435
|
+
console.log(chalk7.dim(` Hash: ${card.golden_thread.hash}`));
|
|
1436
|
+
try {
|
|
1437
|
+
const verification = verifyGoldenThreadHashSync(components, card.golden_thread.hash);
|
|
1438
|
+
if (verification.verified) {
|
|
1439
|
+
console.log(chalk7.dim(" Status: ") + chalk7.green("\u2713 Verified"));
|
|
1440
|
+
} else {
|
|
1441
|
+
console.log(chalk7.dim(" Status: ") + chalk7.red("\u2717 Verification Failed"));
|
|
1442
|
+
if (verification.mismatch_reason) {
|
|
1443
|
+
console.log(chalk7.dim(" Reason: ") + chalk7.yellow(verification.mismatch_reason));
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
console.log(chalk7.dim(" Status: ") + chalk7.red("\u2717 Error"));
|
|
1448
|
+
}
|
|
1449
|
+
if (card.golden_thread?.signature) {
|
|
1450
|
+
console.log(chalk7.dim(" Signature: ") + chalk7.green("\u2713 RSA-SHA256"));
|
|
1451
|
+
}
|
|
1452
|
+
console.log();
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1036
1455
|
|
|
1037
|
-
// src/commands/
|
|
1456
|
+
// src/commands/hash.ts
|
|
1038
1457
|
import { Command as Command6 } from "commander";
|
|
1039
|
-
import
|
|
1458
|
+
import chalk8 from "chalk";
|
|
1040
1459
|
import ora4 from "ora";
|
|
1460
|
+
import path7 from "path";
|
|
1461
|
+
import {
|
|
1462
|
+
loadAssetCard as loadAssetCard3,
|
|
1463
|
+
extractGoldenThreadComponents as extractGoldenThreadComponents2,
|
|
1464
|
+
computeGoldenThreadHashSync,
|
|
1465
|
+
verifyGoldenThreadHashSync as verifyGoldenThreadHashSync2,
|
|
1466
|
+
computeCanonicalString
|
|
1467
|
+
} from "@aigrc/core";
|
|
1468
|
+
var hashCommand = new Command6("hash").description("Compute or verify Golden Thread hashes").argument("[path]", "Path to asset card file").option("-v, --verify", "Verify the hash in the asset card").option("-o, --output <format>", "Output format (text, json)", "text").option("--ticket-id <id>", "Ticket ID for manual hash computation").option("--approved-by <email>", "Approver email for manual hash computation").option("--approved-at <datetime>", "Approval timestamp (ISO 8601) for manual hash computation").action(async (cardPath, options) => {
|
|
1469
|
+
await runHash(cardPath, options);
|
|
1470
|
+
});
|
|
1471
|
+
async function runHash(cardPath, options) {
|
|
1472
|
+
if (options.output === "text") {
|
|
1473
|
+
printHeader();
|
|
1474
|
+
}
|
|
1475
|
+
if (options.ticketId && options.approvedBy && options.approvedAt) {
|
|
1476
|
+
await runManualHash(options);
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
if (!cardPath) {
|
|
1480
|
+
if (options.output === "json") {
|
|
1481
|
+
console.log(JSON.stringify({
|
|
1482
|
+
success: false,
|
|
1483
|
+
error: "No asset card path provided. Use --ticket-id, --approved-by, --approved-at for manual computation."
|
|
1484
|
+
}));
|
|
1485
|
+
} else {
|
|
1486
|
+
console.log(chalk8.red("Error: No asset card path provided."));
|
|
1487
|
+
console.log(chalk8.gray("Usage: aigrc hash <path-to-card>"));
|
|
1488
|
+
console.log(chalk8.gray(" or: aigrc hash --ticket-id <id> --approved-by <email> --approved-at <datetime>"));
|
|
1489
|
+
}
|
|
1490
|
+
process.exit(1);
|
|
1491
|
+
}
|
|
1492
|
+
const spinner = options.output === "text" ? ora4("Loading asset card...").start() : null;
|
|
1493
|
+
try {
|
|
1494
|
+
const resolvedPath = path7.resolve(process.cwd(), cardPath);
|
|
1495
|
+
const asset = loadAssetCard3(resolvedPath);
|
|
1496
|
+
spinner?.succeed("Asset card loaded");
|
|
1497
|
+
const components = extractGoldenThreadComponents2(asset);
|
|
1498
|
+
if (!components) {
|
|
1499
|
+
const result = {
|
|
1500
|
+
success: false,
|
|
1501
|
+
error: "Asset card does not have Golden Thread components (missing ticket_id, approved_by, or approved_at)"
|
|
1502
|
+
};
|
|
1503
|
+
outputResult(result, options.output);
|
|
1504
|
+
process.exit(1);
|
|
1505
|
+
}
|
|
1506
|
+
if (options.verify) {
|
|
1507
|
+
await runVerify(components, asset.golden_thread?.hash, options);
|
|
1508
|
+
} else {
|
|
1509
|
+
await runCompute(components, options);
|
|
1510
|
+
}
|
|
1511
|
+
} catch (error) {
|
|
1512
|
+
spinner?.fail("Failed to load asset card");
|
|
1513
|
+
const result = {
|
|
1514
|
+
success: false,
|
|
1515
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1516
|
+
};
|
|
1517
|
+
outputResult(result, options.output);
|
|
1518
|
+
process.exit(1);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
async function runManualHash(options) {
|
|
1522
|
+
const components = {
|
|
1523
|
+
ticket_id: options.ticketId,
|
|
1524
|
+
approved_by: options.approvedBy,
|
|
1525
|
+
approved_at: options.approvedAt
|
|
1526
|
+
};
|
|
1527
|
+
const spinner = options.output === "text" ? ora4("Computing hash...").start() : null;
|
|
1528
|
+
try {
|
|
1529
|
+
const { canonical_string, hash } = computeGoldenThreadHashSync(components);
|
|
1530
|
+
spinner?.succeed("Hash computed");
|
|
1531
|
+
const result = {
|
|
1532
|
+
success: true,
|
|
1533
|
+
canonical_string,
|
|
1534
|
+
hash
|
|
1535
|
+
};
|
|
1536
|
+
outputResult(result, options.output);
|
|
1537
|
+
} catch (error) {
|
|
1538
|
+
spinner?.fail("Failed to compute hash");
|
|
1539
|
+
const result = {
|
|
1540
|
+
success: false,
|
|
1541
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1542
|
+
};
|
|
1543
|
+
outputResult(result, options.output);
|
|
1544
|
+
process.exit(1);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
async function runCompute(components, options) {
|
|
1548
|
+
const spinner = options.output === "text" ? ora4("Computing hash...").start() : null;
|
|
1549
|
+
try {
|
|
1550
|
+
const { canonical_string, hash } = computeGoldenThreadHashSync(components);
|
|
1551
|
+
spinner?.succeed("Hash computed");
|
|
1552
|
+
const result = {
|
|
1553
|
+
success: true,
|
|
1554
|
+
canonical_string,
|
|
1555
|
+
hash
|
|
1556
|
+
};
|
|
1557
|
+
outputResult(result, options.output);
|
|
1558
|
+
} catch (error) {
|
|
1559
|
+
spinner?.fail("Failed to compute hash");
|
|
1560
|
+
const result = {
|
|
1561
|
+
success: false,
|
|
1562
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1563
|
+
};
|
|
1564
|
+
outputResult(result, options.output);
|
|
1565
|
+
process.exit(1);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
async function runVerify(components, expectedHash, options) {
|
|
1569
|
+
const spinner = options.output === "text" ? ora4("Verifying hash...").start() : null;
|
|
1570
|
+
if (!expectedHash) {
|
|
1571
|
+
spinner?.fail("No hash to verify");
|
|
1572
|
+
const result = {
|
|
1573
|
+
success: false,
|
|
1574
|
+
error: "Asset card does not have a hash in golden_thread.hash"
|
|
1575
|
+
};
|
|
1576
|
+
outputResult(result, options.output);
|
|
1577
|
+
process.exit(1);
|
|
1578
|
+
}
|
|
1579
|
+
try {
|
|
1580
|
+
const verification = verifyGoldenThreadHashSync2(components, expectedHash);
|
|
1581
|
+
if (verification.verified) {
|
|
1582
|
+
spinner?.succeed("Hash verified successfully");
|
|
1583
|
+
} else {
|
|
1584
|
+
spinner?.fail("Hash verification failed");
|
|
1585
|
+
}
|
|
1586
|
+
const result = {
|
|
1587
|
+
success: verification.verified,
|
|
1588
|
+
canonical_string: computeCanonicalString(components),
|
|
1589
|
+
hash: verification.computed,
|
|
1590
|
+
verified: verification.verified,
|
|
1591
|
+
expected_hash: verification.expected,
|
|
1592
|
+
error: verification.mismatch_reason
|
|
1593
|
+
};
|
|
1594
|
+
outputResult(result, options.output);
|
|
1595
|
+
if (!verification.verified) {
|
|
1596
|
+
process.exit(1);
|
|
1597
|
+
}
|
|
1598
|
+
} catch (error) {
|
|
1599
|
+
spinner?.fail("Failed to verify hash");
|
|
1600
|
+
const result = {
|
|
1601
|
+
success: false,
|
|
1602
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1603
|
+
};
|
|
1604
|
+
outputResult(result, options.output);
|
|
1605
|
+
process.exit(1);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
function outputResult(result, format) {
|
|
1609
|
+
if (format === "json") {
|
|
1610
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
if (result.success) {
|
|
1614
|
+
if (result.verified !== void 0) {
|
|
1615
|
+
if (result.verified) {
|
|
1616
|
+
console.log(chalk8.green("\u2713 Golden Thread hash verified"));
|
|
1617
|
+
} else {
|
|
1618
|
+
console.log(chalk8.red("\u2717 Golden Thread hash verification failed"));
|
|
1619
|
+
if (result.expected_hash) {
|
|
1620
|
+
console.log(chalk8.dim(`Expected: ${result.expected_hash}`));
|
|
1621
|
+
}
|
|
1622
|
+
if (result.hash) {
|
|
1623
|
+
console.log(chalk8.dim(`Computed: ${result.hash}`));
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
} else {
|
|
1627
|
+
if (result.hash) {
|
|
1628
|
+
console.log(result.hash);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
} else {
|
|
1632
|
+
console.log(chalk8.red("Error: ") + result.error);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
// src/commands/version.ts
|
|
1637
|
+
import { Command as Command7 } from "commander";
|
|
1638
|
+
import chalk9 from "chalk";
|
|
1639
|
+
import fs5 from "fs/promises";
|
|
1640
|
+
import path8 from "path";
|
|
1641
|
+
var versionCommand = new Command7("version").description("Show version information for AIGRC CLI and packages").option("--json", "Output version information as JSON").action(async (options) => {
|
|
1642
|
+
await runVersion(options);
|
|
1643
|
+
});
|
|
1644
|
+
async function runVersion(options) {
|
|
1645
|
+
const versionInfo = await getVersionInfo();
|
|
1646
|
+
if (options.json) {
|
|
1647
|
+
console.log(JSON.stringify(versionInfo, null, 2));
|
|
1648
|
+
} else {
|
|
1649
|
+
printVersionText(versionInfo);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
async function getVersionInfo() {
|
|
1653
|
+
const cliVersion = await getPackageVersion("@aigrc/cli");
|
|
1654
|
+
const coreVersion = await getPackageVersion("@aigrc/core");
|
|
1655
|
+
const nodeVersion = process.version.replace("v", "");
|
|
1656
|
+
const platform = process.platform;
|
|
1657
|
+
const arch = process.arch;
|
|
1658
|
+
return {
|
|
1659
|
+
cli: cliVersion,
|
|
1660
|
+
core: coreVersion,
|
|
1661
|
+
node: nodeVersion,
|
|
1662
|
+
platform,
|
|
1663
|
+
arch
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
async function getPackageVersion(packageName) {
|
|
1667
|
+
try {
|
|
1668
|
+
const pkgPath = __require.resolve(`${packageName}/package.json`);
|
|
1669
|
+
const pkgContent = await fs5.readFile(pkgPath, "utf-8");
|
|
1670
|
+
const pkg = JSON.parse(pkgContent);
|
|
1671
|
+
return pkg.version || "unknown";
|
|
1672
|
+
} catch {
|
|
1673
|
+
try {
|
|
1674
|
+
const currentPkgPath = path8.join(process.cwd(), "package.json");
|
|
1675
|
+
const pkgContent = await fs5.readFile(currentPkgPath, "utf-8");
|
|
1676
|
+
const pkg = JSON.parse(pkgContent);
|
|
1677
|
+
if (pkg.name === packageName) {
|
|
1678
|
+
return pkg.version || "unknown";
|
|
1679
|
+
}
|
|
1680
|
+
if (pkg.dependencies?.[packageName]) {
|
|
1681
|
+
return pkg.dependencies[packageName].replace("^", "").replace("~", "");
|
|
1682
|
+
}
|
|
1683
|
+
if (pkg.devDependencies?.[packageName]) {
|
|
1684
|
+
return pkg.devDependencies[packageName].replace("^", "").replace("~", "");
|
|
1685
|
+
}
|
|
1686
|
+
} catch {
|
|
1687
|
+
}
|
|
1688
|
+
return "unknown";
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
function printVersionText(info) {
|
|
1692
|
+
console.log();
|
|
1693
|
+
console.log(chalk9.cyan.bold("AIGRC Version Information"));
|
|
1694
|
+
console.log(chalk9.dim("\u2500".repeat(50)));
|
|
1695
|
+
console.log();
|
|
1696
|
+
console.log(`${chalk9.dim("aigrc CLI:")} ${chalk9.bold(info.cli)}`);
|
|
1697
|
+
console.log(`${chalk9.dim("@aigrc/core:")} ${chalk9.bold(info.core)}`);
|
|
1698
|
+
console.log(`${chalk9.dim("Node.js:")} ${chalk9.bold("v" + info.node)}`);
|
|
1699
|
+
console.log(`${chalk9.dim("Platform:")} ${chalk9.bold(info.platform + " " + info.arch)}`);
|
|
1700
|
+
console.log();
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// src/commands/compliance.ts
|
|
1704
|
+
import { Command as Command8 } from "commander";
|
|
1705
|
+
import chalk10 from "chalk";
|
|
1706
|
+
import ora5 from "ora";
|
|
1041
1707
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
1042
1708
|
import { join } from "path";
|
|
1043
|
-
import
|
|
1709
|
+
import YAML2 from "yaml";
|
|
1044
1710
|
var BUILTIN_PROFILES = [
|
|
1045
1711
|
{
|
|
1046
1712
|
id: "eu-ai-act",
|
|
@@ -1071,16 +1737,16 @@ var BUILTIN_PROFILES = [
|
|
|
1071
1737
|
description: "AI Management System Standard"
|
|
1072
1738
|
}
|
|
1073
1739
|
];
|
|
1074
|
-
var complianceCommand = new
|
|
1075
|
-
new
|
|
1740
|
+
var complianceCommand = new Command8("compliance").description("Manage compliance profiles").addCommand(
|
|
1741
|
+
new Command8("list").description("List available compliance profiles").option("-a, --active", "Show only active profiles").option("-o, --output <format>", "Output format (text, json, yaml)", "text").action(async (options) => {
|
|
1076
1742
|
await listProfiles(options);
|
|
1077
1743
|
})
|
|
1078
1744
|
).addCommand(
|
|
1079
|
-
new
|
|
1745
|
+
new Command8("show").description("Show details of a profile").argument("<profileId>", "Profile ID (e.g., eu-ai-act, us-omb-m24)").option("-o, --output <format>", "Output format (text, json, yaml)", "text").action(async (profileId, options) => {
|
|
1080
1746
|
await showProfile(profileId, options);
|
|
1081
1747
|
})
|
|
1082
1748
|
).addCommand(
|
|
1083
|
-
new
|
|
1749
|
+
new Command8("set").description("Set active profiles in .aigrc.yaml").argument("<profiles...>", "Profile IDs to activate").option("--stack", "Enable profile stacking (strictest wins)").action(async (profiles, options) => {
|
|
1084
1750
|
await setProfiles(profiles, options);
|
|
1085
1751
|
})
|
|
1086
1752
|
);
|
|
@@ -1096,19 +1762,19 @@ async function listProfiles(options) {
|
|
|
1096
1762
|
return;
|
|
1097
1763
|
}
|
|
1098
1764
|
if (options.output === "yaml") {
|
|
1099
|
-
console.log(
|
|
1765
|
+
console.log(YAML2.stringify(profiles));
|
|
1100
1766
|
return;
|
|
1101
1767
|
}
|
|
1102
1768
|
console.log();
|
|
1103
1769
|
console.log(
|
|
1104
|
-
|
|
1770
|
+
chalk10.dim(" ID".padEnd(16)) + chalk10.dim("Name".padEnd(28)) + chalk10.dim("Jurisdiction".padEnd(16)) + chalk10.dim("Active")
|
|
1105
1771
|
);
|
|
1106
|
-
console.log(
|
|
1772
|
+
console.log(chalk10.dim(" " + "\u2500".repeat(70)));
|
|
1107
1773
|
for (const profile of profiles) {
|
|
1108
1774
|
const isActive = activeProfiles.includes(profile.id);
|
|
1109
|
-
const activeIndicator = isActive ?
|
|
1775
|
+
const activeIndicator = isActive ? chalk10.green("\u2713") : chalk10.gray("\xB7");
|
|
1110
1776
|
console.log(
|
|
1111
|
-
` ${
|
|
1777
|
+
` ${chalk10.cyan(profile.id.padEnd(14))} ${profile.name.padEnd(26)} ${chalk10.dim(profile.jurisdiction.padEnd(14))} ${activeIndicator}`
|
|
1112
1778
|
);
|
|
1113
1779
|
}
|
|
1114
1780
|
console.log();
|
|
@@ -1127,21 +1793,21 @@ async function showProfile(profileId, options) {
|
|
|
1127
1793
|
return;
|
|
1128
1794
|
}
|
|
1129
1795
|
if (options.output === "yaml") {
|
|
1130
|
-
console.log(
|
|
1796
|
+
console.log(YAML2.stringify(profile));
|
|
1131
1797
|
return;
|
|
1132
1798
|
}
|
|
1133
1799
|
printHeader();
|
|
1134
|
-
console.log(
|
|
1800
|
+
console.log(chalk10.bold.cyan(`Profile: ${profile.name}`));
|
|
1135
1801
|
console.log();
|
|
1136
|
-
console.log(` ${
|
|
1137
|
-
console.log(` ${
|
|
1138
|
-
console.log(` ${
|
|
1139
|
-
console.log(` ${
|
|
1802
|
+
console.log(` ${chalk10.dim("ID:")} ${profile.id}`);
|
|
1803
|
+
console.log(` ${chalk10.dim("Version:")} ${profile.version}`);
|
|
1804
|
+
console.log(` ${chalk10.dim("Jurisdiction:")} ${profile.jurisdiction}`);
|
|
1805
|
+
console.log(` ${chalk10.dim("Description:")} ${profile.description}`);
|
|
1140
1806
|
const activeProfiles = loadActiveProfiles();
|
|
1141
1807
|
const isActive = activeProfiles.includes(profile.id);
|
|
1142
1808
|
console.log();
|
|
1143
1809
|
console.log(
|
|
1144
|
-
` ${
|
|
1810
|
+
` ${chalk10.dim("Status:")} ${isActive ? chalk10.green("Active") : chalk10.gray("Inactive")}`
|
|
1145
1811
|
);
|
|
1146
1812
|
}
|
|
1147
1813
|
async function setProfiles(profiles, options) {
|
|
@@ -1155,19 +1821,19 @@ async function setProfiles(profiles, options) {
|
|
|
1155
1821
|
printInfo(`Available profiles: ${BUILTIN_PROFILES.map((p) => p.id).join(", ")}`);
|
|
1156
1822
|
process.exit(1);
|
|
1157
1823
|
}
|
|
1158
|
-
const spinner =
|
|
1824
|
+
const spinner = ora5("Updating configuration...").start();
|
|
1159
1825
|
try {
|
|
1160
1826
|
const configPath = join(process.cwd(), ".aigrc.yaml");
|
|
1161
1827
|
let config = {};
|
|
1162
1828
|
if (existsSync(configPath)) {
|
|
1163
1829
|
const content = readFileSync(configPath, "utf-8");
|
|
1164
|
-
config =
|
|
1830
|
+
config = YAML2.parse(content) || {};
|
|
1165
1831
|
}
|
|
1166
1832
|
config.profiles = profiles;
|
|
1167
1833
|
if (options.stack) {
|
|
1168
1834
|
config.stackProfiles = true;
|
|
1169
1835
|
}
|
|
1170
|
-
writeFileSync(configPath,
|
|
1836
|
+
writeFileSync(configPath, YAML2.stringify(config, { indent: 2 }), "utf-8");
|
|
1171
1837
|
spinner.succeed("Configuration updated");
|
|
1172
1838
|
console.log();
|
|
1173
1839
|
printSuccess(`Active profiles: ${profiles.join(", ")}`);
|
|
@@ -1187,7 +1853,7 @@ function loadActiveProfiles() {
|
|
|
1187
1853
|
}
|
|
1188
1854
|
try {
|
|
1189
1855
|
const content = readFileSync(configPath, "utf-8");
|
|
1190
|
-
const config =
|
|
1856
|
+
const config = YAML2.parse(content);
|
|
1191
1857
|
return config?.profiles || ["eu-ai-act"];
|
|
1192
1858
|
} catch {
|
|
1193
1859
|
return ["eu-ai-act"];
|
|
@@ -1195,13 +1861,13 @@ function loadActiveProfiles() {
|
|
|
1195
1861
|
}
|
|
1196
1862
|
|
|
1197
1863
|
// src/commands/classify.ts
|
|
1198
|
-
import { Command as
|
|
1199
|
-
import
|
|
1200
|
-
import
|
|
1864
|
+
import { Command as Command9 } from "commander";
|
|
1865
|
+
import chalk11 from "chalk";
|
|
1866
|
+
import ora6 from "ora";
|
|
1201
1867
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
1202
1868
|
import { join as join2 } from "path";
|
|
1203
|
-
import
|
|
1204
|
-
import { loadAssetCard as
|
|
1869
|
+
import YAML3 from "yaml";
|
|
1870
|
+
import { loadAssetCard as loadAssetCard4, saveAssetCard as saveAssetCard3 } from "@aigrc/core";
|
|
1205
1871
|
var RISK_MAPPINGS = {
|
|
1206
1872
|
"eu-ai-act": {
|
|
1207
1873
|
minimal: "minimal",
|
|
@@ -1234,7 +1900,7 @@ var PROFILE_NAMES = {
|
|
|
1234
1900
|
"nist-ai-rmf": "NIST AI RMF",
|
|
1235
1901
|
"iso-42001": "ISO/IEC 42001"
|
|
1236
1902
|
};
|
|
1237
|
-
var classifyCommand = new
|
|
1903
|
+
var classifyCommand = new Command9("classify").description("Classify an asset against compliance profiles").argument("<assetPath>", "Path to asset card YAML file").option("-p, --profiles <profiles...>", "Profile IDs to classify against").option("-a, --all", "Classify against all available profiles").option("-o, --output <format>", "Output format (text, json, yaml)", "text").option("-u, --update", "Update asset card with classification results").action(async (assetPath, options) => {
|
|
1238
1904
|
await runClassify(assetPath, options);
|
|
1239
1905
|
});
|
|
1240
1906
|
async function runClassify(assetPath, options) {
|
|
@@ -1245,10 +1911,10 @@ async function runClassify(assetPath, options) {
|
|
|
1245
1911
|
printError(`Asset card not found: ${assetPath}`);
|
|
1246
1912
|
process.exit(1);
|
|
1247
1913
|
}
|
|
1248
|
-
const spinner =
|
|
1914
|
+
const spinner = ora6("Loading asset card...").start();
|
|
1249
1915
|
let card;
|
|
1250
1916
|
try {
|
|
1251
|
-
card =
|
|
1917
|
+
card = loadAssetCard4(assetPath);
|
|
1252
1918
|
spinner.succeed(`Loaded: ${card.name}`);
|
|
1253
1919
|
} catch (error) {
|
|
1254
1920
|
spinner.fail("Failed to load asset card");
|
|
@@ -1296,7 +1962,7 @@ async function runClassify(assetPath, options) {
|
|
|
1296
1962
|
}
|
|
1297
1963
|
if (options.output === "yaml") {
|
|
1298
1964
|
console.log(
|
|
1299
|
-
|
|
1965
|
+
YAML3.stringify({
|
|
1300
1966
|
asset: card.name,
|
|
1301
1967
|
aigrcRiskLevel: aigrcLevel,
|
|
1302
1968
|
classifications
|
|
@@ -1307,12 +1973,12 @@ async function runClassify(assetPath, options) {
|
|
|
1307
1973
|
console.log();
|
|
1308
1974
|
printSubheader(`Classification: ${card.name}`);
|
|
1309
1975
|
console.log();
|
|
1310
|
-
console.log(` ${
|
|
1976
|
+
console.log(` ${chalk11.dim("AIGRC Risk Level:")} ${formatRiskLevel(aigrcLevel)}`);
|
|
1311
1977
|
console.log();
|
|
1312
1978
|
console.log(
|
|
1313
|
-
|
|
1979
|
+
chalk11.dim(" Profile".padEnd(24)) + chalk11.dim("Mapped Risk Level")
|
|
1314
1980
|
);
|
|
1315
|
-
console.log(
|
|
1981
|
+
console.log(chalk11.dim(" " + "\u2500".repeat(50)));
|
|
1316
1982
|
for (const c of classifications) {
|
|
1317
1983
|
console.log(
|
|
1318
1984
|
` ${c.profileName.padEnd(22)} ${formatRiskLevel(c.mappedLevel)}`
|
|
@@ -1329,9 +1995,9 @@ async function runClassify(assetPath, options) {
|
|
|
1329
1995
|
}
|
|
1330
1996
|
}
|
|
1331
1997
|
console.log();
|
|
1332
|
-
console.log(` ${
|
|
1998
|
+
console.log(` ${chalk11.dim("Strictest:")} ${formatRiskLevel(strictestLevel)}`);
|
|
1333
1999
|
if (options.update) {
|
|
1334
|
-
const updateSpinner =
|
|
2000
|
+
const updateSpinner = ora6("Updating asset card...").start();
|
|
1335
2001
|
try {
|
|
1336
2002
|
const jurisdictions = classifications.map((c) => ({
|
|
1337
2003
|
jurisdictionId: c.profileId,
|
|
@@ -1349,19 +2015,19 @@ async function runClassify(assetPath, options) {
|
|
|
1349
2015
|
}
|
|
1350
2016
|
function formatRiskLevel(level) {
|
|
1351
2017
|
const colors = {
|
|
1352
|
-
minimal:
|
|
1353
|
-
neither:
|
|
1354
|
-
low:
|
|
1355
|
-
limited:
|
|
1356
|
-
medium:
|
|
1357
|
-
moderate:
|
|
1358
|
-
high:
|
|
1359
|
-
"rights-impacting":
|
|
1360
|
-
critical:
|
|
1361
|
-
"safety-impacting":
|
|
1362
|
-
unacceptable:
|
|
2018
|
+
minimal: chalk11.green,
|
|
2019
|
+
neither: chalk11.green,
|
|
2020
|
+
low: chalk11.green,
|
|
2021
|
+
limited: chalk11.yellow,
|
|
2022
|
+
medium: chalk11.yellow,
|
|
2023
|
+
moderate: chalk11.yellow,
|
|
2024
|
+
high: chalk11.red,
|
|
2025
|
+
"rights-impacting": chalk11.red,
|
|
2026
|
+
critical: chalk11.magenta,
|
|
2027
|
+
"safety-impacting": chalk11.magenta,
|
|
2028
|
+
unacceptable: chalk11.magenta.bold
|
|
1363
2029
|
};
|
|
1364
|
-
const colorFn = colors[level] ||
|
|
2030
|
+
const colorFn = colors[level] || chalk11.white;
|
|
1365
2031
|
return colorFn(level.toUpperCase());
|
|
1366
2032
|
}
|
|
1367
2033
|
function loadActiveProfiles2() {
|
|
@@ -1371,7 +2037,7 @@ function loadActiveProfiles2() {
|
|
|
1371
2037
|
}
|
|
1372
2038
|
try {
|
|
1373
2039
|
const content = readFileSync2(configPath, "utf-8");
|
|
1374
|
-
const config =
|
|
2040
|
+
const config = YAML3.parse(content);
|
|
1375
2041
|
return config?.profiles || ["eu-ai-act"];
|
|
1376
2042
|
} catch {
|
|
1377
2043
|
return ["eu-ai-act"];
|
|
@@ -1379,13 +2045,13 @@ function loadActiveProfiles2() {
|
|
|
1379
2045
|
}
|
|
1380
2046
|
|
|
1381
2047
|
// src/commands/check.ts
|
|
1382
|
-
import { Command as
|
|
1383
|
-
import
|
|
1384
|
-
import
|
|
2048
|
+
import { Command as Command10 } from "commander";
|
|
2049
|
+
import chalk12 from "chalk";
|
|
2050
|
+
import ora7 from "ora";
|
|
1385
2051
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
1386
2052
|
import { join as join3 } from "path";
|
|
1387
|
-
import
|
|
1388
|
-
import { loadAssetCard as
|
|
2053
|
+
import YAML4 from "yaml";
|
|
2054
|
+
import { loadAssetCard as loadAssetCard5 } from "@aigrc/core";
|
|
1389
2055
|
var PROFILE_CONTROLS = {
|
|
1390
2056
|
"eu-ai-act": [
|
|
1391
2057
|
{ id: "eu-art-9", name: "Risk Management System", category: "Risk Management", riskLevels: ["high"] },
|
|
@@ -1432,7 +2098,7 @@ var PROFILE_NAMES2 = {
|
|
|
1432
2098
|
"nist-ai-rmf": "NIST AI RMF",
|
|
1433
2099
|
"iso-42001": "ISO/IEC 42001"
|
|
1434
2100
|
};
|
|
1435
|
-
var checkCommand = new
|
|
2101
|
+
var checkCommand = new Command10("check").description("Check compliance status for an asset").argument("<assetPath>", "Path to asset card YAML file").option("-p, --profiles <profiles...>", "Profile IDs to check").option("-s, --stack", "Check against stacked profiles (strictest wins)").option("-o, --output <format>", "Output format (text, json, yaml)", "text").option("-v, --verbose", "Show detailed control status").action(async (assetPath, options) => {
|
|
1436
2102
|
await runCheck(assetPath, options);
|
|
1437
2103
|
});
|
|
1438
2104
|
async function runCheck(assetPath, options) {
|
|
@@ -1443,10 +2109,10 @@ async function runCheck(assetPath, options) {
|
|
|
1443
2109
|
printError(`Asset card not found: ${assetPath}`);
|
|
1444
2110
|
process.exit(1);
|
|
1445
2111
|
}
|
|
1446
|
-
const spinner =
|
|
2112
|
+
const spinner = ora7("Loading asset card...").start();
|
|
1447
2113
|
let card;
|
|
1448
2114
|
try {
|
|
1449
|
-
card =
|
|
2115
|
+
card = loadAssetCard5(assetPath);
|
|
1450
2116
|
spinner.succeed(`Loaded: ${card.name}`);
|
|
1451
2117
|
} catch (error) {
|
|
1452
2118
|
spinner.fail("Failed to load asset card");
|
|
@@ -1471,19 +2137,19 @@ async function runCheck(assetPath, options) {
|
|
|
1471
2137
|
return;
|
|
1472
2138
|
}
|
|
1473
2139
|
if (options.output === "yaml") {
|
|
1474
|
-
console.log(
|
|
2140
|
+
console.log(YAML4.stringify({ asset: card.name, results }));
|
|
1475
2141
|
return;
|
|
1476
2142
|
}
|
|
1477
2143
|
console.log();
|
|
1478
2144
|
printSubheader(`Compliance Check: ${card.name}`);
|
|
1479
2145
|
console.log();
|
|
1480
2146
|
console.log(
|
|
1481
|
-
|
|
2147
|
+
chalk12.dim(" Profile".padEnd(20)) + chalk12.dim("Risk Level".padEnd(20)) + chalk12.dim("Compliant".padEnd(12)) + chalk12.dim("Score")
|
|
1482
2148
|
);
|
|
1483
|
-
console.log(
|
|
2149
|
+
console.log(chalk12.dim(" " + "\u2500".repeat(60)));
|
|
1484
2150
|
for (const r of results) {
|
|
1485
|
-
const compliantIcon = r.compliant ?
|
|
1486
|
-
const scoreColor = r.percentage >= 80 ?
|
|
2151
|
+
const compliantIcon = r.compliant ? chalk12.green("\u2713 Yes") : chalk12.red("\u2717 No");
|
|
2152
|
+
const scoreColor = r.percentage >= 80 ? chalk12.green : r.percentage >= 50 ? chalk12.yellow : chalk12.red;
|
|
1487
2153
|
console.log(
|
|
1488
2154
|
` ${r.profileName.padEnd(18)} ${formatRiskLevel2(r.riskLevel).padEnd(28)} ${compliantIcon.padEnd(20)} ${scoreColor(r.percentage + "%")}`
|
|
1489
2155
|
);
|
|
@@ -1493,7 +2159,7 @@ async function runCheck(assetPath, options) {
|
|
|
1493
2159
|
const allCompliant = results.every((r) => r.compliant);
|
|
1494
2160
|
const avgPercentage = Math.round(results.reduce((acc, r) => acc + r.percentage, 0) / results.length);
|
|
1495
2161
|
console.log(
|
|
1496
|
-
` ${
|
|
2162
|
+
` ${chalk12.bold("STACKED")}`.padEnd(18) + `${chalk12.dim("(strictest)")}`.padEnd(28) + `${allCompliant ? chalk12.green("\u2713 Yes") : chalk12.red("\u2717 No")}`.padEnd(20) + `${avgPercentage}%`
|
|
1497
2163
|
);
|
|
1498
2164
|
}
|
|
1499
2165
|
if (options.verbose) {
|
|
@@ -1502,9 +2168,9 @@ async function runCheck(assetPath, options) {
|
|
|
1502
2168
|
printSubheader(`${r.profileName} Details`);
|
|
1503
2169
|
console.log();
|
|
1504
2170
|
console.log(
|
|
1505
|
-
|
|
2171
|
+
chalk12.dim(" Control".padEnd(32)) + chalk12.dim("Status")
|
|
1506
2172
|
);
|
|
1507
|
-
console.log(
|
|
2173
|
+
console.log(chalk12.dim(" " + "\u2500".repeat(50)));
|
|
1508
2174
|
for (const c of r.controls) {
|
|
1509
2175
|
const statusIcon = getStatusIcon(c.status);
|
|
1510
2176
|
console.log(` ${c.controlName.padEnd(30)} ${statusIcon}`);
|
|
@@ -1513,7 +2179,7 @@ async function runCheck(assetPath, options) {
|
|
|
1513
2179
|
console.log();
|
|
1514
2180
|
printWarning(`${r.gaps.length} gap(s) identified:`);
|
|
1515
2181
|
for (const gap of r.gaps) {
|
|
1516
|
-
console.log(
|
|
2182
|
+
console.log(chalk12.yellow(` - ${gap}`));
|
|
1517
2183
|
}
|
|
1518
2184
|
}
|
|
1519
2185
|
}
|
|
@@ -1575,9 +2241,9 @@ function evaluateControl(card, control) {
|
|
|
1575
2241
|
"Risk Management": hasApprovals || hasIntent,
|
|
1576
2242
|
"Data Governance": hasConstraints,
|
|
1577
2243
|
Documentation: hasIntent && !!card.intent.businessJustification,
|
|
1578
|
-
Logging: hasConstraints && card.constraints?.monitoring?.logAllDecisions,
|
|
2244
|
+
Logging: hasConstraints && !!card.constraints?.monitoring?.logAllDecisions,
|
|
1579
2245
|
Transparency: card.intent.linked,
|
|
1580
|
-
Oversight: hasApprovals || hasConstraints && card.constraints?.humanApprovalRequired?.length,
|
|
2246
|
+
Oversight: hasApprovals || hasConstraints && !!card.constraints?.humanApprovalRequired?.length,
|
|
1581
2247
|
Technical: hasConstraints,
|
|
1582
2248
|
Assessment: hasIntent && !!card.intent.businessJustification,
|
|
1583
2249
|
Governance: hasApprovals,
|
|
@@ -1585,12 +2251,12 @@ function evaluateControl(card, control) {
|
|
|
1585
2251
|
Leadership: hasApprovals,
|
|
1586
2252
|
Support: card.ownership.team !== void 0,
|
|
1587
2253
|
Operations: hasConstraints,
|
|
1588
|
-
Performance: hasConstraints && card.constraints?.monitoring?.logAllDecisions,
|
|
2254
|
+
Performance: hasConstraints && !!card.constraints?.monitoring?.logAllDecisions,
|
|
1589
2255
|
Improvement: hasApprovals && card.governance.status !== "draft",
|
|
1590
2256
|
Equity: hasIntent && !!card.intent.businessJustification,
|
|
1591
2257
|
Remediation: hasApprovals,
|
|
1592
2258
|
Testing: hasConstraints,
|
|
1593
|
-
Measurement: hasConstraints && card.constraints?.monitoring,
|
|
2259
|
+
Measurement: hasConstraints && !!card.constraints?.monitoring,
|
|
1594
2260
|
Management: hasApprovals || hasIntent,
|
|
1595
2261
|
"Risk Mapping": hasIntent
|
|
1596
2262
|
};
|
|
@@ -1603,30 +2269,30 @@ function evaluateControl(card, control) {
|
|
|
1603
2269
|
function getStatusIcon(status) {
|
|
1604
2270
|
switch (status) {
|
|
1605
2271
|
case "implemented":
|
|
1606
|
-
return
|
|
2272
|
+
return chalk12.green("\u2713 Implemented");
|
|
1607
2273
|
case "partial":
|
|
1608
|
-
return
|
|
2274
|
+
return chalk12.yellow("\u25D0 Partial");
|
|
1609
2275
|
case "not_implemented":
|
|
1610
|
-
return
|
|
2276
|
+
return chalk12.red("\u2717 Missing");
|
|
1611
2277
|
case "not_applicable":
|
|
1612
|
-
return
|
|
2278
|
+
return chalk12.gray("- N/A");
|
|
1613
2279
|
}
|
|
1614
2280
|
}
|
|
1615
2281
|
function formatRiskLevel2(level) {
|
|
1616
2282
|
const colors = {
|
|
1617
|
-
minimal:
|
|
1618
|
-
neither:
|
|
1619
|
-
low:
|
|
1620
|
-
limited:
|
|
1621
|
-
medium:
|
|
1622
|
-
moderate:
|
|
1623
|
-
high:
|
|
1624
|
-
"rights-impacting":
|
|
1625
|
-
critical:
|
|
1626
|
-
"safety-impacting":
|
|
1627
|
-
unacceptable:
|
|
2283
|
+
minimal: chalk12.green,
|
|
2284
|
+
neither: chalk12.green,
|
|
2285
|
+
low: chalk12.green,
|
|
2286
|
+
limited: chalk12.yellow,
|
|
2287
|
+
medium: chalk12.yellow,
|
|
2288
|
+
moderate: chalk12.yellow,
|
|
2289
|
+
high: chalk12.red,
|
|
2290
|
+
"rights-impacting": chalk12.red,
|
|
2291
|
+
critical: chalk12.magenta,
|
|
2292
|
+
"safety-impacting": chalk12.magenta,
|
|
2293
|
+
unacceptable: chalk12.magenta.bold
|
|
1628
2294
|
};
|
|
1629
|
-
const colorFn = colors[level] ||
|
|
2295
|
+
const colorFn = colors[level] || chalk12.white;
|
|
1630
2296
|
return colorFn(level.toUpperCase());
|
|
1631
2297
|
}
|
|
1632
2298
|
function loadActiveProfiles3() {
|
|
@@ -1636,7 +2302,7 @@ function loadActiveProfiles3() {
|
|
|
1636
2302
|
}
|
|
1637
2303
|
try {
|
|
1638
2304
|
const content = readFileSync3(configPath, "utf-8");
|
|
1639
|
-
const config =
|
|
2305
|
+
const config = YAML4.parse(content);
|
|
1640
2306
|
return config?.profiles || ["eu-ai-act"];
|
|
1641
2307
|
} catch {
|
|
1642
2308
|
return ["eu-ai-act"];
|
|
@@ -1644,12 +2310,12 @@ function loadActiveProfiles3() {
|
|
|
1644
2310
|
}
|
|
1645
2311
|
|
|
1646
2312
|
// src/commands/generate.ts
|
|
1647
|
-
import { Command as
|
|
1648
|
-
import
|
|
2313
|
+
import { Command as Command11 } from "commander";
|
|
2314
|
+
import ora8 from "ora";
|
|
1649
2315
|
import { existsSync as existsSync4, mkdirSync, writeFileSync as writeFileSync3, readFileSync as readFileSync4 } from "fs";
|
|
1650
2316
|
import { join as join4 } from "path";
|
|
1651
|
-
import
|
|
1652
|
-
import { loadAssetCard as
|
|
2317
|
+
import YAML5 from "yaml";
|
|
2318
|
+
import { loadAssetCard as loadAssetCard6 } from "@aigrc/core";
|
|
1653
2319
|
var ARTIFACT_TEMPLATES = {
|
|
1654
2320
|
"eu-ai-act": [
|
|
1655
2321
|
{ id: "risk-management-plan", name: "Risk Management Plan", requiredFor: ["high"], format: "md" },
|
|
@@ -1682,7 +2348,7 @@ var PROFILE_NAMES3 = {
|
|
|
1682
2348
|
"nist-ai-rmf": "NIST AI RMF",
|
|
1683
2349
|
"iso-42001": "ISO/IEC 42001"
|
|
1684
2350
|
};
|
|
1685
|
-
var generateCommand = new
|
|
2351
|
+
var generateCommand = new Command11("generate").description("Generate compliance artifacts from templates").argument("<assetPath>", "Path to asset card YAML file").option("-p, --profile <profile>", "Profile ID for templates").option("-t, --template <template>", "Specific template ID to generate").option("-a, --all", "Generate all required artifacts").option("-o, --output-dir <dir>", "Output directory", ".aigrc/artifacts").option("-f, --force", "Overwrite existing files").action(async (assetPath, options) => {
|
|
1686
2352
|
await runGenerate(assetPath, options);
|
|
1687
2353
|
});
|
|
1688
2354
|
async function runGenerate(assetPath, options) {
|
|
@@ -1691,10 +2357,10 @@ async function runGenerate(assetPath, options) {
|
|
|
1691
2357
|
printError(`Asset card not found: ${assetPath}`);
|
|
1692
2358
|
process.exit(1);
|
|
1693
2359
|
}
|
|
1694
|
-
const spinner =
|
|
2360
|
+
const spinner = ora8("Loading asset card...").start();
|
|
1695
2361
|
let card;
|
|
1696
2362
|
try {
|
|
1697
|
-
card =
|
|
2363
|
+
card = loadAssetCard6(assetPath);
|
|
1698
2364
|
spinner.succeed(`Loaded: ${card.name}`);
|
|
1699
2365
|
} catch (error) {
|
|
1700
2366
|
spinner.fail("Failed to load asset card");
|
|
@@ -1859,7 +2525,7 @@ function generateYamlTemplate(card, profileId, template) {
|
|
|
1859
2525
|
pendingSections: ["All sections require review"]
|
|
1860
2526
|
}
|
|
1861
2527
|
};
|
|
1862
|
-
return
|
|
2528
|
+
return YAML5.stringify(content, { indent: 2 });
|
|
1863
2529
|
}
|
|
1864
2530
|
function getSection2Title(templateId) {
|
|
1865
2531
|
const titles = {
|
|
@@ -1950,7 +2616,7 @@ function loadActiveProfiles4() {
|
|
|
1950
2616
|
}
|
|
1951
2617
|
try {
|
|
1952
2618
|
const content = readFileSync4(configPath, "utf-8");
|
|
1953
|
-
const config =
|
|
2619
|
+
const config = YAML5.parse(content);
|
|
1954
2620
|
return config?.profiles || ["eu-ai-act"];
|
|
1955
2621
|
} catch {
|
|
1956
2622
|
return ["eu-ai-act"];
|
|
@@ -1958,12 +2624,12 @@ function loadActiveProfiles4() {
|
|
|
1958
2624
|
}
|
|
1959
2625
|
|
|
1960
2626
|
// src/commands/report.ts
|
|
1961
|
-
import { Command as
|
|
1962
|
-
import
|
|
2627
|
+
import { Command as Command12 } from "commander";
|
|
2628
|
+
import ora9 from "ora";
|
|
1963
2629
|
import { existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync as writeFileSync4, readFileSync as readFileSync5, readdirSync } from "fs";
|
|
1964
2630
|
import { join as join5 } from "path";
|
|
1965
|
-
import
|
|
1966
|
-
import { loadAssetCard as
|
|
2631
|
+
import YAML6 from "yaml";
|
|
2632
|
+
import { loadAssetCard as loadAssetCard7 } from "@aigrc/core";
|
|
1967
2633
|
var PROFILE_NAMES4 = {
|
|
1968
2634
|
"eu-ai-act": "EU AI Act",
|
|
1969
2635
|
"us-omb-m24": "US OMB M-24",
|
|
@@ -2007,26 +2673,26 @@ var CONTROL_CROSSWALK = {
|
|
|
2007
2673
|
"iso-42001": ["iso-a8"]
|
|
2008
2674
|
}
|
|
2009
2675
|
};
|
|
2010
|
-
var reportCommand = new
|
|
2011
|
-
new
|
|
2676
|
+
var reportCommand = new Command12("report").description("Generate compliance reports").addCommand(
|
|
2677
|
+
new Command12("gap").description("Generate gap analysis report").argument("[assetPath]", "Path to asset card (or all in .aigrc/cards)").option("-p, --profiles <profiles...>", "Profile IDs to analyze").option("-o, --output <file>", "Output file path").option("-f, --format <format>", "Format (md, json, yaml)", "md").action(async (assetPath, options) => {
|
|
2012
2678
|
await generateGapReport(assetPath, options);
|
|
2013
2679
|
})
|
|
2014
2680
|
).addCommand(
|
|
2015
|
-
new
|
|
2681
|
+
new Command12("crosswalk").description("Generate cross-framework mapping report").argument("<assetPath>", "Path to asset card").option("-p, --profiles <profiles...>", "Profile IDs to include").option("-o, --output <file>", "Output file path").option("-f, --format <format>", "Format (md, json, yaml)", "md").action(async (assetPath, options) => {
|
|
2016
2682
|
await generateCrosswalkReport(assetPath, options);
|
|
2017
2683
|
})
|
|
2018
2684
|
).addCommand(
|
|
2019
|
-
new
|
|
2685
|
+
new Command12("audit").description("Generate audit package").option("-p, --profile <profile>", "Profile ID", "eu-ai-act").option("-o, --output <dir>", "Output directory").option("--include-assets <assets...>", "Specific asset card paths").action(async (options) => {
|
|
2020
2686
|
await generateAuditPackage(options);
|
|
2021
2687
|
})
|
|
2022
2688
|
);
|
|
2023
2689
|
async function generateGapReport(assetPath, options) {
|
|
2024
2690
|
printHeader();
|
|
2025
|
-
const spinner =
|
|
2691
|
+
const spinner = ora9("Loading assets...").start();
|
|
2026
2692
|
let cards = [];
|
|
2027
2693
|
if (assetPath) {
|
|
2028
2694
|
try {
|
|
2029
|
-
const card =
|
|
2695
|
+
const card = loadAssetCard7(assetPath);
|
|
2030
2696
|
cards.push({ path: assetPath, card });
|
|
2031
2697
|
} catch (error) {
|
|
2032
2698
|
spinner.fail("Failed to load asset card");
|
|
@@ -2039,7 +2705,7 @@ async function generateGapReport(assetPath, options) {
|
|
|
2039
2705
|
const files = readdirSync(cardsDir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
2040
2706
|
for (const file of files) {
|
|
2041
2707
|
try {
|
|
2042
|
-
const card =
|
|
2708
|
+
const card = loadAssetCard7(join5(cardsDir, file));
|
|
2043
2709
|
cards.push({ path: join5(cardsDir, file), card });
|
|
2044
2710
|
} catch {
|
|
2045
2711
|
}
|
|
@@ -2093,7 +2759,7 @@ async function generateGapReport(assetPath, options) {
|
|
|
2093
2759
|
return;
|
|
2094
2760
|
}
|
|
2095
2761
|
if (options.format === "yaml") {
|
|
2096
|
-
const output =
|
|
2762
|
+
const output = YAML6.stringify({ generated: now, gaps });
|
|
2097
2763
|
if (options.output) {
|
|
2098
2764
|
writeFileSync4(options.output, output, "utf-8");
|
|
2099
2765
|
printSuccess(`Report saved to: ${options.output}`);
|
|
@@ -2154,7 +2820,7 @@ async function generateCrosswalkReport(assetPath, options) {
|
|
|
2154
2820
|
}
|
|
2155
2821
|
let card;
|
|
2156
2822
|
try {
|
|
2157
|
-
card =
|
|
2823
|
+
card = loadAssetCard7(assetPath);
|
|
2158
2824
|
} catch (error) {
|
|
2159
2825
|
printError(error instanceof Error ? error.message : String(error));
|
|
2160
2826
|
process.exit(1);
|
|
@@ -2193,7 +2859,7 @@ async function generateCrosswalkReport(assetPath, options) {
|
|
|
2193
2859
|
return;
|
|
2194
2860
|
}
|
|
2195
2861
|
if (options.format === "yaml") {
|
|
2196
|
-
const output =
|
|
2862
|
+
const output = YAML6.stringify({
|
|
2197
2863
|
generated: now,
|
|
2198
2864
|
asset: { id: card.id, name: card.name },
|
|
2199
2865
|
aigrcRiskLevel: card.classification.riskLevel,
|
|
@@ -2256,7 +2922,7 @@ async function generateCrosswalkReport(assetPath, options) {
|
|
|
2256
2922
|
async function generateAuditPackage(options) {
|
|
2257
2923
|
printHeader();
|
|
2258
2924
|
printSubheader("Generating Audit Package");
|
|
2259
|
-
const spinner =
|
|
2925
|
+
const spinner = ora9("Collecting assets...").start();
|
|
2260
2926
|
let cardPaths = [];
|
|
2261
2927
|
if (options.includeAssets && options.includeAssets.length > 0) {
|
|
2262
2928
|
cardPaths = options.includeAssets;
|
|
@@ -2271,9 +2937,9 @@ async function generateAuditPackage(options) {
|
|
|
2271
2937
|
process.exit(1);
|
|
2272
2938
|
}
|
|
2273
2939
|
const cards = [];
|
|
2274
|
-
for (const
|
|
2940
|
+
for (const path9 of cardPaths) {
|
|
2275
2941
|
try {
|
|
2276
|
-
cards.push(
|
|
2942
|
+
cards.push(loadAssetCard7(path9));
|
|
2277
2943
|
} catch {
|
|
2278
2944
|
}
|
|
2279
2945
|
}
|
|
@@ -2311,7 +2977,7 @@ ${cards.map((c) => `| ${c.name} | ${c.classification.riskLevel} | ${c.governance
|
|
|
2311
2977
|
mkdirSync2(assetsDir, { recursive: true });
|
|
2312
2978
|
for (const card of cards) {
|
|
2313
2979
|
const filename = `${card.id}.yaml`;
|
|
2314
|
-
writeFileSync4(join5(assetsDir, filename),
|
|
2980
|
+
writeFileSync4(join5(assetsDir, filename), YAML6.stringify(card, { indent: 2 }), "utf-8");
|
|
2315
2981
|
}
|
|
2316
2982
|
const inventory = {
|
|
2317
2983
|
generated: now,
|
|
@@ -2335,7 +3001,7 @@ ${cards.map((c) => `| ${c.name} | ${c.classification.riskLevel} | ${c.governance
|
|
|
2335
3001
|
status: c.governance.status
|
|
2336
3002
|
}))
|
|
2337
3003
|
};
|
|
2338
|
-
writeFileSync4(join5(outputDir, "inventory.yaml"),
|
|
3004
|
+
writeFileSync4(join5(outputDir, "inventory.yaml"), YAML6.stringify(inventory, { indent: 2 }), "utf-8");
|
|
2339
3005
|
console.log();
|
|
2340
3006
|
printSuccess(`Audit package created: ${outputDir}`);
|
|
2341
3007
|
printInfo(` - README.md (Executive Summary)`);
|
|
@@ -2349,7 +3015,7 @@ function loadActiveProfiles5() {
|
|
|
2349
3015
|
}
|
|
2350
3016
|
try {
|
|
2351
3017
|
const content = readFileSync5(configPath, "utf-8");
|
|
2352
|
-
const config =
|
|
3018
|
+
const config = YAML6.parse(content);
|
|
2353
3019
|
return config?.profiles || ["eu-ai-act"];
|
|
2354
3020
|
} catch {
|
|
2355
3021
|
return ["eu-ai-act"];
|
|
@@ -2363,6 +3029,8 @@ program.addCommand(initCommand);
|
|
|
2363
3029
|
program.addCommand(registerCommand);
|
|
2364
3030
|
program.addCommand(validateCommand);
|
|
2365
3031
|
program.addCommand(statusCommand);
|
|
3032
|
+
program.addCommand(hashCommand);
|
|
3033
|
+
program.addCommand(versionCommand);
|
|
2366
3034
|
program.addCommand(complianceCommand);
|
|
2367
3035
|
program.addCommand(classifyCommand);
|
|
2368
3036
|
program.addCommand(checkCommand);
|