aienvmp 0.1.44 → 0.1.46
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 -4
- package/package.json +1 -1
- package/src/actions.js +13 -0
- package/src/cli.js +6 -1
- package/src/commands/checkpoint.js +68 -0
- package/src/commands/context.js +2 -0
- package/src/commands/handoff.js +2 -1
- package/src/commands/plan.js +10 -6
- package/src/commands/record.js +3 -1
- package/src/commands/status.js +1 -0
- package/src/contract.js +1 -1
- package/src/decision.js +1 -0
- package/src/dependencies.js +70 -2
- package/src/manifest.js +2 -1
- package/src/preflight.js +6 -1
- package/src/render.js +14 -13
package/BUGFIXES.md
CHANGED
|
@@ -52,6 +52,18 @@ Short record of bugs, fixes, and follow-up checks.
|
|
|
52
52
|
- Fix: `doctor`, status/context, handoff, and the dashboard now expose same-target multi-agent records through advisory warnings and `agentActivity`.
|
|
53
53
|
- Verification: tests cover multi-agent record detection, later handoff reset, status JSON, handoff text, dashboard HTML, and recommended actions.
|
|
54
54
|
|
|
55
|
+
### Post-change loop was too many commands for routine AI handoff
|
|
56
|
+
|
|
57
|
+
- Issue: after an environment change, agents had to remember separate record, sync, status, and handoff commands.
|
|
58
|
+
- Fix: `aienvmp checkpoint` now performs the post-change loop and records an explicit sync ledger entry so follow-ups can be closed.
|
|
59
|
+
- Verification: checkpoint tests cover text/JSON output, status artifact refresh, timeline entries, handoff recording, and cleared follow-ups.
|
|
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
|
+
|
|
55
67
|
## Template
|
|
56
68
|
|
|
57
69
|
### Title
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.46
|
|
4
|
+
|
|
5
|
+
- Added `lightSbom.riskSummary` with compact risk level, score, scanner state, signals, review targets, and next commands.
|
|
6
|
+
- Added `sbomRisk` to the shared AI preflight contract so status/context consumers can read SBOM risk without deep parsing.
|
|
7
|
+
- Surfaced SBOM risk in `context --json`, `AIENV.md`, and the dashboard Light SBOM card.
|
|
8
|
+
- Added recommended actions for read-only security scans and high light-SBOM risk review.
|
|
9
|
+
- Used top-risk package severity as a fallback when scanner summary severity counts are incomplete.
|
|
10
|
+
- Updated dependency change hints to point agents to `aienvmp checkpoint` after accepted dependency changes.
|
|
11
|
+
- Added regression tests for risk scoring, scanner-off guidance, status/context outputs, dashboard rendering, and recommended actions.
|
|
12
|
+
|
|
13
|
+
## 0.1.45
|
|
14
|
+
|
|
15
|
+
- Added `aienvmp checkpoint` as a one-command post-environment-change flow for record, sync, status, and handoff.
|
|
16
|
+
- Added quiet command support so checkpoint can compose existing commands without noisy intermediate output.
|
|
17
|
+
- Added checkpoint sync ledger entries so follow-up detection can prove the refresh step happened.
|
|
18
|
+
- Added checkpoint guidance to the shared preflight, decision, manifest, dependency protocol, and status outputs.
|
|
19
|
+
- Updated AIENV, handoff, plan, dashboard, and agent pointer rendering to prefer checkpoint after environment changes.
|
|
20
|
+
- Shortened the README environment-change flow to intent plus checkpoint.
|
|
21
|
+
- Added checkpoint regression coverage for JSON output, ledger updates, follow-up closure, status artifacts, and rendered guidance.
|
|
22
|
+
|
|
3
23
|
## 0.1.44
|
|
4
24
|
|
|
5
25
|
- Added multi-agent record warnings when multiple agents record environment changes for the same target after the last handoff.
|
package/README.md
CHANGED
|
@@ -23,10 +23,7 @@ Before environment changes:
|
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
25
|
npx aienvmp intent --actor agent:codex --action "change dependency" --target dependency
|
|
26
|
-
npx aienvmp
|
|
27
|
-
npx aienvmp sync
|
|
28
|
-
npx aienvmp status --write
|
|
29
|
-
npx aienvmp handoff --record --actor agent:codex
|
|
26
|
+
npx aienvmp checkpoint --actor agent:codex --summary "dependency-change" --target dependency
|
|
30
27
|
```
|
|
31
28
|
|
|
32
29
|
Use `--dir <workspace>` when AI or CI runs outside the target project.
|
|
@@ -50,6 +47,7 @@ AIENV.md # Markdown env map for AI agents
|
|
|
50
47
|
- `schema --json` prints the stable AI-readable output contract without scanning.
|
|
51
48
|
- `status.json.nextAgent` tells the next AI what to read and whether to review first.
|
|
52
49
|
- `dependencyReadSet` lists manifests and lockfiles before package or security changes.
|
|
50
|
+
- `sbomRisk` gives AI a compact light-SBOM risk level, signals, and next command.
|
|
53
51
|
- `coordination.conflictTargets` shows where multiple agents are planning changes.
|
|
54
52
|
- `agentActivity.multiActorTargets` shows where multiple agents actually recorded env changes.
|
|
55
53
|
- `followUps` shows records that still need `sync`, `status`, or `handoff`.
|
|
@@ -79,6 +77,7 @@ aienvmp plan --write # read-only action plan
|
|
|
79
77
|
aienvmp handoff --record # next-agent summary
|
|
80
78
|
aienvmp intent # record planned env change
|
|
81
79
|
aienvmp record # record what changed
|
|
80
|
+
aienvmp checkpoint # record + sync + status + handoff after env change
|
|
82
81
|
aienvmp doctor --strict security|policy|coordination|all
|
|
83
82
|
```
|
|
84
83
|
|
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
|
@@ -14,6 +14,7 @@ import { handoffWorkspace } from "./commands/handoff.js";
|
|
|
14
14
|
import { planWorkspace } from "./commands/plan.js";
|
|
15
15
|
import { statusWorkspace } from "./commands/status.js";
|
|
16
16
|
import { schemaWorkspace } from "./commands/schema.js";
|
|
17
|
+
import { checkpointWorkspace } from "./commands/checkpoint.js";
|
|
17
18
|
import { readFileSync } from "node:fs";
|
|
18
19
|
|
|
19
20
|
const commands = new Map([
|
|
@@ -32,7 +33,8 @@ const commands = new Map([
|
|
|
32
33
|
["handoff", handoffWorkspace],
|
|
33
34
|
["plan", planWorkspace],
|
|
34
35
|
["status", statusWorkspace],
|
|
35
|
-
["schema", schemaWorkspace]
|
|
36
|
+
["schema", schemaWorkspace],
|
|
37
|
+
["checkpoint", checkpointWorkspace]
|
|
36
38
|
]);
|
|
37
39
|
|
|
38
40
|
const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
@@ -108,6 +110,7 @@ Usage:
|
|
|
108
110
|
aienvmp context [--dir .] [--json]
|
|
109
111
|
aienvmp status [--dir .] [--json] [--write] [--quiet]
|
|
110
112
|
aienvmp handoff [--dir .] [--json] [--record --actor agent:id]
|
|
113
|
+
aienvmp checkpoint [--dir .] --actor agent:id --summary "what changed" [--target dependency] [--json]
|
|
111
114
|
aienvmp plan [--dir .] [--json] [--write]
|
|
112
115
|
aienvmp schema [--json]
|
|
113
116
|
|
|
@@ -116,6 +119,7 @@ Common:
|
|
|
116
119
|
aienvmp status print one simple environment decision; --write saves .aienvmp/status.json
|
|
117
120
|
aienvmp context print the AI preflight brief
|
|
118
121
|
aienvmp handoff print the next-agent handoff summary
|
|
122
|
+
aienvmp checkpoint record, sync, status, and handoff after an env change
|
|
119
123
|
aienvmp plan print a read-only AI environment action plan
|
|
120
124
|
aienvmp schema print the stable AI-readable output contract
|
|
121
125
|
aienvmp snippet print an AGENTS.md pointer snippet
|
|
@@ -127,6 +131,7 @@ Advanced:
|
|
|
127
131
|
aienvmp intent [--dir .] --actor agent:codex --action "install pnpm"
|
|
128
132
|
aienvmp resolve [--dir .] --actor human:you --id <intent-id> [--status resolved|cancelled]
|
|
129
133
|
aienvmp record [--dir .] --actor agent:codex --summary "updated .nvmrc" [--target node] [--before 20] [--after 24]
|
|
134
|
+
aienvmp checkpoint [--dir .] --actor agent:codex --summary "updated dependency" [--target dependency]
|
|
130
135
|
aienvmp snippet [agents|claude|gemini] [--write AGENTS.md]
|
|
131
136
|
aienvmp compile [--dir .]
|
|
132
137
|
aienvmp diff [--dir .]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { handoffWorkspace } from "./handoff.js";
|
|
2
|
+
import { recordWorkspace } from "./record.js";
|
|
3
|
+
import { statusWorkspace } from "./status.js";
|
|
4
|
+
import { syncWorkspace } from "./sync.js";
|
|
5
|
+
import { appendJsonLine } from "../fsutil.js";
|
|
6
|
+
import { timelinePath, workspaceDir } from "../paths.js";
|
|
7
|
+
import { observedTrust } from "../trust.js";
|
|
8
|
+
|
|
9
|
+
export async function checkpointWorkspace(args) {
|
|
10
|
+
const actor = required(args.actor, "actor");
|
|
11
|
+
const summary = required(args.summary || args.change, "summary");
|
|
12
|
+
const quiet = args.quiet || args.json;
|
|
13
|
+
const base = { ...args, actor, summary, quiet: true };
|
|
14
|
+
|
|
15
|
+
const record = await recordWorkspace(base);
|
|
16
|
+
const sync = await syncWorkspace({ ...base, json: false, quiet: true });
|
|
17
|
+
await recordCheckpointSync(base);
|
|
18
|
+
const handoff = await handoffWorkspace({ ...base, json: false, record: true, quiet: true });
|
|
19
|
+
const status = await statusWorkspace({ ...base, json: false, write: true, quiet: true });
|
|
20
|
+
|
|
21
|
+
const result = {
|
|
22
|
+
status: "ok",
|
|
23
|
+
mode: "post-environment-change",
|
|
24
|
+
actor,
|
|
25
|
+
summary,
|
|
26
|
+
target: args.target || "",
|
|
27
|
+
record,
|
|
28
|
+
outputs: sync.outputs,
|
|
29
|
+
state: status.state,
|
|
30
|
+
warnings: status.counts?.warnings || 0,
|
|
31
|
+
followUps: status.followUps || [],
|
|
32
|
+
agentActivity: status.agentActivity || {},
|
|
33
|
+
handoff: {
|
|
34
|
+
status: handoff.status,
|
|
35
|
+
recommendedNext: handoff.recommendedNext
|
|
36
|
+
},
|
|
37
|
+
next: "Share .aienvmp/status.json or run `aienvmp context --json` before another environment change."
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (args.json) {
|
|
41
|
+
console.log(JSON.stringify(result, null, 2));
|
|
42
|
+
} else if (!quiet) {
|
|
43
|
+
console.log(`checkpoint: ${result.state}`);
|
|
44
|
+
console.log(`record: ${record.summary}${record.target ? ` (${record.target})` : ""}`);
|
|
45
|
+
console.log(`status: ${result.outputs.status}`);
|
|
46
|
+
console.log(`handoff: ${handoff.status}`);
|
|
47
|
+
console.log(`next: ${result.next}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function recordCheckpointSync(args) {
|
|
54
|
+
const now = new Date();
|
|
55
|
+
await appendJsonLine(timelinePath(workspaceDir(args)), {
|
|
56
|
+
at: now.toISOString(),
|
|
57
|
+
actor: args.actor,
|
|
58
|
+
type: "sync",
|
|
59
|
+
summary: "checkpoint sync",
|
|
60
|
+
target: "",
|
|
61
|
+
trust: observedTrust(now)
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function required(value, name) {
|
|
66
|
+
if (!value) throw new Error(`checkpoint: --${name} is required`);
|
|
67
|
+
return String(value);
|
|
68
|
+
}
|
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 || [],
|
package/src/commands/handoff.js
CHANGED
|
@@ -24,7 +24,7 @@ export async function handoffWorkspace(args) {
|
|
|
24
24
|
|
|
25
25
|
if (args.json) {
|
|
26
26
|
console.log(JSON.stringify(handoff, null, 2));
|
|
27
|
-
} else {
|
|
27
|
+
} else if (!args.quiet) {
|
|
28
28
|
console.log(renderHandoff(handoff));
|
|
29
29
|
}
|
|
30
30
|
return handoff;
|
|
@@ -95,6 +95,7 @@ function dependencyHandoffSummary(preflight = {}) {
|
|
|
95
95
|
packageManagerPolicy: protocol.packageManagerPolicy || "not-detected",
|
|
96
96
|
recordIntent: protocol.commands?.recordIntent || "aienvmp intent --actor agent:id --action planned-change --target dependency",
|
|
97
97
|
recordAfterChange: protocol.commands?.recordAfterChange || "aienvmp record --actor agent:id --summary dependency-change --target dependency",
|
|
98
|
+
checkpointAfterChange: protocol.commands?.checkpointAfterChange || "aienvmp checkpoint --actor agent:id --summary dependency-change --target dependency",
|
|
98
99
|
handoff: protocol.commands?.handoff || "aienvmp handoff --record --actor agent:id"
|
|
99
100
|
}
|
|
100
101
|
};
|
package/src/commands/plan.js
CHANGED
|
@@ -103,7 +103,7 @@ function environmentSteps(warnings = []) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
function environmentCategory(code = "") {
|
|
106
|
-
if (["conflicting-open-intents", "stale-open-intent", "handoff-stale"].includes(code)) return "coordination";
|
|
106
|
+
if (["conflicting-open-intents", "stale-open-intent", "handoff-stale", "multi-agent-records"].includes(code)) return "coordination";
|
|
107
107
|
if (code.includes("docker")) return "container";
|
|
108
108
|
if (code.includes("lockfile") || code.includes("package-manager")) return "package-manager";
|
|
109
109
|
if (code.includes("node") || code.includes("python") || code.includes("version") || code.includes("runtime")) return "runtime";
|
|
@@ -115,13 +115,13 @@ function environmentStepLines(code = "") {
|
|
|
115
115
|
"Read .nvmrc and the detected Node version before changing tools.",
|
|
116
116
|
"Prefer project-local version managers such as nvm, mise, or asdf.",
|
|
117
117
|
"Ask before changing global Node or npm installations.",
|
|
118
|
-
"Run aienvmp
|
|
118
|
+
"Run aienvmp checkpoint --actor agent:id --summary what-changed --target node after alignment."
|
|
119
119
|
];
|
|
120
120
|
if (code === "python-version-mismatch") return [
|
|
121
121
|
"Read .python-version and the detected Python version before changing tools.",
|
|
122
122
|
"Prefer project-local virtual environments or version managers.",
|
|
123
123
|
"Ask before changing global Python installations.",
|
|
124
|
-
"Run aienvmp
|
|
124
|
+
"Run aienvmp checkpoint --actor agent:id --summary what-changed --target python after alignment."
|
|
125
125
|
];
|
|
126
126
|
if (code === "mixed-node-lockfiles" || code === "package-manager-policy-mismatch") return [
|
|
127
127
|
"Identify the intended package manager from project policy and lockfiles.",
|
|
@@ -133,13 +133,13 @@ function environmentStepLines(code = "") {
|
|
|
133
133
|
"Confirm whether the Python project is active.",
|
|
134
134
|
"Prefer project-local Python setup before global installation.",
|
|
135
135
|
"Ask before installing a global Python runtime.",
|
|
136
|
-
"Run aienvmp
|
|
136
|
+
"Run aienvmp checkpoint --actor agent:id --summary what-changed --target python after setup."
|
|
137
137
|
];
|
|
138
138
|
if (code === "docker-missing") return [
|
|
139
139
|
"Confirm whether Docker is required for the current task.",
|
|
140
140
|
"Do not change Docker daemon or context without user approval.",
|
|
141
141
|
"Document fallback commands if Docker is unavailable.",
|
|
142
|
-
"Run aienvmp
|
|
142
|
+
"Run aienvmp checkpoint --actor agent:id --summary what-changed --target docker after Docker availability changes."
|
|
143
143
|
];
|
|
144
144
|
if (code === "manifest-stale") return [
|
|
145
145
|
"Run aienvmp sync before environment changes.",
|
|
@@ -158,10 +158,14 @@ function environmentStepLines(code = "") {
|
|
|
158
158
|
"Run aienvmp handoff --record --actor agent:id before the next AI continues.",
|
|
159
159
|
"Review recent changes before new environment work."
|
|
160
160
|
];
|
|
161
|
+
if (code === "multi-agent-records") return [
|
|
162
|
+
"Review agentActivity and recent timeline records for the shared target.",
|
|
163
|
+
"Run aienvmp checkpoint --actor agent:id --summary what-changed --target environment before more environment work."
|
|
164
|
+
];
|
|
161
165
|
return [
|
|
162
166
|
"Review the warning before changing environment state.",
|
|
163
167
|
"Ask before global environment changes.",
|
|
164
|
-
"Run aienvmp
|
|
168
|
+
"Run aienvmp checkpoint --actor agent:id --summary what-changed --target environment after any accepted change."
|
|
165
169
|
];
|
|
166
170
|
}
|
|
167
171
|
|
package/src/commands/record.js
CHANGED
|
@@ -22,7 +22,8 @@ export async function recordWorkspace(args) {
|
|
|
22
22
|
trust: changedTrust(now, requiresReview)
|
|
23
23
|
};
|
|
24
24
|
await appendJsonLine(timelinePath(dir), entry);
|
|
25
|
-
console.log(`recorded ${entry.type} by ${actor}`);
|
|
25
|
+
if (!args.quiet) console.log(`recorded ${entry.type} by ${actor}`);
|
|
26
|
+
return entry;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export function followUpForRecord(record = {}) {
|
|
@@ -42,6 +43,7 @@ export function followUpForRecord(record = {}) {
|
|
|
42
43
|
? "Dependency or security records should refresh the env map and handoff context."
|
|
43
44
|
: "Environment records should refresh the env map and handoff context.",
|
|
44
45
|
commands: [
|
|
46
|
+
`aienvmp checkpoint --actor agent:id --summary ${isDependency ? "dependency-change" : "what-changed"} --target ${isDependency ? "dependency" : target || "environment"}`,
|
|
45
47
|
"aienvmp sync",
|
|
46
48
|
"aienvmp status --write",
|
|
47
49
|
"aienvmp handoff --record --actor agent:id"
|
package/src/commands/status.js
CHANGED
|
@@ -25,6 +25,7 @@ export async function statusWorkspace(args) {
|
|
|
25
25
|
console.log(`next: ${output.nextCommand}`);
|
|
26
26
|
console.log(`ai: ${output.quickstart.readFirst} -> ${output.quickstart.detailCommand}`);
|
|
27
27
|
console.log(`intent: ${output.intentTargets[0]?.command || output.commands.recordIntent}`);
|
|
28
|
+
console.log(`checkpoint: ${output.commands.checkpoint}`);
|
|
28
29
|
console.log(`handoff: ${output.nextAgent.handoffCommand}`);
|
|
29
30
|
console.log(`strict: ${output.enforcement.recommendedCommand}`);
|
|
30
31
|
if (artifact) console.log(`status: ${artifact}`);
|
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
|
}
|
package/src/decision.js
CHANGED
|
@@ -25,6 +25,7 @@ export function aiDecision(warnings = [], intents = []) {
|
|
|
25
25
|
: ["continue with project-local work", "run aienvmp intent before environment changes"],
|
|
26
26
|
requiredCommands: {
|
|
27
27
|
beforeEnvironmentChange: "aienvmp intent --actor agent:id --action planned-change --target <runtime|package-manager|docker>",
|
|
28
|
+
checkpointAfterChange: "aienvmp checkpoint --actor agent:id --summary what-changed --target environment",
|
|
28
29
|
refreshAfterChange: "aienvmp sync",
|
|
29
30
|
recordAfterChange: "aienvmp record --actor agent:id --summary what-changed",
|
|
30
31
|
handoff: "aienvmp handoff --record --actor agent:id",
|
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/manifest.js
CHANGED
|
@@ -42,7 +42,8 @@ export async function buildManifest(dir, options = {}) {
|
|
|
42
42
|
handoffCommand: "aienvmp handoff",
|
|
43
43
|
intentCommand: "aienvmp intent --actor agent:id --action planned-change",
|
|
44
44
|
recordCommand: "aienvmp record --actor agent:id --summary what-changed",
|
|
45
|
-
|
|
45
|
+
checkpointCommand: "aienvmp checkpoint --actor agent:id --summary what-changed --target environment",
|
|
46
|
+
afterEnvironmentChange: ["aienvmp checkpoint --actor agent:id --summary what-changed --target environment"],
|
|
46
47
|
trustModel: {
|
|
47
48
|
agentWritable: ["observed", "planned", "changed", "review", "stale"],
|
|
48
49
|
verifiedRequires: "human-or-ci",
|
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,
|
|
@@ -76,6 +78,7 @@ export function buildPreflight(manifest = {}, warnings = [], intents = [], timel
|
|
|
76
78
|
context: "aienvmp context --json",
|
|
77
79
|
plan: "aienvmp plan --write",
|
|
78
80
|
handoff: "aienvmp handoff --record --actor agent:id",
|
|
81
|
+
checkpoint: "aienvmp checkpoint --actor agent:id --summary what-changed --target environment",
|
|
79
82
|
recordIntent: intentTargets[0]?.command || "aienvmp intent --actor agent:id --action planned-change"
|
|
80
83
|
},
|
|
81
84
|
topAction,
|
|
@@ -220,6 +223,7 @@ function dependencyProtocol(manifest = {}, dependencyReadSet = []) {
|
|
|
220
223
|
recordIntent: "aienvmp intent --actor agent:id --action planned-change --target dependency",
|
|
221
224
|
refreshAfterChange: "aienvmp sync",
|
|
222
225
|
recordAfterChange: "aienvmp record --actor agent:id --summary dependency-change --target dependency",
|
|
226
|
+
checkpointAfterChange: "aienvmp checkpoint --actor agent:id --summary dependency-change --target dependency",
|
|
223
227
|
handoff: "aienvmp handoff --record --actor agent:id"
|
|
224
228
|
},
|
|
225
229
|
mustNotDo: [
|
|
@@ -335,7 +339,8 @@ function agentQuickstart(reviewRequired) {
|
|
|
335
339
|
readFirst: "aienvmp status --write",
|
|
336
340
|
detailCommand: "aienvmp context --json",
|
|
337
341
|
beforeEnvironmentChange: "aienvmp intent --actor agent:id --action planned-change --target <runtime|package-manager|docker|dependency>",
|
|
338
|
-
afterEnvironmentChange: "aienvmp
|
|
342
|
+
afterEnvironmentChange: "aienvmp checkpoint --actor agent:id --summary what-changed --target environment",
|
|
343
|
+
checkpointAfterChange: "aienvmp checkpoint --actor agent:id --summary what-changed --target environment",
|
|
339
344
|
handoff: "aienvmp handoff --record --actor agent:id",
|
|
340
345
|
rule: reviewRequired
|
|
341
346
|
? "Review warnings or open intents before environment changes; project-local work may continue."
|
package/src/render.js
CHANGED
|
@@ -13,9 +13,7 @@ export function renderAIEnv(manifest, timeline = [], warnings = [], intents = []
|
|
|
13
13
|
lines.push("2. Prefer project-local version files such as `.nvmrc`, `.python-version`, `mise.toml`, and `.tool-versions`.");
|
|
14
14
|
lines.push("3. Ask the user before changing global environment state.");
|
|
15
15
|
lines.push("4. Record planned environment changes with `aienvmp intent --actor agent:id --action planned-change`.");
|
|
16
|
-
lines.push("5. After environment changes, run `aienvmp
|
|
17
|
-
lines.push("6. Record what changed with `aienvmp record --actor agent:id --summary what-changed`.");
|
|
18
|
-
lines.push("7. At handoff, run `aienvmp handoff --record --actor agent:id`.", "");
|
|
16
|
+
lines.push("5. After environment changes, run `aienvmp checkpoint --actor agent:id --summary what-changed --target environment`.", "");
|
|
19
17
|
lines.push(...preflightLines(manifest.preflight), "");
|
|
20
18
|
lines.push("## Current Policy", "");
|
|
21
19
|
lines.push(...policyLines(policy));
|
|
@@ -107,7 +105,7 @@ function preflightLines(preflight = {}) {
|
|
|
107
105
|
lines.push(`- Mode: ${dependencyProtocol.mode}`);
|
|
108
106
|
lines.push(`- Package manager policy: ${dependencyProtocol.packageManagerPolicy}`);
|
|
109
107
|
lines.push(`- Intent: \`${dependencyProtocol.commands.recordIntent}\``);
|
|
110
|
-
lines.push(`- After change: \`${dependencyProtocol.commands.
|
|
108
|
+
lines.push(`- After change: \`${dependencyProtocol.commands.checkpointAfterChange || dependencyProtocol.commands.recordAfterChange}\``);
|
|
111
109
|
lines.push(`- Handoff: \`${dependencyProtocol.commands.handoff}\``);
|
|
112
110
|
for (const item of dependencyProtocol.mustNotDo.slice(0, 3)) lines.push(`- Must not: ${item}`);
|
|
113
111
|
}
|
|
@@ -127,8 +125,7 @@ Before changing runtimes, package managers, Docker settings, global packages, or
|
|
|
127
125
|
3. Read \`AIENV.md\`.
|
|
128
126
|
4. If status or context says \`review-required\`, ask the user before changing the environment.
|
|
129
127
|
5. Record planned environment changes with the recommended target, for example \`aienvmp intent --actor agent:id --action planned-change --target dependency\`.
|
|
130
|
-
6. After environment changes, run \`aienvmp
|
|
131
|
-
7. At handoff, run \`aienvmp handoff --record --actor agent:id\`.
|
|
128
|
+
6. After environment changes, run \`aienvmp checkpoint --actor agent:id --summary what-changed --target environment\`.
|
|
132
129
|
|
|
133
130
|
\`aienvmp\` does not replace this instruction file. It provides the live env map, lightweight runtime SBOM, intent log, timeline, and dashboard.`;
|
|
134
131
|
}
|
|
@@ -160,9 +157,7 @@ export function renderContext(manifest, timeline = [], warnings = [], intents =
|
|
|
160
157
|
"- Treat policy mismatches as review-required, not as permission to break ongoing operations.",
|
|
161
158
|
"- Prefer project-local version files and local environments.",
|
|
162
159
|
"- Before planned env changes, run `aienvmp intent --actor agent:id --action planned-change`.",
|
|
163
|
-
"- After env changes, run `aienvmp
|
|
164
|
-
"- Then run `aienvmp record --actor agent:id --summary what-changed`.",
|
|
165
|
-
"- Before handing work to another AI, run `aienvmp handoff --record --actor agent:id`.",
|
|
160
|
+
"- After env changes, run `aienvmp checkpoint --actor agent:id --summary what-changed --target environment`.",
|
|
166
161
|
"",
|
|
167
162
|
"Warnings:",
|
|
168
163
|
...(warnings.length ? warnings.map((w) => `- ${w.message}`) : ["- none"]),
|
|
@@ -254,7 +249,7 @@ function dependencyHandoffLines(dependencyHandoff = {}) {
|
|
|
254
249
|
lines.push("- Read: no dependency files detected");
|
|
255
250
|
}
|
|
256
251
|
lines.push(`- Intent: ${protocol.recordIntent || "aienvmp intent --actor agent:id --action planned-change --target dependency"}`);
|
|
257
|
-
lines.push(`- After change: ${protocol.recordAfterChange || "aienvmp
|
|
252
|
+
lines.push(`- After change: ${protocol.checkpointAfterChange || protocol.recordAfterChange || "aienvmp checkpoint --actor agent:id --summary dependency-change --target dependency"}`);
|
|
258
253
|
lines.push(`- Handoff: ${protocol.handoff || "aienvmp handoff --record --actor agent:id"}`);
|
|
259
254
|
return lines;
|
|
260
255
|
}
|
|
@@ -304,7 +299,7 @@ function dependencyProtocolPlanLines(protocol = {}) {
|
|
|
304
299
|
`- Mode: ${protocol.mode || "advisory"}`,
|
|
305
300
|
`- Package manager policy: ${protocol.packageManagerPolicy || "not-detected"}`,
|
|
306
301
|
`- Intent: ${protocol.commands.recordIntent}`,
|
|
307
|
-
`- After change: ${protocol.commands.refreshAfterChange}; ${protocol.commands.recordAfterChange}`,
|
|
302
|
+
`- After change: ${protocol.commands.checkpointAfterChange || `${protocol.commands.refreshAfterChange}; ${protocol.commands.recordAfterChange}`}`,
|
|
308
303
|
...(protocol.mustNotDo || []).slice(0, 3).map((item) => `- Must not: ${item}`)
|
|
309
304
|
];
|
|
310
305
|
}
|
|
@@ -421,10 +416,12 @@ const lightSbom=manifest.lightSbom||{};
|
|
|
421
416
|
const lightSbomSummary=lightSbom.summary||{};
|
|
422
417
|
const pmPolicy=lightSbom.packageManagerPolicy||{};
|
|
423
418
|
const topRisk=lightSbom.topRisk||[];
|
|
419
|
+
const riskSummary=lightSbom.riskSummary||{};
|
|
424
420
|
const dependencyHints=lightSbom.dependencyChangeHints||[];
|
|
425
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>';
|
|
426
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>';
|
|
427
|
-
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}\`;
|
|
428
425
|
const sec=manifest.security||{};
|
|
429
426
|
const secSummary=sec.summary||{total:0,critical:0,high:0,moderate:0,low:0,info:0};
|
|
430
427
|
const secPackages=sec.topPackages||[];
|
|
@@ -470,7 +467,7 @@ const activityHtml=activityTargets.length?'<div class="timeline">'+activityTarge
|
|
|
470
467
|
const dependencyReadSet=manifest.preflight?.dependencyReadSet||[];
|
|
471
468
|
const dependencyReadSetHtml=dependencyReadSet.length?'<div class="timeline">'+dependencyReadSet.slice(0,5).map(d=>\`<div class="event"><time>\${esc(d.ecosystem||'deps')}</time><div><b>\${esc(d.manifest||'dependency files')}</b> <code>\${esc(d.manager||'unknown')}</code><div class="path">\${esc([d.manifest,...(d.lockfiles||[])].filter(Boolean).join(', '))}</div>\${d.riskPackages?.length?\`<div class="path">risk: \${esc(d.riskPackages.join(', '))}</div>\`:''}</div></div>\`).join('')+'</div>':'<div class="okline">No dependency files detected.</div>';
|
|
472
469
|
const dependencyProtocol=manifest.preflight?.dependencyChangeProtocol||{};
|
|
473
|
-
const dependencyProtocolHtml=dependencyProtocol.commands?'<table><tr><th>Mode</th><td><code>'+esc(dependencyProtocol.mode||'advisory')+'</code></td></tr><tr><th>Policy</th><td><code>'+esc(dependencyProtocol.packageManagerPolicy||'not-detected')+'</code></td></tr><tr><th>Intent</th><td><code>'+esc(dependencyProtocol.commands.recordIntent)+'</code></td></tr><tr><th>After</th><td><code>'+esc(dependencyProtocol.commands.
|
|
470
|
+
const dependencyProtocolHtml=dependencyProtocol.commands?'<table><tr><th>Mode</th><td><code>'+esc(dependencyProtocol.mode||'advisory')+'</code></td></tr><tr><th>Policy</th><td><code>'+esc(dependencyProtocol.packageManagerPolicy||'not-detected')+'</code></td></tr><tr><th>Intent</th><td><code>'+esc(dependencyProtocol.commands.recordIntent)+'</code></td></tr><tr><th>After</th><td><code>'+esc(dependencyProtocol.commands.checkpointAfterChange||dependencyProtocol.commands.recordAfterChange)+'</code></td></tr></table><div class="timeline">'+(dependencyProtocol.mustNotDo||[]).slice(0,3).map(item=>\`<div class="event"><time>avoid</time><div>\${esc(item)}</div></div>\`).join('')+'</div>':'<div class="okline">No dependency change protocol available.</div>';
|
|
474
471
|
const card=(title,badge,body)=>\`<section class="card"><div class="card-head"><h2>\${title}</h2>\${badge||''}</div>\${body}</section>\`;
|
|
475
472
|
const reviewRequired=warnings.length>0||intents.length>0;
|
|
476
473
|
const recentChanges=timeline.slice(-8).length;
|
|
@@ -638,6 +635,10 @@ function lightSbomLines(lightSbom = {}) {
|
|
|
638
635
|
lines.push(`- Source: ${lightSbom.source?.dependencies || "project manifests"}; vulnerabilities: ${lightSbom.source?.vulnerabilities || "not scanned"}`);
|
|
639
636
|
lines.push(`- Confidence: direct ${lightSbom.confidence?.directDependencies || "unknown"}; transitive ${lightSbom.confidence?.transitiveDependencies || "unknown"}`);
|
|
640
637
|
}
|
|
638
|
+
if (lightSbom.riskSummary) {
|
|
639
|
+
lines.push(`- Risk summary: ${lightSbom.riskSummary.level || "clear"}/${lightSbom.riskSummary.score || 0}; ${lightSbom.riskSummary.next || "no action"}`);
|
|
640
|
+
if (lightSbom.riskSummary.signals?.length) lines.push(`- Risk signals: ${lightSbom.riskSummary.signals.join("; ")}`);
|
|
641
|
+
}
|
|
641
642
|
if (lightSbom.packageManagerPolicy) {
|
|
642
643
|
lines.push(`- Package manager policy: ${lightSbom.packageManagerPolicy.status}`);
|
|
643
644
|
lines.push(`- Package manager guidance: ${lightSbom.packageManagerPolicy.guidance}`);
|