aienvmp 0.1.13 → 0.1.15
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 -0
- package/action.yml +11 -0
- package/examples/github-action.yml +1 -0
- package/package.json +1 -1
- package/src/cli.js +5 -1
- package/src/commands/dash.js +13 -3
- package/src/commands/plan.js +77 -0
- package/src/paths.js +8 -0
- package/src/render.js +30 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.15
|
|
4
|
+
|
|
5
|
+
- Added dashboard links for written AI plan artifacts.
|
|
6
|
+
- Added `write-plan` support to the GitHub Action so CI can emit read-only plan outputs.
|
|
7
|
+
- Updated the GitHub Action example to show the sync, plan, and doctor flow.
|
|
8
|
+
|
|
9
|
+
## 0.1.14
|
|
10
|
+
|
|
11
|
+
- Added `aienvmp plan` for read-only AI environment action plans.
|
|
12
|
+
- Added optional `aienvmp plan --write` artifacts at `.aienvmp/plan.json` and `.aienvmp/plan.md`.
|
|
13
|
+
- Kept plan output advisory-only with review gates instead of automatic fixes.
|
|
14
|
+
|
|
3
15
|
## 0.1.13
|
|
4
16
|
|
|
5
17
|
- Added `recommendedActions` to AI handoff output.
|
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ Core loop: scan the env, give AI a preflight context, and hand off safe next ste
|
|
|
17
17
|
```bash
|
|
18
18
|
npx aienvmp sync
|
|
19
19
|
npx aienvmp context
|
|
20
|
+
npx aienvmp plan
|
|
20
21
|
npx aienvmp handoff
|
|
21
22
|
npx aienvmp handoff --record --actor agent:codex
|
|
22
23
|
```
|
|
@@ -35,6 +36,8 @@ AIENV.md
|
|
|
35
36
|
.aienvmp/manifest.json
|
|
36
37
|
.aienvmp/intents.jsonl
|
|
37
38
|
.aienvmp/timeline.jsonl
|
|
39
|
+
.aienvmp/plan.json
|
|
40
|
+
.aienvmp/plan.md
|
|
38
41
|
.aienvmp/dashboard.html
|
|
39
42
|
```
|
|
40
43
|
|
|
@@ -56,6 +59,7 @@ npx aienvmp snippet agents
|
|
|
56
59
|
aienvmp sync # update env map, light SBOM, ledger, dashboard
|
|
57
60
|
aienvmp context # AI preflight brief
|
|
58
61
|
aienvmp context --json # machine-readable AI decision context + recommended actions
|
|
62
|
+
aienvmp plan # read-only AI action plan, no automatic fixes
|
|
59
63
|
aienvmp handoff # next-agent handoff summary + recommended actions
|
|
60
64
|
aienvmp intent # record a planned env change
|
|
61
65
|
aienvmp record # record what changed
|
package/action.yml
CHANGED
|
@@ -11,6 +11,10 @@ inputs:
|
|
|
11
11
|
description: Fail when doctor reports warnings
|
|
12
12
|
required: false
|
|
13
13
|
default: "false"
|
|
14
|
+
write-plan:
|
|
15
|
+
description: Write read-only AI plan artifacts
|
|
16
|
+
required: false
|
|
17
|
+
default: "true"
|
|
14
18
|
|
|
15
19
|
runs:
|
|
16
20
|
using: composite
|
|
@@ -19,6 +23,13 @@ runs:
|
|
|
19
23
|
shell: bash
|
|
20
24
|
run: node "$GITHUB_ACTION_PATH/bin/aienvmp.js" sync --dir "${{ inputs.directory }}" --quiet
|
|
21
25
|
|
|
26
|
+
- name: Write AI plan
|
|
27
|
+
shell: bash
|
|
28
|
+
run: |
|
|
29
|
+
if [ "${{ inputs.write-plan }}" = "true" ]; then
|
|
30
|
+
node "$GITHUB_ACTION_PATH/bin/aienvmp.js" plan --dir "${{ inputs.directory }}" --write --json
|
|
31
|
+
fi
|
|
32
|
+
|
|
22
33
|
- name: Doctor
|
|
23
34
|
shell: bash
|
|
24
35
|
run: |
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ import { resolveWorkspace } from "./commands/resolve.js";
|
|
|
11
11
|
import { syncWorkspace } from "./commands/sync.js";
|
|
12
12
|
import { snippetWorkspace } from "./commands/snippet.js";
|
|
13
13
|
import { handoffWorkspace } from "./commands/handoff.js";
|
|
14
|
+
import { planWorkspace } from "./commands/plan.js";
|
|
14
15
|
import { readFileSync } from "node:fs";
|
|
15
16
|
|
|
16
17
|
const commands = new Map([
|
|
@@ -26,7 +27,8 @@ const commands = new Map([
|
|
|
26
27
|
["resolve", resolveWorkspace],
|
|
27
28
|
["sync", syncWorkspace],
|
|
28
29
|
["snippet", snippetWorkspace],
|
|
29
|
-
["handoff", handoffWorkspace]
|
|
30
|
+
["handoff", handoffWorkspace],
|
|
31
|
+
["plan", planWorkspace]
|
|
30
32
|
]);
|
|
31
33
|
|
|
32
34
|
const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
@@ -77,11 +79,13 @@ Usage:
|
|
|
77
79
|
aienvmp sync [--dir .] [--json] [--quiet] [--deep] [--security]
|
|
78
80
|
aienvmp context [--dir .] [--json]
|
|
79
81
|
aienvmp handoff [--dir .] [--json] [--record --actor agent:id]
|
|
82
|
+
aienvmp plan [--dir .] [--json] [--write]
|
|
80
83
|
|
|
81
84
|
Common:
|
|
82
85
|
aienvmp sync update AIENV.md, manifest, ledger, intents, and dashboard
|
|
83
86
|
aienvmp context print the AI preflight brief
|
|
84
87
|
aienvmp handoff print the next-agent handoff summary
|
|
88
|
+
aienvmp plan print a read-only AI environment action plan
|
|
85
89
|
aienvmp snippet print an AGENTS.md pointer snippet
|
|
86
90
|
aienvmp dash regenerate/open the lightweight dashboard
|
|
87
91
|
|
package/src/commands/dash.js
CHANGED
|
@@ -2,9 +2,9 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { diagnose } from "../doctor.js";
|
|
5
|
-
import { readJson } from "../fsutil.js";
|
|
5
|
+
import { exists, readJson } from "../fsutil.js";
|
|
6
6
|
import { openIntents, readJsonl, readTimeline } from "../timeline.js";
|
|
7
|
-
import { dashboardPath, intentsPath, manifestPath, timelinePath, workspaceDir } from "../paths.js";
|
|
7
|
+
import { dashboardPath, intentsPath, manifestPath, planJsonPath, planMdPath, timelinePath, workspaceDir } from "../paths.js";
|
|
8
8
|
import { renderDashboard } from "../render.js";
|
|
9
9
|
import { loadPolicy, policyWarnings } from "../policy.js";
|
|
10
10
|
import { recommendedActions } from "../actions.js";
|
|
@@ -17,7 +17,8 @@ export async function dashWorkspace(args) {
|
|
|
17
17
|
const intents = openIntents(await readJsonl(intentsPath(dir)));
|
|
18
18
|
const policy = await loadPolicy(dir);
|
|
19
19
|
const warnings = [...diagnose(manifest, { timeline, intents }), ...policyWarnings(manifest, policy)];
|
|
20
|
-
const
|
|
20
|
+
const planArtifacts = await detectedPlanArtifacts(dir);
|
|
21
|
+
const html = renderDashboard({ ...manifest, recommendedActions: recommendedActions(manifest, { warnings, intents }), planArtifacts }, timeline, warnings, intents, policy);
|
|
21
22
|
const out = dashboardPath(dir);
|
|
22
23
|
await fs.mkdir(path.dirname(out), { recursive: true });
|
|
23
24
|
await fs.writeFile(out, html, "utf8");
|
|
@@ -26,6 +27,15 @@ export async function dashWorkspace(args) {
|
|
|
26
27
|
return { dashboard: out };
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
async function detectedPlanArtifacts(dir) {
|
|
31
|
+
const json = planJsonPath(dir);
|
|
32
|
+
const markdown = planMdPath(dir);
|
|
33
|
+
return {
|
|
34
|
+
json: await exists(json) ? ".aienvmp/plan.json" : "",
|
|
35
|
+
markdown: await exists(markdown) ? ".aienvmp/plan.md" : ""
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
function openFile(file) {
|
|
30
40
|
if (process.platform === "win32") {
|
|
31
41
|
execFile("cmd", ["/c", "start", "", file], { windowsHide: true });
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { diagnose } from "../doctor.js";
|
|
3
|
+
import { readJson, writeJson } from "../fsutil.js";
|
|
4
|
+
import { loadPolicy, policyWarnings } from "../policy.js";
|
|
5
|
+
import { intentsPath, manifestPath, planJsonPath, planMdPath, timelinePath, workspaceDir } from "../paths.js";
|
|
6
|
+
import { renderPlan } from "../render.js";
|
|
7
|
+
import { recommendedActions } from "../actions.js";
|
|
8
|
+
import { openIntents, readJsonl, readTimeline } from "../timeline.js";
|
|
9
|
+
|
|
10
|
+
export async function planWorkspace(args) {
|
|
11
|
+
const dir = workspaceDir(args);
|
|
12
|
+
const manifest = await readJson(manifestPath(dir));
|
|
13
|
+
if (!manifest) throw new Error("missing manifest; run `aienvmp sync` first");
|
|
14
|
+
const timeline = await readTimeline(timelinePath(dir));
|
|
15
|
+
const intents = openIntents(await readJsonl(intentsPath(dir)));
|
|
16
|
+
const policy = await loadPolicy(dir);
|
|
17
|
+
const warnings = [...diagnose(manifest, { timeline, intents }), ...policyWarnings(manifest, policy)];
|
|
18
|
+
const plan = buildPlan(manifest, warnings, intents, policy);
|
|
19
|
+
|
|
20
|
+
if (args.write) {
|
|
21
|
+
await writeJson(planJsonPath(dir), plan);
|
|
22
|
+
await fs.writeFile(planMdPath(dir), renderPlan(plan), "utf8");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (args.json) {
|
|
26
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
27
|
+
} else {
|
|
28
|
+
console.log(renderPlan(plan));
|
|
29
|
+
}
|
|
30
|
+
return plan;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function buildPlan(manifest, warnings = [], intents = [], policy = {}) {
|
|
34
|
+
const actions = recommendedActions(manifest, { warnings, intents });
|
|
35
|
+
const status = warnings.length || intents.length ? "review-required" : "clear";
|
|
36
|
+
return {
|
|
37
|
+
schemaVersion: 1,
|
|
38
|
+
generatedAt: new Date().toISOString(),
|
|
39
|
+
status,
|
|
40
|
+
workspace: manifest.workspace || {},
|
|
41
|
+
trust: manifest.trust || {},
|
|
42
|
+
policy: {
|
|
43
|
+
node: policy.node || "not set",
|
|
44
|
+
python: policy.python || "not set",
|
|
45
|
+
packageManager: policy.packageManager || "not set",
|
|
46
|
+
runtimeChanges: policy.runtimeChanges || "ask-first",
|
|
47
|
+
globalInstalls: policy.globalInstalls || "ask-first"
|
|
48
|
+
},
|
|
49
|
+
recommendedActions: actions,
|
|
50
|
+
reviewGates: reviewGates(warnings, intents, manifest.security),
|
|
51
|
+
warnings,
|
|
52
|
+
openIntents: intents.slice(-5).reverse(),
|
|
53
|
+
security: {
|
|
54
|
+
mode: manifest.security?.mode || "basic",
|
|
55
|
+
enabled: manifest.security?.enabled === true,
|
|
56
|
+
summary: manifest.security?.summary || { total: 0, critical: 0, high: 0, moderate: 0, low: 0, info: 0 },
|
|
57
|
+
topPackages: manifest.security?.topPackages || []
|
|
58
|
+
},
|
|
59
|
+
notes: [
|
|
60
|
+
"This plan is read-only.",
|
|
61
|
+
"Ask the user before global runtime, package manager, Docker, or global package changes.",
|
|
62
|
+
"Prefer project-local version files and local environments."
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function reviewGates(warnings, intents, security = {}) {
|
|
68
|
+
const gates = [];
|
|
69
|
+
if (warnings.length) gates.push("Review warnings before changing the environment.");
|
|
70
|
+
if (intents.length) gates.push("Resolve or coordinate open intents before overlapping environment changes.");
|
|
71
|
+
const summary = security.summary || {};
|
|
72
|
+
if (security.enabled && (Number(summary.critical || 0) > 0 || Number(summary.high || 0) > 0)) {
|
|
73
|
+
gates.push("Review high or critical vulnerability remediation before dependency or deployment changes.");
|
|
74
|
+
}
|
|
75
|
+
if (!gates.length) gates.push("No blocking gates detected; continue project-local work and record intent before environment changes.");
|
|
76
|
+
return gates;
|
|
77
|
+
}
|
package/src/paths.js
CHANGED
|
@@ -28,6 +28,14 @@ export function dashboardPath(dir) {
|
|
|
28
28
|
return path.join(stateDir(dir), "dashboard.html");
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
export function planJsonPath(dir) {
|
|
32
|
+
return path.join(stateDir(dir), "plan.json");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function planMdPath(dir) {
|
|
36
|
+
return path.join(stateDir(dir), "plan.md");
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
export function aiEnvPath(dir) {
|
|
32
40
|
return path.join(dir, "AIENV.md");
|
|
33
41
|
}
|
package/src/render.js
CHANGED
|
@@ -164,6 +164,31 @@ export function renderHandoff(handoff) {
|
|
|
164
164
|
return lines.join("\n");
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
export function renderPlan(plan) {
|
|
168
|
+
const lines = [
|
|
169
|
+
"# AI Environment Plan",
|
|
170
|
+
"",
|
|
171
|
+
`Status: ${plan.status}`,
|
|
172
|
+
`Generated: ${plan.generatedAt}`,
|
|
173
|
+
`Workspace: ${plan.workspace?.path || "unknown"}`,
|
|
174
|
+
"",
|
|
175
|
+
"Purpose: read-only review plan for AI agents and humans. It does not install, remove, upgrade, downgrade, or lock anything.",
|
|
176
|
+
"",
|
|
177
|
+
"Recommended actions:",
|
|
178
|
+
...(plan.recommendedActions.length
|
|
179
|
+
? plan.recommendedActions.map((item) => `- [${item.priority}] ${item.category}: ${item.summary}${item.command ? ` (${item.command})` : ""}`)
|
|
180
|
+
: ["- none"]),
|
|
181
|
+
"",
|
|
182
|
+
"Review gates:",
|
|
183
|
+
...plan.reviewGates.map((item) => `- ${item}`),
|
|
184
|
+
"",
|
|
185
|
+
"Warnings:",
|
|
186
|
+
...(plan.warnings.length ? plan.warnings.map((warning) => `- [${warning.code}] ${warning.message}`) : ["- none"]),
|
|
187
|
+
""
|
|
188
|
+
];
|
|
189
|
+
return lines.join("\n");
|
|
190
|
+
}
|
|
191
|
+
|
|
167
192
|
export function renderDashboard(manifest, timeline = [], warnings = [], intents = [], policy = {}) {
|
|
168
193
|
const data = JSON.stringify({ manifest, timeline, warnings, intents, policy });
|
|
169
194
|
return `<!doctype html>
|
|
@@ -227,7 +252,7 @@ const sec=manifest.security||{};
|
|
|
227
252
|
const secSummary=sec.summary||{total:0,critical:0,high:0,moderate:0,low:0,info:0};
|
|
228
253
|
const secPackages=sec.topPackages||[];
|
|
229
254
|
const securityFix=p=>p.fixVersions?.length?\`fix \${p.fixVersions.slice(0,3).join(', ')}\`:(p.fixAvailable?'fix available':'review required');
|
|
230
|
-
const securityRefs=p=>p.advisories?.length?\`
|
|
255
|
+
const securityRefs=p=>p.advisories?.length?\` - \${p.advisories.map(a=>a.id||a.title).filter(Boolean).slice(0,2).join(', ')}\`:'';
|
|
231
256
|
const securityHtml=sec.enabled?\`<table><tr><th>Total</th><td><code>\${esc(secSummary.total||0)}</code></td></tr><tr><th>Critical</th><td><code>\${esc(secSummary.critical||0)}</code></td></tr><tr><th>High</th><td><code>\${esc(secSummary.high||0)}</code></td></tr><tr><th>Moderate</th><td><code>\${esc(secSummary.moderate||0)}</code></td></tr><tr><th>Low</th><td><code>\${esc(secSummary.low||0)}</code></td></tr></table>\${secPackages.length?'<div class="timeline">'+secPackages.slice(0,5).map(p=>\`<div class="event"><time>\${esc(p.severity)}</time><div><b>\${esc(p.name)}</b> \${esc(securityFix(p))}\${esc(securityRefs(p))}</div></div>\`).join('')+'</div>':'<div class="okline" style="margin-top:10px">No vulnerable packages reported.</div>'}\`:'<div class="okline">Security scan is off. Run <code>aienvmp sync --security</code> for read-only vulnerability summary.</div>';
|
|
232
257
|
const change=c=>c.type==='changed'?\`\${c.scope} \${c.key}: \${c.before} -> \${c.after}\`:\`\${c.scope} \${c.key}: \${c.type} \${c.after||c.before}\`;
|
|
233
258
|
const timelineLabel=t=>t.change?change(t.change):(t.summary||t.action||t.type||'recorded change');
|
|
@@ -239,6 +264,8 @@ const intentsHtml=intents.length?'<div class="timeline">'+intents.slice(-6).reve
|
|
|
239
264
|
const policyHtml=entries(policy).length?\`<table>\${rows(policy)}</table>\`:'<div class="okline">No explicit version policy set.</div>';
|
|
240
265
|
const actions=manifest.recommendedActions||[];
|
|
241
266
|
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>';
|
|
267
|
+
const plan=manifest.planArtifacts||{};
|
|
268
|
+
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>';
|
|
242
269
|
const card=(title,badge,body)=>\`<section class="card"><div class="card-head"><h2>\${title}</h2>\${badge||''}</div>\${body}</section>\`;
|
|
243
270
|
const reviewRequired=warnings.length>0||intents.length>0;
|
|
244
271
|
const recentChanges=timeline.slice(-8).length;
|
|
@@ -280,6 +307,8 @@ document.getElementById('app').innerHTML=\`
|
|
|
280
307
|
<aside>
|
|
281
308
|
\${card('Recommended Actions','<span class="pill">'+actions.length+' actions</span>',actionsHtml)}
|
|
282
309
|
<div style="height:14px"></div>
|
|
310
|
+
\${card('AI Plan Artifacts',plan.markdown||plan.json?'<span class="pill">written</span>':'<span class="pill off">not written</span>',planHtml)}
|
|
311
|
+
<div style="height:14px"></div>
|
|
283
312
|
\${card('Environment Health',warnings.length?'<span class="pill warn">attention</span>':'<span class="pill">clear</span>',warnHtml)}
|
|
284
313
|
<div style="height:14px"></div>
|
|
285
314
|
\${card('Version Policy','<span class="pill">'+entries(policy).length+' rules</span>',policyHtml)}
|