aienvmp 0.1.48 → 0.1.50

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
@@ -76,6 +76,24 @@ Short record of bugs, fixes, and follow-up checks.
76
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
77
  - Verification: tests cover component mapping, vulnerability hints, sync output, schema metadata, and dashboard links.
78
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
+
85
+ ### CI and AI review still needed a short handoff view
86
+
87
+ - Issue: `status.json` was AI-friendly, but GitHub Actions and quick human review still required opening JSON artifacts or the dashboard.
88
+ - Fix: `aienvmp summary` now writes `.aienvmp/summary.md`, `sync` creates it by default, and the GitHub Action appends it to `GITHUB_STEP_SUMMARY`.
89
+ - Verification: tests cover summary rendering, sync artifact creation, schema metadata, Action wiring, and example upload paths.
90
+
91
+ ### Windows-created JSON files could be missed
92
+
93
+ - Issue: UTF-8 BOM JSON files created by some Windows tools could fail JSON parsing and make dependency snapshots look empty.
94
+ - Fix: `readJson` now strips a leading UTF-8 BOM before parsing.
95
+ - Verification: regression tests cover BOM JSON parsing and Windows smoke testing confirmed dependency counts in `summary.md`.
96
+
79
97
  ## Template
80
98
 
81
99
  ### Title
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.50
4
+
5
+ - Added `aienvmp summary` for a compact Markdown AI/CI handoff view.
6
+ - Added `.aienvmp/summary.md` writing through the default `sync` flow.
7
+ - Added `summary` to preflight artifacts and the stable output contract.
8
+ - Added a GitHub Action `write-summary` input.
9
+ - Appended `.aienvmp/summary.md` to GitHub Step Summary when the Action runs in GitHub Actions.
10
+ - Updated the GitHub Action example and README to include the summary artifact.
11
+ - Fixed UTF-8 BOM JSON parsing so Windows-created `package.json` files are scanned correctly.
12
+ - Added regression tests for summary rendering, sync output, schema metadata, Action Step Summary wiring, and BOM JSON parsing.
13
+
14
+ ## 0.1.49
15
+
16
+ - Added a `write-sbom` GitHub Action input for explicit SBOM artifact generation.
17
+ - Added Action steps for writing `.aienvmp/sbom.json` and `.aienvmp/sbom.cdx.json`.
18
+ - Updated the GitHub Action example to enable SBOM writing.
19
+ - Added native light SBOM artifact upload paths to the example workflow.
20
+ - Added CycloneDX-lite artifact upload paths to the example workflow.
21
+ - Updated README CI usage to mention SBOM artifacts.
22
+ - Added regression coverage for Action SBOM inputs, commands, and example upload paths.
23
+
3
24
  ## 0.1.48
4
25
 
5
26
  - Added `aienvmp sbom --format cyclonedx-lite` for a lightweight CycloneDX-compatible export.
package/README.md CHANGED
@@ -34,6 +34,7 @@ Warnings are advisory by default. Use `doctor --strict <scope>` only when you wa
34
34
  ```text
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
+ .aienvmp/summary.md # compact AI/CI summary
37
38
  .aienvmp/manifest.json # runtime map + light SBOM
38
39
  .aienvmp/sbom.json # standalone AI-readable light SBOM
39
40
  .aienvmp/sbom.cdx.json # CycloneDX-lite export from project manifests
@@ -47,6 +48,7 @@ AIENV.md # Markdown env map for AI agents
47
48
 
48
49
  - `status`, `context`, `plan`, and `handoff` share one preflight contract.
49
50
  - `schema --json` prints the stable AI-readable output contract without scanning.
51
+ - `summary.md` is the short CI/AI handoff view.
50
52
  - `status.json.nextAgent` tells the next AI what to read and whether to review first.
51
53
  - `dependencyReadSet` lists manifests and lockfiles before package or security changes.
52
54
  - `sbomRisk` gives AI a compact light-SBOM risk level, signals, and next command.
@@ -71,8 +73,9 @@ npx aienvmp snippet gemini
71
73
  ## Commands
72
74
 
73
75
  ```bash
74
- aienvmp sync # update env map, status, ledger, dashboard
76
+ aienvmp sync # update env map, status, summary, SBOM, dashboard
75
77
  aienvmp status --write # refresh compact AI status
78
+ aienvmp summary --write # refresh compact Markdown summary
76
79
  aienvmp context --json # AI decision contract
77
80
  aienvmp sbom --json # standalone light SBOM
78
81
  aienvmp sbom --format cyclonedx-lite --json
@@ -87,13 +90,15 @@ aienvmp doctor --strict security|policy|coordination|all
87
90
 
88
91
  ## CI
89
92
 
90
- The GitHub Action writes status, schema, doctor, plan, and dashboard artifacts. `strict: "off"` reports warnings without failing the job.
93
+ The GitHub Action writes status, summary, schema, doctor, plan, SBOM, and dashboard artifacts. `strict: "off"` reports warnings without failing the job.
91
94
 
92
95
  ```yaml
93
96
  - uses: soovwv/aienvmp@main
94
97
  with:
95
98
  write-status: "true"
96
99
  write-plan: "true"
100
+ write-sbom: "true"
101
+ write-summary: "true"
97
102
  strict: "off"
98
103
  ```
99
104
 
package/action.yml CHANGED
@@ -27,6 +27,14 @@ 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"
34
+ write-summary:
35
+ description: Write compact Markdown summary and append it to GitHub Step Summary
36
+ required: false
37
+ default: "true"
30
38
 
31
39
  runs:
32
40
  using: composite
@@ -65,6 +73,24 @@ runs:
65
73
  node "$GITHUB_ACTION_PATH/bin/aienvmp.js" doctor --dir "${{ inputs.directory }}" --json > "${{ inputs.directory }}/.aienvmp/doctor.json"
66
74
  fi
67
75
 
76
+ - name: Write SBOM artifacts
77
+ shell: bash
78
+ run: |
79
+ if [ "${{ inputs.write-sbom }}" = "true" ]; then
80
+ node "$GITHUB_ACTION_PATH/bin/aienvmp.js" sbom --dir "${{ inputs.directory }}" --write --quiet
81
+ node "$GITHUB_ACTION_PATH/bin/aienvmp.js" sbom --dir "${{ inputs.directory }}" --format cyclonedx-lite --write --quiet
82
+ fi
83
+
84
+ - name: Write AI summary
85
+ shell: bash
86
+ run: |
87
+ if [ "${{ inputs.write-summary }}" = "true" ]; then
88
+ node "$GITHUB_ACTION_PATH/bin/aienvmp.js" summary --dir "${{ inputs.directory }}" --write --quiet
89
+ if [ -n "$GITHUB_STEP_SUMMARY" ]; then
90
+ cat "${{ inputs.directory }}/.aienvmp/summary.md" >> "$GITHUB_STEP_SUMMARY"
91
+ fi
92
+ fi
93
+
68
94
  - name: Doctor
69
95
  shell: bash
70
96
  run: |
@@ -16,6 +16,8 @@ jobs:
16
16
  write-plan: "true"
17
17
  write-schema: "true"
18
18
  write-doctor-json: "true"
19
+ write-sbom: "true"
20
+ write-summary: "true"
19
21
  strict: "off" # security, policy, coordination, all, or off
20
22
 
21
23
  - uses: actions/upload-artifact@v4
@@ -26,6 +28,9 @@ jobs:
26
28
  AIENV.md
27
29
  .aienvmp/manifest.json
28
30
  .aienvmp/status.json
31
+ .aienvmp/summary.md
32
+ .aienvmp/sbom.json
33
+ .aienvmp/sbom.cdx.json
29
34
  .aienvmp/schema.json
30
35
  .aienvmp/doctor.json
31
36
  .aienvmp/plan.json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aienvmp",
3
- "version": "0.1.48",
3
+ "version": "0.1.50",
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
@@ -16,6 +16,7 @@ import { statusWorkspace } from "./commands/status.js";
16
16
  import { schemaWorkspace } from "./commands/schema.js";
17
17
  import { checkpointWorkspace } from "./commands/checkpoint.js";
18
18
  import { sbomWorkspace } from "./commands/sbom.js";
19
+ import { summaryWorkspace } from "./commands/summary.js";
19
20
  import { readFileSync } from "node:fs";
20
21
 
21
22
  const commands = new Map([
@@ -36,7 +37,8 @@ const commands = new Map([
36
37
  ["status", statusWorkspace],
37
38
  ["schema", schemaWorkspace],
38
39
  ["checkpoint", checkpointWorkspace],
39
- ["sbom", sbomWorkspace]
40
+ ["sbom", sbomWorkspace],
41
+ ["summary", summaryWorkspace]
40
42
  ]);
41
43
 
42
44
  const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
@@ -115,16 +117,18 @@ Usage:
115
117
  aienvmp checkpoint [--dir .] --actor agent:id --summary "what changed" [--target dependency] [--json]
116
118
  aienvmp plan [--dir .] [--json] [--write]
117
119
  aienvmp sbom [--dir .] [--json] [--write]
120
+ aienvmp summary [--dir .] [--write]
118
121
  aienvmp schema [--json]
119
122
 
120
123
  Common:
121
- aienvmp sync update AIENV.md, manifest, status, ledger, intents, and dashboard
124
+ aienvmp sync update AIENV.md, manifest, status, summary, SBOM, ledger, intents, and dashboard
122
125
  aienvmp status print one simple environment decision; --write saves .aienvmp/status.json
123
126
  aienvmp context print the AI preflight brief
124
127
  aienvmp handoff print the next-agent handoff summary
125
128
  aienvmp checkpoint record, sync, status, and handoff after an env change
126
129
  aienvmp plan print a read-only AI environment action plan
127
130
  aienvmp sbom print/write standalone light SBOM artifact
131
+ aienvmp summary print/write a compact Markdown summary for AI and CI
128
132
  aienvmp schema print the stable AI-readable output contract
129
133
  aienvmp snippet print an AGENTS.md pointer snippet
130
134
  aienvmp dash regenerate/open the lightweight dashboard
@@ -141,6 +145,7 @@ Advanced:
141
145
  aienvmp diff [--dir .]
142
146
  aienvmp doctor [--dir .] [--json] [--ci] [--strict security|policy|coordination|all]
143
147
  aienvmp sbom [--dir .] [--json] [--write]
148
+ aienvmp summary [--dir .] [--write]
144
149
  aienvmp dash [--dir .] [--open]
145
150
  `);
146
151
  }
@@ -0,0 +1,96 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { readJson } from "../fsutil.js";
4
+ import { manifestPath, statusJsonPath, summaryMdPath, workspaceDir } from "../paths.js";
5
+ import { statusWorkspace } from "./status.js";
6
+
7
+ export async function summaryWorkspace(args) {
8
+ const dir = workspaceDir(args);
9
+ let status = await readJson(statusJsonPath(dir));
10
+ if (!status) {
11
+ status = await statusWorkspace({ ...args, dir, json: false, write: true, quiet: true });
12
+ }
13
+ const manifest = await readJson(manifestPath(dir), {});
14
+ const markdown = renderSummary(status, manifest);
15
+ const artifact = args.write ? await writeSummaryArtifact(dir, markdown) : "";
16
+ const output = { artifact, summary: markdown, state: status.state || "unknown", sbomRisk: status.sbomRisk || {}, nextCommand: status.nextCommand || "" };
17
+
18
+ if (args.json) {
19
+ console.log(JSON.stringify(output, null, 2));
20
+ } else if (!args.quiet) {
21
+ console.log(markdown.trimEnd());
22
+ if (artifact) console.log(`\nsummary: ${artifact}`);
23
+ }
24
+
25
+ return output;
26
+ }
27
+
28
+ export async function writeSummaryArtifact(dir, markdown) {
29
+ const out = summaryMdPath(dir);
30
+ await fs.mkdir(path.dirname(out), { recursive: true });
31
+ await fs.writeFile(out, `${markdown.trimEnd()}\n`, "utf8");
32
+ return out;
33
+ }
34
+
35
+ export function renderSummary(status = {}, manifest = {}) {
36
+ const counts = status.counts || {};
37
+ const sbomRisk = status.sbomRisk || manifest.lightSbom?.riskSummary || {};
38
+ const coordination = status.coordination || {};
39
+ const activity = status.agentActivity || {};
40
+ const workspace = manifest.workspace?.root || manifest.workspace?.name || ".";
41
+ const next = status.nextCommand || status.decision?.nextCommand || "aienvmp status";
42
+ const readFirst = status.nextAgent?.readFirst || ".aienvmp/status.json";
43
+ const detail = status.quickstart?.detailCommand || "aienvmp context --json";
44
+ const strict = status.enforcement?.recommendedCommand || "aienvmp doctor --strict all";
45
+ const riskLevel = sbomRisk.level || "unknown";
46
+ const riskScore = valueOrZero(sbomRisk.score);
47
+ const scanner = sbomRisk.scanner || manifest.lightSbom?.source?.scanner || "not run";
48
+ const riskSignals = toList(sbomRisk.signals).slice(0, 3);
49
+ const conflictTargets = toList(coordination.conflictTargets);
50
+ const multiActorTargets = toList(activity.multiActorTargets);
51
+
52
+ return [
53
+ "# aienvmp summary",
54
+ "",
55
+ `- state: ${status.state || "unknown"}`,
56
+ `- workspace: ${workspace}`,
57
+ `- warnings: ${valueOrZero(counts.warnings)} / open intents: ${valueOrZero(counts.openIntents)}`,
58
+ `- runtimes: ${valueOrZero(counts.runtimes)} / dependencies: ${valueOrZero(counts.dependencies)} / vulnerabilities: ${valueOrZero(counts.vulnerabilities)}`,
59
+ `- light SBOM risk: ${riskLevel} (${riskScore}) / scanner: ${scanner}`,
60
+ `- next: ${next}`,
61
+ `- AI read first: ${readFirst}, then ${detail}`,
62
+ `- mode: advisory by default; strict is opt-in with ${strict}`,
63
+ "",
64
+ "## AI handoff",
65
+ "",
66
+ `- environment changes: ${status.agentUse?.environmentChanges || "intent-and-review-first"}`,
67
+ `- coordination: ${coordination.next || "No open environment intents."}`,
68
+ `- recent agent activity: ${activity.next || "No environment records need coordination."}`,
69
+ `- conflict targets: ${conflictTargets.length ? conflictTargets.join(", ") : "none"}`,
70
+ `- multi-actor targets: ${multiActorTargets.length ? multiActorTargets.join(", ") : "none"}`,
71
+ "",
72
+ "## SBOM",
73
+ "",
74
+ `- source: ${manifest.lightSbom?.source?.dependencies || "project manifests"}`,
75
+ `- confidence: transitive ${manifest.lightSbom?.confidence?.transitiveDependencies || "not-resolved"}`,
76
+ `- signals: ${riskSignals.length ? riskSignals.join("; ") : "none"}`,
77
+ `- verify: ${sbomRisk.next || "Use a dedicated scanner for security decisions."}`,
78
+ "",
79
+ "## Artifacts",
80
+ "",
81
+ "- AIENV.md",
82
+ "- .aienvmp/status.json",
83
+ "- .aienvmp/manifest.json",
84
+ "- .aienvmp/sbom.json",
85
+ "- .aienvmp/sbom.cdx.json",
86
+ "- .aienvmp/dashboard.html"
87
+ ].join("\n");
88
+ }
89
+
90
+ function valueOrZero(value) {
91
+ return Number.isFinite(Number(value)) ? Number(value) : 0;
92
+ }
93
+
94
+ function toList(value) {
95
+ return Array.isArray(value) ? value.filter(Boolean) : [];
96
+ }
@@ -4,6 +4,7 @@ import { compileWorkspace } from "./compile.js";
4
4
  import { dashWorkspace } from "./dash.js";
5
5
  import { statusWorkspace } from "./status.js";
6
6
  import { sbomWorkspace } from "./sbom.js";
7
+ import { summaryWorkspace } from "./summary.js";
7
8
 
8
9
  export async function syncWorkspace(args) {
9
10
  const quiet = args.quiet || args.json;
@@ -16,6 +17,7 @@ export async function syncWorkspace(args) {
16
17
  const status = await statusWorkspace({ ...next, json: false, write: true, quiet: true });
17
18
  const sbom = await sbomWorkspace({ ...next, json: false, write: true, quiet: true });
18
19
  const cyclonedx = await sbomWorkspace({ ...next, json: false, write: true, quiet: true, format: "cyclonedx-lite" });
20
+ const summary = await summaryWorkspace({ ...next, json: false, write: true, quiet: true });
19
21
 
20
22
  const result = {
21
23
  status: "ok",
@@ -26,6 +28,7 @@ export async function syncWorkspace(args) {
26
28
  status: status.artifact,
27
29
  sbom: sbom.artifact,
28
30
  cyclonedx: cyclonedx.artifact,
31
+ summary: summary.artifact,
29
32
  dashboard: dashboard.dashboard
30
33
  },
31
34
  changes: scanned.changes,
@@ -35,7 +38,7 @@ export async function syncWorkspace(args) {
35
38
  if (args.json) {
36
39
  console.log(JSON.stringify(result, null, 2));
37
40
  } else if (!quiet) {
38
- console.log("sync complete: AIENV.md, manifest, status, ledger, intents, and dashboard are up to date");
41
+ console.log("sync complete: AIENV.md, manifest, status, SBOM, summary, ledger, intents, and dashboard are up to date");
39
42
  }
40
43
 
41
44
  return result;
package/src/contract.js CHANGED
@@ -20,6 +20,12 @@ export function schemaContract() {
20
20
  command: "aienvmp status --json",
21
21
  contract: preflightContract()
22
22
  },
23
+ summary: {
24
+ file: ".aienvmp/summary.md",
25
+ command: "aienvmp summary --write",
26
+ format: "markdown",
27
+ purpose: "Compact AI and CI step summary for quick review."
28
+ },
23
29
  context: {
24
30
  command: "aienvmp context --json",
25
31
  rootFields: ["status", "preflight", "coordination", "decision", "recommendedActions", "workspace", "dependencySnapshot", "lightSbom", "warnings"]
package/src/fsutil.js CHANGED
@@ -12,12 +12,16 @@ export async function exists(file) {
12
12
 
13
13
  export async function readJson(file, fallback = null) {
14
14
  try {
15
- return JSON.parse(await fs.readFile(file, "utf8"));
15
+ return JSON.parse(stripBom(await fs.readFile(file, "utf8")));
16
16
  } catch {
17
17
  return fallback;
18
18
  }
19
19
  }
20
20
 
21
+ export function stripBom(value) {
22
+ return String(value).replace(/^\uFEFF/, "");
23
+ }
24
+
21
25
  export async function writeJson(file, data) {
22
26
  await fs.mkdir(path.dirname(file), { recursive: true });
23
27
  await fs.writeFile(file, `${JSON.stringify(data, null, 2)}\n`, "utf8");
package/src/paths.js CHANGED
@@ -48,6 +48,10 @@ export function planMdPath(dir) {
48
48
  return path.join(stateDir(dir), "plan.md");
49
49
  }
50
50
 
51
+ export function summaryMdPath(dir) {
52
+ return path.join(stateDir(dir), "summary.md");
53
+ }
54
+
51
55
  export function aiEnvPath(dir) {
52
56
  return path.join(dir, "AIENV.md");
53
57
  }
package/src/preflight.js CHANGED
@@ -351,6 +351,7 @@ function agentQuickstart(reviewRequired) {
351
351
  export function preflightArtifacts() {
352
352
  return {
353
353
  status: ".aienvmp/status.json",
354
+ summary: ".aienvmp/summary.md",
354
355
  envMap: "AIENV.md",
355
356
  manifest: ".aienvmp/manifest.json",
356
357
  sbom: ".aienvmp/sbom.json",