aienvmp 0.1.28 → 0.1.29

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.29
4
+
5
+ - Added lightweight remediation priority scoring for vulnerable packages.
6
+ - Exposed priority level, score, and reasons in security summaries, plans, compact context, and dashboard rows.
7
+ - Kept scoring advisory-only so AI agents can choose safer next steps without blocking local operation.
8
+
3
9
  ## 0.1.28
4
10
 
5
11
  - Linked vulnerable package summaries to the dependency snapshot when the package is directly declared.
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  It helps Codex, Claude, Gemini, and humans avoid silent Node, Python, package manager, dependency, Docker, and security drift.
12
12
 
13
- Core loop: scan once, link runtime/dependency/security context, give AI a shared decision contract, and hand off safe next steps.
13
+ Core loop: scan once, link runtime/dependency/security context, give AI a shared decision contract with advisory priorities, and hand off safe next steps.
14
14
 
15
15
  ## Quick Start
16
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aienvmp",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "AI-first environment maps and lightweight runtime SBOMs for shared development machines.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/actions.js CHANGED
@@ -42,7 +42,8 @@ function securityActions(security = {}) {
42
42
 
43
43
  const packageHints = packages.map((pkg) => {
44
44
  const fix = pkg.fixVersions?.length ? `fix ${pkg.fixVersions.slice(0, 2).join(", ")}` : pkg.fixAvailable ? "fix available" : "review required";
45
- return `${pkg.name} (${pkg.severity}, ${fix})`;
45
+ const priority = pkg.remediationPriority ? `${pkg.remediationPriority.level}/${pkg.remediationPriority.score}, ` : "";
46
+ return `${pkg.name} (${priority}${pkg.severity}, ${fix})`;
46
47
  });
47
48
 
48
49
  return [action(
@@ -73,6 +73,8 @@ export function compactStepSummary(plan = {}) {
73
73
  remediation: (plan.remediationSteps || []).slice(0, 3).map((item) => ({
74
74
  package: item.package,
75
75
  severity: item.severity,
76
+ priority: item.remediationPriority?.level || "low",
77
+ score: item.remediationPriority?.score || 0,
76
78
  fixVersions: (item.fixVersions || []).slice(0, 3),
77
79
  advisoryIds: (item.advisories || []).map((advisory) => advisory.id || advisory.title).filter(Boolean).slice(0, 3)
78
80
  })),
@@ -175,6 +177,7 @@ function remediationSteps(security = {}) {
175
177
  package: pkg.name,
176
178
  scanner: pkg.scanner || "unknown",
177
179
  severity: pkg.severity || "unknown",
180
+ remediationPriority: pkg.remediationPriority || { level: "low", score: 0, reasons: [] },
178
181
  directDependency: pkg.directDependency === true,
179
182
  dependency: pkg.dependency || null,
180
183
  fixAvailable: pkg.fixAvailable === true,
@@ -29,20 +29,43 @@ export function linkVulnerableDependencies(security = {}, snapshot = {}) {
29
29
  ...security,
30
30
  topPackages: (security.topPackages || []).map((pkg) => {
31
31
  const dependency = dependencyIndex.get(dependencyKey(scannerEcosystem(pkg.scanner), pkg.name));
32
+ const directDependency = Boolean(dependency);
32
33
  return {
33
34
  ...pkg,
34
- directDependency: Boolean(dependency),
35
+ directDependency,
35
36
  dependency: dependency ? {
36
37
  ecosystem: dependency.ecosystem,
37
38
  manifest: dependency.manifest,
38
39
  group: dependency.group,
39
40
  version: dependency.version
40
- } : null
41
+ } : null,
42
+ remediationPriority: remediationPriority(pkg, { directDependency })
41
43
  };
42
44
  })
43
45
  };
44
46
  }
45
47
 
48
+ export function remediationPriority(pkg = {}, context = {}) {
49
+ const severityScore = { critical: 90, high: 70, moderate: 45, low: 20, info: 5, unknown: 30 };
50
+ const severity = String(pkg.severity || "unknown").toLowerCase();
51
+ const reasons = [`severity:${severity}`];
52
+ let score = severityScore[severity] ?? severityScore.unknown;
53
+ if (context.directDependency) {
54
+ score += 15;
55
+ reasons.push("direct-dependency");
56
+ } else {
57
+ reasons.push("not-direct-in-snapshot");
58
+ }
59
+ if (pkg.fixAvailable === true || (Array.isArray(pkg.fixVersions) && pkg.fixVersions.length)) {
60
+ score += 5;
61
+ reasons.push("fix-available");
62
+ } else {
63
+ reasons.push("fix-review-needed");
64
+ }
65
+ const level = score >= 95 ? "urgent" : score >= 75 ? "high" : score >= 50 ? "medium" : "low";
66
+ return { level, score, reasons };
67
+ }
68
+
46
69
  async function scanNodeDependencies(dir) {
47
70
  const file = path.join(dir, "package.json");
48
71
  if (!(await exists(file))) return { manifests: [], packages: [] };
package/src/render.js CHANGED
@@ -212,8 +212,9 @@ function remediationLines(item) {
212
212
  const fix = item.fixVersions?.length ? `fix ${item.fixVersions.join(", ")}` : item.fixAvailable ? "fix available" : "review required";
213
213
  const advisories = (item.advisories || []).map((advisory) => advisory.id || advisory.title).filter(Boolean).slice(0, 2);
214
214
  const dependency = item.directDependency && item.dependency ? `; declared in ${item.dependency.manifest} ${item.dependency.version}` : "; not found in dependency snapshot";
215
+ const priority = item.remediationPriority ? `priority ${item.remediationPriority.level}/${item.remediationPriority.score}` : "priority unscored";
215
216
  return [
216
- `- ${item.package}: ${item.severity}; ${fix}${dependency}${advisories.length ? `; advisories ${advisories.join(", ")}` : ""}`,
217
+ `- ${item.package}: ${item.severity}; ${priority}; ${fix}${dependency}${advisories.length ? `; advisories ${advisories.join(", ")}` : ""}`,
217
218
  ...item.steps.slice(0, 4).map((step) => ` - ${step}`)
218
219
  ];
219
220
  }
@@ -287,7 +288,8 @@ const secPackages=sec.topPackages||[];
287
288
  const securityFix=p=>p.fixVersions?.length?\`fix \${p.fixVersions.slice(0,3).join(', ')}\`:(p.fixAvailable?'fix available':'review required');
288
289
  const securityRefs=p=>p.advisories?.length?\` - \${p.advisories.map(a=>a.id||a.title).filter(Boolean).slice(0,2).join(', ')}\`:'';
289
290
  const securityDep=p=>p.directDependency&&p.dependency?\`<div class="path">\${esc(p.dependency.manifest)} / \${esc(p.dependency.group)} / \${esc(p.dependency.version)}</div>\`:'<div class="path">not found in dependency snapshot</div>';
290
- const securityHtml=sec.enabled?\`<table><tr><th>Total</th><td><code>\${esc(secSummary.total||0)}</code></td></tr><tr><th>Critical</th><td><code>\${esc(secSummary.critical||0)}</code></td></tr><tr><th>High</th><td><code>\${esc(secSummary.high||0)}</code></td></tr><tr><th>Moderate</th><td><code>\${esc(secSummary.moderate||0)}</code></td></tr><tr><th>Low</th><td><code>\${esc(secSummary.low||0)}</code></td></tr></table>\${secPackages.length?'<div class="timeline">'+secPackages.slice(0,5).map(p=>\`<div class="event"><time>\${esc(p.severity)}</time><div><b>\${esc(p.name)}</b> \${esc(securityFix(p))}\${esc(securityRefs(p))}\${securityDep(p)}</div></div>\`).join('')+'</div>':'<div class="okline" style="margin-top:10px">No vulnerable packages reported.</div>'}\`:'<div class="okline">Security scan is off. Run <code>aienvmp sync --security</code> for read-only vulnerability summary.</div>';
291
+ const securityPriority=p=>p.remediationPriority?\`<code>\${esc(p.remediationPriority.level)} \${esc(p.remediationPriority.score)}</code> \`:'';
292
+ const securityHtml=sec.enabled?\`<table><tr><th>Total</th><td><code>\${esc(secSummary.total||0)}</code></td></tr><tr><th>Critical</th><td><code>\${esc(secSummary.critical||0)}</code></td></tr><tr><th>High</th><td><code>\${esc(secSummary.high||0)}</code></td></tr><tr><th>Moderate</th><td><code>\${esc(secSummary.moderate||0)}</code></td></tr><tr><th>Low</th><td><code>\${esc(secSummary.low||0)}</code></td></tr></table>\${secPackages.length?'<div class="timeline">'+secPackages.slice(0,5).map(p=>\`<div class="event"><time>\${esc(p.severity)}</time><div><b>\${esc(p.name)}</b> \${securityPriority(p)}\${esc(securityFix(p))}\${esc(securityRefs(p))}\${securityDep(p)}</div></div>\`).join('')+'</div>':'<div class="okline" style="margin-top:10px">No vulnerable packages reported.</div>'}\`:'<div class="okline">Security scan is off. Run <code>aienvmp sync --security</code> for read-only vulnerability summary.</div>';
291
293
  const change=c=>c.type==='changed'?\`\${c.scope} \${c.key}: \${c.before} -> \${c.after}\`:\`\${c.scope} \${c.key}: \${c.type} \${c.after||c.before}\`;
292
294
  const timelineLabel=t=>t.change?change(t.change):(t.summary||t.action||t.type||'recorded change');
293
295
  const agentNames={agents:'Codex',claude:'Claude',gemini:'Gemini'};
@@ -465,12 +467,13 @@ function securityLines(security = {}) {
465
467
 
466
468
  function securityPackageNote(pkg) {
467
469
  const fix = pkg.fixVersions?.length ? `fix ${pkg.fixVersions.slice(0, 3).join(", ")}` : pkg.fixAvailable ? "fix available" : "review required";
470
+ const priority = pkg.remediationPriority ? `priority ${pkg.remediationPriority.level}/${pkg.remediationPriority.score}; ` : "";
468
471
  const dependency = pkg.directDependency && pkg.dependency ? `; declared in ${pkg.dependency.manifest} ${pkg.dependency.version}` : "; not found in dependency snapshot";
469
472
  const advisories = (pkg.advisories || [])
470
473
  .map((item) => item.id || item.title)
471
474
  .filter(Boolean)
472
475
  .slice(0, 2);
473
- return advisories.length ? `${fix}${dependency}; advisories ${advisories.join(", ")}` : `${fix}${dependency}`;
476
+ return advisories.length ? `${priority}${fix}${dependency}; advisories ${advisories.join(", ")}` : `${priority}${fix}${dependency}`;
474
477
  }
475
478
 
476
479
  function policyLines(policy) {