aienvmp 0.1.15 → 0.1.17
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/CHANGELOG.md +12 -0
- package/README.md +4 -2
- package/action.yml +4 -2
- package/examples/github-action.yml +1 -1
- package/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/commands/doctor.js +38 -5
- package/src/commands/plan.js +30 -0
- package/src/render.js +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.17
|
|
4
|
+
|
|
5
|
+
- Added bounded security remediation steps to `aienvmp plan`.
|
|
6
|
+
- Included package fix versions and advisory references in plan JSON and markdown output.
|
|
7
|
+
- Kept remediation output read-only and review-oriented.
|
|
8
|
+
|
|
9
|
+
## 0.1.16
|
|
10
|
+
|
|
11
|
+
- Added scoped strict checks with `doctor --strict security|policy|coordination|all`.
|
|
12
|
+
- Kept `doctor --ci` compatible as strict `all`.
|
|
13
|
+
- Updated the GitHub Action `strict` input to support scoped enforcement while keeping advisory mode by default.
|
|
14
|
+
|
|
3
15
|
## 0.1.15
|
|
4
16
|
|
|
5
17
|
- Added dashboard links for written AI plan artifacts.
|
package/README.md
CHANGED
|
@@ -59,11 +59,12 @@ npx aienvmp snippet agents
|
|
|
59
59
|
aienvmp sync # update env map, light SBOM, ledger, dashboard
|
|
60
60
|
aienvmp context # AI preflight brief
|
|
61
61
|
aienvmp context --json # machine-readable AI decision context + recommended actions
|
|
62
|
-
aienvmp plan # read-only AI action plan
|
|
62
|
+
aienvmp plan # read-only AI action plan with remediation steps
|
|
63
63
|
aienvmp handoff # next-agent handoff summary + recommended actions
|
|
64
64
|
aienvmp intent # record a planned env change
|
|
65
65
|
aienvmp record # record what changed
|
|
66
|
-
aienvmp doctor --ci # strict CI check
|
|
66
|
+
aienvmp doctor --ci # strict CI check for all warnings
|
|
67
|
+
aienvmp doctor --strict security # fail only scoped warnings
|
|
67
68
|
```
|
|
68
69
|
|
|
69
70
|
## Principles
|
|
@@ -72,6 +73,7 @@ aienvmp doctor --ci # strict CI check
|
|
|
72
73
|
- AI-first
|
|
73
74
|
- lightweight
|
|
74
75
|
- one advisory engine, optional enforcement with `doctor --ci`
|
|
76
|
+
- scoped enforcement with `doctor --strict security|policy|coordination|all`
|
|
75
77
|
- non-blocking unless strict mode is explicitly requested
|
|
76
78
|
- security checks are opt-in and read-only
|
|
77
79
|
|
package/action.yml
CHANGED
|
@@ -8,9 +8,9 @@ inputs:
|
|
|
8
8
|
required: false
|
|
9
9
|
default: "."
|
|
10
10
|
strict:
|
|
11
|
-
description: Fail when doctor reports warnings
|
|
11
|
+
description: Fail when doctor reports scoped warnings
|
|
12
12
|
required: false
|
|
13
|
-
default: "
|
|
13
|
+
default: "off"
|
|
14
14
|
write-plan:
|
|
15
15
|
description: Write read-only AI plan artifacts
|
|
16
16
|
required: false
|
|
@@ -35,6 +35,8 @@ runs:
|
|
|
35
35
|
run: |
|
|
36
36
|
if [ "${{ inputs.strict }}" = "true" ]; then
|
|
37
37
|
node "$GITHUB_ACTION_PATH/bin/aienvmp.js" doctor --dir "${{ inputs.directory }}" --ci
|
|
38
|
+
elif [ "${{ inputs.strict }}" != "false" ] && [ "${{ inputs.strict }}" != "off" ]; then
|
|
39
|
+
node "$GITHUB_ACTION_PATH/bin/aienvmp.js" doctor --dir "${{ inputs.directory }}" --strict "${{ inputs.strict }}"
|
|
38
40
|
else
|
|
39
41
|
node "$GITHUB_ACTION_PATH/bin/aienvmp.js" doctor --dir "${{ inputs.directory }}"
|
|
40
42
|
fi
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -98,7 +98,7 @@ Advanced:
|
|
|
98
98
|
aienvmp snippet [agents|claude|gemini] [--write AGENTS.md]
|
|
99
99
|
aienvmp compile [--dir .]
|
|
100
100
|
aienvmp diff [--dir .]
|
|
101
|
-
aienvmp doctor [--dir .] [--json] [--ci]
|
|
101
|
+
aienvmp doctor [--dir .] [--json] [--ci] [--strict security|policy|coordination|all]
|
|
102
102
|
aienvmp dash [--dir .] [--open]
|
|
103
103
|
`);
|
|
104
104
|
}
|
package/src/commands/doctor.js
CHANGED
|
@@ -14,16 +14,18 @@ export async function doctorWorkspace(args) {
|
|
|
14
14
|
const intents = openIntents(await readJsonl(intentsPath(dir)));
|
|
15
15
|
const warnings = [...diagnose(manifest, { timeline, intents }), ...policyWarnings(manifest, policy)];
|
|
16
16
|
const actions = recommendedActions(manifest, { warnings, intents });
|
|
17
|
+
const strict = strictResult(warnings, args);
|
|
17
18
|
if (args.json) {
|
|
18
19
|
console.log(JSON.stringify({
|
|
19
|
-
status: warnings.length ? "warning" : "ok",
|
|
20
|
+
status: strict.fail ? "fail" : warnings.length ? "warning" : "ok",
|
|
20
21
|
trust: manifest.trust || {},
|
|
21
22
|
policy,
|
|
22
23
|
openIntentCount: intents.length,
|
|
23
24
|
warnings,
|
|
24
|
-
recommendedActions: actions
|
|
25
|
+
recommendedActions: actions,
|
|
26
|
+
strict
|
|
25
27
|
}, null, 2));
|
|
26
|
-
if (
|
|
28
|
+
if (strict.fail) {
|
|
27
29
|
process.exitCode = 1;
|
|
28
30
|
}
|
|
29
31
|
return;
|
|
@@ -39,8 +41,39 @@ export async function doctorWorkspace(args) {
|
|
|
39
41
|
for (const item of actions) {
|
|
40
42
|
console.log(`- [${item.priority}] ${item.summary}${item.command ? ` (${item.command})` : ""}`);
|
|
41
43
|
}
|
|
42
|
-
console.log(
|
|
43
|
-
if (
|
|
44
|
+
console.log(`doctor: warnings are non-blocking by default; pass --ci or --strict ${strict.availableScopes.join("|")} to fail automation.`);
|
|
45
|
+
if (strict.fail) {
|
|
44
46
|
process.exitCode = 1;
|
|
45
47
|
}
|
|
46
48
|
}
|
|
49
|
+
|
|
50
|
+
export function strictResult(warnings = [], args = {}) {
|
|
51
|
+
const scope = normalizeStrictScope(args.strict || (args.ci ? "all" : ""));
|
|
52
|
+
const matchedWarnings = scope ? warnings.filter((warning) => warningMatchesScope(warning, scope)) : [];
|
|
53
|
+
return {
|
|
54
|
+
enabled: Boolean(scope),
|
|
55
|
+
scope: scope || "off",
|
|
56
|
+
fail: matchedWarnings.length > 0,
|
|
57
|
+
matchedWarningCodes: matchedWarnings.map((warning) => warning.code),
|
|
58
|
+
availableScopes: ["security", "policy", "coordination", "all"]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeStrictScope(value) {
|
|
63
|
+
if (value === true) return "all";
|
|
64
|
+
const scope = String(value || "").trim().toLowerCase();
|
|
65
|
+
if (!scope || scope === "false" || scope === "off") return "";
|
|
66
|
+
if (["security", "policy", "coordination", "all"].includes(scope)) return scope;
|
|
67
|
+
return "all";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function warningMatchesScope(warning, scope) {
|
|
71
|
+
if (scope === "all") return true;
|
|
72
|
+
return warningScope(warning.code) === scope;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function warningScope(code = "") {
|
|
76
|
+
if (code === "security-vulnerabilities") return "security";
|
|
77
|
+
if (["conflicting-open-intents", "stale-open-intent", "handoff-stale"].includes(code)) return "coordination";
|
|
78
|
+
return "policy";
|
|
79
|
+
}
|
package/src/commands/plan.js
CHANGED
|
@@ -56,6 +56,7 @@ export function buildPlan(manifest, warnings = [], intents = [], policy = {}) {
|
|
|
56
56
|
summary: manifest.security?.summary || { total: 0, critical: 0, high: 0, moderate: 0, low: 0, info: 0 },
|
|
57
57
|
topPackages: manifest.security?.topPackages || []
|
|
58
58
|
},
|
|
59
|
+
remediationSteps: remediationSteps(manifest.security),
|
|
59
60
|
notes: [
|
|
60
61
|
"This plan is read-only.",
|
|
61
62
|
"Ask the user before global runtime, package manager, Docker, or global package changes.",
|
|
@@ -64,6 +65,35 @@ export function buildPlan(manifest, warnings = [], intents = [], policy = {}) {
|
|
|
64
65
|
};
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
function remediationSteps(security = {}) {
|
|
69
|
+
if (!security.enabled) return [];
|
|
70
|
+
return (security.topPackages || []).slice(0, 8).map((pkg) => {
|
|
71
|
+
const fixVersions = Array.isArray(pkg.fixVersions) ? pkg.fixVersions.slice(0, 5) : [];
|
|
72
|
+
const advisories = Array.isArray(pkg.advisories)
|
|
73
|
+
? pkg.advisories.map((item) => ({
|
|
74
|
+
id: item.id || "",
|
|
75
|
+
title: item.title || "",
|
|
76
|
+
url: item.url || "",
|
|
77
|
+
severity: item.severity || pkg.severity || "unknown"
|
|
78
|
+
})).slice(0, 5)
|
|
79
|
+
: [];
|
|
80
|
+
return {
|
|
81
|
+
package: pkg.name,
|
|
82
|
+
scanner: pkg.scanner || "unknown",
|
|
83
|
+
severity: pkg.severity || "unknown",
|
|
84
|
+
fixAvailable: pkg.fixAvailable === true,
|
|
85
|
+
fixVersions,
|
|
86
|
+
advisories,
|
|
87
|
+
steps: [
|
|
88
|
+
"Review the package changelog and advisory details.",
|
|
89
|
+
fixVersions.length ? `Prefer an upgrade path to ${fixVersions.join(", ")} if compatible.` : "Identify a compatible patched version before changing dependencies.",
|
|
90
|
+
"Run project tests after dependency changes.",
|
|
91
|
+
"Record the environment or dependency change with aienvmp record."
|
|
92
|
+
]
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
67
97
|
function reviewGates(warnings, intents, security = {}) {
|
|
68
98
|
const gates = [];
|
|
69
99
|
if (warnings.length) gates.push("Review warnings before changing the environment.");
|
package/src/render.js
CHANGED
|
@@ -182,6 +182,9 @@ export function renderPlan(plan) {
|
|
|
182
182
|
"Review gates:",
|
|
183
183
|
...plan.reviewGates.map((item) => `- ${item}`),
|
|
184
184
|
"",
|
|
185
|
+
"Remediation steps:",
|
|
186
|
+
...(plan.remediationSteps?.length ? plan.remediationSteps.slice(0, 5).flatMap(remediationLines) : ["- none"]),
|
|
187
|
+
"",
|
|
185
188
|
"Warnings:",
|
|
186
189
|
...(plan.warnings.length ? plan.warnings.map((warning) => `- [${warning.code}] ${warning.message}`) : ["- none"]),
|
|
187
190
|
""
|
|
@@ -189,6 +192,15 @@ export function renderPlan(plan) {
|
|
|
189
192
|
return lines.join("\n");
|
|
190
193
|
}
|
|
191
194
|
|
|
195
|
+
function remediationLines(item) {
|
|
196
|
+
const fix = item.fixVersions?.length ? `fix ${item.fixVersions.join(", ")}` : item.fixAvailable ? "fix available" : "review required";
|
|
197
|
+
const advisories = (item.advisories || []).map((advisory) => advisory.id || advisory.title).filter(Boolean).slice(0, 2);
|
|
198
|
+
return [
|
|
199
|
+
`- ${item.package}: ${item.severity}; ${fix}${advisories.length ? `; advisories ${advisories.join(", ")}` : ""}`,
|
|
200
|
+
...item.steps.slice(0, 4).map((step) => ` - ${step}`)
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
|
|
192
204
|
export function renderDashboard(manifest, timeline = [], warnings = [], intents = [], policy = {}) {
|
|
193
205
|
const data = JSON.stringify({ manifest, timeline, warnings, intents, policy });
|
|
194
206
|
return `<!doctype html>
|