aienvmp 0.1.17 → 0.1.19

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.19
4
+
5
+ - Added runtime, package manager, Docker, and coordination environment steps to `aienvmp plan`.
6
+ - Kept environment plans read-only and ask-first for global changes.
7
+ - Improved AI guidance for resolving version drift and multi-agent coordination warnings.
8
+
9
+ ## 0.1.18
10
+
11
+ - Added a dashboard remediation steps card backed by `.aienvmp/plan.json`.
12
+ - Kept dashboard remediation details bounded to package, severity, fix version, and advisory references.
13
+ - Improved human visibility for AI-generated read-only security plans.
14
+
3
15
  ## 0.1.17
4
16
 
5
17
  - Added bounded security remediation steps to `aienvmp plan`.
package/README.md CHANGED
@@ -38,7 +38,7 @@ AIENV.md
38
38
  .aienvmp/timeline.jsonl
39
39
  .aienvmp/plan.json
40
40
  .aienvmp/plan.md
41
- .aienvmp/dashboard.html
41
+ .aienvmp/dashboard.html # includes plan and remediation cards
42
42
  ```
43
43
 
44
44
  Trust states are machine-readable: `observed`, `planned`, `changed`, `review`, `verified`, `stale`.
@@ -59,7 +59,7 @@ 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 with remediation steps
62
+ aienvmp plan # read-only AI action plan for drift and remediation
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aienvmp",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "AI-first environment maps and lightweight runtime SBOMs for shared development machines.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,7 +18,8 @@ export async function dashWorkspace(args) {
18
18
  const policy = await loadPolicy(dir);
19
19
  const warnings = [...diagnose(manifest, { timeline, intents }), ...policyWarnings(manifest, policy)];
20
20
  const planArtifacts = await detectedPlanArtifacts(dir);
21
- const html = renderDashboard({ ...manifest, recommendedActions: recommendedActions(manifest, { warnings, intents }), planArtifacts }, timeline, warnings, intents, policy);
21
+ const planRemediation = await detectedPlanRemediation(dir);
22
+ const html = renderDashboard({ ...manifest, recommendedActions: recommendedActions(manifest, { warnings, intents }), planArtifacts, planRemediation }, timeline, warnings, intents, policy);
22
23
  const out = dashboardPath(dir);
23
24
  await fs.mkdir(path.dirname(out), { recursive: true });
24
25
  await fs.writeFile(out, html, "utf8");
@@ -36,6 +37,17 @@ async function detectedPlanArtifacts(dir) {
36
37
  };
37
38
  }
38
39
 
40
+ async function detectedPlanRemediation(dir) {
41
+ const plan = await readJson(planJsonPath(dir), {});
42
+ return (plan.remediationSteps || []).slice(0, 5).map((item) => ({
43
+ package: item.package || "unknown",
44
+ severity: item.severity || "unknown",
45
+ fixVersions: Array.isArray(item.fixVersions) ? item.fixVersions.slice(0, 3) : [],
46
+ fixAvailable: item.fixAvailable === true,
47
+ advisories: Array.isArray(item.advisories) ? item.advisories.map((advisory) => advisory.id || advisory.title).filter(Boolean).slice(0, 2) : []
48
+ }));
49
+ }
50
+
39
51
  function openFile(file) {
40
52
  if (process.platform === "win32") {
41
53
  execFile("cmd", ["/c", "start", "", file], { windowsHide: true });
@@ -57,6 +57,7 @@ export function buildPlan(manifest, warnings = [], intents = [], policy = {}) {
57
57
  topPackages: manifest.security?.topPackages || []
58
58
  },
59
59
  remediationSteps: remediationSteps(manifest.security),
60
+ environmentSteps: environmentSteps(warnings),
60
61
  notes: [
61
62
  "This plan is read-only.",
62
63
  "Ask the user before global runtime, package manager, Docker, or global package changes.",
@@ -65,6 +66,81 @@ export function buildPlan(manifest, warnings = [], intents = [], policy = {}) {
65
66
  };
66
67
  }
67
68
 
69
+ function environmentSteps(warnings = []) {
70
+ return warnings
71
+ .filter((warning) => warning.code !== "security-vulnerabilities")
72
+ .slice(0, 8)
73
+ .map((warning) => ({
74
+ code: warning.code,
75
+ category: environmentCategory(warning.code),
76
+ summary: warning.message,
77
+ steps: environmentStepLines(warning.code)
78
+ }));
79
+ }
80
+
81
+ function environmentCategory(code = "") {
82
+ if (["conflicting-open-intents", "stale-open-intent", "handoff-stale"].includes(code)) return "coordination";
83
+ if (code.includes("docker")) return "container";
84
+ if (code.includes("lockfile") || code.includes("package-manager")) return "package-manager";
85
+ if (code.includes("node") || code.includes("python") || code.includes("version") || code.includes("runtime")) return "runtime";
86
+ return "environment";
87
+ }
88
+
89
+ function environmentStepLines(code = "") {
90
+ if (code === "node-version-mismatch") return [
91
+ "Read .nvmrc and the detected Node version before changing tools.",
92
+ "Prefer project-local version managers such as nvm, mise, or asdf.",
93
+ "Ask before changing global Node or npm installations.",
94
+ "Run aienvmp sync and record the change after alignment."
95
+ ];
96
+ if (code === "python-version-mismatch") return [
97
+ "Read .python-version and the detected Python version before changing tools.",
98
+ "Prefer project-local virtual environments or version managers.",
99
+ "Ask before changing global Python installations.",
100
+ "Run aienvmp sync and record the change after alignment."
101
+ ];
102
+ if (code === "mixed-node-lockfiles" || code === "package-manager-policy-mismatch") return [
103
+ "Identify the intended package manager from project policy and lockfiles.",
104
+ "Do not delete lockfiles without user approval.",
105
+ "Use one package manager for dependency changes.",
106
+ "Record the chosen package manager policy if it changes."
107
+ ];
108
+ if (code === "python-missing") return [
109
+ "Confirm whether the Python project is active.",
110
+ "Prefer project-local Python setup before global installation.",
111
+ "Ask before installing a global Python runtime.",
112
+ "Run aienvmp sync after setup."
113
+ ];
114
+ if (code === "docker-missing") return [
115
+ "Confirm whether Docker is required for the current task.",
116
+ "Do not change Docker daemon or context without user approval.",
117
+ "Document fallback commands if Docker is unavailable.",
118
+ "Run aienvmp sync after Docker availability changes."
119
+ ];
120
+ if (code === "manifest-stale") return [
121
+ "Run aienvmp sync before environment changes.",
122
+ "Review context again after refresh."
123
+ ];
124
+ if (code === "conflicting-open-intents") return [
125
+ "Review open intents before changing the environment.",
126
+ "Coordinate with the user or other agent.",
127
+ "Resolve stale or superseded intents before proceeding."
128
+ ];
129
+ if (code === "stale-open-intent") return [
130
+ "Confirm whether the old intent is still valid.",
131
+ "Resolve or renew the intent before changing the environment."
132
+ ];
133
+ if (code === "handoff-stale") return [
134
+ "Run aienvmp handoff --record --actor agent:id before the next AI continues.",
135
+ "Review recent changes before new environment work."
136
+ ];
137
+ return [
138
+ "Review the warning before changing environment state.",
139
+ "Ask before global environment changes.",
140
+ "Run aienvmp sync after any accepted change."
141
+ ];
142
+ }
143
+
68
144
  function remediationSteps(security = {}) {
69
145
  if (!security.enabled) return [];
70
146
  return (security.topPackages || []).slice(0, 8).map((pkg) => {
package/src/render.js CHANGED
@@ -185,6 +185,9 @@ export function renderPlan(plan) {
185
185
  "Remediation steps:",
186
186
  ...(plan.remediationSteps?.length ? plan.remediationSteps.slice(0, 5).flatMap(remediationLines) : ["- none"]),
187
187
  "",
188
+ "Environment steps:",
189
+ ...(plan.environmentSteps?.length ? plan.environmentSteps.slice(0, 5).flatMap(environmentLines) : ["- none"]),
190
+ "",
188
191
  "Warnings:",
189
192
  ...(plan.warnings.length ? plan.warnings.map((warning) => `- [${warning.code}] ${warning.message}`) : ["- none"]),
190
193
  ""
@@ -192,6 +195,13 @@ export function renderPlan(plan) {
192
195
  return lines.join("\n");
193
196
  }
194
197
 
198
+ function environmentLines(item) {
199
+ return [
200
+ `- ${item.category}: ${item.summary}`,
201
+ ...item.steps.slice(0, 4).map((step) => ` - ${step}`)
202
+ ];
203
+ }
204
+
195
205
  function remediationLines(item) {
196
206
  const fix = item.fixVersions?.length ? `fix ${item.fixVersions.join(", ")}` : item.fixAvailable ? "fix available" : "review required";
197
207
  const advisories = (item.advisories || []).map((advisory) => advisory.id || advisory.title).filter(Boolean).slice(0, 2);
@@ -278,6 +288,10 @@ const actions=manifest.recommendedActions||[];
278
288
  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>';
279
289
  const plan=manifest.planArtifacts||{};
280
290
  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>';
291
+ const remediation=manifest.planRemediation||[];
292
+ const remediationFix=r=>r.fixVersions?.length?\`fix \${r.fixVersions.join(', ')}\`:(r.fixAvailable?'fix available':'review required');
293
+ const remediationRefs=r=>r.advisories?.length?\` - \${r.advisories.join(', ')}\`:'';
294
+ const remediationHtml=remediation.length?'<div class="timeline">'+remediation.map(r=>\`<div class="event"><time>\${esc(r.severity)}</time><div><b>\${esc(r.package)}</b> \${esc(remediationFix(r))}\${esc(remediationRefs(r))}</div></div>\`).join('')+'</div>':'<div class="okline">No remediation steps in the current plan.</div>';
281
295
  const card=(title,badge,body)=>\`<section class="card"><div class="card-head"><h2>\${title}</h2>\${badge||''}</div>\${body}</section>\`;
282
296
  const reviewRequired=warnings.length>0||intents.length>0;
283
297
  const recentChanges=timeline.slice(-8).length;
@@ -321,6 +335,8 @@ document.getElementById('app').innerHTML=\`
321
335
  <div style="height:14px"></div>
322
336
  \${card('AI Plan Artifacts',plan.markdown||plan.json?'<span class="pill">written</span>':'<span class="pill off">not written</span>',planHtml)}
323
337
  <div style="height:14px"></div>
338
+ \${card('Remediation Steps',remediation.length?'<span class="pill warn">'+remediation.length+' items</span>':'<span class="pill off">none</span>',remediationHtml)}
339
+ <div style="height:14px"></div>
324
340
  \${card('Environment Health',warnings.length?'<span class="pill warn">attention</span>':'<span class="pill">clear</span>',warnHtml)}
325
341
  <div style="height:14px"></div>
326
342
  \${card('Version Policy','<span class="pill">'+entries(policy).length+' rules</span>',policyHtml)}