PackageMedicDocs
Dependency simulation

See the graph before changing the package.

Dependency Time Machine restore-validates one exact package candidate in two independent snapshots, compares its dependency impact, and leaves the checkout untouched.

Ask one precise what-if question

Terminal
package-medic simulate Example.Package --to 2.0.0 ./MySolution.sln

# Include official NuGet risk evidence
package-medic simulate Example.Package --to 2.0.0 .   --audit --deprecated --include-transitive --format json   --output artifacts/example-2.0.0.simulation.json

The package must already be an effective direct PackageReference or centralPackageVersion. The candidate must be one exact NuGet version.

Know exactly what a pass means

Restore

Run independently for the committed baseline and candidate.

Dependency graph

Compare versions, kinds, causal paths, risks, provenance, and Impact Gate policy.

Build

Not run by default; opt in with --verify build.

Tests

Opt in with --verify test; runtime remains unverified.

PackageMedic 0.6 can opt into comparative build and test evidence. See the Verified experiments guide for the execution boundary, conservative verdicts, and structured test requirements.

Two snapshots, one immutable commit

  1. 1
    Require a clean tree

    Resolve one immutable HEAD and reject tracked or untracked changes.

  2. 2
    Build the baseline

    Materialize, restore, and analyze snapshot A.

  3. 3
    Edit only the candidate

    Materialize snapshot B, verify the XML and SHA-256 precondition, then replace only the encoded version value.

  4. 4
    Restore and compare

    Analyze B and apply the same diagnostics, risk evidence, and Dependency Impact Gate.

  5. 5
    Clean and verify

    Delete owned snapshots without following links and recheck the original worktree before reporting.

The editor preserves encoding, BOM, line endings, comments, spacing, quote style, and attribute order. It refuses dynamic, conditional, ambiguous, external, transitive-only, or unsafe XML declarations instead of guessing.

Verdicts and exit codes

VerdictExitMeaning
pass0Restore and comparison completed with no observed rejection.
noChange0The declaration is NuGet-equivalent, or no package, diagnostic, setting, provenance, or risk delta was observed.
reject1The candidate restore or a configured diagnostic/Impact Gate rejected the change.
incomplete2An operational failure prevented a trustworthy conclusion.

A version missing from reachable configured feeds is a complete rejection. Authentication, unavailable-source, unknown-restore, timeout, extraction, evaluation, audit, cleanup, or infrastructure failures are incomplete and never become a pass.

Lock files stay authoritative

Candidate restore respects RestoreLockedMode and trackedpackages.lock.json files. Time Machine never silently disables locked mode or rewrites the original lock file. A locked-mode conflict is reported as a complete rejection that requires an intentional lockfile update—not as binary incompatibility.

Private-feed state is opt-in

Each snapshot receives separate NuGet package, HTTP, plugin, CLI-home, app-data, user-home, and temporary caches. Secret environment variables are not inherited automatically.

Terminal
package-medic simulate Contoso.Package --to 4.2.0 .   --credential-env VSS_NUGET_EXTERNAL_FEED_ENDPOINTS   --credential-env CONTOSO_FEED_TOKEN

Every named variable must exist and is registered for output redaction. The option is repeatable. NuGet configuration still controls which sources restore may contact. Source policy accepts credential-free HTTPS URLs without queries or fragments, or the explicitlocal value.

A separate deterministic report contract

Simulation JSON schema version 2 separates repository, request, mutation, verification, comparison, rejection reasons, and operational errors. Reports contain no timestamps or temporary paths. Hypothetical results are not emitted as SARIF or uploaded by the GitHub Action.

Complete schema-v2 example
{
  "schemaVersion": 2,
  "kind": "dependencySimulation",
  "toolVersion": "0.6.0",
  "repository": {
    "headCommit": "0000000000000000000000000000000000000000",
    "analysisTarget": "MySolution.sln",
    "workingTreeRequiredClean": true
  },
  "request": { "packageId": "Example.Package", "candidateVersion": "2.0.0" },
  "mutation": {
    "packageId": "Example.Package",
    "file": "Directory.Packages.props",
    "line": 18,
    "kind": "centralPackageVersion",
    "beforeVersion": "1.5.0",
    "candidateVersion": "2.0.0",
    "affectedProjects": ["src/App/App.csproj"],
    "noChange": false,
    "sourceSha256Before": "1111111111111111111111111111111111111111111111111111111111111111",
    "sourceSha256After": "2222222222222222222222222222222222222222222222222222222222222222"
  },
  "verification": {
    "restore": "passed",
    "build": "notRun",
    "tests": "notRun",
    "runtimeCompatibility": "notVerified",
    "evidenceLevel": "restoreOnly",
    "auditedVulnerabilities": false,
    "auditedDeprecations": false,
    "lockedMode": "notEnabled"
  },
  "comparison": {
    "diagnosticSummary": { "added": 0, "resolved": 0, "severityChanged": 0 },
    "diagnosticChanges": [],
    "packageSummary": {
      "added": 0, "removed": 0, "upgraded": 0, "downgraded": 0,
      "uncomparableVersionChanges": 0, "directToTransitive": 0,
      "transitiveToDirect": 0, "otherModified": 0
    },
    "packageChanges": [],
    "riskSummary": {
      "vulnerabilitiesIntroduced": 0, "vulnerabilitiesResolved": 0,
      "deprecationsIntroduced": 0, "deprecationsResolved": 0,
      "vulnerabilitiesPersistent": 0, "deprecationsPersistent": 0
    },
    "projectSettingsChanges": [],
    "isComplete": true
  },
  "isComplete": true,
  "verdict": "pass",
  "rejectionReasons": [],
  "errors": []
}

Bounded for large repositories

Cost is approximately two independent snapshots and restores plus optional audits. Select the narrowest representative solution or project. Archive size, entries, expanded bytes, free space, extraction time, subprocess output, restore/evaluation time, graph traversal, assets, XML, and parallelism all have hard bounds; exceeding one returns incomplete.