aienvmp 0.1.14 → 0.1.16
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 +5 -1
- package/action.yml +15 -2
- package/examples/github-action.yml +2 -1
- package/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/commands/dash.js +13 -3
- package/src/commands/doctor.js +38 -5
- package/src/render.js +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.16
|
|
4
|
+
|
|
5
|
+
- Added scoped strict checks with `doctor --strict security|policy|coordination|all`.
|
|
6
|
+
- Kept `doctor --ci` compatible as strict `all`.
|
|
7
|
+
- Updated the GitHub Action `strict` input to support scoped enforcement while keeping advisory mode by default.
|
|
8
|
+
|
|
9
|
+
## 0.1.15
|
|
10
|
+
|
|
11
|
+
- Added dashboard links for written AI plan artifacts.
|
|
12
|
+
- Added `write-plan` support to the GitHub Action so CI can emit read-only plan outputs.
|
|
13
|
+
- Updated the GitHub Action example to show the sync, plan, and doctor flow.
|
|
14
|
+
|
|
3
15
|
## 0.1.14
|
|
4
16
|
|
|
5
17
|
- Added `aienvmp plan` for read-only AI environment action plans.
|
package/README.md
CHANGED
|
@@ -36,6 +36,8 @@ AIENV.md
|
|
|
36
36
|
.aienvmp/manifest.json
|
|
37
37
|
.aienvmp/intents.jsonl
|
|
38
38
|
.aienvmp/timeline.jsonl
|
|
39
|
+
.aienvmp/plan.json
|
|
40
|
+
.aienvmp/plan.md
|
|
39
41
|
.aienvmp/dashboard.html
|
|
40
42
|
```
|
|
41
43
|
|
|
@@ -61,7 +63,8 @@ aienvmp plan # read-only AI action plan, no automatic fixes
|
|
|
61
63
|
aienvmp handoff # next-agent handoff summary + recommended actions
|
|
62
64
|
aienvmp intent # record a planned env change
|
|
63
65
|
aienvmp record # record what changed
|
|
64
|
-
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
|
|
65
68
|
```
|
|
66
69
|
|
|
67
70
|
## Principles
|
|
@@ -70,6 +73,7 @@ aienvmp doctor --ci # strict CI check
|
|
|
70
73
|
- AI-first
|
|
71
74
|
- lightweight
|
|
72
75
|
- one advisory engine, optional enforcement with `doctor --ci`
|
|
76
|
+
- scoped enforcement with `doctor --strict security|policy|coordination|all`
|
|
73
77
|
- non-blocking unless strict mode is explicitly requested
|
|
74
78
|
- security checks are opt-in and read-only
|
|
75
79
|
|
package/action.yml
CHANGED
|
@@ -8,9 +8,13 @@ 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
|
+
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,11 +23,20 @@ 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: |
|
|
25
36
|
if [ "${{ inputs.strict }}" = "true" ]; then
|
|
26
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 }}"
|
|
27
40
|
else
|
|
28
41
|
node "$GITHUB_ACTION_PATH/bin/aienvmp.js" doctor --dir "${{ inputs.directory }}"
|
|
29
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/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 });
|
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/render.js
CHANGED
|
@@ -252,7 +252,7 @@ const sec=manifest.security||{};
|
|
|
252
252
|
const secSummary=sec.summary||{total:0,critical:0,high:0,moderate:0,low:0,info:0};
|
|
253
253
|
const secPackages=sec.topPackages||[];
|
|
254
254
|
const securityFix=p=>p.fixVersions?.length?\`fix \${p.fixVersions.slice(0,3).join(', ')}\`:(p.fixAvailable?'fix available':'review required');
|
|
255
|
-
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(', ')}\`:'';
|
|
256
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>';
|
|
257
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}\`;
|
|
258
258
|
const timelineLabel=t=>t.change?change(t.change):(t.summary||t.action||t.type||'recorded change');
|
|
@@ -264,6 +264,8 @@ const intentsHtml=intents.length?'<div class="timeline">'+intents.slice(-6).reve
|
|
|
264
264
|
const policyHtml=entries(policy).length?\`<table>\${rows(policy)}</table>\`:'<div class="okline">No explicit version policy set.</div>';
|
|
265
265
|
const actions=manifest.recommendedActions||[];
|
|
266
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>';
|
|
267
269
|
const card=(title,badge,body)=>\`<section class="card"><div class="card-head"><h2>\${title}</h2>\${badge||''}</div>\${body}</section>\`;
|
|
268
270
|
const reviewRequired=warnings.length>0||intents.length>0;
|
|
269
271
|
const recentChanges=timeline.slice(-8).length;
|
|
@@ -305,6 +307,8 @@ document.getElementById('app').innerHTML=\`
|
|
|
305
307
|
<aside>
|
|
306
308
|
\${card('Recommended Actions','<span class="pill">'+actions.length+' actions</span>',actionsHtml)}
|
|
307
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>
|
|
308
312
|
\${card('Environment Health',warnings.length?'<span class="pill warn">attention</span>':'<span class="pill">clear</span>',warnHtml)}
|
|
309
313
|
<div style="height:14px"></div>
|
|
310
314
|
\${card('Version Policy','<span class="pill">'+entries(policy).length+' rules</span>',policyHtml)}
|