aienvmp 0.1.37 → 0.1.39
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 +18 -0
- package/README.md +11 -1
- package/package.json +1 -1
- package/src/actions.js +3 -3
- package/src/cli.js +26 -2
- package/src/commands/compile.js +5 -1
- package/src/commands/status.js +2 -0
- package/src/doctor.js +8 -2
- package/src/preflight.js +147 -1
- package/src/render.js +77 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.39
|
|
4
|
+
|
|
5
|
+
- Added a dependency read set to preflight, `AIENV.md`, and the dashboard so agents know which manifests and lockfiles to read before package or security changes.
|
|
6
|
+
- Added an advisory dependency change protocol so agents follow the same intent, refresh, record, and handoff flow for package/security edits.
|
|
7
|
+
- Surfaced the dependency change protocol in `plan.md` so human-readable plans match the AI preflight contract.
|
|
8
|
+
- Pointed security remediation recommendations at the dependency intent workflow instead of a generic context read.
|
|
9
|
+
- Treated dependency records and package/security intents as coordination signals for stale handoff and multi-agent conflict warnings.
|
|
10
|
+
- Added CLI regression coverage for `context --dir <workspace> --json` so remote and CI agents can safely inspect another workspace.
|
|
11
|
+
- Allowed `--dir <workspace>` before the command, so AI and CI agents can use either global-style or command-style workspace targeting.
|
|
12
|
+
|
|
13
|
+
## 0.1.38
|
|
14
|
+
|
|
15
|
+
- Added a 10-second AI quickstart flow to the shared preflight contract and status output.
|
|
16
|
+
- Added preflight intent target recommendations so agents can record runtime, package manager, dependency, Docker, or coordination changes consistently.
|
|
17
|
+
- Surfaced AI intent target recommendations in the dashboard so humans can review the same target guidance.
|
|
18
|
+
- Added the same quickstart and intent target guidance to `AIENV.md` so Markdown-first agents receive the current preflight.
|
|
19
|
+
- Aligned AGENTS/Claude/Gemini pointer snippets with the same status-first, target-aware AI flow.
|
|
20
|
+
|
|
3
21
|
## 0.1.37
|
|
4
22
|
|
|
5
23
|
- Added `lightSbom` to the manifest as an AI-ready package and vulnerability summary.
|
package/README.md
CHANGED
|
@@ -22,6 +22,9 @@ npx aienvmp plan
|
|
|
22
22
|
npx aienvmp handoff
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
10-second AI flow: `aienvmp status --write` -> `aienvmp context --json` -> intent before environment changes.
|
|
26
|
+
Use `--dir <workspace>` before or after the command when an AI or CI process runs outside the target project.
|
|
27
|
+
|
|
25
28
|
Optional deeper read-only checks:
|
|
26
29
|
|
|
27
30
|
```bash
|
|
@@ -49,13 +52,20 @@ AIENV.md
|
|
|
49
52
|
.aienvmp/intents.jsonl
|
|
50
53
|
.aienvmp/timeline.jsonl
|
|
51
54
|
.aienvmp/plan.json
|
|
52
|
-
.aienvmp/plan.md
|
|
55
|
+
.aienvmp/plan.md # read-only plan with dependency protocol
|
|
53
56
|
.aienvmp/dashboard.html # includes dependencies, plan, remediation, and environment cards
|
|
54
57
|
```
|
|
55
58
|
|
|
59
|
+
`AIENV.md` includes the 10-second AI flow and recommended intent targets for Markdown-first agents.
|
|
60
|
+
|
|
56
61
|
Trust states are machine-readable: `observed`, `planned`, `changed`, `review`, `verified`, `stale`.
|
|
57
62
|
`status.json` also lists AI read order, artifact paths, and safe commands.
|
|
58
63
|
`status`, `context`, `plan`, and `handoff` share the same AI preflight contract.
|
|
64
|
+
Preflight also recommends the intent target, so agents do not guess between runtime, package manager, dependency, Docker, or coordination changes.
|
|
65
|
+
It also lists dependency manifests and lockfiles to read before package or security changes.
|
|
66
|
+
Dependency and security changes stay advisory: record dependency intent, refresh with `sync`, then record what changed.
|
|
67
|
+
Dependency records also trigger handoff/coordination warnings for the next agent.
|
|
68
|
+
The dashboard shows the same intent target guidance for human review.
|
|
59
69
|
|
|
60
70
|
AI agents can observe, plan, and record. Only a human or CI should mark environment facts as verified.
|
|
61
71
|
|
package/package.json
CHANGED
package/src/actions.js
CHANGED
|
@@ -51,9 +51,9 @@ function securityActions(security = {}) {
|
|
|
51
51
|
highRisk ? "high" : "medium",
|
|
52
52
|
"security",
|
|
53
53
|
packageHints.length
|
|
54
|
-
? `Review
|
|
55
|
-
: "Review
|
|
56
|
-
"aienvmp
|
|
54
|
+
? `Review dependency read set and protocol before remediation: ${packageHints.join("; ")}.`
|
|
55
|
+
: "Review dependency read set and protocol before vulnerability remediation.",
|
|
56
|
+
"aienvmp intent --actor agent:id --action planned-change --target dependency"
|
|
57
57
|
)];
|
|
58
58
|
}
|
|
59
59
|
|
package/src/cli.js
CHANGED
|
@@ -34,9 +34,10 @@ const commands = new Map([
|
|
|
34
34
|
]);
|
|
35
35
|
|
|
36
36
|
const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
37
|
+
const globalValueOptions = new Set(["--dir"]);
|
|
37
38
|
|
|
38
39
|
export async function main(argv) {
|
|
39
|
-
const
|
|
40
|
+
const { command, rest, globalArgs } = splitCommand(argv);
|
|
40
41
|
if (command === "-v" || command === "--version" || command === "version") {
|
|
41
42
|
console.log(version);
|
|
42
43
|
return;
|
|
@@ -50,7 +51,30 @@ export async function main(argv) {
|
|
|
50
51
|
printUsage();
|
|
51
52
|
throw new Error(`unknown command "${command}"`);
|
|
52
53
|
}
|
|
53
|
-
await run(parseArgs(rest));
|
|
54
|
+
await run({ ...globalArgs, ...parseArgs(rest) });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function splitCommand(argv) {
|
|
58
|
+
const leading = [];
|
|
59
|
+
for (let i = 0; i < argv.length; i++) {
|
|
60
|
+
const arg = argv[i];
|
|
61
|
+
if (!arg.startsWith("--")) {
|
|
62
|
+
return {
|
|
63
|
+
command: arg,
|
|
64
|
+
rest: argv.slice(i + 1),
|
|
65
|
+
globalArgs: parseArgs(leading)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
leading.push(arg);
|
|
69
|
+
if (!arg.includes("=") && globalValueOptions.has(arg) && argv[i + 1] && !argv[i + 1].startsWith("--")) {
|
|
70
|
+
leading.push(argv[++i]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
command: argv[0],
|
|
75
|
+
rest: [],
|
|
76
|
+
globalArgs: parseArgs(leading)
|
|
77
|
+
};
|
|
54
78
|
}
|
|
55
79
|
|
|
56
80
|
export function parseArgs(argv) {
|
package/src/commands/compile.js
CHANGED
|
@@ -5,6 +5,7 @@ import { openIntents, readJsonl, readTimeline } from "../timeline.js";
|
|
|
5
5
|
import { aiEnvPath, intentsPath, manifestPath, timelinePath, workspaceDir } from "../paths.js";
|
|
6
6
|
import { renderAIEnv } from "../render.js";
|
|
7
7
|
import { loadPolicy, policyWarnings } from "../policy.js";
|
|
8
|
+
import { buildPreflight } from "../preflight.js";
|
|
8
9
|
|
|
9
10
|
export async function compileWorkspace(args) {
|
|
10
11
|
const dir = workspaceDir(args);
|
|
@@ -14,7 +15,10 @@ export async function compileWorkspace(args) {
|
|
|
14
15
|
const intents = openIntents(await readJsonl(intentsPath(dir)));
|
|
15
16
|
const policy = await loadPolicy(dir);
|
|
16
17
|
const warnings = [...diagnose(manifest, { timeline, intents }), ...policyWarnings(manifest, policy)];
|
|
17
|
-
const rendered = renderAIEnv(
|
|
18
|
+
const rendered = renderAIEnv({
|
|
19
|
+
...manifest,
|
|
20
|
+
preflight: buildPreflight(manifest, warnings, intents)
|
|
21
|
+
}, timeline, warnings, intents, policy);
|
|
18
22
|
await fs.writeFile(aiEnvPath(dir), rendered, "utf8");
|
|
19
23
|
if (!args.quiet) {
|
|
20
24
|
console.log(`compiled ${aiEnvPath(dir)}`);
|
package/src/commands/status.js
CHANGED
|
@@ -23,6 +23,8 @@ export async function statusWorkspace(args) {
|
|
|
23
23
|
} else if (!args.quiet) {
|
|
24
24
|
console.log(`${output.state}: ${output.summary}`);
|
|
25
25
|
console.log(`next: ${output.nextCommand}`);
|
|
26
|
+
console.log(`ai: ${output.quickstart.readFirst} -> ${output.quickstart.detailCommand}`);
|
|
27
|
+
console.log(`intent: ${output.intentTargets[0]?.command || output.commands.recordIntent}`);
|
|
26
28
|
console.log(`strict: ${output.enforcement.recommendedCommand}`);
|
|
27
29
|
if (artifact) console.log(`status: ${artifact}`);
|
|
28
30
|
}
|
package/src/doctor.js
CHANGED
|
@@ -110,17 +110,23 @@ export function handoffWarnings(timeline = []) {
|
|
|
110
110
|
|
|
111
111
|
function inferTarget(action = "") {
|
|
112
112
|
const normalized = String(action).toLowerCase();
|
|
113
|
-
for (const target of ["node", "python", "docker", "npm", "pnpm", "yarn", "uv", "pip", "pipx"]) {
|
|
113
|
+
for (const target of ["dependency", "node", "python", "docker", "npm", "pnpm", "yarn", "uv", "pip", "pipx"]) {
|
|
114
114
|
if (normalized.includes(target)) return target;
|
|
115
115
|
}
|
|
116
|
+
if (normalized.includes("package") || normalized.includes("lockfile") || normalized.includes("vulnerab")) return "dependency";
|
|
116
117
|
return "";
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
function isEnvironmentChange(item = {}) {
|
|
120
|
-
if (["runtime", "package-manager", "container"].includes(item.change?.scope)) return true;
|
|
121
|
+
if (["runtime", "package-manager", "container", "dependency"].includes(item.change?.scope)) return true;
|
|
121
122
|
if (item.type === "detected-change") return false;
|
|
122
123
|
const text = `${item.type || ""} ${item.target || ""} ${item.summary || ""} ${item.action || ""} ${item.change?.scope || ""} ${item.change?.key || ""}`.toLowerCase();
|
|
123
124
|
return [
|
|
125
|
+
"dependency",
|
|
126
|
+
"dependencies",
|
|
127
|
+
"package",
|
|
128
|
+
"lockfile",
|
|
129
|
+
"vulnerability",
|
|
124
130
|
"runtime",
|
|
125
131
|
"node",
|
|
126
132
|
"python",
|
package/src/preflight.js
CHANGED
|
@@ -8,6 +8,9 @@ export function buildPreflight(manifest = {}, warnings = [], intents = []) {
|
|
|
8
8
|
const actions = recommendedActions(manifest, { warnings, intents });
|
|
9
9
|
const state = decision.reviewRequired ? "review-required" : "clear";
|
|
10
10
|
const topAction = actions[0] || null;
|
|
11
|
+
const intentTargets = recommendedIntentTargets(manifest, warnings, intents);
|
|
12
|
+
const dependencyReadSet = dependencyPreflightReadSet(manifest);
|
|
13
|
+
const dependencyChangeProtocol = dependencyProtocol(manifest, dependencyReadSet);
|
|
11
14
|
return {
|
|
12
15
|
schemaVersion: 1,
|
|
13
16
|
state,
|
|
@@ -43,6 +46,10 @@ export function buildPreflight(manifest = {}, warnings = [], intents = []) {
|
|
|
43
46
|
projectLocalWork: decision.canContinueProjectLocalWork ? "allowed" : "review-first",
|
|
44
47
|
environmentChanges: decision.canChangeEnvironmentWithoutReview ? "allowed" : "intent-and-review-first"
|
|
45
48
|
},
|
|
49
|
+
quickstart: agentQuickstart(decision.reviewRequired),
|
|
50
|
+
intentTargets,
|
|
51
|
+
dependencyReadSet,
|
|
52
|
+
dependencyChangeProtocol,
|
|
46
53
|
artifacts: preflightArtifacts(),
|
|
47
54
|
readOrder: [
|
|
48
55
|
".aienvmp/status.json",
|
|
@@ -58,13 +65,152 @@ export function buildPreflight(manifest = {}, warnings = [], intents = []) {
|
|
|
58
65
|
context: "aienvmp context --json",
|
|
59
66
|
plan: "aienvmp plan --write",
|
|
60
67
|
handoff: "aienvmp handoff --record --actor agent:id",
|
|
61
|
-
recordIntent: "aienvmp intent --actor agent:id --action planned-change"
|
|
68
|
+
recordIntent: intentTargets[0]?.command || "aienvmp intent --actor agent:id --action planned-change"
|
|
62
69
|
},
|
|
63
70
|
topAction,
|
|
64
71
|
nextCommand: topAction?.command || decision.nextCommand
|
|
65
72
|
};
|
|
66
73
|
}
|
|
67
74
|
|
|
75
|
+
function dependencyProtocol(manifest = {}, dependencyReadSet = []) {
|
|
76
|
+
const pmPolicy = manifest.lightSbom?.packageManagerPolicy || {};
|
|
77
|
+
return {
|
|
78
|
+
mode: "advisory",
|
|
79
|
+
appliesWhen: "Before package, lockfile, or vulnerability remediation changes.",
|
|
80
|
+
packageManagerPolicy: pmPolicy.status || "not-detected",
|
|
81
|
+
beforeChange: [
|
|
82
|
+
"Read dependencyReadSet manifests and lockfiles.",
|
|
83
|
+
"Check lightSbom.packageManagerPolicy before choosing npm, pnpm, yarn, pip, uv, or another manager.",
|
|
84
|
+
"Record dependency intent before edits."
|
|
85
|
+
],
|
|
86
|
+
commands: {
|
|
87
|
+
readContext: "aienvmp context --json",
|
|
88
|
+
recordIntent: "aienvmp intent --actor agent:id --action planned-change --target dependency",
|
|
89
|
+
refreshAfterChange: "aienvmp sync",
|
|
90
|
+
recordAfterChange: "aienvmp record --actor agent:id --summary dependency-change --target dependency",
|
|
91
|
+
handoff: "aienvmp handoff --record --actor agent:id"
|
|
92
|
+
},
|
|
93
|
+
mustNotDo: [
|
|
94
|
+
"Do not switch package managers because another lockfile exists without user approval.",
|
|
95
|
+
"Do not delete lockfiles or rewrite dependency manifests only to satisfy a tool preference.",
|
|
96
|
+
"Do not run automatic fix commands without reviewing the dependency read set first."
|
|
97
|
+
],
|
|
98
|
+
readSetCount: dependencyReadSet.length
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function dependencyPreflightReadSet(manifest = {}) {
|
|
103
|
+
const hints = manifest.lightSbom?.dependencyChangeHints || [];
|
|
104
|
+
const readSet = hints.map((hint) => ({
|
|
105
|
+
manifest: hint.manifest,
|
|
106
|
+
ecosystem: hint.ecosystem || "unknown",
|
|
107
|
+
manager: hint.manager || "unknown",
|
|
108
|
+
groups: hint.groups || [],
|
|
109
|
+
lockfiles: (hint.lockfiles || []).map((item) => item.file).filter(Boolean),
|
|
110
|
+
riskPackages: (hint.riskPackages || []).map((item) => item.name).filter(Boolean).slice(0, 5),
|
|
111
|
+
reason: hint.riskPackages?.length
|
|
112
|
+
? "Read before dependency or security remediation; vulnerable packages are linked to this manifest."
|
|
113
|
+
: "Read before dependency changes; this manifest defines project packages."
|
|
114
|
+
}));
|
|
115
|
+
if (readSet.length) return readSet.slice(0, 8);
|
|
116
|
+
|
|
117
|
+
const manifests = manifest.lightSbom?.summary?.manifests || manifest.dependencySnapshot?.manifests || [];
|
|
118
|
+
const lockfiles = (manifest.lightSbom?.summary?.lockfiles || manifest.dependencySnapshot?.lockfiles || [])
|
|
119
|
+
.map((item) => item.file || item)
|
|
120
|
+
.filter(Boolean);
|
|
121
|
+
if (!manifests.length && !lockfiles.length) return [];
|
|
122
|
+
return [{
|
|
123
|
+
manifest: manifests[0] || "",
|
|
124
|
+
ecosystem: "unknown",
|
|
125
|
+
manager: "unknown",
|
|
126
|
+
groups: [],
|
|
127
|
+
lockfiles,
|
|
128
|
+
riskPackages: [],
|
|
129
|
+
reason: "Read detected dependency files before package changes."
|
|
130
|
+
}];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function recommendedIntentTargets(manifest = {}, warnings = [], intents = []) {
|
|
134
|
+
const targets = [];
|
|
135
|
+
for (const warning of warnings) {
|
|
136
|
+
addTarget(targets, targetFromWarning(warning), warning.message || warning.code, warning.code);
|
|
137
|
+
}
|
|
138
|
+
for (const intent of intents) {
|
|
139
|
+
addTarget(targets, intent.target || targetFromText(intent.action), `Open intent from ${intent.actor || "unknown"}.`, "open-intent");
|
|
140
|
+
}
|
|
141
|
+
const pmPolicy = manifest.lightSbom?.packageManagerPolicy;
|
|
142
|
+
if (pmPolicy?.status === "review-required") {
|
|
143
|
+
addTarget(targets, "package-manager", pmPolicy.guidance, "package-manager-policy");
|
|
144
|
+
}
|
|
145
|
+
if (Number(manifest.security?.summary?.total || 0) > 0) {
|
|
146
|
+
addTarget(targets, "dependency", "Security findings are dependency-related; record dependency intent before remediation.", "security");
|
|
147
|
+
}
|
|
148
|
+
if (Number(manifest.dependencySnapshot?.summary?.packages || 0) > 0) {
|
|
149
|
+
addTarget(targets, "dependency", "Dependency manifests detected; use this target before package changes.", "dependency-snapshot");
|
|
150
|
+
}
|
|
151
|
+
if (!targets.length) {
|
|
152
|
+
addTarget(targets, "environment", "Default target when the change affects runtime, dependency, container, or global tool state.", "default");
|
|
153
|
+
}
|
|
154
|
+
return targets.slice(0, 5).map((item) => ({
|
|
155
|
+
...item,
|
|
156
|
+
command: `aienvmp intent --actor agent:id --action planned-change --target ${item.target}`
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function addTarget(targets, target, reason, source) {
|
|
161
|
+
const normalized = normalizeTarget(target);
|
|
162
|
+
if (!normalized) return;
|
|
163
|
+
const existing = targets.find((item) => item.target === normalized);
|
|
164
|
+
if (existing) {
|
|
165
|
+
if (source && !existing.sources.includes(source)) existing.sources.push(source);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
targets.push({ target: normalized, reason: reason || "Environment change target.", sources: source ? [source] : [] });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function targetFromWarning(warning = {}) {
|
|
172
|
+
if (warning.target) return warning.target;
|
|
173
|
+
const code = warning.code || "";
|
|
174
|
+
if (code.includes("node")) return "node";
|
|
175
|
+
if (code.includes("python")) return "python";
|
|
176
|
+
if (code.includes("docker")) return "docker";
|
|
177
|
+
if (code.includes("lockfile") || code.includes("package-manager")) return "package-manager";
|
|
178
|
+
if (code.includes("security")) return "dependency";
|
|
179
|
+
if (code.includes("intent") || code.includes("handoff")) return "coordination";
|
|
180
|
+
return targetFromText(`${warning.message || ""} ${code}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function targetFromText(text = "") {
|
|
184
|
+
const normalized = String(text).toLowerCase();
|
|
185
|
+
for (const target of ["node", "python", "docker", "package-manager", "dependency", "npm", "pnpm", "yarn", "uv", "pip", "pipx"]) {
|
|
186
|
+
if (normalized.includes(target)) return target;
|
|
187
|
+
}
|
|
188
|
+
if (normalized.includes("package manager") || normalized.includes("lockfile")) return "package-manager";
|
|
189
|
+
if (normalized.includes("vulnerab") || normalized.includes("package")) return "dependency";
|
|
190
|
+
return "";
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function normalizeTarget(target = "") {
|
|
194
|
+
const normalized = String(target).trim().toLowerCase();
|
|
195
|
+
if (["npm", "pnpm", "yarn"].includes(normalized)) return "package-manager";
|
|
196
|
+
if (["pip", "pipx", "uv"].includes(normalized)) return "python";
|
|
197
|
+
return normalized;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function agentQuickstart(reviewRequired) {
|
|
201
|
+
return {
|
|
202
|
+
label: "10-second AI flow",
|
|
203
|
+
readFirst: "aienvmp status --write",
|
|
204
|
+
detailCommand: "aienvmp context --json",
|
|
205
|
+
beforeEnvironmentChange: "aienvmp intent --actor agent:id --action planned-change --target <runtime|package-manager|docker|dependency>",
|
|
206
|
+
afterEnvironmentChange: "aienvmp sync && aienvmp record --actor agent:id --summary what-changed",
|
|
207
|
+
handoff: "aienvmp handoff --record --actor agent:id",
|
|
208
|
+
rule: reviewRequired
|
|
209
|
+
? "Review warnings or open intents before environment changes; project-local work may continue."
|
|
210
|
+
: "Continue project-local work; record intent before environment changes."
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
68
214
|
export function preflightArtifacts() {
|
|
69
215
|
return {
|
|
70
216
|
status: ".aienvmp/status.json",
|
package/src/render.js
CHANGED
|
@@ -16,6 +16,7 @@ export function renderAIEnv(manifest, timeline = [], warnings = [], intents = []
|
|
|
16
16
|
lines.push("5. After environment changes, run `aienvmp sync`.");
|
|
17
17
|
lines.push("6. Record what changed with `aienvmp record --actor agent:id --summary what-changed`.");
|
|
18
18
|
lines.push("7. At handoff, run `aienvmp handoff --record --actor agent:id`.", "");
|
|
19
|
+
lines.push(...preflightLines(manifest.preflight), "");
|
|
19
20
|
lines.push("## Current Policy", "");
|
|
20
21
|
lines.push(...policyLines(policy));
|
|
21
22
|
lines.push("- Enforcement: non-blocking by default; warnings require review but do not lock the machine.");
|
|
@@ -69,6 +70,50 @@ export function renderAIEnv(manifest, timeline = [], warnings = [], intents = []
|
|
|
69
70
|
return lines.join("\n");
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
function preflightLines(preflight = {}) {
|
|
74
|
+
const quickstart = preflight.quickstart;
|
|
75
|
+
const targets = preflight.intentTargets || [];
|
|
76
|
+
const lines = ["## 10-Second AI Flow", ""];
|
|
77
|
+
if (quickstart) {
|
|
78
|
+
lines.push(`- Read first: \`${quickstart.readFirst}\``);
|
|
79
|
+
lines.push(`- Detail: \`${quickstart.detailCommand}\``);
|
|
80
|
+
lines.push(`- Before env change: \`${quickstart.beforeEnvironmentChange}\``);
|
|
81
|
+
lines.push(`- After env change: \`${quickstart.afterEnvironmentChange}\``);
|
|
82
|
+
lines.push(`- Handoff: \`${quickstart.handoff}\``);
|
|
83
|
+
lines.push(`- Rule: ${quickstart.rule}`);
|
|
84
|
+
} else {
|
|
85
|
+
lines.push("- Run `aienvmp status --write`, then `aienvmp context --json` before environment changes.");
|
|
86
|
+
}
|
|
87
|
+
lines.push("", "## Recommended Intent Targets", "");
|
|
88
|
+
if (targets.length) {
|
|
89
|
+
for (const target of targets.slice(0, 5)) {
|
|
90
|
+
lines.push(`- ${target.target}: \`${target.command}\` - ${target.reason}`);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
lines.push("- environment: `aienvmp intent --actor agent:id --action planned-change --target environment`");
|
|
94
|
+
}
|
|
95
|
+
const dependencyReadSet = preflight.dependencyReadSet || [];
|
|
96
|
+
if (dependencyReadSet.length) {
|
|
97
|
+
lines.push("", "## Dependency Read Set", "");
|
|
98
|
+
for (const item of dependencyReadSet.slice(0, 5)) {
|
|
99
|
+
const files = [item.manifest, ...(item.lockfiles || [])].filter(Boolean).join(", ");
|
|
100
|
+
const risk = item.riskPackages?.length ? `; risk: ${item.riskPackages.join(", ")}` : "";
|
|
101
|
+
lines.push(`- ${files}: ${item.ecosystem}/${item.manager}${risk} - ${item.reason}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const dependencyProtocol = preflight.dependencyChangeProtocol;
|
|
105
|
+
if (dependencyProtocol) {
|
|
106
|
+
lines.push("", "## Dependency Change Protocol", "");
|
|
107
|
+
lines.push(`- Mode: ${dependencyProtocol.mode}`);
|
|
108
|
+
lines.push(`- Package manager policy: ${dependencyProtocol.packageManagerPolicy}`);
|
|
109
|
+
lines.push(`- Intent: \`${dependencyProtocol.commands.recordIntent}\``);
|
|
110
|
+
lines.push(`- After change: \`${dependencyProtocol.commands.refreshAfterChange}\` then \`${dependencyProtocol.commands.recordAfterChange}\``);
|
|
111
|
+
lines.push(`- Handoff: \`${dependencyProtocol.commands.handoff}\``);
|
|
112
|
+
for (const item of dependencyProtocol.mustNotDo.slice(0, 3)) lines.push(`- Must not: ${item}`);
|
|
113
|
+
}
|
|
114
|
+
return lines;
|
|
115
|
+
}
|
|
116
|
+
|
|
72
117
|
export function renderAgentPointer(target = "agents") {
|
|
73
118
|
const label = target === "claude" ? "Claude" : target === "gemini" ? "Gemini" : "AI agents";
|
|
74
119
|
return `## aienvmp Environment Map
|
|
@@ -77,12 +122,12 @@ ${label} should use \`aienvmp\` as the workspace environment source of truth.
|
|
|
77
122
|
|
|
78
123
|
Before changing runtimes, package managers, Docker settings, global packages, or environment policy:
|
|
79
124
|
|
|
80
|
-
1. Run \`aienvmp
|
|
81
|
-
2.
|
|
82
|
-
3.
|
|
83
|
-
4.
|
|
84
|
-
5.
|
|
85
|
-
6.
|
|
125
|
+
1. Run \`aienvmp status --write\`.
|
|
126
|
+
2. Run \`aienvmp context --json\` for details.
|
|
127
|
+
3. Read \`AIENV.md\`.
|
|
128
|
+
4. If status or context says \`review-required\`, ask the user before changing the environment.
|
|
129
|
+
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 sync\` and \`aienvmp record --actor agent:id --summary what-changed\`.
|
|
86
131
|
7. At handoff, run \`aienvmp handoff --record --actor agent:id\`.
|
|
87
132
|
|
|
88
133
|
\`aienvmp\` does not replace this instruction file. It provides the live env map, lightweight runtime SBOM, intent log, timeline, and dashboard.`;
|
|
@@ -192,6 +237,9 @@ export function renderPlan(plan) {
|
|
|
192
237
|
"Review gates:",
|
|
193
238
|
...plan.reviewGates.map((item) => `- ${item}`),
|
|
194
239
|
"",
|
|
240
|
+
"Dependency protocol:",
|
|
241
|
+
...dependencyProtocolPlanLines(plan.preflight?.dependencyChangeProtocol),
|
|
242
|
+
"",
|
|
195
243
|
"Remediation steps:",
|
|
196
244
|
...(plan.remediationSteps?.length ? plan.remediationSteps.slice(0, 5).flatMap(remediationLines) : ["- none"]),
|
|
197
245
|
"",
|
|
@@ -205,6 +253,17 @@ export function renderPlan(plan) {
|
|
|
205
253
|
return lines.join("\n");
|
|
206
254
|
}
|
|
207
255
|
|
|
256
|
+
function dependencyProtocolPlanLines(protocol = {}) {
|
|
257
|
+
if (!protocol.commands) return ["- none"];
|
|
258
|
+
return [
|
|
259
|
+
`- Mode: ${protocol.mode || "advisory"}`,
|
|
260
|
+
`- Package manager policy: ${protocol.packageManagerPolicy || "not-detected"}`,
|
|
261
|
+
`- Intent: ${protocol.commands.recordIntent}`,
|
|
262
|
+
`- After change: ${protocol.commands.refreshAfterChange}; ${protocol.commands.recordAfterChange}`,
|
|
263
|
+
...(protocol.mustNotDo || []).slice(0, 3).map((item) => `- Must not: ${item}`)
|
|
264
|
+
];
|
|
265
|
+
}
|
|
266
|
+
|
|
208
267
|
function environmentLines(item) {
|
|
209
268
|
return [
|
|
210
269
|
`- ${item.category}: ${item.summary}`,
|
|
@@ -326,6 +385,12 @@ const ciReadinessHtml=ciReadiness.length?'<table>'+ciReadiness.map(s=>\`<tr><th>
|
|
|
326
385
|
const enforcementProfile=manifest.preflight?.enforcementProfile||{};
|
|
327
386
|
const strictCommands=enforcementProfile.strictCommands||[];
|
|
328
387
|
const enforcementHtml=\`<table><tr><th>Default</th><td><code>\${esc(enforcementProfile.defaultMode||'advisory')}</code></td></tr><tr><th>Local</th><td>\${esc(enforcementProfile.localOperation||'non-blocking')}</td></tr><tr><th>Strict</th><td>\${esc(enforcementProfile.strictUse||'CI or explicit checks only')}</td></tr><tr><th>Recommended</th><td><code>\${esc(enforcementProfile.recommendedStrictCommand||'aienvmp doctor --strict all')}</code></td></tr></table><div class="timeline">\${strictCommands.slice(0,4).map(cmd=>\`<div class="event"><time>CI</time><div><code>\${esc(cmd)}</code></div></div>\`).join('')}</div><div class="path">\${esc(enforcementProfile.reason||'Warnings stay advisory unless strict mode is requested.')}</div>\`;
|
|
388
|
+
const intentTargets=manifest.preflight?.intentTargets||[];
|
|
389
|
+
const intentTargetsHtml=intentTargets.length?'<div class="timeline">'+intentTargets.slice(0,5).map(t=>\`<div class="event"><time>\${esc(t.target)}</time><div><b>\${esc(t.target)}</b> \${esc(t.reason||'Record this target before environment changes.')}\${t.command?\`<div class="path">\${esc(t.command)}</div>\`:''}</div></div>\`).join('')+'</div>':'<div class="okline">No specific target recommendation. Use <code>aienvmp intent --actor agent:id --action planned-change</code>.</div>';
|
|
390
|
+
const dependencyReadSet=manifest.preflight?.dependencyReadSet||[];
|
|
391
|
+
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>';
|
|
392
|
+
const dependencyProtocol=manifest.preflight?.dependencyChangeProtocol||{};
|
|
393
|
+
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.refreshAfterChange)+'</code> then <code>'+esc(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>';
|
|
329
394
|
const card=(title,badge,body)=>\`<section class="card"><div class="card-head"><h2>\${title}</h2>\${badge||''}</div>\${body}</section>\`;
|
|
330
395
|
const reviewRequired=warnings.length>0||intents.length>0;
|
|
331
396
|
const recentChanges=timeline.slice(-8).length;
|
|
@@ -369,6 +434,12 @@ document.getElementById('app').innerHTML=\`
|
|
|
369
434
|
<aside>
|
|
370
435
|
\${card('Recommended Actions','<span class="pill">'+actions.length+' actions</span>',actionsHtml)}
|
|
371
436
|
<div style="height:14px"></div>
|
|
437
|
+
\${card('AI Intent Targets','<span class="pill">'+intentTargets.length+' targets</span>',intentTargetsHtml)}
|
|
438
|
+
<div style="height:14px"></div>
|
|
439
|
+
\${card('Dependency Read Set','<span class="pill">'+dependencyReadSet.length+' files</span>',dependencyReadSetHtml)}
|
|
440
|
+
<div style="height:14px"></div>
|
|
441
|
+
\${card('Dependency Protocol','<span class="pill">'+(dependencyProtocol.mode||'advisory')+'</span>',dependencyProtocolHtml)}
|
|
442
|
+
<div style="height:14px"></div>
|
|
372
443
|
\${card('AI Plan Artifacts',plan.markdown||plan.json?'<span class="pill">written</span>':'<span class="pill off">not written</span>',planHtml)}
|
|
373
444
|
<div style="height:14px"></div>
|
|
374
445
|
\${card('Remediation Steps',remediation.length?'<span class="pill warn">'+remediation.length+' items</span>':'<span class="pill off">none</span>',remediationHtml)}
|