aienvmp 0.1.45 → 0.1.47
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 +20 -0
- package/README.md +3 -0
- package/package.json +1 -1
- package/src/actions.js +13 -0
- package/src/cli.js +6 -1
- package/src/commands/context.js +2 -0
- package/src/commands/sbom.js +51 -0
- package/src/commands/sync.js +3 -0
- package/src/contract.js +6 -1
- package/src/dependencies.js +70 -2
- package/src/paths.js +4 -0
- package/src/preflight.js +3 -0
- package/src/render.js +10 -1
package/BUGFIXES.md
CHANGED
|
@@ -58,6 +58,18 @@ Short record of bugs, fixes, and follow-up checks.
|
|
|
58
58
|
- Fix: `aienvmp checkpoint` now performs the post-change loop and records an explicit sync ledger entry so follow-ups can be closed.
|
|
59
59
|
- Verification: checkpoint tests cover text/JSON output, status artifact refresh, timeline entries, handoff recording, and cleared follow-ups.
|
|
60
60
|
|
|
61
|
+
### Light SBOM risk required too much parsing
|
|
62
|
+
|
|
63
|
+
- Issue: AI consumers could see package and vulnerability details, but had to infer risk level and next commands from several nested fields.
|
|
64
|
+
- Fix: `lightSbom.riskSummary` and preflight `sbomRisk` now provide a compact risk score, signals, review targets, and advisory next steps.
|
|
65
|
+
- Verification: tests cover risk scoring, top-risk severity fallback, scanner-off guidance, status/context exposure, dashboard rendering, and recommended actions.
|
|
66
|
+
|
|
67
|
+
### Light SBOM was only nested inside larger artifacts
|
|
68
|
+
|
|
69
|
+
- Issue: CI tools and AI agents that only need dependency/SBOM context had to read the full manifest or context payload.
|
|
70
|
+
- Fix: `aienvmp sbom --json` and `.aienvmp/sbom.json` now expose a standalone light SBOM artifact.
|
|
71
|
+
- Verification: tests cover standalone SBOM construction, writing, sync output, schema metadata, and dashboard linking.
|
|
72
|
+
|
|
61
73
|
## Template
|
|
62
74
|
|
|
63
75
|
### Title
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.47
|
|
4
|
+
|
|
5
|
+
- Added `aienvmp sbom` for standalone AI-readable light SBOM output without deep manifest parsing.
|
|
6
|
+
- Added `.aienvmp/sbom.json` writing through `aienvmp sbom --write` and the default `sync` flow.
|
|
7
|
+
- Added the SBOM artifact path to the shared preflight artifacts map.
|
|
8
|
+
- Added SBOM output metadata to `aienvmp schema --json`.
|
|
9
|
+
- Added a dashboard Light SBOM Artifact card linking to `sbom.json`.
|
|
10
|
+
- Updated README outputs and commands with the standalone SBOM artifact.
|
|
11
|
+
- Added regression tests for SBOM artifact building, writing, sync output, schema contract, and dashboard rendering.
|
|
12
|
+
|
|
13
|
+
## 0.1.46
|
|
14
|
+
|
|
15
|
+
- Added `lightSbom.riskSummary` with compact risk level, score, scanner state, signals, review targets, and next commands.
|
|
16
|
+
- Added `sbomRisk` to the shared AI preflight contract so status/context consumers can read SBOM risk without deep parsing.
|
|
17
|
+
- Surfaced SBOM risk in `context --json`, `AIENV.md`, and the dashboard Light SBOM card.
|
|
18
|
+
- Added recommended actions for read-only security scans and high light-SBOM risk review.
|
|
19
|
+
- Used top-risk package severity as a fallback when scanner summary severity counts are incomplete.
|
|
20
|
+
- Updated dependency change hints to point agents to `aienvmp checkpoint` after accepted dependency changes.
|
|
21
|
+
- Added regression tests for risk scoring, scanner-off guidance, status/context outputs, dashboard rendering, and recommended actions.
|
|
22
|
+
|
|
3
23
|
## 0.1.45
|
|
4
24
|
|
|
5
25
|
- Added `aienvmp checkpoint` as a one-command post-environment-change flow for record, sync, status, and handoff.
|
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ Warnings are advisory by default. Use `doctor --strict <scope>` only when you wa
|
|
|
35
35
|
AIENV.md # Markdown env map for AI agents
|
|
36
36
|
.aienvmp/status.json # first AI read: clear/review, next command, nextAgent hint
|
|
37
37
|
.aienvmp/manifest.json # runtime map + light SBOM
|
|
38
|
+
.aienvmp/sbom.json # standalone AI-readable light SBOM
|
|
38
39
|
.aienvmp/intents.jsonl # planned env changes
|
|
39
40
|
.aienvmp/timeline.jsonl # append-only change ledger
|
|
40
41
|
.aienvmp/plan.md # read-only action plan
|
|
@@ -47,6 +48,7 @@ AIENV.md # Markdown env map for AI agents
|
|
|
47
48
|
- `schema --json` prints the stable AI-readable output contract without scanning.
|
|
48
49
|
- `status.json.nextAgent` tells the next AI what to read and whether to review first.
|
|
49
50
|
- `dependencyReadSet` lists manifests and lockfiles before package or security changes.
|
|
51
|
+
- `sbomRisk` gives AI a compact light-SBOM risk level, signals, and next command.
|
|
50
52
|
- `coordination.conflictTargets` shows where multiple agents are planning changes.
|
|
51
53
|
- `agentActivity.multiActorTargets` shows where multiple agents actually recorded env changes.
|
|
52
54
|
- `followUps` shows records that still need `sync`, `status`, or `handoff`.
|
|
@@ -71,6 +73,7 @@ npx aienvmp snippet gemini
|
|
|
71
73
|
aienvmp sync # update env map, status, ledger, dashboard
|
|
72
74
|
aienvmp status --write # refresh compact AI status
|
|
73
75
|
aienvmp context --json # AI decision contract
|
|
76
|
+
aienvmp sbom --json # standalone light SBOM
|
|
74
77
|
aienvmp schema --json # stable output contract for AI/CI consumers
|
|
75
78
|
aienvmp plan --write # read-only action plan
|
|
76
79
|
aienvmp handoff --record # next-agent summary
|
package/package.json
CHANGED
package/src/actions.js
CHANGED
|
@@ -20,6 +20,7 @@ export function recommendedActions(manifest = {}, context = {}) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
actions.push(...securityActions(manifest.security));
|
|
23
|
+
actions.push(...sbomRiskActions(manifest.lightSbom?.riskSummary));
|
|
23
24
|
|
|
24
25
|
if (hasRuntimePolicyWarning(warnings)) {
|
|
25
26
|
actions.push(action("review-version-policy", "medium", "runtime", "Detected runtime or package manager policy drift. Prefer project-local version files and ask before global changes."));
|
|
@@ -36,6 +37,18 @@ export function recommendedActions(manifest = {}, context = {}) {
|
|
|
36
37
|
return dedupeActions(actions).slice(0, 8);
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
function sbomRiskActions(risk = {}) {
|
|
41
|
+
const actions = [];
|
|
42
|
+
if (!risk.level) return actions;
|
|
43
|
+
if (risk.scanner === "off" && risk.vulnerabilityCount === 0) {
|
|
44
|
+
actions.push(action("scan-sbom-risk", "medium", "security", "Run a read-only security scan before dependency or release decisions.", "aienvmp sync --security"));
|
|
45
|
+
}
|
|
46
|
+
if (["urgent", "high"].includes(risk.level)) {
|
|
47
|
+
actions.push(action("review-sbom-risk", "high", "security", `Review light SBOM risk summary before dependency changes: ${(risk.signals || []).slice(0, 2).join("; ") || risk.level}.`, "aienvmp plan --write"));
|
|
48
|
+
}
|
|
49
|
+
return actions;
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
function securityActions(security = {}) {
|
|
40
53
|
if (!security.enabled) return [];
|
|
41
54
|
|
package/src/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import { planWorkspace } from "./commands/plan.js";
|
|
|
15
15
|
import { statusWorkspace } from "./commands/status.js";
|
|
16
16
|
import { schemaWorkspace } from "./commands/schema.js";
|
|
17
17
|
import { checkpointWorkspace } from "./commands/checkpoint.js";
|
|
18
|
+
import { sbomWorkspace } from "./commands/sbom.js";
|
|
18
19
|
import { readFileSync } from "node:fs";
|
|
19
20
|
|
|
20
21
|
const commands = new Map([
|
|
@@ -34,7 +35,8 @@ const commands = new Map([
|
|
|
34
35
|
["plan", planWorkspace],
|
|
35
36
|
["status", statusWorkspace],
|
|
36
37
|
["schema", schemaWorkspace],
|
|
37
|
-
["checkpoint", checkpointWorkspace]
|
|
38
|
+
["checkpoint", checkpointWorkspace],
|
|
39
|
+
["sbom", sbomWorkspace]
|
|
38
40
|
]);
|
|
39
41
|
|
|
40
42
|
const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
@@ -112,6 +114,7 @@ Usage:
|
|
|
112
114
|
aienvmp handoff [--dir .] [--json] [--record --actor agent:id]
|
|
113
115
|
aienvmp checkpoint [--dir .] --actor agent:id --summary "what changed" [--target dependency] [--json]
|
|
114
116
|
aienvmp plan [--dir .] [--json] [--write]
|
|
117
|
+
aienvmp sbom [--dir .] [--json] [--write]
|
|
115
118
|
aienvmp schema [--json]
|
|
116
119
|
|
|
117
120
|
Common:
|
|
@@ -121,6 +124,7 @@ Common:
|
|
|
121
124
|
aienvmp handoff print the next-agent handoff summary
|
|
122
125
|
aienvmp checkpoint record, sync, status, and handoff after an env change
|
|
123
126
|
aienvmp plan print a read-only AI environment action plan
|
|
127
|
+
aienvmp sbom print/write standalone light SBOM artifact
|
|
124
128
|
aienvmp schema print the stable AI-readable output contract
|
|
125
129
|
aienvmp snippet print an AGENTS.md pointer snippet
|
|
126
130
|
aienvmp dash regenerate/open the lightweight dashboard
|
|
@@ -136,6 +140,7 @@ Advanced:
|
|
|
136
140
|
aienvmp compile [--dir .]
|
|
137
141
|
aienvmp diff [--dir .]
|
|
138
142
|
aienvmp doctor [--dir .] [--json] [--ci] [--strict security|policy|coordination|all]
|
|
143
|
+
aienvmp sbom [--dir .] [--json] [--write]
|
|
139
144
|
aienvmp dash [--dir .] [--open]
|
|
140
145
|
`);
|
|
141
146
|
}
|
package/src/commands/context.js
CHANGED
|
@@ -27,6 +27,7 @@ export async function contextWorkspace(args) {
|
|
|
27
27
|
status: warnings.length ? "review-required" : "clear",
|
|
28
28
|
preflight,
|
|
29
29
|
coordination: preflight.coordination,
|
|
30
|
+
sbomRisk: preflight.sbomRisk,
|
|
30
31
|
followUps: preflight.followUps,
|
|
31
32
|
decision,
|
|
32
33
|
enforcement: enforcementAdvice(warnings),
|
|
@@ -75,6 +76,7 @@ function lightSbomSummary(lightSbom = {}) {
|
|
|
75
76
|
guidance: "No lockfile policy detected."
|
|
76
77
|
},
|
|
77
78
|
dependencyChangeHints: (lightSbom.dependencyChangeHints || []).slice(0, 8),
|
|
79
|
+
riskSummary: lightSbom.riskSummary || {},
|
|
78
80
|
source: lightSbom.source || {},
|
|
79
81
|
confidence: lightSbom.confidence || {},
|
|
80
82
|
limitations: lightSbom.limitations || [],
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readJson, writeJson } from "../fsutil.js";
|
|
2
|
+
import { manifestPath, sbomJsonPath, workspaceDir } from "../paths.js";
|
|
3
|
+
|
|
4
|
+
export async function sbomWorkspace(args = {}) {
|
|
5
|
+
const dir = workspaceDir(args);
|
|
6
|
+
const manifest = await readJson(manifestPath(dir));
|
|
7
|
+
if (!manifest) throw new Error("missing manifest; run `aienvmp sync` first");
|
|
8
|
+
const sbom = buildSbomArtifact(manifest);
|
|
9
|
+
const artifact = args.write ? await writeSbomArtifact(dir, sbom) : "";
|
|
10
|
+
const output = artifact ? { ...sbom, artifact } : sbom;
|
|
11
|
+
if (args.json || args.write || args.quiet) {
|
|
12
|
+
if (args.json) console.log(JSON.stringify(output, null, 2));
|
|
13
|
+
} else {
|
|
14
|
+
console.log(`sbom: ${sbom.riskSummary.level}/${sbom.riskSummary.score}`);
|
|
15
|
+
console.log(`packages: ${sbom.summary.packages || 0}`);
|
|
16
|
+
console.log(`vulnerabilities: ${sbom.summary.vulnerabilities || 0}`);
|
|
17
|
+
console.log(`next: ${sbom.riskSummary.next}`);
|
|
18
|
+
}
|
|
19
|
+
return output;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function buildSbomArtifact(manifest = {}) {
|
|
23
|
+
const lightSbom = manifest.lightSbom || {};
|
|
24
|
+
return {
|
|
25
|
+
schemaVersion: 1,
|
|
26
|
+
schemaName: "aienvmp.light-sbom",
|
|
27
|
+
generatedAt: manifest.generatedAt || "",
|
|
28
|
+
workspace: manifest.workspace || {},
|
|
29
|
+
mode: lightSbom.mode || "light-sbom",
|
|
30
|
+
source: lightSbom.source || {},
|
|
31
|
+
confidence: lightSbom.confidence || {},
|
|
32
|
+
limitations: lightSbom.limitations || [],
|
|
33
|
+
summary: lightSbom.summary || { packages: 0, vulnerabilities: 0 },
|
|
34
|
+
riskSummary: lightSbom.riskSummary || { level: "clear", score: 0, signals: [], commands: [] },
|
|
35
|
+
topRisk: (lightSbom.topRisk || []).slice(0, 20),
|
|
36
|
+
packageManagerPolicy: lightSbom.packageManagerPolicy || {},
|
|
37
|
+
dependencyChangeHints: (lightSbom.dependencyChangeHints || []).slice(0, 20),
|
|
38
|
+
aiUse: {
|
|
39
|
+
purpose: "Standalone AI-readable light SBOM artifact.",
|
|
40
|
+
readBefore: "Dependency changes, vulnerability remediation, release review, or shared AI handoff.",
|
|
41
|
+
nextCommand: lightSbom.riskSummary?.commands?.[0] || "aienvmp context --json",
|
|
42
|
+
rule: "Use as a lightweight planning map; verify security claims with dedicated scanners."
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function writeSbomArtifact(dir, sbom) {
|
|
48
|
+
const out = sbomJsonPath(dir);
|
|
49
|
+
await writeJson(out, sbom);
|
|
50
|
+
return out;
|
|
51
|
+
}
|
package/src/commands/sync.js
CHANGED
|
@@ -3,6 +3,7 @@ import { scanWorkspace } from "./scan.js";
|
|
|
3
3
|
import { compileWorkspace } from "./compile.js";
|
|
4
4
|
import { dashWorkspace } from "./dash.js";
|
|
5
5
|
import { statusWorkspace } from "./status.js";
|
|
6
|
+
import { sbomWorkspace } from "./sbom.js";
|
|
6
7
|
|
|
7
8
|
export async function syncWorkspace(args) {
|
|
8
9
|
const quiet = args.quiet || args.json;
|
|
@@ -13,6 +14,7 @@ export async function syncWorkspace(args) {
|
|
|
13
14
|
const compiled = await compileWorkspace(next);
|
|
14
15
|
const dashboard = await dashWorkspace(next);
|
|
15
16
|
const status = await statusWorkspace({ ...next, json: false, write: true, quiet: true });
|
|
17
|
+
const sbom = await sbomWorkspace({ ...next, json: false, write: true, quiet: true });
|
|
16
18
|
|
|
17
19
|
const result = {
|
|
18
20
|
status: "ok",
|
|
@@ -21,6 +23,7 @@ export async function syncWorkspace(args) {
|
|
|
21
23
|
manifest: scanned.manifest,
|
|
22
24
|
timeline: scanned.timeline,
|
|
23
25
|
status: status.artifact,
|
|
26
|
+
sbom: sbom.artifact,
|
|
24
27
|
dashboard: dashboard.dashboard
|
|
25
28
|
},
|
|
26
29
|
changes: scanned.changes,
|
package/src/contract.js
CHANGED
|
@@ -4,7 +4,7 @@ export function preflightContract() {
|
|
|
4
4
|
version: 1,
|
|
5
5
|
stability: "additive",
|
|
6
6
|
requiredFields: ["schemaVersion", "state", "decision", "quickstart", "commands", "artifacts"],
|
|
7
|
-
aiEntryFields: ["state", "summary", "nextAgent", "coordination", "agentActivity", "followUps", "dependencyReadSet", "dependencyChangeProtocol"],
|
|
7
|
+
aiEntryFields: ["state", "summary", "nextAgent", "coordination", "agentActivity", "sbomRisk", "followUps", "dependencyReadSet", "dependencyChangeProtocol"],
|
|
8
8
|
rule: "Consumers should ignore unknown fields and treat missing optional fields as unavailable."
|
|
9
9
|
};
|
|
10
10
|
}
|
|
@@ -31,6 +31,11 @@ export function schemaContract() {
|
|
|
31
31
|
manifest: {
|
|
32
32
|
file: ".aienvmp/manifest.json",
|
|
33
33
|
rootFields: ["schemaVersion", "workspace", "runtimes", "packageManagers", "dependencySnapshot", "lightSbom", "security", "trust"]
|
|
34
|
+
},
|
|
35
|
+
sbom: {
|
|
36
|
+
file: ".aienvmp/sbom.json",
|
|
37
|
+
command: "aienvmp sbom --json",
|
|
38
|
+
rootFields: ["schemaVersion", "schemaName", "workspace", "summary", "riskSummary", "topRisk", "packageManagerPolicy", "dependencyChangeHints"]
|
|
34
39
|
}
|
|
35
40
|
},
|
|
36
41
|
compatibility: {
|
package/src/dependencies.js
CHANGED
|
@@ -108,6 +108,7 @@ export function buildLightSbom(snapshot = {}, security = {}) {
|
|
|
108
108
|
transitiveOrUnmatchedVulnerablePackages: transitiveOrUnmatched.length
|
|
109
109
|
},
|
|
110
110
|
topRisk,
|
|
111
|
+
riskSummary: lightSbomRiskSummary({ packages, security, directVulnerable, transitiveOrUnmatched, topRisk, lockfiles: snapshot.lockfiles || [] }),
|
|
111
112
|
packageManagerPolicy: packageManagerPolicy(snapshot.lockfiles || []),
|
|
112
113
|
dependencyChangeHints: dependencyChangeHints(packages, topRisk, snapshot.lockfiles || []),
|
|
113
114
|
aiUse: {
|
|
@@ -119,6 +120,74 @@ export function buildLightSbom(snapshot = {}, security = {}) {
|
|
|
119
120
|
};
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
export function lightSbomRiskSummary({ packages = [], security = {}, directVulnerable = [], transitiveOrUnmatched = [], topRisk = [], lockfiles = [] } = {}) {
|
|
124
|
+
const signals = [];
|
|
125
|
+
let score = 0;
|
|
126
|
+
const critical = Math.max(Number(security.summary?.critical || 0), topRisk.filter((pkg) => pkg.severity === "critical").length);
|
|
127
|
+
const high = Math.max(Number(security.summary?.high || 0), topRisk.filter((pkg) => pkg.severity === "high").length);
|
|
128
|
+
const total = Number(security.summary?.total || 0);
|
|
129
|
+
if (critical > 0) {
|
|
130
|
+
score += 45;
|
|
131
|
+
signals.push(`${critical} critical vulnerability finding(s)`);
|
|
132
|
+
}
|
|
133
|
+
if (high > 0) {
|
|
134
|
+
score += 30;
|
|
135
|
+
signals.push(`${high} high vulnerability finding(s)`);
|
|
136
|
+
}
|
|
137
|
+
if (directVulnerable.length) {
|
|
138
|
+
score += 15;
|
|
139
|
+
signals.push(`${directVulnerable.length} vulnerable direct dependency package(s)`);
|
|
140
|
+
}
|
|
141
|
+
if (transitiveOrUnmatched.length) {
|
|
142
|
+
score += 8;
|
|
143
|
+
signals.push(`${transitiveOrUnmatched.length} vulnerable transitive or unmatched package(s)`);
|
|
144
|
+
}
|
|
145
|
+
if (packageManagerPolicy(lockfiles).status === "review-required") {
|
|
146
|
+
score += 10;
|
|
147
|
+
signals.push("mixed package manager lockfiles");
|
|
148
|
+
}
|
|
149
|
+
if (packages.length && security.enabled !== true) {
|
|
150
|
+
score += 5;
|
|
151
|
+
signals.push("security scanner summary is off");
|
|
152
|
+
}
|
|
153
|
+
const level = score >= 90 ? "urgent" : score >= 60 ? "high" : score >= 30 ? "medium" : score > 0 ? "low" : "clear";
|
|
154
|
+
const reviewTargets = [
|
|
155
|
+
...new Set([
|
|
156
|
+
...topRisk.map((pkg) => pkg.manifest).filter(Boolean),
|
|
157
|
+
...topRisk.map((pkg) => pkg.name).filter(Boolean).slice(0, 5)
|
|
158
|
+
])
|
|
159
|
+
].slice(0, 8);
|
|
160
|
+
return {
|
|
161
|
+
level,
|
|
162
|
+
score,
|
|
163
|
+
signals,
|
|
164
|
+
scanner: security.enabled ? "enabled" : "off",
|
|
165
|
+
vulnerabilityCount: total,
|
|
166
|
+
directVulnerablePackages: directVulnerable.length,
|
|
167
|
+
reviewTargets,
|
|
168
|
+
next: riskNext(level, security.enabled, topRisk),
|
|
169
|
+
commands: riskCommands(level, security.enabled)
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function riskNext(level, securityEnabled, topRisk = []) {
|
|
174
|
+
if (!securityEnabled) return "Run read-only security scan before dependency or release decisions.";
|
|
175
|
+
if (level === "urgent" || level === "high") return "Review dependency read set and topRisk before remediation; do not auto-fix without user approval.";
|
|
176
|
+
if (topRisk.length) return "Review topRisk packages before dependency changes.";
|
|
177
|
+
return "No SBOM risk signal requires action in the lightweight snapshot.";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function riskCommands(level, securityEnabled) {
|
|
181
|
+
const commands = [];
|
|
182
|
+
if (!securityEnabled) commands.push("aienvmp sync --security");
|
|
183
|
+
if (["urgent", "high", "medium"].includes(level)) {
|
|
184
|
+
commands.push("aienvmp intent --actor agent:id --action dependency-review --target dependency");
|
|
185
|
+
commands.push("aienvmp plan --write");
|
|
186
|
+
}
|
|
187
|
+
commands.push("aienvmp checkpoint --actor agent:id --summary dependency-review --target dependency");
|
|
188
|
+
return commands;
|
|
189
|
+
}
|
|
190
|
+
|
|
122
191
|
function packageManagerPolicy(lockfiles = []) {
|
|
123
192
|
const byEcosystem = {};
|
|
124
193
|
for (const lockfile of lockfiles) {
|
|
@@ -196,8 +265,7 @@ function dependencyChangeHints(packages = [], topRisk = [], lockfiles = []) {
|
|
|
196
265
|
],
|
|
197
266
|
afterChange: [
|
|
198
267
|
"Run project tests or the narrowest relevant validation.",
|
|
199
|
-
"Run aienvmp
|
|
200
|
-
"Record the dependency change with aienvmp record."
|
|
268
|
+
"Run aienvmp checkpoint --actor agent:id --summary dependency-change --target dependency."
|
|
201
269
|
]
|
|
202
270
|
}));
|
|
203
271
|
}
|
package/src/paths.js
CHANGED
|
@@ -16,6 +16,10 @@ export function statusJsonPath(dir) {
|
|
|
16
16
|
return path.join(stateDir(dir), "status.json");
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export function sbomJsonPath(dir) {
|
|
20
|
+
return path.join(stateDir(dir), "sbom.json");
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
export function previousManifestPath(dir) {
|
|
20
24
|
return path.join(stateDir(dir), "manifest.previous.json");
|
|
21
25
|
}
|
package/src/preflight.js
CHANGED
|
@@ -16,6 +16,7 @@ export function buildPreflight(manifest = {}, warnings = [], intents = [], timel
|
|
|
16
16
|
const coordination = coordinationSummary(intents);
|
|
17
17
|
const followUps = pendingFollowUps(timeline);
|
|
18
18
|
const agentActivity = agentActivitySummary(timeline);
|
|
19
|
+
const sbomRisk = manifest.lightSbom?.riskSummary || {};
|
|
19
20
|
return {
|
|
20
21
|
schemaVersion: 1,
|
|
21
22
|
contract: preflightContract(),
|
|
@@ -57,6 +58,7 @@ export function buildPreflight(manifest = {}, warnings = [], intents = [], timel
|
|
|
57
58
|
nextAgent: nextAgentHint(state, dependencyReadSet, dependencyChangeProtocol),
|
|
58
59
|
coordination,
|
|
59
60
|
agentActivity,
|
|
61
|
+
sbomRisk,
|
|
60
62
|
followUps,
|
|
61
63
|
intentTargets,
|
|
62
64
|
dependencyReadSet,
|
|
@@ -351,6 +353,7 @@ export function preflightArtifacts() {
|
|
|
351
353
|
status: ".aienvmp/status.json",
|
|
352
354
|
envMap: "AIENV.md",
|
|
353
355
|
manifest: ".aienvmp/manifest.json",
|
|
356
|
+
sbom: ".aienvmp/sbom.json",
|
|
354
357
|
dashboard: ".aienvmp/dashboard.html",
|
|
355
358
|
planJson: ".aienvmp/plan.json",
|
|
356
359
|
planMarkdown: ".aienvmp/plan.md",
|
package/src/render.js
CHANGED
|
@@ -416,10 +416,12 @@ const lightSbom=manifest.lightSbom||{};
|
|
|
416
416
|
const lightSbomSummary=lightSbom.summary||{};
|
|
417
417
|
const pmPolicy=lightSbom.packageManagerPolicy||{};
|
|
418
418
|
const topRisk=lightSbom.topRisk||[];
|
|
419
|
+
const riskSummary=lightSbom.riskSummary||{};
|
|
419
420
|
const dependencyHints=lightSbom.dependencyChangeHints||[];
|
|
420
421
|
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>';
|
|
421
422
|
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>';
|
|
422
|
-
const
|
|
423
|
+
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>';
|
|
424
|
+
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}\`;
|
|
423
425
|
const sec=manifest.security||{};
|
|
424
426
|
const secSummary=sec.summary||{total:0,critical:0,high:0,moderate:0,low:0,info:0};
|
|
425
427
|
const secPackages=sec.topPackages||[];
|
|
@@ -440,6 +442,7 @@ const actions=manifest.recommendedActions||[];
|
|
|
440
442
|
const actionsHtml=actions.length?'<div class="timeline">'+actions.slice(0,6).map(a=>\`<div class="event"><time>\${esc(a.priority)}</time><div><b>\${esc(a.category)}</b> \${esc(a.summary)}\${a.command?\`<div class="path">\${esc(a.command)}</div>\`:''}</div></div>\`).join('')+'</div>':'<div class="okline">No recommended actions. Continue project-local work.</div>';
|
|
441
443
|
const plan=manifest.planArtifacts||{};
|
|
442
444
|
const planHtml=plan.markdown||plan.json?\`<table><tr><th>Markdown</th><td>\${plan.markdown?'<a href="plan.md">plan.md</a>':'not written'}</td></tr><tr><th>JSON</th><td>\${plan.json?'<a href="plan.json">plan.json</a>':'not written'}</td></tr></table>\`:'<div class="okline">No plan artifacts yet. Run <code>aienvmp plan --write</code>.</div>';
|
|
445
|
+
const sbomArtifactHtml='<table><tr><th>JSON</th><td><a href="sbom.json">sbom.json</a></td></tr><tr><th>Command</th><td><code>aienvmp sbom --write</code></td></tr></table>';
|
|
443
446
|
const remediation=manifest.planRemediation||[];
|
|
444
447
|
const remediationFix=r=>r.fixVersions?.length?\`fix \${r.fixVersions.join(', ')}\`:(r.fixAvailable?'fix available':'review required');
|
|
445
448
|
const remediationRefs=r=>r.advisories?.length?\` - \${r.advisories.join(', ')}\`:'';
|
|
@@ -528,6 +531,8 @@ document.getElementById('app').innerHTML=\`
|
|
|
528
531
|
<div style="height:14px"></div>
|
|
529
532
|
\${card('AI Plan Artifacts',plan.markdown||plan.json?'<span class="pill">written</span>':'<span class="pill off">not written</span>',planHtml)}
|
|
530
533
|
<div style="height:14px"></div>
|
|
534
|
+
\${card('Light SBOM Artifact','<span class="pill">json</span>',sbomArtifactHtml)}
|
|
535
|
+
<div style="height:14px"></div>
|
|
531
536
|
\${card('Remediation Steps',remediation.length?'<span class="pill warn">'+remediation.length+' items</span>':'<span class="pill off">none</span>',remediationHtml)}
|
|
532
537
|
<div style="height:14px"></div>
|
|
533
538
|
\${card('Environment Steps',envSteps.length?'<span class="pill warn">'+envSteps.length+' items</span>':'<span class="pill off">none</span>',envStepsHtml)}
|
|
@@ -633,6 +638,10 @@ function lightSbomLines(lightSbom = {}) {
|
|
|
633
638
|
lines.push(`- Source: ${lightSbom.source?.dependencies || "project manifests"}; vulnerabilities: ${lightSbom.source?.vulnerabilities || "not scanned"}`);
|
|
634
639
|
lines.push(`- Confidence: direct ${lightSbom.confidence?.directDependencies || "unknown"}; transitive ${lightSbom.confidence?.transitiveDependencies || "unknown"}`);
|
|
635
640
|
}
|
|
641
|
+
if (lightSbom.riskSummary) {
|
|
642
|
+
lines.push(`- Risk summary: ${lightSbom.riskSummary.level || "clear"}/${lightSbom.riskSummary.score || 0}; ${lightSbom.riskSummary.next || "no action"}`);
|
|
643
|
+
if (lightSbom.riskSummary.signals?.length) lines.push(`- Risk signals: ${lightSbom.riskSummary.signals.join("; ")}`);
|
|
644
|
+
}
|
|
636
645
|
if (lightSbom.packageManagerPolicy) {
|
|
637
646
|
lines.push(`- Package manager policy: ${lightSbom.packageManagerPolicy.status}`);
|
|
638
647
|
lines.push(`- Package manager guidance: ${lightSbom.packageManagerPolicy.guidance}`);
|