aienvmp 0.1.47 → 0.1.49
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 +4 -1
- package/action.yml +12 -0
- package/examples/github-action.yml +3 -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,18 @@ 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
|
+
|
|
79
|
+
### CI example did not surface SBOM artifacts clearly
|
|
80
|
+
|
|
81
|
+
- Issue: sync could generate SBOM artifacts, but the GitHub Action and example upload list did not make them explicit.
|
|
82
|
+
- Fix: the Action now has `write-sbom` and the example uploads both native and CycloneDX-lite SBOM artifacts.
|
|
83
|
+
- Verification: Action tests cover input metadata, SBOM write commands, and example artifact paths.
|
|
84
|
+
|
|
73
85
|
## Template
|
|
74
86
|
|
|
75
87
|
### Title
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.49
|
|
4
|
+
|
|
5
|
+
- Added a `write-sbom` GitHub Action input for explicit SBOM artifact generation.
|
|
6
|
+
- Added Action steps for writing `.aienvmp/sbom.json` and `.aienvmp/sbom.cdx.json`.
|
|
7
|
+
- Updated the GitHub Action example to enable SBOM writing.
|
|
8
|
+
- Added native light SBOM artifact upload paths to the example workflow.
|
|
9
|
+
- Added CycloneDX-lite artifact upload paths to the example workflow.
|
|
10
|
+
- Updated README CI usage to mention SBOM artifacts.
|
|
11
|
+
- Added regression coverage for Action SBOM inputs, commands, and example upload paths.
|
|
12
|
+
|
|
13
|
+
## 0.1.48
|
|
14
|
+
|
|
15
|
+
- Added `aienvmp sbom --format cyclonedx-lite` for a lightweight CycloneDX-compatible export.
|
|
16
|
+
- Added `.aienvmp/sbom.cdx.json` writing through the default `sync` flow.
|
|
17
|
+
- Added CycloneDX-lite artifact paths to status/preflight outputs and schema metadata.
|
|
18
|
+
- Added dashboard links for `sbom.cdx.json` beside the native light SBOM artifact.
|
|
19
|
+
- Added README guidance for the CycloneDX-lite command and output.
|
|
20
|
+
- Included explicit metadata limitations so consumers know no install or dependency resolver was run.
|
|
21
|
+
- Added regression tests for CycloneDX-lite component mapping, vulnerability hints, sync output, schema contract, and dashboard rendering.
|
|
22
|
+
|
|
3
23
|
## 0.1.47
|
|
4
24
|
|
|
5
25
|
- 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
|
|
@@ -85,13 +87,14 @@ aienvmp doctor --strict security|policy|coordination|all
|
|
|
85
87
|
|
|
86
88
|
## CI
|
|
87
89
|
|
|
88
|
-
The GitHub Action writes status, schema, doctor, plan, and dashboard artifacts. `strict: "off"` reports warnings without failing the job.
|
|
90
|
+
The GitHub Action writes status, schema, doctor, plan, SBOM, and dashboard artifacts. `strict: "off"` reports warnings without failing the job.
|
|
89
91
|
|
|
90
92
|
```yaml
|
|
91
93
|
- uses: soovwv/aienvmp@main
|
|
92
94
|
with:
|
|
93
95
|
write-status: "true"
|
|
94
96
|
write-plan: "true"
|
|
97
|
+
write-sbom: "true"
|
|
95
98
|
strict: "off"
|
|
96
99
|
```
|
|
97
100
|
|
package/action.yml
CHANGED
|
@@ -27,6 +27,10 @@ inputs:
|
|
|
27
27
|
description: Write doctor JSON with advisory/strict exit behavior
|
|
28
28
|
required: false
|
|
29
29
|
default: "true"
|
|
30
|
+
write-sbom:
|
|
31
|
+
description: Write standalone light SBOM and CycloneDX-lite artifacts
|
|
32
|
+
required: false
|
|
33
|
+
default: "true"
|
|
30
34
|
|
|
31
35
|
runs:
|
|
32
36
|
using: composite
|
|
@@ -65,6 +69,14 @@ runs:
|
|
|
65
69
|
node "$GITHUB_ACTION_PATH/bin/aienvmp.js" doctor --dir "${{ inputs.directory }}" --json > "${{ inputs.directory }}/.aienvmp/doctor.json"
|
|
66
70
|
fi
|
|
67
71
|
|
|
72
|
+
- name: Write SBOM artifacts
|
|
73
|
+
shell: bash
|
|
74
|
+
run: |
|
|
75
|
+
if [ "${{ inputs.write-sbom }}" = "true" ]; then
|
|
76
|
+
node "$GITHUB_ACTION_PATH/bin/aienvmp.js" sbom --dir "${{ inputs.directory }}" --write --quiet
|
|
77
|
+
node "$GITHUB_ACTION_PATH/bin/aienvmp.js" sbom --dir "${{ inputs.directory }}" --format cyclonedx-lite --write --quiet
|
|
78
|
+
fi
|
|
79
|
+
|
|
68
80
|
- name: Doctor
|
|
69
81
|
shell: bash
|
|
70
82
|
run: |
|
|
@@ -16,6 +16,7 @@ jobs:
|
|
|
16
16
|
write-plan: "true"
|
|
17
17
|
write-schema: "true"
|
|
18
18
|
write-doctor-json: "true"
|
|
19
|
+
write-sbom: "true"
|
|
19
20
|
strict: "off" # security, policy, coordination, all, or off
|
|
20
21
|
|
|
21
22
|
- uses: actions/upload-artifact@v4
|
|
@@ -26,6 +27,8 @@ jobs:
|
|
|
26
27
|
AIENV.md
|
|
27
28
|
.aienvmp/manifest.json
|
|
28
29
|
.aienvmp/status.json
|
|
30
|
+
.aienvmp/sbom.json
|
|
31
|
+
.aienvmp/sbom.cdx.json
|
|
29
32
|
.aienvmp/schema.json
|
|
30
33
|
.aienvmp/doctor.json
|
|
31
34
|
.aienvmp/plan.json
|
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(', ')}\`:'';
|