aienvmp 0.1.57 → 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
@@ -136,6 +136,18 @@ Short record of bugs, fixes, and follow-up checks.
136
136
  - Fix: `aiDependencyReview` now gives a top-level advisory review block with read order, safe actions, review targets, before-change commands, and after-change checkpoint guidance.
137
137
  - Verification: regression tests cover the new SBOM block and schema metadata; local sync smoke confirmed generated `sbom.json` separates before-change commands from after-change checkpoint commands.
138
138
 
139
+ ### SBOM dependency review was not visible across all review surfaces
140
+
141
+ - Issue: `aiDependencyReview` was available in standalone SBOM output, but the manifest-backed dashboard and compact summary did not surface the same dependency-review guidance.
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
+ - 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
+
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
+
139
151
  ## Template
140
152
 
141
153
  ### Title
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
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
+
11
+ ## 0.1.58
12
+
13
+ - Added `lightSbom.aiDependencyReview` during manifest generation so all generated artifacts share the same dependency-review block.
14
+ - Reused generated `aiDependencyReview` in standalone `.aienvmp/sbom.json`.
15
+ - Added an `AI Dependency Review` section to the dashboard Light SBOM card.
16
+ - Added the AI dependency review status and first command to `.aienvmp/summary.md`.
17
+ - Added regression tests for light SBOM generation, dashboard rendering, SBOM artifact output, and summary output.
18
+
3
19
  ## 0.1.57
4
20
 
5
21
  - Added `aiDependencyReview` to the standalone `.aienvmp/sbom.json` artifact.
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.57",
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": {
@@ -36,7 +36,7 @@ export function buildSbomArtifact(manifest = {}) {
36
36
  topRisk: (lightSbom.topRisk || []).slice(0, 20),
37
37
  packageManagerPolicy: lightSbom.packageManagerPolicy || {},
38
38
  dependencyChangeHints: (lightSbom.dependencyChangeHints || []).slice(0, 20),
39
- aiDependencyReview: aiDependencyReview(lightSbom),
39
+ aiDependencyReview: lightSbom.aiDependencyReview || aiDependencyReview(lightSbom),
40
40
  aiUse: {
41
41
  purpose: "Standalone AI-readable light SBOM artifact.",
42
42
  readBefore: "Dependency changes, vulnerability remediation, release review, or shared AI handoff.",
@@ -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),
@@ -55,6 +55,7 @@ export function renderSummary(status = {}, manifest = {}) {
55
55
  const aiReadiness = status.aiReadiness || {};
56
56
  const aiSignals = toList(aiReadiness.signals).slice(0, 5);
57
57
  const aiNext = aiReadiness.next || "Run aienvmp context --json for details.";
58
+ const aiDependencyReview = manifest.lightSbom?.aiDependencyReview || {};
58
59
 
59
60
  return [
60
61
  "# aienvmp summary",
@@ -85,6 +86,7 @@ export function renderSummary(status = {}, manifest = {}) {
85
86
  "",
86
87
  `- source: ${manifest.lightSbom?.source?.dependencies || "project manifests"}`,
87
88
  `- confidence: transitive ${manifest.lightSbom?.confidence?.transitiveDependencies || "not-resolved"}`,
89
+ `- AI dependency review: ${aiDependencyReview.status || "unknown"} / ${aiDependencyReview.securityConfidence || "unknown"} / ${aiDependencyReview.beforeDependencyChange?.[0] || "aienvmp sbom --json"}`,
88
90
  `- signals: ${riskSignals.length ? riskSignals.join("; ") : "none"}`,
89
91
  `- verify: ${sbomRisk.next || "Use a dedicated scanner for security decisions."}`,
90
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,9 +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
+ riskSummary,
115
+ packageManagerPolicy: pmPolicy,
116
+ dependencyChangeHints: changeHints,
117
+ aiDependencyReview: aiDependencyReview({ riskSummary, packageManagerPolicy: pmPolicy, security }),
114
118
  aiUse: {
115
119
  beforeDependencyChanges: "Read lightSbom.summary and lightSbom.topRisk before changing dependencies.",
116
120
  securityMode: security.enabled ? "scanner-summary" : "scanner-off",
@@ -120,6 +124,40 @@ export function buildLightSbom(snapshot = {}, security = {}) {
120
124
  };
121
125
  }
122
126
 
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;
130
+ return {
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",
138
+ mode: "advisory",
139
+ readFirst: ["riskSummary", "dependencyChangeHints", "packageManagerPolicy", "topRisk"],
140
+ reviewTargets: riskSummary.reviewTargets || [],
141
+ safeActions: [
142
+ "read SBOM, status, summary, context, and dependency manifests before dependency changes",
143
+ "plan remediation without installing, upgrading, downgrading, or switching package managers",
144
+ "record intent before dependency or lockfile changes when another AI may be working"
145
+ ],
146
+ beforeDependencyChange: [
147
+ "aienvmp sync --security",
148
+ "aienvmp intent --actor agent:id --action dependency-review --target dependency",
149
+ "aienvmp plan --write"
150
+ ],
151
+ afterDependencyChange: [
152
+ "run the narrowest relevant project validation",
153
+ "aienvmp checkpoint --actor agent:id --summary dependency-change --target dependency"
154
+ ],
155
+ rule: review
156
+ ? "Review SBOM risk and package manager policy before dependency changes; default behavior is advisory and non-blocking."
157
+ : "No light SBOM signal requires action; still record intent before dependency or lockfile changes."
158
+ };
159
+ }
160
+
123
161
  export function lightSbomRiskSummary({ packages = [], security = {}, directVulnerable = [], transitiveOrUnmatched = [], topRisk = [], lockfiles = [] } = {}) {
124
162
  const signals = [];
125
163
  let score = 0;
package/src/render.js CHANGED
@@ -430,10 +430,12 @@ const pmPolicy=lightSbom.packageManagerPolicy||{};
430
430
  const topRisk=lightSbom.topRisk||[];
431
431
  const riskSummary=lightSbom.riskSummary||{};
432
432
  const dependencyHints=lightSbom.dependencyChangeHints||[];
433
+ const aiDependencyReview=lightSbom.aiDependencyReview||{};
433
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>';
434
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>';
435
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>';
436
- 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">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}\`;
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
+ 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}\`;
437
439
  const sec=manifest.security||{};
438
440
  const secSummary=sec.summary||{total:0,critical:0,high:0,moderate:0,low:0,info:0};
439
441
  const secPackages=sec.topPackages||[];