aienvmp 0.1.47 → 0.1.48
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 +6 -0
- package/CHANGELOG.md +10 -0
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/commands/sbom.js +96 -4
- package/src/commands/sync.js +2 -0
- package/src/contract.js +5 -0
- package/src/paths.js +4 -0
- package/src/preflight.js +1 -0
- package/src/render.js +1 -1
package/BUGFIXES.md
CHANGED
|
@@ -70,6 +70,12 @@ Short record of bugs, fixes, and follow-up checks.
|
|
|
70
70
|
- Fix: `aienvmp sbom --json` and `.aienvmp/sbom.json` now expose a standalone light SBOM artifact.
|
|
71
71
|
- Verification: tests cover standalone SBOM construction, writing, sync output, schema metadata, and dashboard linking.
|
|
72
72
|
|
|
73
|
+
### Light SBOM needed a lightweight standard export
|
|
74
|
+
|
|
75
|
+
- Issue: the standalone SBOM was AI-friendly, but less convenient for tools expecting CycloneDX-shaped data.
|
|
76
|
+
- Fix: `aienvmp sbom --format cyclonedx-lite` and `.aienvmp/sbom.cdx.json` now export project-manifest packages in a CycloneDX-compatible shape with explicit limitations.
|
|
77
|
+
- Verification: tests cover component mapping, vulnerability hints, sync output, schema metadata, and dashboard links.
|
|
78
|
+
|
|
73
79
|
## Template
|
|
74
80
|
|
|
75
81
|
### Title
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.48
|
|
4
|
+
|
|
5
|
+
- Added `aienvmp sbom --format cyclonedx-lite` for a lightweight CycloneDX-compatible export.
|
|
6
|
+
- Added `.aienvmp/sbom.cdx.json` writing through the default `sync` flow.
|
|
7
|
+
- Added CycloneDX-lite artifact paths to status/preflight outputs and schema metadata.
|
|
8
|
+
- Added dashboard links for `sbom.cdx.json` beside the native light SBOM artifact.
|
|
9
|
+
- Added README guidance for the CycloneDX-lite command and output.
|
|
10
|
+
- Included explicit metadata limitations so consumers know no install or dependency resolver was run.
|
|
11
|
+
- Added regression tests for CycloneDX-lite component mapping, vulnerability hints, sync output, schema contract, and dashboard rendering.
|
|
12
|
+
|
|
3
13
|
## 0.1.47
|
|
4
14
|
|
|
5
15
|
- Added `aienvmp sbom` for standalone AI-readable light SBOM output without deep manifest parsing.
|
package/README.md
CHANGED
|
@@ -36,6 +36,7 @@ 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
38
|
.aienvmp/sbom.json # standalone AI-readable light SBOM
|
|
39
|
+
.aienvmp/sbom.cdx.json # CycloneDX-lite export from project manifests
|
|
39
40
|
.aienvmp/intents.jsonl # planned env changes
|
|
40
41
|
.aienvmp/timeline.jsonl # append-only change ledger
|
|
41
42
|
.aienvmp/plan.md # read-only action plan
|
|
@@ -74,6 +75,7 @@ aienvmp sync # update env map, status, ledger, dashboard
|
|
|
74
75
|
aienvmp status --write # refresh compact AI status
|
|
75
76
|
aienvmp context --json # AI decision contract
|
|
76
77
|
aienvmp sbom --json # standalone light SBOM
|
|
78
|
+
aienvmp sbom --format cyclonedx-lite --json
|
|
77
79
|
aienvmp schema --json # stable output contract for AI/CI consumers
|
|
78
80
|
aienvmp plan --write # read-only action plan
|
|
79
81
|
aienvmp handoff --record # next-agent summary
|
package/package.json
CHANGED
package/src/commands/sbom.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { readJson, writeJson } from "../fsutil.js";
|
|
2
|
-
import { manifestPath, sbomJsonPath, workspaceDir } from "../paths.js";
|
|
2
|
+
import { cyclonedxSbomPath, manifestPath, sbomJsonPath, workspaceDir } from "../paths.js";
|
|
3
3
|
|
|
4
4
|
export async function sbomWorkspace(args = {}) {
|
|
5
5
|
const dir = workspaceDir(args);
|
|
6
6
|
const manifest = await readJson(manifestPath(dir));
|
|
7
7
|
if (!manifest) throw new Error("missing manifest; run `aienvmp sync` first");
|
|
8
|
-
const
|
|
9
|
-
const
|
|
8
|
+
const format = normalizeFormat(args.format);
|
|
9
|
+
const sbom = format === "cyclonedx-lite" ? buildCycloneDxLite(manifest) : buildSbomArtifact(manifest);
|
|
10
|
+
const artifact = args.write ? await writeSbomArtifact(dir, sbom, format) : "";
|
|
10
11
|
const output = artifact ? { ...sbom, artifact } : sbom;
|
|
11
12
|
if (args.json || args.write || args.quiet) {
|
|
12
13
|
if (args.json) console.log(JSON.stringify(output, null, 2));
|
|
@@ -45,7 +46,98 @@ export function buildSbomArtifact(manifest = {}) {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
export async function writeSbomArtifact(dir, sbom) {
|
|
48
|
-
const out = sbomJsonPath(dir);
|
|
49
|
+
const out = sbom.bomFormat === "CycloneDX" ? cyclonedxSbomPath(dir) : sbomJsonPath(dir);
|
|
49
50
|
await writeJson(out, sbom);
|
|
50
51
|
return out;
|
|
51
52
|
}
|
|
53
|
+
|
|
54
|
+
export function buildCycloneDxLite(manifest = {}) {
|
|
55
|
+
const snapshot = manifest.dependencySnapshot || {};
|
|
56
|
+
const packages = snapshot.packages || [];
|
|
57
|
+
const lightSbom = manifest.lightSbom || {};
|
|
58
|
+
return {
|
|
59
|
+
bomFormat: "CycloneDX",
|
|
60
|
+
specVersion: "1.6",
|
|
61
|
+
serialNumber: `urn:uuid:aienvmp-${hashText(`${manifest.workspace?.path || ""}:${manifest.generatedAt || ""}`)}`,
|
|
62
|
+
version: 1,
|
|
63
|
+
metadata: {
|
|
64
|
+
timestamp: manifest.generatedAt || "",
|
|
65
|
+
tools: {
|
|
66
|
+
components: [{
|
|
67
|
+
type: "application",
|
|
68
|
+
name: "aienvmp",
|
|
69
|
+
version: manifest.generatedBy?.version || "unknown"
|
|
70
|
+
}]
|
|
71
|
+
},
|
|
72
|
+
component: {
|
|
73
|
+
type: "application",
|
|
74
|
+
name: manifest.workspace?.name || "workspace",
|
|
75
|
+
bomRef: "workspace"
|
|
76
|
+
},
|
|
77
|
+
properties: [
|
|
78
|
+
{ name: "aienvmp:format", value: "cyclonedx-lite" },
|
|
79
|
+
{ name: "aienvmp:source", value: lightSbom.source?.dependencies || "project manifests" },
|
|
80
|
+
{ name: "aienvmp:confidence:transitiveDependencies", value: lightSbom.confidence?.transitiveDependencies || "not-resolved" },
|
|
81
|
+
{ name: "aienvmp:risk:level", value: lightSbom.riskSummary?.level || "clear" },
|
|
82
|
+
{ name: "aienvmp:risk:score", value: String(lightSbom.riskSummary?.score || 0) }
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
components: packages.slice(0, 200).map(cycloneComponent),
|
|
86
|
+
vulnerabilities: (lightSbom.topRisk || []).slice(0, 50).map(cycloneVulnerability),
|
|
87
|
+
properties: [
|
|
88
|
+
{ name: "aienvmp:limitation", value: "Light SBOM from project manifests only; no install or dependency resolver was run." },
|
|
89
|
+
{ name: "aienvmp:verifyWith", value: "CycloneDX, Syft, Trivy, npm audit, pip-audit, or another dedicated scanner before security claims." }
|
|
90
|
+
]
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function cycloneComponent(pkg = {}) {
|
|
95
|
+
const version = String(pkg.version || "unspecified");
|
|
96
|
+
return {
|
|
97
|
+
type: "library",
|
|
98
|
+
name: pkg.name || "unknown",
|
|
99
|
+
version,
|
|
100
|
+
purl: packageUrl(pkg, version),
|
|
101
|
+
bomRef: `${pkg.ecosystem || "pkg"}:${pkg.name || "unknown"}@${version}`,
|
|
102
|
+
properties: [
|
|
103
|
+
{ name: "aienvmp:ecosystem", value: pkg.ecosystem || "unknown" },
|
|
104
|
+
{ name: "aienvmp:manager", value: pkg.manager || "unknown" },
|
|
105
|
+
{ name: "aienvmp:manifest", value: pkg.manifest || "" },
|
|
106
|
+
{ name: "aienvmp:group", value: pkg.group || "" }
|
|
107
|
+
]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function cycloneVulnerability(pkg = {}) {
|
|
112
|
+
return {
|
|
113
|
+
id: pkg.name || "unknown",
|
|
114
|
+
source: { name: "aienvmp-light-sbom" },
|
|
115
|
+
ratings: [{ severity: pkg.severity || "unknown" }],
|
|
116
|
+
affects: [{
|
|
117
|
+
ref: `${pkg.ecosystem || "pkg"}:${pkg.name || "unknown"}@${pkg.version || "unspecified"}`
|
|
118
|
+
}],
|
|
119
|
+
properties: [
|
|
120
|
+
{ name: "aienvmp:priority", value: pkg.priority || "low" },
|
|
121
|
+
{ name: "aienvmp:score", value: String(pkg.score || 0) },
|
|
122
|
+
{ name: "aienvmp:directDependency", value: String(pkg.directDependency === true) },
|
|
123
|
+
{ name: "aienvmp:manifest", value: pkg.manifest || "" }
|
|
124
|
+
]
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function packageUrl(pkg = {}, version = "") {
|
|
129
|
+
const type = pkg.ecosystem === "python" ? "pypi" : "npm";
|
|
130
|
+
return `pkg:${type}/${encodeURIComponent(pkg.name || "unknown")}@${encodeURIComponent(version)}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function normalizeFormat(format = "") {
|
|
134
|
+
const value = String(format || "aienvmp").toLowerCase();
|
|
135
|
+
if (["cyclonedx", "cyclonedx-lite", "cdx"].includes(value)) return "cyclonedx-lite";
|
|
136
|
+
return "aienvmp";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function hashText(text = "") {
|
|
140
|
+
let hash = 0;
|
|
141
|
+
for (const ch of String(text)) hash = ((hash << 5) - hash + ch.charCodeAt(0)) >>> 0;
|
|
142
|
+
return `${hash.toString(16).padStart(8, "0")}-0000-4000-8000-000000000000`;
|
|
143
|
+
}
|
package/src/commands/sync.js
CHANGED
|
@@ -15,6 +15,7 @@ export async function syncWorkspace(args) {
|
|
|
15
15
|
const dashboard = await dashWorkspace(next);
|
|
16
16
|
const status = await statusWorkspace({ ...next, json: false, write: true, quiet: true });
|
|
17
17
|
const sbom = await sbomWorkspace({ ...next, json: false, write: true, quiet: true });
|
|
18
|
+
const cyclonedx = await sbomWorkspace({ ...next, json: false, write: true, quiet: true, format: "cyclonedx-lite" });
|
|
18
19
|
|
|
19
20
|
const result = {
|
|
20
21
|
status: "ok",
|
|
@@ -24,6 +25,7 @@ export async function syncWorkspace(args) {
|
|
|
24
25
|
timeline: scanned.timeline,
|
|
25
26
|
status: status.artifact,
|
|
26
27
|
sbom: sbom.artifact,
|
|
28
|
+
cyclonedx: cyclonedx.artifact,
|
|
27
29
|
dashboard: dashboard.dashboard
|
|
28
30
|
},
|
|
29
31
|
changes: scanned.changes,
|
package/src/contract.js
CHANGED
|
@@ -36,6 +36,11 @@ export function schemaContract() {
|
|
|
36
36
|
file: ".aienvmp/sbom.json",
|
|
37
37
|
command: "aienvmp sbom --json",
|
|
38
38
|
rootFields: ["schemaVersion", "schemaName", "workspace", "summary", "riskSummary", "topRisk", "packageManagerPolicy", "dependencyChangeHints"]
|
|
39
|
+
},
|
|
40
|
+
cyclonedxLite: {
|
|
41
|
+
file: ".aienvmp/sbom.cdx.json",
|
|
42
|
+
command: "aienvmp sbom --format cyclonedx-lite --json",
|
|
43
|
+
rootFields: ["bomFormat", "specVersion", "metadata", "components", "vulnerabilities", "properties"]
|
|
39
44
|
}
|
|
40
45
|
},
|
|
41
46
|
compatibility: {
|
package/src/paths.js
CHANGED
|
@@ -20,6 +20,10 @@ export function sbomJsonPath(dir) {
|
|
|
20
20
|
return path.join(stateDir(dir), "sbom.json");
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
export function cyclonedxSbomPath(dir) {
|
|
24
|
+
return path.join(stateDir(dir), "sbom.cdx.json");
|
|
25
|
+
}
|
|
26
|
+
|
|
23
27
|
export function previousManifestPath(dir) {
|
|
24
28
|
return path.join(stateDir(dir), "manifest.previous.json");
|
|
25
29
|
}
|
package/src/preflight.js
CHANGED
|
@@ -354,6 +354,7 @@ export function preflightArtifacts() {
|
|
|
354
354
|
envMap: "AIENV.md",
|
|
355
355
|
manifest: ".aienvmp/manifest.json",
|
|
356
356
|
sbom: ".aienvmp/sbom.json",
|
|
357
|
+
cyclonedx: ".aienvmp/sbom.cdx.json",
|
|
357
358
|
dashboard: ".aienvmp/dashboard.html",
|
|
358
359
|
planJson: ".aienvmp/plan.json",
|
|
359
360
|
planMarkdown: ".aienvmp/plan.md",
|
package/src/render.js
CHANGED
|
@@ -442,7 +442,7 @@ const actions=manifest.recommendedActions||[];
|
|
|
442
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>';
|
|
443
443
|
const plan=manifest.planArtifacts||{};
|
|
444
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>';
|
|
445
|
+
const sbomArtifactHtml='<table><tr><th>JSON</th><td><a href="sbom.json">sbom.json</a></td></tr><tr><th>CDX Lite</th><td><a href="sbom.cdx.json">sbom.cdx.json</a></td></tr><tr><th>Command</th><td><code>aienvmp sbom --write</code></td></tr></table>';
|
|
446
446
|
const remediation=manifest.planRemediation||[];
|
|
447
447
|
const remediationFix=r=>r.fixVersions?.length?\`fix \${r.fixVersions.join(', ')}\`:(r.fixAvailable?'fix available':'review required');
|
|
448
448
|
const remediationRefs=r=>r.advisories?.length?\` - \${r.advisories.join(', ')}\`:'';
|