aienvmp 0.1.46 → 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 CHANGED
@@ -64,6 +64,12 @@ Short record of bugs, fixes, and follow-up checks.
64
64
  - Fix: `lightSbom.riskSummary` and preflight `sbomRisk` now provide a compact risk score, signals, review targets, and advisory next steps.
65
65
  - Verification: tests cover risk scoring, top-risk severity fallback, scanner-off guidance, status/context exposure, dashboard rendering, and recommended actions.
66
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
+
67
73
  ## Template
68
74
 
69
75
  ### Title
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
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
+
3
13
  ## 0.1.46
4
14
 
5
15
  - Added `lightSbom.riskSummary` with compact risk level, score, scanner state, signals, review targets, and next commands.
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
@@ -72,6 +73,7 @@ npx aienvmp snippet gemini
72
73
  aienvmp sync # update env map, status, ledger, dashboard
73
74
  aienvmp status --write # refresh compact AI status
74
75
  aienvmp context --json # AI decision contract
76
+ aienvmp sbom --json # standalone light SBOM
75
77
  aienvmp schema --json # stable output contract for AI/CI consumers
76
78
  aienvmp plan --write # read-only action plan
77
79
  aienvmp handoff --record # next-agent summary
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aienvmp",
3
- "version": "0.1.46",
3
+ "version": "0.1.47",
4
4
  "description": "AI-first environment maps and lightweight runtime SBOMs for shared development machines.",
5
5
  "type": "module",
6
6
  "bin": {
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
  }
@@ -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
+ }
@@ -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
@@ -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/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
@@ -353,6 +353,7 @@ export function preflightArtifacts() {
353
353
  status: ".aienvmp/status.json",
354
354
  envMap: "AIENV.md",
355
355
  manifest: ".aienvmp/manifest.json",
356
+ sbom: ".aienvmp/sbom.json",
356
357
  dashboard: ".aienvmp/dashboard.html",
357
358
  planJson: ".aienvmp/plan.json",
358
359
  planMarkdown: ".aienvmp/plan.md",
package/src/render.js CHANGED
@@ -442,6 +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
446
  const remediation=manifest.planRemediation||[];
446
447
  const remediationFix=r=>r.fixVersions?.length?\`fix \${r.fixVersions.join(', ')}\`:(r.fixAvailable?'fix available':'review required');
447
448
  const remediationRefs=r=>r.advisories?.length?\` - \${r.advisories.join(', ')}\`:'';
@@ -530,6 +531,8 @@ document.getElementById('app').innerHTML=\`
530
531
  <div style="height:14px"></div>
531
532
  \${card('AI Plan Artifacts',plan.markdown||plan.json?'<span class="pill">written</span>':'<span class="pill off">not written</span>',planHtml)}
532
533
  <div style="height:14px"></div>
534
+ \${card('Light SBOM Artifact','<span class="pill">json</span>',sbomArtifactHtml)}
535
+ <div style="height:14px"></div>
533
536
  \${card('Remediation Steps',remediation.length?'<span class="pill warn">'+remediation.length+' items</span>':'<span class="pill off">none</span>',remediationHtml)}
534
537
  <div style="height:14px"></div>
535
538
  \${card('Environment Steps',envSteps.length?'<span class="pill warn">'+envSteps.length+' items</span>':'<span class="pill off">none</span>',envStepsHtml)}