This file contains detailed guidance for AI agents working in the PackageGuard repository.
# Full build (compile, test, package)
./build.ps1
# Or with the Fallout global tool
fallout
# See all available build targets
fallout --help
fallout --plan# All unit tests
dotnet test Src/PackageGuard.Specs/PackageGuard.Specs.csproj
# Single test method (MSTest filter syntax)
dotnet test Src/PackageGuard.Specs/PackageGuard.Specs.csproj --filter "FullyQualifiedName~Can_deny_an_entire_package"
# API verification tests
dotnet test Src/PackageGuard.ApiVerificationTests/PackageGuard.ApiVerificationTests.csprojdotnet run --project Src/PackageGuard -- <path> [options]
# Example:
dotnet run --project Src/PackageGuard -- . --config-path .packageguard/config.jsonAfter changing public APIs in PackageGuard.Core:
./AcceptApiChanges.ps1 # Windows
./AcceptApiChanges.sh # Linux/macOSPackageGuard is a .NET global CLI tool (target: .NET 9) that analyzes NuGet and NPM dependency trees against user-defined allow/deny policies and optionally scores packages on legal, security, and operational risk.
| Project | Role |
|---|---|
Src/PackageGuard |
Spectre.Console CLI — parses arguments, wires up services, renders output |
Src/PackageGuard.Core |
Platform-agnostic analysis engine (NuGet package on NuGet.org) |
Src/PackageGuard.Specs |
MSTest unit tests; uses real .csproj/package.json fixtures under TestCases/ |
Src/PackageGuard.ApiVerificationTests |
Verify snapshot tests that lock the public API surface |
Build/ |
Fallout build project |
- CLI (
AnalyzeCommand) buildsAnalyzerSettingsfrom CLI flags and resolvesGetPolicyByProjectviaConfigurationLoader(hierarchical JSON config discovery). ProjectAnalyzer(entry point intoPackageGuard.Core) iterates twoIProjectAnalysisStrategyimplementations in sequence:CSharpProjectAnalysisStrategy— discovers.sln/.slnx/.csprojfiles, restores viadotnet restore, readsproject.assets.jsonlock files withDotNetLockFileLoader, and callsNuGetPackageAnalyzerto fetch metadata and licenses.NpmProjectAnalysisStrategy— detects the JS package manager (npm/yarn/pnpm), parses the appropriate lock file (NpmLockFileParser,YarnLockFileParser,PnpmLockFileParser), and fetches metadata from the npm registry viaNpmRegistryMetadataFetcher.
- All discovered packages accumulate in
PackageInfoCollection, which also handles binary caching (cache.binvia MemoryPack). - License fetching is handled by
LicenseFetcher, which chains strategies (GitHubLicenseFetcher,UrlLicenseFetcher,CorrectMisbehavingPackagesFetcher) to resolve SPDX license identifiers from NuGet/npm metadata or GitHub repository data. - Policy evaluation — each package is checked against the project's
ProjectPolicy(merged from hierarchical config files). Violations are returned asPolicyViolation[]. - Risk scoring (opt-in via
--report-risk) —ParallelPackageRiskEnricherfans out to multipleIEnrichPackageRiskenrichers (GitHubRepositoryRiskEnricher,OsvRiskEnricher,NuGetPackageSigningRiskEnricher, etc.) that populatePackageInfowith raw signals. ThenRiskEvaluatorcallsLegalRiskEvaluator,SecurityRiskEvaluator, andOperationalRiskEvaluator(each composed ofIEvaluateRiskFactorinstances) to produce a 0–100 weighted score. The CLI writes the results to a self-contained HTML report and a SARIF file viaRiskHtmlReportWriter/RiskSarifReportWriter.
- Strategy pattern —
IProjectAnalysisStrategylets NuGet and NPM scanning be extended independently. - Chain-of-responsibility —
IFetchLicenseimplementations try one source after another until a license is found. - Enricher pipeline —
IEnrichPackageRiskenrichers are run in parallel (viaParallelPackageRiskEnricher) and write directly intoPackageInfofields; no aggregation step is needed before scoring. - Risk factor decomposition — each risk dimension (
IEvaluateRiskDimension) composes multipleIEvaluateRiskFactorinstances, each contributing a weighted score plus a human-readable rationale stored inRiskFactorContribution.
Config files are JSON (packageguard.config.json or .packageguard/config.json), discovered hierarchically from project directory up to solution directory. ConfigurationLoader.GetEffectiveConfigurationForProject merges them: arrays (licenses, packages, feeds) are unioned; booleans prefer the innermost value. The merged result is a ProjectPolicy with AllowList and DenyList.
- Framework: MSTest (
[TestClass],[TestMethod]) - Assertions: FluentAssertions
- Mocking: FakeItEasy
- Snapshot testing: Verify (used in
PackageGuard.ApiVerificationTests) - Test fixtures: real
.csproj/package.jsonprojects live underSrc/PackageGuard.Specs/TestCases/and are copied to the output directory; tests reference them by path. - Test methods are named in
Can_do_somethingstyle (underscore-separated, verb-first).
This project follows C# Coding Guidelines by Dennis Doomen. Key rules enforced by Roslyn analyzers (StyleCop, Roslynator, CSharpGuidelinesAnalyzer, Meziantou):
- No field name prefixes (no
_,m_,s_) varonly when the type is evident from the right-hand side; never for built-in typesprivatefields,internal sealedtypes by default — open up visibility deliberately- Methods ≤ 7 statements; split otherwise
- XML doc comments on all
public,protected, andinternalmembers - Return read-only collection interfaces (
IEnumerable<T>,IReadOnlyCollection<T>, etc.) from public members - Strings, collections, and tasks must never be
nullfrom public APIs — return empty equivalents instead - Max line length: 130 characters; indent with 4 spaces
All code must pass the analyzers without warnings before merging.
- Target branch:
main(GitHub Flow — no separatedevelopbranch) - API surface changes: run
AcceptApiChanges.ps1and commit the updated snapshots - Code coverage must not decrease (tracked via Coveralls)