@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/index.js
CHANGED
|
@@ -700,59 +700,414 @@ function sanitizeFileName2(name) {
|
|
|
700
700
|
|
|
701
701
|
// src/commands/validate.ts
|
|
702
702
|
import { Command as Command4 } from "commander";
|
|
703
|
-
import
|
|
703
|
+
import chalk6 from "chalk";
|
|
704
704
|
import ora3 from "ora";
|
|
705
|
-
import
|
|
705
|
+
import path5 from "path";
|
|
706
706
|
import fs3 from "fs/promises";
|
|
707
|
+
import YAML from "yaml";
|
|
707
708
|
import {
|
|
708
709
|
loadAssetCard,
|
|
709
710
|
classifyRisk,
|
|
710
711
|
validateAssetCard
|
|
711
712
|
} from "@aigrc/core";
|
|
713
|
+
|
|
714
|
+
// src/utils/exit-codes.ts
|
|
715
|
+
function exit(code) {
|
|
716
|
+
process.exit(code);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// src/formatters/sarif.ts
|
|
720
|
+
function formatSarif(results, version = "0.1.0") {
|
|
721
|
+
const sarifResults = [];
|
|
722
|
+
const artifacts = [];
|
|
723
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
724
|
+
for (const result of results) {
|
|
725
|
+
if (!seenPaths.has(result.path)) {
|
|
726
|
+
seenPaths.add(result.path);
|
|
727
|
+
artifacts.push({
|
|
728
|
+
location: {
|
|
729
|
+
uri: result.path
|
|
730
|
+
},
|
|
731
|
+
mimeType: "application/x-yaml"
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
if (!result.validation.valid) {
|
|
735
|
+
for (const error of result.validation.errors) {
|
|
736
|
+
sarifResults.push({
|
|
737
|
+
ruleId: "AIGRC001",
|
|
738
|
+
level: "error",
|
|
739
|
+
message: {
|
|
740
|
+
text: error
|
|
741
|
+
},
|
|
742
|
+
locations: [
|
|
743
|
+
{
|
|
744
|
+
physicalLocation: {
|
|
745
|
+
artifactLocation: {
|
|
746
|
+
uri: result.path
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
],
|
|
751
|
+
properties: {
|
|
752
|
+
cardId: result.card?.id,
|
|
753
|
+
cardName: result.card?.name
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
} else {
|
|
758
|
+
sarifResults.push({
|
|
759
|
+
ruleId: "AIGRC001",
|
|
760
|
+
level: "none",
|
|
761
|
+
message: {
|
|
762
|
+
text: "Asset card validation passed"
|
|
763
|
+
},
|
|
764
|
+
locations: [
|
|
765
|
+
{
|
|
766
|
+
physicalLocation: {
|
|
767
|
+
artifactLocation: {
|
|
768
|
+
uri: result.path
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
],
|
|
773
|
+
properties: {
|
|
774
|
+
cardId: result.card?.id,
|
|
775
|
+
cardName: result.card?.name,
|
|
776
|
+
riskLevel: result.card?.classification?.riskLevel
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
const sarif = {
|
|
782
|
+
version: "2.1.0",
|
|
783
|
+
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
|
784
|
+
runs: [
|
|
785
|
+
{
|
|
786
|
+
tool: {
|
|
787
|
+
driver: {
|
|
788
|
+
name: "AIGRC",
|
|
789
|
+
version,
|
|
790
|
+
informationUri: "https://github.com/aigrc/aigrc",
|
|
791
|
+
rules: [
|
|
792
|
+
{
|
|
793
|
+
id: "AIGRC001",
|
|
794
|
+
name: "AssetCardValidation",
|
|
795
|
+
shortDescription: {
|
|
796
|
+
text: "Asset card must be valid according to AIGRC schema"
|
|
797
|
+
},
|
|
798
|
+
fullDescription: {
|
|
799
|
+
text: "Validates asset cards against AIGRC compliance requirements including risk classification, ownership, and regulatory framework mappings."
|
|
800
|
+
},
|
|
801
|
+
help: {
|
|
802
|
+
text: "Ensure your asset card includes all required fields and follows the AIGRC schema. Run 'aigrc validate --help' for more information."
|
|
803
|
+
},
|
|
804
|
+
defaultConfiguration: {
|
|
805
|
+
level: "error"
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
]
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
results: sarifResults,
|
|
812
|
+
artifacts
|
|
813
|
+
}
|
|
814
|
+
]
|
|
815
|
+
};
|
|
816
|
+
return sarif;
|
|
817
|
+
}
|
|
818
|
+
function sarifToJson(sarif, pretty = true) {
|
|
819
|
+
return JSON.stringify(sarif, null, pretty ? 2 : 0);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// src/formatters/text.ts
|
|
823
|
+
import chalk5 from "chalk";
|
|
824
|
+
import path4 from "path";
|
|
825
|
+
function printValidationSummary(results) {
|
|
826
|
+
console.log(chalk5.bold("Validation Summary"));
|
|
827
|
+
console.log(chalk5.dim("\u2500".repeat(50)));
|
|
828
|
+
console.log();
|
|
829
|
+
for (const result of results) {
|
|
830
|
+
const fileName = path4.basename(result.path);
|
|
831
|
+
if (!result.validation.valid) {
|
|
832
|
+
console.log(chalk5.red(`\u2717 ${fileName}`));
|
|
833
|
+
for (const error of result.validation.errors) {
|
|
834
|
+
console.log(chalk5.red(` Error: ${error}`));
|
|
835
|
+
}
|
|
836
|
+
if (result.fixed && result.fixedFields && result.fixedFields.length > 0) {
|
|
837
|
+
console.log(chalk5.yellow(` Fixed: ${result.fixedFields.join(", ")}`));
|
|
838
|
+
}
|
|
839
|
+
} else {
|
|
840
|
+
console.log(chalk5.green(`\u2713 ${fileName}`));
|
|
841
|
+
if (result.fixed && result.fixedFields && result.fixedFields.length > 0) {
|
|
842
|
+
console.log(chalk5.yellow(` Fixed: ${result.fixedFields.join(", ")}`));
|
|
843
|
+
}
|
|
844
|
+
if (result.card && result.classification) {
|
|
845
|
+
const tierColor = getRiskLevelColor(result.classification.riskLevel);
|
|
846
|
+
console.log(
|
|
847
|
+
chalk5.dim(" Risk Level: ") + tierColor(result.classification.riskLevel)
|
|
848
|
+
);
|
|
849
|
+
if (result.classification.euAiActCategory) {
|
|
850
|
+
console.log(
|
|
851
|
+
chalk5.dim(" EU AI Act: ") + chalk5.yellow(result.classification.euAiActCategory)
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
console.log();
|
|
857
|
+
}
|
|
858
|
+
const valid = results.filter((r) => r.validation.valid).length;
|
|
859
|
+
const invalid = results.filter((r) => !r.validation.valid).length;
|
|
860
|
+
const fixed = results.filter((r) => r.fixed).length;
|
|
861
|
+
console.log(chalk5.dim("\u2500".repeat(50)));
|
|
862
|
+
console.log(
|
|
863
|
+
`Total: ${results.length} | ` + chalk5.green(`Valid: ${valid}`) + ` | ` + chalk5.red(`Invalid: ${invalid}`) + (fixed > 0 ? ` | ${chalk5.yellow(`Fixed: ${fixed}`)}` : "")
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
function getRiskLevelColor(level) {
|
|
867
|
+
switch (level) {
|
|
868
|
+
case "minimal":
|
|
869
|
+
return chalk5.green;
|
|
870
|
+
case "limited":
|
|
871
|
+
return chalk5.yellow;
|
|
872
|
+
case "high":
|
|
873
|
+
return chalk5.red;
|
|
874
|
+
case "unacceptable":
|
|
875
|
+
return chalk5.magenta;
|
|
876
|
+
default:
|
|
877
|
+
return chalk5.white;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// src/fixers/auto-fix.ts
|
|
882
|
+
function autoFixAssetCard(card) {
|
|
883
|
+
const fixedFields = [];
|
|
884
|
+
const fixedCard = JSON.parse(JSON.stringify(card));
|
|
885
|
+
if (!fixedCard.metadata?.createdAt) {
|
|
886
|
+
if (!fixedCard.metadata) {
|
|
887
|
+
fixedCard.metadata = {
|
|
888
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
889
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
890
|
+
version: "1.0.0"
|
|
891
|
+
};
|
|
892
|
+
} else {
|
|
893
|
+
fixedCard.metadata.createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
894
|
+
}
|
|
895
|
+
fixedFields.push("metadata.createdAt");
|
|
896
|
+
} else if (!isValidISODate(fixedCard.metadata.createdAt)) {
|
|
897
|
+
fixedCard.metadata.createdAt = normalizeDate(fixedCard.metadata.createdAt);
|
|
898
|
+
fixedFields.push("metadata.createdAt (format)");
|
|
899
|
+
}
|
|
900
|
+
if (!fixedCard.metadata?.updatedAt) {
|
|
901
|
+
if (!fixedCard.metadata) {
|
|
902
|
+
fixedCard.metadata = {
|
|
903
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
904
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
905
|
+
version: "1.0.0"
|
|
906
|
+
};
|
|
907
|
+
} else {
|
|
908
|
+
fixedCard.metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
909
|
+
}
|
|
910
|
+
fixedFields.push("metadata.updatedAt");
|
|
911
|
+
} else if (!isValidISODate(fixedCard.metadata.updatedAt)) {
|
|
912
|
+
fixedCard.metadata.updatedAt = normalizeDate(fixedCard.metadata.updatedAt);
|
|
913
|
+
fixedFields.push("metadata.updatedAt (format)");
|
|
914
|
+
}
|
|
915
|
+
if (!fixedCard.metadata?.version) {
|
|
916
|
+
if (!fixedCard.metadata) {
|
|
917
|
+
fixedCard.metadata = {
|
|
918
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
919
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
920
|
+
version: "1.0.0"
|
|
921
|
+
};
|
|
922
|
+
} else {
|
|
923
|
+
fixedCard.metadata.version = "1.0.0";
|
|
924
|
+
}
|
|
925
|
+
fixedFields.push("metadata.version");
|
|
926
|
+
}
|
|
927
|
+
if (fixedCard.classification?.riskLevel) {
|
|
928
|
+
const normalized = normalizeRiskLevel(fixedCard.classification.riskLevel);
|
|
929
|
+
if (normalized !== fixedCard.classification.riskLevel) {
|
|
930
|
+
fixedCard.classification.riskLevel = normalized;
|
|
931
|
+
fixedFields.push("classification.riskLevel");
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
if (fixedCard.classification?.riskFactors?.piiProcessing !== void 0) {
|
|
935
|
+
const pii = fixedCard.classification.riskFactors.piiProcessing;
|
|
936
|
+
if (typeof pii === "boolean") {
|
|
937
|
+
fixedCard.classification.riskFactors.piiProcessing = pii ? "yes" : "no";
|
|
938
|
+
fixedFields.push("classification.riskFactors.piiProcessing");
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (!fixedCard.id) {
|
|
942
|
+
fixedCard.id = generateId(fixedCard.name);
|
|
943
|
+
fixedFields.push("id");
|
|
944
|
+
}
|
|
945
|
+
if (!fixedCard.name) {
|
|
946
|
+
fixedCard.name = "Unnamed Asset";
|
|
947
|
+
fixedFields.push("name");
|
|
948
|
+
}
|
|
949
|
+
if (!fixedCard.description) {
|
|
950
|
+
fixedCard.description = "No description provided";
|
|
951
|
+
fixedFields.push("description");
|
|
952
|
+
}
|
|
953
|
+
if (!fixedCard.classification) {
|
|
954
|
+
fixedCard.classification = {
|
|
955
|
+
riskLevel: "minimal",
|
|
956
|
+
riskFactors: {
|
|
957
|
+
autonomousDecisions: false,
|
|
958
|
+
customerFacing: false,
|
|
959
|
+
toolExecution: false,
|
|
960
|
+
externalDataAccess: false,
|
|
961
|
+
piiProcessing: "unknown",
|
|
962
|
+
highStakesDecisions: false
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
fixedFields.push("classification");
|
|
966
|
+
}
|
|
967
|
+
if (!fixedCard.classification.riskFactors) {
|
|
968
|
+
fixedCard.classification.riskFactors = {
|
|
969
|
+
autonomousDecisions: false,
|
|
970
|
+
customerFacing: false,
|
|
971
|
+
toolExecution: false,
|
|
972
|
+
externalDataAccess: false,
|
|
973
|
+
piiProcessing: "unknown",
|
|
974
|
+
highStakesDecisions: false
|
|
975
|
+
};
|
|
976
|
+
fixedFields.push("classification.riskFactors");
|
|
977
|
+
}
|
|
978
|
+
return {
|
|
979
|
+
fixed: fixedFields.length > 0,
|
|
980
|
+
fixedFields,
|
|
981
|
+
card: fixedCard
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
function isValidISODate(dateStr) {
|
|
985
|
+
const iso8601Regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/;
|
|
986
|
+
if (!iso8601Regex.test(dateStr)) {
|
|
987
|
+
return false;
|
|
988
|
+
}
|
|
989
|
+
const date = new Date(dateStr);
|
|
990
|
+
return !isNaN(date.getTime());
|
|
991
|
+
}
|
|
992
|
+
function normalizeDate(dateStr) {
|
|
993
|
+
const date = new Date(dateStr);
|
|
994
|
+
if (isNaN(date.getTime())) {
|
|
995
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
996
|
+
}
|
|
997
|
+
return date.toISOString();
|
|
998
|
+
}
|
|
999
|
+
function normalizeRiskLevel(level) {
|
|
1000
|
+
const normalized = level.toLowerCase().trim();
|
|
1001
|
+
const validLevels = ["minimal", "limited", "high", "unacceptable"];
|
|
1002
|
+
if (validLevels.includes(normalized)) {
|
|
1003
|
+
return normalized;
|
|
1004
|
+
}
|
|
1005
|
+
const mappings = {
|
|
1006
|
+
low: "minimal",
|
|
1007
|
+
medium: "limited",
|
|
1008
|
+
critical: "unacceptable",
|
|
1009
|
+
extreme: "unacceptable",
|
|
1010
|
+
none: "minimal"
|
|
1011
|
+
};
|
|
1012
|
+
return mappings[normalized] || "minimal";
|
|
1013
|
+
}
|
|
1014
|
+
function generateId(name) {
|
|
1015
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").substring(0, 50);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// src/commands/validate.ts
|
|
712
1019
|
var DEFAULT_CARDS_DIR3 = ".aigrc/cards";
|
|
713
|
-
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) => {
|
|
1020
|
+
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) => {
|
|
714
1021
|
await runValidate(cardPath, options);
|
|
715
1022
|
});
|
|
716
1023
|
async function runValidate(cardPath, options) {
|
|
717
|
-
if (options.
|
|
1024
|
+
if (options.dryRun && !options.fix) {
|
|
1025
|
+
if (options.output === "text") {
|
|
1026
|
+
console.error(chalk6.red("Error: --dry-run requires --fix"));
|
|
1027
|
+
} else {
|
|
1028
|
+
console.error(JSON.stringify({ error: "--dry-run requires --fix" }));
|
|
1029
|
+
}
|
|
1030
|
+
exit(2 /* INVALID_ARGUMENTS */);
|
|
1031
|
+
}
|
|
1032
|
+
if (options.output === "text" && !options.outputFile) {
|
|
718
1033
|
printHeader();
|
|
719
1034
|
}
|
|
720
1035
|
const cardsToValidate = [];
|
|
721
1036
|
if (options.all || !cardPath) {
|
|
722
|
-
const cardsDir =
|
|
1037
|
+
const cardsDir = path5.join(process.cwd(), DEFAULT_CARDS_DIR3);
|
|
723
1038
|
try {
|
|
724
1039
|
const files = await fs3.readdir(cardsDir);
|
|
725
1040
|
const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
726
1041
|
for (const file of yamlFiles) {
|
|
727
|
-
cardsToValidate.push(
|
|
1042
|
+
cardsToValidate.push(path5.join(cardsDir, file));
|
|
728
1043
|
}
|
|
729
|
-
} catch {
|
|
730
|
-
if (options.output === "text") {
|
|
731
|
-
console.log(
|
|
732
|
-
console.log(
|
|
733
|
-
console.log(
|
|
1044
|
+
} catch (error) {
|
|
1045
|
+
if (options.output === "text" && !options.outputFile) {
|
|
1046
|
+
console.log(chalk6.yellow("No cards directory found."));
|
|
1047
|
+
console.log(chalk6.dim(`Expected: ${cardsDir}`));
|
|
1048
|
+
console.log(chalk6.dim("Run `aigrc init` to initialize AIGRC."));
|
|
734
1049
|
} else {
|
|
735
|
-
|
|
1050
|
+
const output = JSON.stringify({ error: "No cards directory found" });
|
|
1051
|
+
if (options.outputFile) {
|
|
1052
|
+
await fs3.writeFile(options.outputFile, output);
|
|
1053
|
+
} else {
|
|
1054
|
+
console.log(output);
|
|
1055
|
+
}
|
|
736
1056
|
}
|
|
737
|
-
|
|
1057
|
+
exit(4 /* FILE_NOT_FOUND */);
|
|
738
1058
|
}
|
|
739
1059
|
} else {
|
|
740
|
-
|
|
1060
|
+
const resolvedPath = path5.resolve(process.cwd(), cardPath);
|
|
1061
|
+
try {
|
|
1062
|
+
await fs3.access(resolvedPath);
|
|
1063
|
+
cardsToValidate.push(resolvedPath);
|
|
1064
|
+
} catch {
|
|
1065
|
+
if (options.output === "text" && !options.outputFile) {
|
|
1066
|
+
console.log(chalk6.red(`Error: File not found: ${cardPath}`));
|
|
1067
|
+
} else {
|
|
1068
|
+
const output = JSON.stringify({ error: `File not found: ${cardPath}` });
|
|
1069
|
+
if (options.outputFile) {
|
|
1070
|
+
await fs3.writeFile(options.outputFile, output);
|
|
1071
|
+
} else {
|
|
1072
|
+
console.log(output);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
exit(4 /* FILE_NOT_FOUND */);
|
|
1076
|
+
}
|
|
741
1077
|
}
|
|
742
1078
|
if (cardsToValidate.length === 0) {
|
|
743
|
-
if (options.output === "text") {
|
|
744
|
-
console.log(
|
|
1079
|
+
if (options.output === "text" && !options.outputFile) {
|
|
1080
|
+
console.log(chalk6.yellow("No asset cards found to validate."));
|
|
745
1081
|
} else {
|
|
746
|
-
|
|
1082
|
+
const output = JSON.stringify({ error: "No asset cards found" });
|
|
1083
|
+
if (options.outputFile) {
|
|
1084
|
+
await fs3.writeFile(options.outputFile, output);
|
|
1085
|
+
} else {
|
|
1086
|
+
console.log(output);
|
|
1087
|
+
}
|
|
747
1088
|
}
|
|
748
|
-
|
|
1089
|
+
exit(4 /* FILE_NOT_FOUND */);
|
|
749
1090
|
}
|
|
750
1091
|
const results = [];
|
|
751
1092
|
let hasErrors = false;
|
|
752
1093
|
for (const cardFile of cardsToValidate) {
|
|
753
|
-
const spinner = options.output === "text" ? ora3(`Validating ${
|
|
1094
|
+
const spinner = options.output === "text" && !options.outputFile ? ora3(`Validating ${path5.basename(cardFile)}...`).start() : void 0;
|
|
754
1095
|
try {
|
|
755
|
-
|
|
1096
|
+
let card = loadAssetCard(cardFile);
|
|
1097
|
+
let fixed = false;
|
|
1098
|
+
let fixedFields = [];
|
|
1099
|
+
if (options.fix) {
|
|
1100
|
+
const fixResult = autoFixAssetCard(card);
|
|
1101
|
+
if (fixResult.fixed) {
|
|
1102
|
+
fixed = true;
|
|
1103
|
+
fixedFields = fixResult.fixedFields;
|
|
1104
|
+
card = fixResult.card;
|
|
1105
|
+
if (!options.dryRun) {
|
|
1106
|
+
const yamlContent = YAML.stringify(card);
|
|
1107
|
+
await fs3.writeFile(cardFile, yamlContent, "utf-8");
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
756
1111
|
const validation = validateAssetCard(card);
|
|
757
1112
|
const classification = classifyRisk(card.classification.riskFactors);
|
|
758
1113
|
results.push({
|
|
@@ -762,13 +1117,17 @@ async function runValidate(cardPath, options) {
|
|
|
762
1117
|
valid: validation.valid,
|
|
763
1118
|
errors: validation.errors ?? []
|
|
764
1119
|
},
|
|
765
|
-
classification
|
|
1120
|
+
classification,
|
|
1121
|
+
fixed,
|
|
1122
|
+
fixedFields
|
|
766
1123
|
});
|
|
767
1124
|
if (!validation.valid) {
|
|
768
1125
|
hasErrors = true;
|
|
769
|
-
spinner?.fail(`${
|
|
1126
|
+
spinner?.fail(`${path5.basename(cardFile)}: Invalid`);
|
|
1127
|
+
} else if (fixed) {
|
|
1128
|
+
spinner?.succeed(`${path5.basename(cardFile)}: Valid (fixed ${fixedFields.length} issues)`);
|
|
770
1129
|
} else {
|
|
771
|
-
spinner?.succeed(`${
|
|
1130
|
+
spinner?.succeed(`${path5.basename(cardFile)}: Valid`);
|
|
772
1131
|
}
|
|
773
1132
|
} catch (error) {
|
|
774
1133
|
hasErrors = true;
|
|
@@ -780,76 +1139,93 @@ async function runValidate(cardPath, options) {
|
|
|
780
1139
|
errors: [errorMessage]
|
|
781
1140
|
}
|
|
782
1141
|
});
|
|
783
|
-
spinner?.fail(`${
|
|
1142
|
+
spinner?.fail(`${path5.basename(cardFile)}: Parse error`);
|
|
784
1143
|
}
|
|
785
1144
|
}
|
|
786
|
-
|
|
787
|
-
console.log(JSON.stringify({ results }, null, 2));
|
|
788
|
-
} else {
|
|
789
|
-
console.log();
|
|
790
|
-
printValidationSummary(results);
|
|
791
|
-
}
|
|
1145
|
+
await outputResults(results, options);
|
|
792
1146
|
if (hasErrors) {
|
|
793
|
-
|
|
1147
|
+
exit(3 /* VALIDATION_ERRORS */);
|
|
794
1148
|
}
|
|
1149
|
+
exit(0 /* SUCCESS */);
|
|
795
1150
|
}
|
|
796
|
-
function
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1151
|
+
async function outputResults(results, options) {
|
|
1152
|
+
let output;
|
|
1153
|
+
switch (options.output) {
|
|
1154
|
+
case "sarif": {
|
|
1155
|
+
const sarif = formatSarif(results);
|
|
1156
|
+
output = sarifToJson(sarif);
|
|
1157
|
+
break;
|
|
1158
|
+
}
|
|
1159
|
+
case "json": {
|
|
1160
|
+
output = JSON.stringify({ results }, null, 2);
|
|
1161
|
+
break;
|
|
1162
|
+
}
|
|
1163
|
+
case "text":
|
|
1164
|
+
default: {
|
|
1165
|
+
if (options.outputFile) {
|
|
1166
|
+
output = formatTextOutput(results);
|
|
1167
|
+
} else {
|
|
1168
|
+
console.log();
|
|
1169
|
+
printValidationSummary(results);
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
if (options.outputFile) {
|
|
1175
|
+
await fs3.writeFile(options.outputFile, output, "utf-8");
|
|
1176
|
+
if (options.output === "text") {
|
|
1177
|
+
console.log(chalk6.green(`\u2713 Output written to ${options.outputFile}`));
|
|
1178
|
+
}
|
|
1179
|
+
} else {
|
|
1180
|
+
console.log(output);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
function formatTextOutput(results) {
|
|
1184
|
+
const lines = [];
|
|
1185
|
+
lines.push("Validation Summary");
|
|
1186
|
+
lines.push("\u2500".repeat(50));
|
|
1187
|
+
lines.push("");
|
|
800
1188
|
for (const result of results) {
|
|
801
|
-
const fileName =
|
|
1189
|
+
const fileName = path5.basename(result.path);
|
|
802
1190
|
if (!result.validation.valid) {
|
|
803
|
-
|
|
1191
|
+
lines.push(`\u2717 ${fileName}`);
|
|
804
1192
|
for (const error of result.validation.errors) {
|
|
805
|
-
|
|
1193
|
+
lines.push(` Error: ${error}`);
|
|
806
1194
|
}
|
|
807
1195
|
} else {
|
|
808
|
-
|
|
1196
|
+
lines.push(`\u2713 ${fileName}`);
|
|
1197
|
+
if (result.fixed && result.fixedFields && result.fixedFields.length > 0) {
|
|
1198
|
+
lines.push(` Fixed: ${result.fixedFields.join(", ")}`);
|
|
1199
|
+
}
|
|
809
1200
|
if (result.card && result.classification) {
|
|
810
|
-
|
|
811
|
-
console.log(
|
|
812
|
-
chalk5.dim(" Risk Level: ") + tierColor(result.classification.riskLevel)
|
|
813
|
-
);
|
|
1201
|
+
lines.push(` Risk Level: ${result.classification.riskLevel}`);
|
|
814
1202
|
if (result.classification.euAiActCategory) {
|
|
815
|
-
|
|
816
|
-
chalk5.dim(" EU AI Act: ") + chalk5.yellow(result.classification.euAiActCategory)
|
|
817
|
-
);
|
|
1203
|
+
lines.push(` EU AI Act: ${result.classification.euAiActCategory}`);
|
|
818
1204
|
}
|
|
819
1205
|
}
|
|
820
1206
|
}
|
|
821
|
-
|
|
1207
|
+
lines.push("");
|
|
822
1208
|
}
|
|
823
1209
|
const valid = results.filter((r) => r.validation.valid).length;
|
|
824
1210
|
const invalid = results.filter((r) => !r.validation.valid).length;
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1211
|
+
const fixed = results.filter((r) => r.fixed).length;
|
|
1212
|
+
lines.push("\u2500".repeat(50));
|
|
1213
|
+
lines.push(
|
|
1214
|
+
`Total: ${results.length} | Valid: ${valid} | Invalid: ${invalid}` + (fixed > 0 ? ` | Fixed: ${fixed}` : "")
|
|
828
1215
|
);
|
|
829
|
-
|
|
830
|
-
function getRiskLevelColor(level) {
|
|
831
|
-
switch (level) {
|
|
832
|
-
case "minimal":
|
|
833
|
-
return chalk5.green;
|
|
834
|
-
case "limited":
|
|
835
|
-
return chalk5.yellow;
|
|
836
|
-
case "high":
|
|
837
|
-
return chalk5.red;
|
|
838
|
-
case "unacceptable":
|
|
839
|
-
return chalk5.magenta;
|
|
840
|
-
default:
|
|
841
|
-
return chalk5.white;
|
|
842
|
-
}
|
|
1216
|
+
return lines.join("\n");
|
|
843
1217
|
}
|
|
844
1218
|
|
|
845
1219
|
// src/commands/status.ts
|
|
846
1220
|
import { Command as Command5 } from "commander";
|
|
847
|
-
import
|
|
848
|
-
import
|
|
1221
|
+
import chalk7 from "chalk";
|
|
1222
|
+
import path6 from "path";
|
|
849
1223
|
import fs4 from "fs/promises";
|
|
850
1224
|
import {
|
|
851
1225
|
loadAssetCard as loadAssetCard2,
|
|
852
|
-
classifyRisk as classifyRisk2
|
|
1226
|
+
classifyRisk as classifyRisk2,
|
|
1227
|
+
extractGoldenThreadComponents,
|
|
1228
|
+
verifyGoldenThreadHashSync
|
|
853
1229
|
} from "@aigrc/core";
|
|
854
1230
|
var DEFAULT_CARDS_DIR4 = ".aigrc/cards";
|
|
855
1231
|
var DEFAULT_CONFIG_FILE2 = ".aigrc.yaml";
|
|
@@ -861,14 +1237,14 @@ async function runStatus(options) {
|
|
|
861
1237
|
if (options.output === "text") {
|
|
862
1238
|
printHeader();
|
|
863
1239
|
}
|
|
864
|
-
const configPath =
|
|
865
|
-
const cardsDir =
|
|
1240
|
+
const configPath = path6.join(cwd, DEFAULT_CONFIG_FILE2);
|
|
1241
|
+
const cardsDir = path6.join(cwd, DEFAULT_CARDS_DIR4);
|
|
866
1242
|
const configExists = await fileExists2(configPath);
|
|
867
1243
|
const cardsDirExists = await directoryExists(cardsDir);
|
|
868
1244
|
if (!configExists && !cardsDirExists) {
|
|
869
1245
|
if (options.output === "text") {
|
|
870
|
-
console.log(
|
|
871
|
-
console.log(
|
|
1246
|
+
console.log(chalk7.yellow("AIGRC is not initialized in this directory."));
|
|
1247
|
+
console.log(chalk7.dim("\nRun `aigrc init` to get started."));
|
|
872
1248
|
} else {
|
|
873
1249
|
console.log(JSON.stringify({ initialized: false }));
|
|
874
1250
|
}
|
|
@@ -881,7 +1257,7 @@ async function runStatus(options) {
|
|
|
881
1257
|
const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
882
1258
|
for (const file of yamlFiles) {
|
|
883
1259
|
try {
|
|
884
|
-
const filePath =
|
|
1260
|
+
const filePath = path6.join(cardsDir, file);
|
|
885
1261
|
const card = loadAssetCard2(filePath);
|
|
886
1262
|
const classification = classifyRisk2(card.classification.riskFactors);
|
|
887
1263
|
cards.push({ path: filePath, card, classification });
|
|
@@ -912,19 +1288,21 @@ async function runStatus(options) {
|
|
|
912
1288
|
);
|
|
913
1289
|
return;
|
|
914
1290
|
}
|
|
915
|
-
console.log(
|
|
916
|
-
console.log(
|
|
1291
|
+
console.log(chalk7.bold("AIGRC Status"));
|
|
1292
|
+
console.log(chalk7.dim("\u2500".repeat(50)));
|
|
917
1293
|
console.log();
|
|
918
|
-
console.log(
|
|
919
|
-
console.log(
|
|
1294
|
+
console.log(chalk7.dim("Config:"), configExists ? chalk7.green("\u2713") : chalk7.red("\u2717"), configPath);
|
|
1295
|
+
console.log(chalk7.dim("Cards:"), cardsDirExists ? chalk7.green("\u2713") : chalk7.red("\u2717"), cardsDir);
|
|
1296
|
+
console.log();
|
|
1297
|
+
printGoldenThreadStatus(cards);
|
|
920
1298
|
console.log();
|
|
921
1299
|
if (cards.length === 0) {
|
|
922
|
-
console.log(
|
|
923
|
-
console.log(
|
|
1300
|
+
console.log(chalk7.yellow("No asset cards registered."));
|
|
1301
|
+
console.log(chalk7.dim("\nRun `aigrc init` or `aigrc register` to create an asset card."));
|
|
924
1302
|
return;
|
|
925
1303
|
}
|
|
926
|
-
console.log(
|
|
927
|
-
console.log(
|
|
1304
|
+
console.log(chalk7.bold(`Registered Assets (${cards.length})`));
|
|
1305
|
+
console.log(chalk7.dim("\u2500".repeat(50)));
|
|
928
1306
|
console.log();
|
|
929
1307
|
const byLevel = groupByRiskLevel(cards);
|
|
930
1308
|
for (const level of ["unacceptable", "high", "limited", "minimal"]) {
|
|
@@ -934,18 +1312,18 @@ async function runStatus(options) {
|
|
|
934
1312
|
console.log(levelColor(`${level.toUpperCase()} (${levelCards.length})`));
|
|
935
1313
|
console.log();
|
|
936
1314
|
for (const { card, classification } of levelCards) {
|
|
937
|
-
console.log(` ${
|
|
938
|
-
console.log(
|
|
939
|
-
console.log(
|
|
1315
|
+
console.log(` ${chalk7.bold(card.name)}`);
|
|
1316
|
+
console.log(chalk7.dim(` ID: ${card.id}`));
|
|
1317
|
+
console.log(chalk7.dim(` Risk Level: ${classification.riskLevel}`));
|
|
940
1318
|
if (classification.euAiActCategory) {
|
|
941
|
-
console.log(
|
|
1319
|
+
console.log(chalk7.dim(` EU AI Act: `) + chalk7.yellow(classification.euAiActCategory));
|
|
942
1320
|
}
|
|
943
1321
|
if (card.ownership?.owner) {
|
|
944
|
-
console.log(
|
|
1322
|
+
console.log(chalk7.dim(` Owner: ${card.ownership.owner.name} <${card.ownership.owner.email}>`));
|
|
945
1323
|
}
|
|
946
1324
|
const activeRisks = getActiveRiskFactors(card);
|
|
947
1325
|
if (activeRisks.length > 0) {
|
|
948
|
-
console.log(
|
|
1326
|
+
console.log(chalk7.dim(` Risks: `) + activeRisks.join(", "));
|
|
949
1327
|
}
|
|
950
1328
|
console.log();
|
|
951
1329
|
}
|
|
@@ -964,17 +1342,17 @@ function groupByRiskLevel(cards) {
|
|
|
964
1342
|
return byLevel;
|
|
965
1343
|
}
|
|
966
1344
|
function printStatusSummary(cards) {
|
|
967
|
-
console.log(
|
|
1345
|
+
console.log(chalk7.dim("\u2500".repeat(50)));
|
|
968
1346
|
const minimal = cards.filter((c) => c.classification.riskLevel === "minimal").length;
|
|
969
1347
|
const limited = cards.filter((c) => c.classification.riskLevel === "limited").length;
|
|
970
1348
|
const high = cards.filter((c) => c.classification.riskLevel === "high").length;
|
|
971
1349
|
const unacceptable = cards.filter((c) => c.classification.riskLevel === "unacceptable").length;
|
|
972
1350
|
console.log(
|
|
973
|
-
`Total: ${cards.length} | ` +
|
|
1351
|
+
`Total: ${cards.length} | ` + chalk7.green(`Minimal: ${minimal}`) + ` | ` + chalk7.yellow(`Limited: ${limited}`) + ` | ` + chalk7.red(`High: ${high}`) + ` | ` + chalk7.magenta(`Unacceptable: ${unacceptable}`)
|
|
974
1352
|
);
|
|
975
1353
|
if (high > 0 || unacceptable > 0) {
|
|
976
1354
|
console.log();
|
|
977
|
-
console.log(
|
|
1355
|
+
console.log(chalk7.yellow("\u26A0 High-risk assets detected. Review compliance requirements."));
|
|
978
1356
|
}
|
|
979
1357
|
}
|
|
980
1358
|
function getActiveRiskFactors(card) {
|
|
@@ -992,15 +1370,15 @@ function getActiveRiskFactors(card) {
|
|
|
992
1370
|
function getRiskLevelColor2(level) {
|
|
993
1371
|
switch (level) {
|
|
994
1372
|
case "minimal":
|
|
995
|
-
return
|
|
1373
|
+
return chalk7.green;
|
|
996
1374
|
case "limited":
|
|
997
|
-
return
|
|
1375
|
+
return chalk7.yellow;
|
|
998
1376
|
case "high":
|
|
999
|
-
return
|
|
1377
|
+
return chalk7.red;
|
|
1000
1378
|
case "unacceptable":
|
|
1001
|
-
return
|
|
1379
|
+
return chalk7.magenta;
|
|
1002
1380
|
default:
|
|
1003
|
-
return
|
|
1381
|
+
return chalk7.white;
|
|
1004
1382
|
}
|
|
1005
1383
|
}
|
|
1006
1384
|
async function fileExists2(filePath) {
|
|
@@ -1019,6 +1397,41 @@ async function directoryExists(dirPath) {
|
|
|
1019
1397
|
return false;
|
|
1020
1398
|
}
|
|
1021
1399
|
}
|
|
1400
|
+
function printGoldenThreadStatus(cards) {
|
|
1401
|
+
const cardsWithGoldenThread = cards.filter((c) => {
|
|
1402
|
+
const components = extractGoldenThreadComponents(c.card);
|
|
1403
|
+
return components && c.card.golden_thread?.hash;
|
|
1404
|
+
});
|
|
1405
|
+
if (cardsWithGoldenThread.length === 0) {
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
console.log(chalk7.bold("Golden Thread"));
|
|
1409
|
+
console.log(chalk7.dim("\u2500".repeat(50)));
|
|
1410
|
+
console.log();
|
|
1411
|
+
for (const { card } of cardsWithGoldenThread) {
|
|
1412
|
+
const components = extractGoldenThreadComponents(card);
|
|
1413
|
+
if (!components || !card.golden_thread?.hash) continue;
|
|
1414
|
+
console.log(chalk7.bold(card.name));
|
|
1415
|
+
console.log(chalk7.dim(` Hash: ${card.golden_thread.hash}`));
|
|
1416
|
+
try {
|
|
1417
|
+
const verification = verifyGoldenThreadHashSync(components, card.golden_thread.hash);
|
|
1418
|
+
if (verification.verified) {
|
|
1419
|
+
console.log(chalk7.dim(" Status: ") + chalk7.green("\u2713 Verified"));
|
|
1420
|
+
} else {
|
|
1421
|
+
console.log(chalk7.dim(" Status: ") + chalk7.red("\u2717 Verification Failed"));
|
|
1422
|
+
if (verification.mismatch_reason) {
|
|
1423
|
+
console.log(chalk7.dim(" Reason: ") + chalk7.yellow(verification.mismatch_reason));
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
} catch (error) {
|
|
1427
|
+
console.log(chalk7.dim(" Status: ") + chalk7.red("\u2717 Error"));
|
|
1428
|
+
}
|
|
1429
|
+
if (card.golden_thread?.signature) {
|
|
1430
|
+
console.log(chalk7.dim(" Signature: ") + chalk7.green("\u2713 RSA-SHA256"));
|
|
1431
|
+
}
|
|
1432
|
+
console.log();
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1022
1435
|
export {
|
|
1023
1436
|
initCommand,
|
|
1024
1437
|
registerCommand,
|