aienvmp 0.1.58 → 0.1.59

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/BUGFIXES.md CHANGED
@@ -142,6 +142,12 @@ Short record of bugs, fixes, and follow-up checks.
142
142
  - Fix: manifest generation now includes `lightSbom.aiDependencyReview`; the dashboard Light SBOM card and `summary.md` show the AI dependency review status and commands.
143
143
  - Verification: regression tests cover generation, dashboard rendering, SBOM artifact reuse, and summary output; local sync smoke confirmed dashboard and SBOM artifacts include the block.
144
144
 
145
+ ### Scanner-off SBOM state could look safer than intended
146
+
147
+ - Issue: AI agents could read `aiDependencyReview.status: ready` while the security scanner was off, making the state look more verified than it was.
148
+ - Fix: `aiDependencyReview` now includes `statusReason` and `securityConfidence`, and generation reuses the computed risk summary instead of recalculating a weaker review signal.
149
+ - Verification: regression tests cover scanner-off confidence, dashboard/summary rendering, schema metadata, and standalone SBOM fallback behavior; local sync smoke confirmed `scanner-off` appears in generated artifacts.
150
+
145
151
  ## Template
146
152
 
147
153
  ### Title
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.59
4
+
5
+ - Added `aiDependencyReview.statusReason` so AI agents can distinguish actual review risk from scanner-off uncertainty.
6
+ - Added `aiDependencyReview.securityConfidence` across generated light SBOM, standalone SBOM, dashboard, summary, and schema metadata.
7
+ - Reused the computed `riskSummary`, package-manager policy, and dependency hints when building `aiDependencyReview` to avoid drift between artifacts.
8
+ - Updated dashboard and summary surfaces to show dependency-review confidence.
9
+ - Updated README and regression tests for scanner-off dependency review behavior.
10
+
3
11
  ## 0.1.58
4
12
 
5
13
  - Added `lightSbom.aiDependencyReview` during manifest generation so all generated artifacts share the same dependency-review block.
package/README.md CHANGED
@@ -53,7 +53,7 @@ AIENV.md # Markdown env map for AI agents
53
53
  - `aiReadiness` gives a one-field ready/review signal for AI continuation.
54
54
  - `dependencyReadSet` lists manifests and lockfiles before package or security changes.
55
55
  - `sbomRisk` gives AI a compact light-SBOM risk level, signals, and next command.
56
- - `sbom.json.aiDependencyReview` connects SBOM risk to safe dependency-change steps.
56
+ - `sbom.json.aiDependencyReview` connects SBOM risk, scanner confidence, and safe dependency-change steps.
57
57
  - `coordination.conflictTargets` shows where multiple agents are planning changes.
58
58
  - `agentActivity.multiActorTargets` shows where multiple agents actually recorded env changes.
59
59
  - `followUps` shows records that still need `sync`, `status`, or `handoff`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aienvmp",
3
- "version": "0.1.58",
3
+ "version": "0.1.59",
4
4
  "description": "AI-first environment maps and lightweight runtime SBOMs for shared development machines.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -55,8 +55,15 @@ function aiDependencyReview(lightSbom = {}) {
55
55
  ? risk.reviewTargets
56
56
  : hints.map((item) => item.manifest).filter(Boolean);
57
57
  const review = ["urgent", "high", "medium"].includes(level) || policy.status === "review-required";
58
+ const scannerOff = risk.scanner === "off" || lightSbom.source?.vulnerabilities === "not scanned" || lightSbom.confidence?.vulnerabilities === "not-scanned";
58
59
  return {
59
60
  status: review ? "review" : "ready",
61
+ statusReason: review
62
+ ? "SBOM risk or package manager policy requires dependency review before changes."
63
+ : scannerOff
64
+ ? "No scanned vulnerability finding is present because the security scanner is off; run read-only security scan before security decisions."
65
+ : "No light SBOM signal requires dependency review.",
66
+ securityConfidence: scannerOff ? "scanner-off" : "scanner-summary",
60
67
  mode: "advisory",
61
68
  readFirst: ["riskSummary", "dependencyChangeHints", "packageManagerPolicy", "topRisk"],
62
69
  reviewTargets: [...new Set(reviewTargets)].slice(0, 8),
@@ -86,7 +86,7 @@ export function renderSummary(status = {}, manifest = {}) {
86
86
  "",
87
87
  `- source: ${manifest.lightSbom?.source?.dependencies || "project manifests"}`,
88
88
  `- confidence: transitive ${manifest.lightSbom?.confidence?.transitiveDependencies || "not-resolved"}`,
89
- `- AI dependency review: ${aiDependencyReview.status || "unknown"} / ${aiDependencyReview.beforeDependencyChange?.[0] || "aienvmp sbom --json"}`,
89
+ `- AI dependency review: ${aiDependencyReview.status || "unknown"} / ${aiDependencyReview.securityConfidence || "unknown"} / ${aiDependencyReview.beforeDependencyChange?.[0] || "aienvmp sbom --json"}`,
90
90
  `- signals: ${riskSignals.length ? riskSignals.join("; ") : "none"}`,
91
91
  `- verify: ${sbomRisk.next || "Use a dedicated scanner for security decisions."}`,
92
92
  "",
package/src/contract.js CHANGED
@@ -42,7 +42,8 @@ export function schemaContract() {
42
42
  sbom: {
43
43
  file: ".aienvmp/sbom.json",
44
44
  command: "aienvmp sbom --json",
45
- rootFields: ["schemaVersion", "schemaName", "workspace", "summary", "riskSummary", "topRisk", "packageManagerPolicy", "dependencyChangeHints", "aiDependencyReview"]
45
+ rootFields: ["schemaVersion", "schemaName", "workspace", "summary", "riskSummary", "topRisk", "packageManagerPolicy", "dependencyChangeHints", "aiDependencyReview"],
46
+ aiDependencyReviewFields: ["status", "statusReason", "securityConfidence", "readFirst", "reviewTargets", "beforeDependencyChange", "afterDependencyChange", "rule"]
46
47
  },
47
48
  cyclonedxLite: {
48
49
  file: ".aienvmp/sbom.cdx.json",
@@ -75,6 +75,9 @@ export function buildLightSbom(snapshot = {}, security = {}) {
75
75
  fixAvailable: pkg.fixAvailable === true || Boolean(pkg.fixVersions?.length),
76
76
  fixVersions: (pkg.fixVersions || []).slice(0, 3)
77
77
  }));
78
+ const riskSummary = lightSbomRiskSummary({ packages, security, directVulnerable, transitiveOrUnmatched, topRisk, lockfiles: snapshot.lockfiles || [] });
79
+ const pmPolicy = packageManagerPolicy(snapshot.lockfiles || []);
80
+ const changeHints = dependencyChangeHints(packages, topRisk, snapshot.lockfiles || []);
78
81
  return {
79
82
  schemaVersion: 1,
80
83
  mode: "light-sbom",
@@ -108,10 +111,10 @@ export function buildLightSbom(snapshot = {}, security = {}) {
108
111
  transitiveOrUnmatchedVulnerablePackages: transitiveOrUnmatched.length
109
112
  },
110
113
  topRisk,
111
- riskSummary: lightSbomRiskSummary({ packages, security, directVulnerable, transitiveOrUnmatched, topRisk, lockfiles: snapshot.lockfiles || [] }),
112
- packageManagerPolicy: packageManagerPolicy(snapshot.lockfiles || []),
113
- dependencyChangeHints: dependencyChangeHints(packages, topRisk, snapshot.lockfiles || []),
114
- aiDependencyReview: aiDependencyReview({ topRisk, lockfiles: snapshot.lockfiles || [] }),
114
+ riskSummary,
115
+ packageManagerPolicy: pmPolicy,
116
+ dependencyChangeHints: changeHints,
117
+ aiDependencyReview: aiDependencyReview({ riskSummary, packageManagerPolicy: pmPolicy, security }),
115
118
  aiUse: {
116
119
  beforeDependencyChanges: "Read lightSbom.summary and lightSbom.topRisk before changing dependencies.",
117
120
  securityMode: security.enabled ? "scanner-summary" : "scanner-off",
@@ -121,14 +124,20 @@ export function buildLightSbom(snapshot = {}, security = {}) {
121
124
  };
122
125
  }
123
126
 
124
- function aiDependencyReview({ topRisk = [], lockfiles = [] } = {}) {
125
- const risk = lightSbomRiskSummary({ topRisk, lockfiles });
126
- const review = ["urgent", "high", "medium"].includes(risk.level) || packageManagerPolicy(lockfiles).status === "review-required";
127
+ function aiDependencyReview({ riskSummary = {}, packageManagerPolicy = {}, security = {} } = {}) {
128
+ const review = ["urgent", "high", "medium"].includes(riskSummary.level) || packageManagerPolicy.status === "review-required";
129
+ const scannerOff = security.enabled !== true;
127
130
  return {
128
131
  status: review ? "review" : "ready",
132
+ statusReason: review
133
+ ? "SBOM risk or package manager policy requires dependency review before changes."
134
+ : scannerOff
135
+ ? "No scanned vulnerability finding is present because the security scanner is off; run read-only security scan before security decisions."
136
+ : "No light SBOM signal requires dependency review.",
137
+ securityConfidence: scannerOff ? "scanner-off" : "scanner-summary",
129
138
  mode: "advisory",
130
139
  readFirst: ["riskSummary", "dependencyChangeHints", "packageManagerPolicy", "topRisk"],
131
- reviewTargets: risk.reviewTargets,
140
+ reviewTargets: riskSummary.reviewTargets || [],
132
141
  safeActions: [
133
142
  "read SBOM, status, summary, context, and dependency manifests before dependency changes",
134
143
  "plan remediation without installing, upgrading, downgrading, or switching package managers",
package/src/render.js CHANGED
@@ -434,7 +434,7 @@ const aiDependencyReview=lightSbom.aiDependencyReview||{};
434
434
  const dependencyHintsHtml=dependencyHints.length?'<div class="timeline">'+dependencyHints.slice(0,5).map(h=>\`<div class="event"><time>\${esc(h.ecosystem||'deps')}</time><div><b>\${esc(h.manifest)}</b> <code>\${esc(h.manager||'unknown')}</code> \${esc(h.packages||0)} packages\${h.riskPackages?.length?\`<div class="path">risk: \${esc(h.riskPackages.map(p=>p.name).join(', '))}</div>\`:''}<div class="path">\${esc((h.groups||[]).join(', ')||'no groups')}\${h.lockfiles?.length?\` / lockfiles: \${esc(h.lockfiles.map(l=>l.file).join(', '))}\`:''}</div></div></div>\`).join('')+'</div>':'<div class="okline">No dependency change hints available.</div>';
435
435
  const pmPolicyHtml='<table><tr><th>Status</th><td><code>'+esc(pmPolicy.status||'no-lockfile')+'</code></td></tr><tr><th>Guidance</th><td>'+esc(pmPolicy.guidance||'No lockfile policy detected.')+'</td></tr></table>';
436
436
  const riskSummaryHtml=riskSummary.level?\`<table><tr><th>Level</th><td><code>\${esc(riskSummary.level)}</code> \${esc(riskSummary.score||0)}</td></tr><tr><th>Scanner</th><td><code>\${esc(riskSummary.scanner||'unknown')}</code></td></tr><tr><th>Next</th><td>\${esc(riskSummary.next||'No SBOM action required.')}</td></tr><tr><th>Targets</th><td>\${esc((riskSummary.reviewTargets||[]).join(', ')||'none')}</td></tr></table>\${riskSummary.signals?.length?'<div class="timeline">'+riskSummary.signals.slice(0,5).map(s=>\`<div class="event"><time>risk</time><div>\${esc(s)}</div></div>\`).join('')+'</div>':''}\`:'<div class="okline">No risk summary available.</div>';
437
- const aiDependencyReviewHtml=aiDependencyReview.status?\`<table><tr><th>Status</th><td><code>\${esc(aiDependencyReview.status)}</code> \${esc(aiDependencyReview.mode||'advisory')}</td></tr><tr><th>Read first</th><td>\${esc((aiDependencyReview.readFirst||[]).join(', ')||'riskSummary')}</td></tr><tr><th>Targets</th><td>\${esc((aiDependencyReview.reviewTargets||[]).join(', ')||'none')}</td></tr><tr><th>Before</th><td><code>\${esc((aiDependencyReview.beforeDependencyChange||[]).slice(0,3).join(' -> ')||'aienvmp intent --actor agent:id --action dependency-review --target dependency')}</code></td></tr><tr><th>After</th><td><code>\${esc((aiDependencyReview.afterDependencyChange||[]).slice(-1)[0]||'aienvmp checkpoint --actor agent:id --summary dependency-change --target dependency')}</code></td></tr></table><div class="path">\${esc(aiDependencyReview.rule||'Dependency review is advisory and non-blocking.')}</div>\`:'<div class="okline">No AI dependency review block available. Run <code>aienvmp sync</code>.</div>';
437
+ const aiDependencyReviewHtml=aiDependencyReview.status?\`<table><tr><th>Status</th><td><code>\${esc(aiDependencyReview.status)}</code> \${esc(aiDependencyReview.mode||'advisory')}</td></tr><tr><th>Reason</th><td>\${esc(aiDependencyReview.statusReason||'No dependency review reason provided.')}</td></tr><tr><th>Security confidence</th><td><code>\${esc(aiDependencyReview.securityConfidence||'unknown')}</code></td></tr><tr><th>Read first</th><td>\${esc((aiDependencyReview.readFirst||[]).join(', ')||'riskSummary')}</td></tr><tr><th>Targets</th><td>\${esc((aiDependencyReview.reviewTargets||[]).join(', ')||'none')}</td></tr><tr><th>Before</th><td><code>\${esc((aiDependencyReview.beforeDependencyChange||[]).slice(0,3).join(' -> ')||'aienvmp intent --actor agent:id --action dependency-review --target dependency')}</code></td></tr><tr><th>After</th><td><code>\${esc((aiDependencyReview.afterDependencyChange||[]).slice(-1)[0]||'aienvmp checkpoint --actor agent:id --summary dependency-change --target dependency')}</code></td></tr></table><div class="path">\${esc(aiDependencyReview.rule||'Dependency review is advisory and non-blocking.')}</div>\`:'<div class="okline">No AI dependency review block available. Run <code>aienvmp sync</code>.</div>';
438
438
  const lightSbomHtml=\`<table><tr><th>Packages</th><td><code>\${esc(lightSbomSummary.packages||0)}</code></td></tr><tr><th>Vulnerabilities</th><td><code>\${esc(lightSbomSummary.vulnerabilities||0)}</code></td></tr><tr><th>Direct vulnerable</th><td><code>\${esc(lightSbomSummary.directVulnerablePackages||0)}</code></td></tr><tr><th>Manifests</th><td><code>\${esc((lightSbomSummary.manifests||[]).join(', ')||'none')}</code></td></tr><tr><th>Lockfiles</th><td><code>\${esc((lightSbomSummary.lockfiles||[]).map(l=>l.file).join(', ')||'none')}</code></td></tr></table><h3 style="margin-top:12px">AI Dependency Review</h3>\${aiDependencyReviewHtml}<h3 style="margin-top:12px">Risk summary</h3>\${riskSummaryHtml}<h3 style="margin-top:12px">Package manager policy</h3>\${pmPolicyHtml}\${topRisk.length?'<div class="timeline">'+topRisk.slice(0,5).map(p=>\`<div class="event"><time>\${esc(p.priority)}</time><div><b>\${esc(p.name)}</b> \${esc(p.severity)} \${p.directDependency?'<code>direct</code>':'<code>transitive</code>'}<div class="path">\${esc(p.manifest||p.ecosystem)} \${esc(p.version||'')}</div></div></div>\`).join('')+'</div>':'<div class="okline">No high-risk package summary in the current light SBOM.</div>'}<h3 style="margin-top:12px">Dependency change hints</h3>\${dependencyHintsHtml}\`;
439
439
  const sec=manifest.security||{};
440
440
  const secSummary=sec.summary||{total:0,critical:0,high:0,moderate:0,low:0,info:0};