aienvmp 0.1.56 → 0.1.58
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 +12 -0
- package/CHANGELOG.md +17 -0
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/commands/sbom.js +43 -0
- package/src/commands/summary.js +2 -0
- package/src/contract.js +1 -1
- package/src/dependencies.js +29 -0
- package/src/render.js +3 -1
package/BUGFIXES.md
CHANGED
|
@@ -130,6 +130,18 @@ Short record of bugs, fixes, and follow-up checks.
|
|
|
130
130
|
- Fix: `aiReadiness` now includes `requiresHumanReview`, `safeProjectLocalActions`, and `reviewOnlyEnvironmentChanges`; `summary.md` includes the first safe-local-work hint.
|
|
131
131
|
- Verification: regression tests cover status, context, summary, and schema outputs; Windows smoke confirmed generated JSON and summary fields.
|
|
132
132
|
|
|
133
|
+
### Standalone SBOM did not expose the dependency-change loop directly
|
|
134
|
+
|
|
135
|
+
- Issue: AI agents reading only `.aienvmp/sbom.json` could see risk and hints, but still had to infer the safe dependency-change sequence from several fields.
|
|
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
|
+
- 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
|
+
|
|
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
|
+
|
|
133
145
|
## Template
|
|
134
146
|
|
|
135
147
|
### Title
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.58
|
|
4
|
+
|
|
5
|
+
- Added `lightSbom.aiDependencyReview` during manifest generation so all generated artifacts share the same dependency-review block.
|
|
6
|
+
- Reused generated `aiDependencyReview` in standalone `.aienvmp/sbom.json`.
|
|
7
|
+
- Added an `AI Dependency Review` section to the dashboard Light SBOM card.
|
|
8
|
+
- Added the AI dependency review status and first command to `.aienvmp/summary.md`.
|
|
9
|
+
- Added regression tests for light SBOM generation, dashboard rendering, SBOM artifact output, and summary output.
|
|
10
|
+
|
|
11
|
+
## 0.1.57
|
|
12
|
+
|
|
13
|
+
- Added `aiDependencyReview` to the standalone `.aienvmp/sbom.json` artifact.
|
|
14
|
+
- Linked SBOM risk to dependency-change read order, safe actions, review targets, and before/after commands.
|
|
15
|
+
- Kept dependency review advisory and non-blocking while making the AI handoff more explicit.
|
|
16
|
+
- Documented `sbom.json.aiDependencyReview` in the README and schema contract.
|
|
17
|
+
- Fixed dependency-review command grouping so `checkpoint` appears after dependency changes, not before.
|
|
18
|
+
- Added regression tests for SBOM dependency review and schema metadata.
|
|
19
|
+
|
|
3
20
|
## 0.1.56
|
|
4
21
|
|
|
5
22
|
- Added `aiReadiness.requiresHumanReview` so agents can distinguish review-needed signals from hard blocking.
|
package/README.md
CHANGED
|
@@ -53,6 +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
57
|
- `coordination.conflictTargets` shows where multiple agents are planning changes.
|
|
57
58
|
- `agentActivity.multiActorTargets` shows where multiple agents actually recorded env changes.
|
|
58
59
|
- `followUps` shows records that still need `sync`, `status`, or `handoff`.
|
package/package.json
CHANGED
package/src/commands/sbom.js
CHANGED
|
@@ -36,6 +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: lightSbom.aiDependencyReview || aiDependencyReview(lightSbom),
|
|
39
40
|
aiUse: {
|
|
40
41
|
purpose: "Standalone AI-readable light SBOM artifact.",
|
|
41
42
|
readBefore: "Dependency changes, vulnerability remediation, release review, or shared AI handoff.",
|
|
@@ -45,6 +46,40 @@ export function buildSbomArtifact(manifest = {}) {
|
|
|
45
46
|
};
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
function aiDependencyReview(lightSbom = {}) {
|
|
50
|
+
const risk = lightSbom.riskSummary || {};
|
|
51
|
+
const hints = lightSbom.dependencyChangeHints || [];
|
|
52
|
+
const policy = lightSbom.packageManagerPolicy || {};
|
|
53
|
+
const level = risk.level || "clear";
|
|
54
|
+
const reviewTargets = (risk.reviewTargets || []).length
|
|
55
|
+
? risk.reviewTargets
|
|
56
|
+
: hints.map((item) => item.manifest).filter(Boolean);
|
|
57
|
+
const review = ["urgent", "high", "medium"].includes(level) || policy.status === "review-required";
|
|
58
|
+
return {
|
|
59
|
+
status: review ? "review" : "ready",
|
|
60
|
+
mode: "advisory",
|
|
61
|
+
readFirst: ["riskSummary", "dependencyChangeHints", "packageManagerPolicy", "topRisk"],
|
|
62
|
+
reviewTargets: [...new Set(reviewTargets)].slice(0, 8),
|
|
63
|
+
safeActions: [
|
|
64
|
+
"read SBOM, status, summary, context, and dependency manifests before dependency changes",
|
|
65
|
+
"plan remediation without installing, upgrading, downgrading, or switching package managers",
|
|
66
|
+
"record intent before dependency or lockfile changes when another AI may be working"
|
|
67
|
+
],
|
|
68
|
+
beforeDependencyChange: uniqueCommands([
|
|
69
|
+
...planningCommands(risk.commands || []),
|
|
70
|
+
"aienvmp intent --actor agent:id --action dependency-review --target dependency",
|
|
71
|
+
"aienvmp plan --write"
|
|
72
|
+
]),
|
|
73
|
+
afterDependencyChange: [
|
|
74
|
+
"run the narrowest relevant project validation",
|
|
75
|
+
"aienvmp checkpoint --actor agent:id --summary dependency-change --target dependency"
|
|
76
|
+
],
|
|
77
|
+
rule: review
|
|
78
|
+
? "Review SBOM risk and package manager policy before dependency changes; default behavior is advisory and non-blocking."
|
|
79
|
+
: "No light SBOM signal requires action; still record intent before dependency or lockfile changes."
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
48
83
|
export async function writeSbomArtifact(dir, sbom) {
|
|
49
84
|
const out = sbom.bomFormat === "CycloneDX" ? cyclonedxSbomPath(dir) : sbomJsonPath(dir);
|
|
50
85
|
await writeJson(out, sbom);
|
|
@@ -136,6 +171,14 @@ function normalizeFormat(format = "") {
|
|
|
136
171
|
return "aienvmp";
|
|
137
172
|
}
|
|
138
173
|
|
|
174
|
+
function uniqueCommands(commands = []) {
|
|
175
|
+
return [...new Set(commands.filter(Boolean))];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function planningCommands(commands = []) {
|
|
179
|
+
return commands.filter((command) => !String(command).includes(" checkpoint "));
|
|
180
|
+
}
|
|
181
|
+
|
|
139
182
|
function hashText(text = "") {
|
|
140
183
|
let hash = 0;
|
|
141
184
|
for (const ch of String(text)) hash = ((hash << 5) - hash + ch.charCodeAt(0)) >>> 0;
|
package/src/commands/summary.js
CHANGED
|
@@ -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.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,7 @@ 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"]
|
|
45
|
+
rootFields: ["schemaVersion", "schemaName", "workspace", "summary", "riskSummary", "topRisk", "packageManagerPolicy", "dependencyChangeHints", "aiDependencyReview"]
|
|
46
46
|
},
|
|
47
47
|
cyclonedxLite: {
|
|
48
48
|
file: ".aienvmp/sbom.cdx.json",
|
package/src/dependencies.js
CHANGED
|
@@ -111,6 +111,7 @@ export function buildLightSbom(snapshot = {}, security = {}) {
|
|
|
111
111
|
riskSummary: lightSbomRiskSummary({ packages, security, directVulnerable, transitiveOrUnmatched, topRisk, lockfiles: snapshot.lockfiles || [] }),
|
|
112
112
|
packageManagerPolicy: packageManagerPolicy(snapshot.lockfiles || []),
|
|
113
113
|
dependencyChangeHints: dependencyChangeHints(packages, topRisk, snapshot.lockfiles || []),
|
|
114
|
+
aiDependencyReview: aiDependencyReview({ topRisk, lockfiles: snapshot.lockfiles || [] }),
|
|
114
115
|
aiUse: {
|
|
115
116
|
beforeDependencyChanges: "Read lightSbom.summary and lightSbom.topRisk before changing dependencies.",
|
|
116
117
|
securityMode: security.enabled ? "scanner-summary" : "scanner-off",
|
|
@@ -120,6 +121,34 @@ export function buildLightSbom(snapshot = {}, security = {}) {
|
|
|
120
121
|
};
|
|
121
122
|
}
|
|
122
123
|
|
|
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
|
+
return {
|
|
128
|
+
status: review ? "review" : "ready",
|
|
129
|
+
mode: "advisory",
|
|
130
|
+
readFirst: ["riskSummary", "dependencyChangeHints", "packageManagerPolicy", "topRisk"],
|
|
131
|
+
reviewTargets: risk.reviewTargets,
|
|
132
|
+
safeActions: [
|
|
133
|
+
"read SBOM, status, summary, context, and dependency manifests before dependency changes",
|
|
134
|
+
"plan remediation without installing, upgrading, downgrading, or switching package managers",
|
|
135
|
+
"record intent before dependency or lockfile changes when another AI may be working"
|
|
136
|
+
],
|
|
137
|
+
beforeDependencyChange: [
|
|
138
|
+
"aienvmp sync --security",
|
|
139
|
+
"aienvmp intent --actor agent:id --action dependency-review --target dependency",
|
|
140
|
+
"aienvmp plan --write"
|
|
141
|
+
],
|
|
142
|
+
afterDependencyChange: [
|
|
143
|
+
"run the narrowest relevant project validation",
|
|
144
|
+
"aienvmp checkpoint --actor agent:id --summary dependency-change --target dependency"
|
|
145
|
+
],
|
|
146
|
+
rule: review
|
|
147
|
+
? "Review SBOM risk and package manager policy before dependency changes; default behavior is advisory and non-blocking."
|
|
148
|
+
: "No light SBOM signal requires action; still record intent before dependency or lockfile changes."
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
123
152
|
export function lightSbomRiskSummary({ packages = [], security = {}, directVulnerable = [], transitiveOrUnmatched = [], topRisk = [], lockfiles = [] } = {}) {
|
|
124
153
|
const signals = [];
|
|
125
154
|
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
|
|
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>';
|
|
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||[];
|