up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
This commit is contained in:
@@ -268,16 +268,22 @@ internal static class CommandFactory
|
||||
{
|
||||
Description = "Include raw NDJSON output."
|
||||
};
|
||||
var includeSemanticOption = new Option<bool>("--semantic")
|
||||
{
|
||||
Description = "Include semantic entrypoint analysis (intent, capabilities, threats)."
|
||||
};
|
||||
|
||||
entryTrace.Add(scanIdOption);
|
||||
entryTrace.Add(includeNdjsonOption);
|
||||
entryTrace.Add(includeSemanticOption);
|
||||
|
||||
entryTrace.SetAction((parseResult, _) =>
|
||||
{
|
||||
var id = parseResult.GetValue(scanIdOption) ?? string.Empty;
|
||||
var includeNdjson = parseResult.GetValue(includeNdjsonOption);
|
||||
var includeSemantic = parseResult.GetValue(includeSemanticOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
return CommandHandlers.HandleScanEntryTraceAsync(services, id, includeNdjson, verbose, cancellationToken);
|
||||
return CommandHandlers.HandleScanEntryTraceAsync(services, id, includeNdjson, includeSemantic, verbose, cancellationToken);
|
||||
});
|
||||
|
||||
scan.Add(entryTrace);
|
||||
@@ -8845,7 +8851,7 @@ internal static class CommandFactory
|
||||
var runOutputOption = new Option<string>("--output", new[] { "-o" })
|
||||
{
|
||||
Description = "Path to write the export bundle.",
|
||||
IsRequired = true
|
||||
Required = true
|
||||
};
|
||||
var runOverwriteOption = new Option<bool>("--overwrite")
|
||||
{
|
||||
@@ -8895,7 +8901,7 @@ internal static class CommandFactory
|
||||
var startProfileOption = new Option<string>("--profile-id")
|
||||
{
|
||||
Description = "Export profile identifier.",
|
||||
IsRequired = true
|
||||
Required = true
|
||||
};
|
||||
var startSelectorOption = new Option<string[]?>("--selector", new[] { "-s" })
|
||||
{
|
||||
|
||||
@@ -509,7 +509,7 @@ internal static class CommandHandlers
|
||||
}
|
||||
}
|
||||
|
||||
private static void RenderEntryTrace(EntryTraceResponseModel result, bool includeNdjson)
|
||||
private static void RenderEntryTrace(EntryTraceResponseModel result, bool includeNdjson, bool includeSemantic)
|
||||
{
|
||||
var console = AnsiConsole.Console;
|
||||
|
||||
@@ -570,6 +570,69 @@ internal static class CommandHandlers
|
||||
console.Write(diagTable);
|
||||
}
|
||||
|
||||
// Semantic entrypoint analysis
|
||||
if (includeSemantic && result.Semantic is not null)
|
||||
{
|
||||
console.WriteLine();
|
||||
console.MarkupLine("[bold]Semantic Entrypoint Analysis[/]");
|
||||
console.MarkupLine($"Intent: [green]{Markup.Escape(result.Semantic.Intent)}[/]");
|
||||
console.MarkupLine($"Language: {Markup.Escape(result.Semantic.Language ?? "unknown")}");
|
||||
console.MarkupLine($"Framework: {Markup.Escape(result.Semantic.Framework ?? "none")}");
|
||||
console.MarkupLine($"Confidence: {result.Semantic.ConfidenceScore:P0} ({Markup.Escape(result.Semantic.ConfidenceTier)})");
|
||||
|
||||
if (result.Semantic.Capabilities.Count > 0)
|
||||
{
|
||||
console.MarkupLine($"Capabilities: [cyan]{Markup.Escape(string.Join(", ", result.Semantic.Capabilities))}[/]");
|
||||
}
|
||||
|
||||
if (result.Semantic.Threats.Count > 0)
|
||||
{
|
||||
console.WriteLine();
|
||||
console.MarkupLine("[bold]Threat Vectors[/]");
|
||||
var threatTable = new Table()
|
||||
.AddColumn("Threat")
|
||||
.AddColumn("CWE")
|
||||
.AddColumn("OWASP")
|
||||
.AddColumn("Confidence");
|
||||
|
||||
foreach (var threat in result.Semantic.Threats)
|
||||
{
|
||||
threatTable.AddRow(
|
||||
threat.Type,
|
||||
threat.CweId ?? "-",
|
||||
threat.OwaspCategory ?? "-",
|
||||
threat.Confidence.ToString("P0", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
console.Write(threatTable);
|
||||
}
|
||||
|
||||
if (result.Semantic.DataBoundaries.Count > 0)
|
||||
{
|
||||
console.WriteLine();
|
||||
console.MarkupLine("[bold]Data Flow Boundaries[/]");
|
||||
var boundaryTable = new Table()
|
||||
.AddColumn("Type")
|
||||
.AddColumn("Direction")
|
||||
.AddColumn("Sensitivity");
|
||||
|
||||
foreach (var boundary in result.Semantic.DataBoundaries)
|
||||
{
|
||||
boundaryTable.AddRow(
|
||||
boundary.Type,
|
||||
boundary.Direction,
|
||||
boundary.Sensitivity);
|
||||
}
|
||||
|
||||
console.Write(boundaryTable);
|
||||
}
|
||||
}
|
||||
else if (includeSemantic && result.Semantic is null)
|
||||
{
|
||||
console.WriteLine();
|
||||
console.MarkupLine("[italic yellow]Semantic analysis not available for this scan.[/]");
|
||||
}
|
||||
|
||||
if (includeNdjson && result.Ndjson.Count > 0)
|
||||
{
|
||||
console.MarkupLine("[bold]NDJSON Output[/]");
|
||||
@@ -685,6 +748,7 @@ internal static class CommandHandlers
|
||||
IServiceProvider services,
|
||||
string scanId,
|
||||
bool includeNdjson,
|
||||
bool includeSemantic,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -697,6 +761,7 @@ internal static class CommandHandlers
|
||||
using var activity = CliActivitySource.Instance.StartActivity("cli.scan.entrytrace", ActivityKind.Client);
|
||||
activity?.SetTag("stellaops.cli.command", "scan entrytrace");
|
||||
activity?.SetTag("stellaops.cli.scan_id", scanId);
|
||||
activity?.SetTag("stellaops.cli.include_semantic", includeSemantic);
|
||||
using var duration = CliMetrics.MeasureCommandDuration("scan entrytrace");
|
||||
|
||||
try
|
||||
@@ -713,7 +778,7 @@ internal static class CommandHandlers
|
||||
return;
|
||||
}
|
||||
|
||||
RenderEntryTrace(result, includeNdjson);
|
||||
RenderEntryTrace(result, includeNdjson, includeSemantic);
|
||||
Environment.ExitCode = 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -6362,6 +6427,8 @@ internal static class CommandHandlers
|
||||
table.AddColumn("Status");
|
||||
table.AddColumn("Severity");
|
||||
table.AddColumn("Score");
|
||||
table.AddColumn("Tier");
|
||||
table.AddColumn("Risk");
|
||||
table.AddColumn("SBOM");
|
||||
table.AddColumn("Advisories");
|
||||
table.AddColumn("Updated (UTC)");
|
||||
@@ -6373,6 +6440,8 @@ internal static class CommandHandlers
|
||||
Markup.Escape(item.Status),
|
||||
Markup.Escape(item.Severity.Normalized),
|
||||
Markup.Escape(FormatScore(item.Severity.Score)),
|
||||
FormatUncertaintyTier(item.Uncertainty?.AggregateTier),
|
||||
Markup.Escape(FormatScore(item.Uncertainty?.RiskScore)),
|
||||
Markup.Escape(item.SbomId),
|
||||
Markup.Escape(FormatListPreview(item.AdvisoryIds)),
|
||||
Markup.Escape(FormatUpdatedAt(item.UpdatedAt)));
|
||||
@@ -6385,11 +6454,13 @@ internal static class CommandHandlers
|
||||
foreach (var item in items)
|
||||
{
|
||||
logger.LogInformation(
|
||||
"{Finding} — Status {Status}, Severity {Severity} ({Score}), SBOM {Sbom}, Updated {Updated}",
|
||||
"{Finding} — Status {Status}, Severity {Severity} ({Score}), Tier {Tier} (Risk {Risk}), SBOM {Sbom}, Updated {Updated}",
|
||||
item.FindingId,
|
||||
item.Status,
|
||||
item.Severity.Normalized,
|
||||
item.Severity.Score?.ToString("0.00", CultureInfo.InvariantCulture) ?? "n/a",
|
||||
FormatUncertaintyTierPlain(item.Uncertainty?.AggregateTier),
|
||||
item.Uncertainty?.RiskScore?.ToString("0.00", CultureInfo.InvariantCulture) ?? "n/a",
|
||||
item.SbomId,
|
||||
FormatUpdatedAt(item.UpdatedAt));
|
||||
}
|
||||
@@ -6420,6 +6491,8 @@ internal static class CommandHandlers
|
||||
table.AddRow("Finding", Markup.Escape(finding.FindingId));
|
||||
table.AddRow("Status", Markup.Escape(finding.Status));
|
||||
table.AddRow("Severity", Markup.Escape(FormatSeverity(finding.Severity)));
|
||||
table.AddRow("Uncertainty Tier", FormatUncertaintyTier(finding.Uncertainty?.AggregateTier));
|
||||
table.AddRow("Risk Score", Markup.Escape(FormatScore(finding.Uncertainty?.RiskScore)));
|
||||
table.AddRow("SBOM", Markup.Escape(finding.SbomId));
|
||||
table.AddRow("Policy Version", Markup.Escape(finding.PolicyVersion.ToString(CultureInfo.InvariantCulture)));
|
||||
table.AddRow("Updated (UTC)", Markup.Escape(FormatUpdatedAt(finding.UpdatedAt)));
|
||||
@@ -6427,6 +6500,11 @@ internal static class CommandHandlers
|
||||
table.AddRow("Advisories", Markup.Escape(FormatListPreview(finding.AdvisoryIds)));
|
||||
table.AddRow("VEX", Markup.Escape(FormatVexMetadata(finding.Vex)));
|
||||
|
||||
if (finding.Uncertainty?.States is { Count: > 0 })
|
||||
{
|
||||
table.AddRow("Uncertainty States", Markup.Escape(FormatUncertaintyStates(finding.Uncertainty.States)));
|
||||
}
|
||||
|
||||
AnsiConsole.Write(table);
|
||||
}
|
||||
else
|
||||
@@ -6434,6 +6512,9 @@ internal static class CommandHandlers
|
||||
logger.LogInformation("Finding {Finding}", finding.FindingId);
|
||||
logger.LogInformation(" Status: {Status}", finding.Status);
|
||||
logger.LogInformation(" Severity: {Severity}", FormatSeverity(finding.Severity));
|
||||
logger.LogInformation(" Uncertainty: {Tier} (Risk {Risk})",
|
||||
FormatUncertaintyTierPlain(finding.Uncertainty?.AggregateTier),
|
||||
finding.Uncertainty?.RiskScore?.ToString("0.00", CultureInfo.InvariantCulture) ?? "n/a");
|
||||
logger.LogInformation(" SBOM: {Sbom}", finding.SbomId);
|
||||
logger.LogInformation(" Policy version: {Version}", finding.PolicyVersion);
|
||||
logger.LogInformation(" Updated (UTC): {Updated}", FormatUpdatedAt(finding.UpdatedAt));
|
||||
@@ -6449,6 +6530,10 @@ internal static class CommandHandlers
|
||||
{
|
||||
logger.LogInformation(" VEX: {Vex}", FormatVexMetadata(finding.Vex));
|
||||
}
|
||||
if (finding.Uncertainty?.States is { Count: > 0 })
|
||||
{
|
||||
logger.LogInformation(" Uncertainty States: {States}", FormatUncertaintyStates(finding.Uncertainty.States));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6569,6 +6654,54 @@ internal static class CommandHandlers
|
||||
private static string FormatScore(double? score)
|
||||
=> score.HasValue ? score.Value.ToString("0.00", CultureInfo.InvariantCulture) : "-";
|
||||
|
||||
private static string FormatUncertaintyTier(string? tier)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tier))
|
||||
{
|
||||
return "[grey]-[/]";
|
||||
}
|
||||
|
||||
var (color, display) = tier.ToUpperInvariant() switch
|
||||
{
|
||||
"T1" => ("red", "T1 (High)"),
|
||||
"T2" => ("yellow", "T2 (Medium)"),
|
||||
"T3" => ("blue", "T3 (Low)"),
|
||||
"T4" => ("green", "T4 (Negligible)"),
|
||||
_ => ("grey", tier)
|
||||
};
|
||||
|
||||
return $"[{color}]{Markup.Escape(display)}[/]";
|
||||
}
|
||||
|
||||
private static string FormatUncertaintyTierPlain(string? tier)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tier))
|
||||
{
|
||||
return "-";
|
||||
}
|
||||
|
||||
return tier.ToUpperInvariant() switch
|
||||
{
|
||||
"T1" => "T1 (High)",
|
||||
"T2" => "T2 (Medium)",
|
||||
"T3" => "T3 (Low)",
|
||||
"T4" => "T4 (Negligible)",
|
||||
_ => tier
|
||||
};
|
||||
}
|
||||
|
||||
private static string FormatUncertaintyStates(IReadOnlyList<PolicyFindingUncertaintyState>? states)
|
||||
{
|
||||
if (states is null || states.Count == 0)
|
||||
{
|
||||
return "-";
|
||||
}
|
||||
|
||||
return string.Join(", ", states
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s.Code))
|
||||
.Select(s => $"{s.Code}={s.Entropy?.ToString("0.00", CultureInfo.InvariantCulture) ?? "?"}"));
|
||||
}
|
||||
|
||||
private static string FormatKeyValuePairs(IReadOnlyDictionary<string, string>? values)
|
||||
{
|
||||
if (values is null || values.Count == 0)
|
||||
|
||||
@@ -2443,6 +2443,29 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient
|
||||
|
||||
var updatedAt = document.UpdatedAt ?? DateTimeOffset.MinValue;
|
||||
|
||||
PolicyFindingUncertainty? uncertainty = null;
|
||||
if (document.Uncertainty is not null)
|
||||
{
|
||||
IReadOnlyList<PolicyFindingUncertaintyState>? states = null;
|
||||
if (document.Uncertainty.States is not null)
|
||||
{
|
||||
states = document.Uncertainty.States
|
||||
.Where(s => s is not null)
|
||||
.Select(s => new PolicyFindingUncertaintyState(
|
||||
string.IsNullOrWhiteSpace(s!.Code) ? null : s.Code,
|
||||
string.IsNullOrWhiteSpace(s.Name) ? null : s.Name,
|
||||
s.Entropy,
|
||||
string.IsNullOrWhiteSpace(s.Tier) ? null : s.Tier))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
uncertainty = new PolicyFindingUncertainty(
|
||||
string.IsNullOrWhiteSpace(document.Uncertainty.AggregateTier) ? null : document.Uncertainty.AggregateTier,
|
||||
document.Uncertainty.RiskScore,
|
||||
states,
|
||||
document.Uncertainty.ComputedAt);
|
||||
}
|
||||
|
||||
return new PolicyFindingDocument(
|
||||
findingId,
|
||||
status,
|
||||
@@ -2450,6 +2473,7 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient
|
||||
sbomId,
|
||||
advisoryIds,
|
||||
vex,
|
||||
uncertainty,
|
||||
document.PolicyVersion ?? 0,
|
||||
updatedAt,
|
||||
string.IsNullOrWhiteSpace(document.RunId) ? null : document.RunId);
|
||||
|
||||
@@ -10,4 +10,36 @@ internal sealed record EntryTraceResponseModel(
|
||||
DateTimeOffset GeneratedAt,
|
||||
EntryTraceGraph Graph,
|
||||
IReadOnlyList<string> Ndjson,
|
||||
EntryTracePlan? BestPlan);
|
||||
EntryTracePlan? BestPlan,
|
||||
SemanticEntrypointSummary? Semantic = null);
|
||||
|
||||
/// <summary>
|
||||
/// Summary of semantic entrypoint analysis for CLI display.
|
||||
/// </summary>
|
||||
internal sealed record SemanticEntrypointSummary
|
||||
{
|
||||
public string Intent { get; init; } = "Unknown";
|
||||
public IReadOnlyList<string> Capabilities { get; init; } = Array.Empty<string>();
|
||||
public IReadOnlyList<ThreatVectorSummary> Threats { get; init; } = Array.Empty<ThreatVectorSummary>();
|
||||
public IReadOnlyList<DataBoundarySummary> DataBoundaries { get; init; } = Array.Empty<DataBoundarySummary>();
|
||||
public string? Framework { get; init; }
|
||||
public string? Language { get; init; }
|
||||
public double ConfidenceScore { get; init; }
|
||||
public string ConfidenceTier { get; init; } = "Unknown";
|
||||
public string AnalyzedAt { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
internal sealed record ThreatVectorSummary
|
||||
{
|
||||
public string Type { get; init; } = string.Empty;
|
||||
public double Confidence { get; init; }
|
||||
public string? CweId { get; init; }
|
||||
public string? OwaspCategory { get; init; }
|
||||
}
|
||||
|
||||
internal sealed record DataBoundarySummary
|
||||
{
|
||||
public string Type { get; init; } = string.Empty;
|
||||
public string Direction { get; init; } = string.Empty;
|
||||
public string Sensitivity { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ internal sealed record PolicyFindingDocument(
|
||||
string SbomId,
|
||||
IReadOnlyList<string> AdvisoryIds,
|
||||
PolicyFindingVexMetadata? Vex,
|
||||
PolicyFindingUncertainty? Uncertainty,
|
||||
int PolicyVersion,
|
||||
DateTimeOffset UpdatedAt,
|
||||
string? RunId);
|
||||
@@ -33,6 +34,18 @@ internal sealed record PolicyFindingSeverity(string Normalized, double? Score);
|
||||
|
||||
internal sealed record PolicyFindingVexMetadata(string? WinningStatementId, string? Source, string? Status);
|
||||
|
||||
internal sealed record PolicyFindingUncertainty(
|
||||
string? AggregateTier,
|
||||
double? RiskScore,
|
||||
IReadOnlyList<PolicyFindingUncertaintyState>? States,
|
||||
DateTimeOffset? ComputedAt);
|
||||
|
||||
internal sealed record PolicyFindingUncertaintyState(
|
||||
string? Code,
|
||||
string? Name,
|
||||
double? Entropy,
|
||||
string? Tier);
|
||||
|
||||
internal sealed record PolicyFindingExplainResult(
|
||||
string FindingId,
|
||||
int PolicyVersion,
|
||||
|
||||
@@ -27,6 +27,8 @@ internal sealed class PolicyFindingDocumentDocument
|
||||
|
||||
public PolicyFindingVexDocument? Vex { get; set; }
|
||||
|
||||
public PolicyFindingUncertaintyDocument? Uncertainty { get; set; }
|
||||
|
||||
public int? PolicyVersion { get; set; }
|
||||
|
||||
public DateTimeOffset? UpdatedAt { get; set; }
|
||||
@@ -34,6 +36,28 @@ internal sealed class PolicyFindingDocumentDocument
|
||||
public string? RunId { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class PolicyFindingUncertaintyDocument
|
||||
{
|
||||
public string? AggregateTier { get; set; }
|
||||
|
||||
public double? RiskScore { get; set; }
|
||||
|
||||
public List<PolicyFindingUncertaintyStateDocument>? States { get; set; }
|
||||
|
||||
public DateTimeOffset? ComputedAt { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class PolicyFindingUncertaintyStateDocument
|
||||
{
|
||||
public string? Code { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
|
||||
public double? Entropy { get; set; }
|
||||
|
||||
public string? Tier { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class PolicyFindingSeverityDocument
|
||||
{
|
||||
public string? Normalized { get; set; }
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" />
|
||||
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="2.1.0" />
|
||||
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
|
||||
<PackageReference Include="Spectre.Console" Version="0.48.0" />
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta5.25306.1" />
|
||||
</ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user