aienvmp 0.1.38 → 0.1.40
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 +19 -0
- package/README.md +48 -77
- package/package.json +1 -1
- package/src/actions.js +3 -3
- package/src/cli.js +26 -2
- package/src/commands/handoff.js +17 -1
- package/src/commands/status.js +1 -0
- package/src/doctor.js +8 -2
- package/src/preflight.js +115 -0
- package/src/render.js +68 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.40
|
|
4
|
+
|
|
5
|
+
- Added dependency handoff summaries so the next AI receives dependency read-set and protocol guidance directly in `handoff`.
|
|
6
|
+
- Added a compact `nextAgent` hint to status/preflight JSON for safer AI-to-AI continuation.
|
|
7
|
+
- Added target-level open intent coordination summaries so agents can detect dependency conflicts without parsing logs.
|
|
8
|
+
- Shortened the README around the 10-second AI flow, generated outputs, and advisory contract.
|
|
9
|
+
- Updated the dashboard handoff card with next-agent read hints, dependency files, and conflict targets.
|
|
10
|
+
- Printed the next-agent handoff command in the plain `status` output.
|
|
11
|
+
|
|
12
|
+
## 0.1.39
|
|
13
|
+
|
|
14
|
+
- 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.
|
|
15
|
+
- Added an advisory dependency change protocol so agents follow the same intent, refresh, record, and handoff flow for package/security edits.
|
|
16
|
+
- Surfaced the dependency change protocol in `plan.md` so human-readable plans match the AI preflight contract.
|
|
17
|
+
- Pointed security remediation recommendations at the dependency intent workflow instead of a generic context read.
|
|
18
|
+
- Treated dependency records and package/security intents as coordination signals for stale handoff and multi-agent conflict warnings.
|
|
19
|
+
- Added CLI regression coverage for `context --dir <workspace> --json` so remote and CI agents can safely inspect another workspace.
|
|
20
|
+
- Allowed `--dir <workspace>` before the command, so AI and CI agents can use either global-style or command-style workspace targeting.
|
|
21
|
+
|
|
3
22
|
## 0.1.38
|
|
4
23
|
|
|
5
24
|
- Added a 10-second AI quickstart flow to the shared preflight contract and status output.
|
package/README.md
CHANGED
|
@@ -6,119 +6,92 @@
|
|
|
6
6
|
|
|
7
7
|
**AI Environment Map.**
|
|
8
8
|
|
|
9
|
-
`aienvmp` is
|
|
9
|
+
`aienvmp` is a lightweight env map and light SBOM for shared AI coding machines.
|
|
10
10
|
|
|
11
|
-
It helps Codex, Claude, Gemini, and humans avoid silent
|
|
11
|
+
It helps Codex, Claude, Gemini, and humans avoid silent runtime, package manager, dependency, Docker, and security drift.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## Quick Start
|
|
13
|
+
## 10-Second Use
|
|
16
14
|
|
|
17
15
|
```bash
|
|
18
16
|
npx aienvmp sync
|
|
19
17
|
npx aienvmp status
|
|
20
|
-
npx aienvmp context
|
|
21
|
-
npx aienvmp plan
|
|
22
|
-
npx aienvmp handoff
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
10-second AI flow: `aienvmp status --write` -> `aienvmp context --json` -> intent before environment changes.
|
|
26
|
-
|
|
27
|
-
Optional deeper read-only checks:
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
npx aienvmp sync --deep
|
|
31
|
-
npx aienvmp sync --security
|
|
18
|
+
npx aienvmp context --json
|
|
32
19
|
```
|
|
33
20
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Tell each agent to read `aienvmp context --json` before environment changes.
|
|
21
|
+
Before environment changes:
|
|
37
22
|
|
|
38
23
|
```bash
|
|
39
|
-
npx aienvmp
|
|
40
|
-
npx aienvmp
|
|
41
|
-
npx aienvmp record --actor agent:codex --summary "updated .nvmrc" --target node
|
|
24
|
+
npx aienvmp intent --actor agent:codex --action "change dependency" --target dependency
|
|
25
|
+
npx aienvmp record --actor agent:codex --summary "dependency-change" --target dependency
|
|
42
26
|
npx aienvmp handoff --record --actor agent:codex
|
|
43
27
|
```
|
|
44
28
|
|
|
45
|
-
|
|
29
|
+
Use `--dir <workspace>` when AI or CI runs outside the target project.
|
|
30
|
+
|
|
31
|
+
## What It Creates
|
|
46
32
|
|
|
47
33
|
```text
|
|
48
|
-
AIENV.md
|
|
49
|
-
.aienvmp/
|
|
50
|
-
.aienvmp/
|
|
51
|
-
.aienvmp/intents.jsonl
|
|
52
|
-
.aienvmp/timeline.jsonl
|
|
53
|
-
.aienvmp/plan.
|
|
54
|
-
.aienvmp/
|
|
55
|
-
.aienvmp/dashboard.html # includes dependencies, plan, remediation, and environment cards
|
|
34
|
+
AIENV.md # Markdown env map for AI agents
|
|
35
|
+
.aienvmp/status.json # first AI read: clear/review, next command, nextAgent hint
|
|
36
|
+
.aienvmp/manifest.json # runtime map + light SBOM
|
|
37
|
+
.aienvmp/intents.jsonl # planned env changes
|
|
38
|
+
.aienvmp/timeline.jsonl # append-only change ledger
|
|
39
|
+
.aienvmp/plan.md # read-only action plan
|
|
40
|
+
.aienvmp/dashboard.html # human dashboard
|
|
56
41
|
```
|
|
57
42
|
|
|
58
|
-
|
|
43
|
+
## AI Contract
|
|
59
44
|
|
|
60
|
-
|
|
61
|
-
`status.json`
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
AI agents can observe, plan, and record. Only a human or CI should mark environment facts as verified.
|
|
45
|
+
- `status`, `context`, `plan`, and `handoff` share one preflight contract.
|
|
46
|
+
- `status.json.nextAgent` tells the next AI what to read and whether to review first.
|
|
47
|
+
- `dependencyReadSet` lists manifests and lockfiles before package or security changes.
|
|
48
|
+
- `coordination.conflictTargets` shows where multiple agents are planning changes.
|
|
49
|
+
- `handoff` carries dependency read-set and protocol guidance for the next AI.
|
|
50
|
+
- Everything is advisory by default; strict failure is opt-in with `doctor --strict`.
|
|
67
51
|
|
|
68
52
|
## Agent Files
|
|
69
53
|
|
|
70
|
-
`aienvmp` does not replace AGENTS.md. It gives
|
|
54
|
+
`aienvmp` does not replace AGENTS.md, CLAUDE.md, or GEMINI.md. It gives them a live environment source of truth.
|
|
71
55
|
|
|
72
56
|
```bash
|
|
73
57
|
npx aienvmp snippet agents
|
|
58
|
+
npx aienvmp snippet claude
|
|
59
|
+
npx aienvmp snippet gemini
|
|
74
60
|
```
|
|
75
61
|
|
|
76
|
-
##
|
|
62
|
+
## Commands
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
aienvmp sync # update env map, status, ledger, dashboard
|
|
66
|
+
aienvmp status --write # refresh compact AI status
|
|
67
|
+
aienvmp context --json # AI decision contract
|
|
68
|
+
aienvmp plan --write # read-only action plan
|
|
69
|
+
aienvmp handoff --record # next-agent summary
|
|
70
|
+
aienvmp intent # record planned env change
|
|
71
|
+
aienvmp record # record what changed
|
|
72
|
+
aienvmp doctor --strict security|policy|coordination|all
|
|
73
|
+
```
|
|
77
74
|
|
|
78
|
-
|
|
79
|
-
CI also writes `.aienvmp/status.json` for a compact AI-readable result.
|
|
80
|
-
The dashboard shows which strict scopes are CI-ready before you enforce them.
|
|
75
|
+
## CI
|
|
81
76
|
|
|
82
77
|
```yaml
|
|
83
78
|
- uses: soovwv/aienvmp@main
|
|
84
79
|
with:
|
|
85
80
|
write-status: "true"
|
|
86
81
|
write-plan: "true"
|
|
87
|
-
strict: "off"
|
|
82
|
+
strict: "off"
|
|
88
83
|
```
|
|
89
84
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
aienvmp sync # update env map, light SBOM, status, ledger, dashboard
|
|
94
|
-
aienvmp status --write # refresh .aienvmp/status.json only
|
|
95
|
-
aienvmp context # AI preflight brief
|
|
96
|
-
aienvmp context --json # AI decision contract + actions + compact step summary
|
|
97
|
-
aienvmp plan # read-only AI action plan using the same decision contract
|
|
98
|
-
aienvmp handoff # next-agent handoff summary using the same decision contract
|
|
99
|
-
aienvmp intent # record a planned env change
|
|
100
|
-
aienvmp record # record what changed
|
|
101
|
-
aienvmp doctor --ci # strict CI check for all warnings
|
|
102
|
-
aienvmp doctor --strict security # fail only scoped warnings
|
|
103
|
-
```
|
|
85
|
+
See [examples/github-action.yml](examples/github-action.yml).
|
|
104
86
|
|
|
105
87
|
## Principles
|
|
106
88
|
|
|
107
|
-
- simple
|
|
108
|
-
- AI-first
|
|
109
|
-
- lightweight
|
|
110
|
-
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
- context and plan expose suggested strict scopes for CI
|
|
114
|
-
- dashboard and preflight explain advisory default vs optional strict mode
|
|
115
|
-
- non-blocking unless strict mode is explicitly requested
|
|
116
|
-
- security checks are opt-in and read-only
|
|
117
|
-
- light SBOM is generated from project files and optional scanner summaries
|
|
118
|
-
- dependency change hints point AI agents to the relevant manifest before edits
|
|
119
|
-
- lockfiles are detected read-only and shown before dependency edits
|
|
120
|
-
- package manager policy is inferred from lockfiles to avoid accidental npm/pnpm/yarn drift
|
|
121
|
-
- dashboard mirrors AI-facing SBOM hints so humans can review the same dependency context
|
|
89
|
+
- simple commands
|
|
90
|
+
- AI-first outputs
|
|
91
|
+
- lightweight, read-only scanning
|
|
92
|
+
- light SBOM from project files
|
|
93
|
+
- advisory locally, strict only when requested
|
|
94
|
+
- humans or CI verify; AI agents observe, plan, record, and hand off
|
|
122
95
|
|
|
123
96
|
## Development
|
|
124
97
|
|
|
@@ -128,8 +101,6 @@ npm run smoke
|
|
|
128
101
|
npm pack --dry-run
|
|
129
102
|
```
|
|
130
103
|
|
|
131
|
-
## Links
|
|
132
|
-
|
|
133
104
|
[Roadmap](ROADMAP.md) / [Security](SECURITY.md) / [Troubleshooting](TROUBLESHOOTING.md) / [Bugfix Log](BUGFIXES.md) / [Contributing](CONTRIBUTING.md)
|
|
134
105
|
|
|
135
106
|
Apache-2.0
|
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/handoff.js
CHANGED
|
@@ -46,11 +46,12 @@ async function recordHandoff(file, handoff, actor) {
|
|
|
46
46
|
export function buildHandoff(manifest, timeline = [], warnings = [], intents = [], policy = {}) {
|
|
47
47
|
const reviewRequired = warnings.length > 0 || intents.length > 0;
|
|
48
48
|
const actions = recommendedActions(manifest, { warnings, intents });
|
|
49
|
+
const preflight = buildPreflight(manifest, warnings, intents);
|
|
49
50
|
return {
|
|
50
51
|
status: reviewRequired ? "review-required" : "clear",
|
|
51
52
|
trust: manifest.trust || {},
|
|
52
53
|
schemaVersion: manifest.schemaVersion || 1,
|
|
53
|
-
preflight
|
|
54
|
+
preflight,
|
|
54
55
|
decision: aiDecision(warnings, intents),
|
|
55
56
|
workspace: manifest.workspace,
|
|
56
57
|
safeRuntime: {
|
|
@@ -60,6 +61,7 @@ export function buildHandoff(manifest, timeline = [], warnings = [], intents = [
|
|
|
60
61
|
},
|
|
61
62
|
inventory: inventorySummary(manifest.inventory),
|
|
62
63
|
security: securitySummary(manifest.security),
|
|
64
|
+
dependencyHandoff: dependencyHandoffSummary(preflight),
|
|
63
65
|
policy: {
|
|
64
66
|
node: policy.node || "not set",
|
|
65
67
|
python: policy.python || "not set",
|
|
@@ -82,6 +84,20 @@ export function buildHandoff(manifest, timeline = [], warnings = [], intents = [
|
|
|
82
84
|
};
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
function dependencyHandoffSummary(preflight = {}) {
|
|
88
|
+
const protocol = preflight.dependencyChangeProtocol || {};
|
|
89
|
+
return {
|
|
90
|
+
readSet: (preflight.dependencyReadSet || []).slice(0, 5),
|
|
91
|
+
protocol: {
|
|
92
|
+
mode: protocol.mode || "advisory",
|
|
93
|
+
packageManagerPolicy: protocol.packageManagerPolicy || "not-detected",
|
|
94
|
+
recordIntent: protocol.commands?.recordIntent || "aienvmp intent --actor agent:id --action planned-change --target dependency",
|
|
95
|
+
recordAfterChange: protocol.commands?.recordAfterChange || "aienvmp record --actor agent:id --summary dependency-change --target dependency",
|
|
96
|
+
handoff: protocol.commands?.handoff || "aienvmp handoff --record --actor agent:id"
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
85
101
|
function securitySummary(security = {}) {
|
|
86
102
|
return {
|
|
87
103
|
mode: security.mode || "basic",
|
package/src/commands/status.js
CHANGED
|
@@ -25,6 +25,7 @@ export async function statusWorkspace(args) {
|
|
|
25
25
|
console.log(`next: ${output.nextCommand}`);
|
|
26
26
|
console.log(`ai: ${output.quickstart.readFirst} -> ${output.quickstart.detailCommand}`);
|
|
27
27
|
console.log(`intent: ${output.intentTargets[0]?.command || output.commands.recordIntent}`);
|
|
28
|
+
console.log(`handoff: ${output.nextAgent.handoffCommand}`);
|
|
28
29
|
console.log(`strict: ${output.enforcement.recommendedCommand}`);
|
|
29
30
|
if (artifact) console.log(`status: ${artifact}`);
|
|
30
31
|
}
|
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
|
@@ -9,6 +9,9 @@ export function buildPreflight(manifest = {}, warnings = [], intents = []) {
|
|
|
9
9
|
const state = decision.reviewRequired ? "review-required" : "clear";
|
|
10
10
|
const topAction = actions[0] || null;
|
|
11
11
|
const intentTargets = recommendedIntentTargets(manifest, warnings, intents);
|
|
12
|
+
const dependencyReadSet = dependencyPreflightReadSet(manifest);
|
|
13
|
+
const dependencyChangeProtocol = dependencyProtocol(manifest, dependencyReadSet);
|
|
14
|
+
const coordination = coordinationSummary(intents);
|
|
12
15
|
return {
|
|
13
16
|
schemaVersion: 1,
|
|
14
17
|
state,
|
|
@@ -45,7 +48,11 @@ export function buildPreflight(manifest = {}, warnings = [], intents = []) {
|
|
|
45
48
|
environmentChanges: decision.canChangeEnvironmentWithoutReview ? "allowed" : "intent-and-review-first"
|
|
46
49
|
},
|
|
47
50
|
quickstart: agentQuickstart(decision.reviewRequired),
|
|
51
|
+
nextAgent: nextAgentHint(state, dependencyReadSet, dependencyChangeProtocol),
|
|
52
|
+
coordination,
|
|
48
53
|
intentTargets,
|
|
54
|
+
dependencyReadSet,
|
|
55
|
+
dependencyChangeProtocol,
|
|
49
56
|
artifacts: preflightArtifacts(),
|
|
50
57
|
readOrder: [
|
|
51
58
|
".aienvmp/status.json",
|
|
@@ -68,6 +75,114 @@ export function buildPreflight(manifest = {}, warnings = [], intents = []) {
|
|
|
68
75
|
};
|
|
69
76
|
}
|
|
70
77
|
|
|
78
|
+
function coordinationSummary(intents = []) {
|
|
79
|
+
const byTarget = new Map();
|
|
80
|
+
for (const intent of intents) {
|
|
81
|
+
const target = normalizeTarget(intent.target || targetFromText(intent.action) || "environment") || "environment";
|
|
82
|
+
const item = byTarget.get(target) || { target, count: 0, actors: [], actions: [], conflict: false };
|
|
83
|
+
item.count += 1;
|
|
84
|
+
if (intent.actor && !item.actors.includes(intent.actor)) item.actors.push(intent.actor);
|
|
85
|
+
if (intent.action) item.actions.push(intent.action);
|
|
86
|
+
byTarget.set(target, item);
|
|
87
|
+
}
|
|
88
|
+
const targets = [...byTarget.values()].map((item) => ({
|
|
89
|
+
target: item.target,
|
|
90
|
+
count: item.count,
|
|
91
|
+
actors: item.actors.slice(0, 5),
|
|
92
|
+
actions: item.actions.slice(-3),
|
|
93
|
+
conflict: item.count > 1 && item.actors.length > 1
|
|
94
|
+
}));
|
|
95
|
+
const conflictTargets = targets.filter((item) => item.conflict).map((item) => item.target);
|
|
96
|
+
return {
|
|
97
|
+
openIntentCount: intents.length,
|
|
98
|
+
targets,
|
|
99
|
+
conflictTargets,
|
|
100
|
+
next: conflictTargets.length
|
|
101
|
+
? "Review or resolve conflicting intents before environment changes."
|
|
102
|
+
: intents.length
|
|
103
|
+
? "Check open intents before environment changes."
|
|
104
|
+
: "No open environment intents."
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function nextAgentHint(state, dependencyReadSet = [], dependencyChangeProtocol = {}) {
|
|
109
|
+
const firstDependency = dependencyReadSet[0];
|
|
110
|
+
const dependencyFiles = firstDependency
|
|
111
|
+
? [firstDependency.manifest, ...(firstDependency.lockfiles || [])].filter(Boolean)
|
|
112
|
+
: [];
|
|
113
|
+
return {
|
|
114
|
+
handoffCommand: dependencyChangeProtocol.commands?.handoff || "aienvmp handoff --record --actor agent:id",
|
|
115
|
+
readFirst: ".aienvmp/status.json",
|
|
116
|
+
readNext: "aienvmp context --json",
|
|
117
|
+
reviewState: state,
|
|
118
|
+
dependencyFiles,
|
|
119
|
+
dependencyProtocol: dependencyChangeProtocol.commands?.recordIntent
|
|
120
|
+
? "record dependency intent, sync after change, then record dependency-change"
|
|
121
|
+
: "no dependency protocol detected",
|
|
122
|
+
rule: state === "clear"
|
|
123
|
+
? "Next AI may continue project-local work; record intent before environment changes."
|
|
124
|
+
: "Next AI should review warnings or open intents before environment changes."
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function dependencyProtocol(manifest = {}, dependencyReadSet = []) {
|
|
129
|
+
const pmPolicy = manifest.lightSbom?.packageManagerPolicy || {};
|
|
130
|
+
return {
|
|
131
|
+
mode: "advisory",
|
|
132
|
+
appliesWhen: "Before package, lockfile, or vulnerability remediation changes.",
|
|
133
|
+
packageManagerPolicy: pmPolicy.status || "not-detected",
|
|
134
|
+
beforeChange: [
|
|
135
|
+
"Read dependencyReadSet manifests and lockfiles.",
|
|
136
|
+
"Check lightSbom.packageManagerPolicy before choosing npm, pnpm, yarn, pip, uv, or another manager.",
|
|
137
|
+
"Record dependency intent before edits."
|
|
138
|
+
],
|
|
139
|
+
commands: {
|
|
140
|
+
readContext: "aienvmp context --json",
|
|
141
|
+
recordIntent: "aienvmp intent --actor agent:id --action planned-change --target dependency",
|
|
142
|
+
refreshAfterChange: "aienvmp sync",
|
|
143
|
+
recordAfterChange: "aienvmp record --actor agent:id --summary dependency-change --target dependency",
|
|
144
|
+
handoff: "aienvmp handoff --record --actor agent:id"
|
|
145
|
+
},
|
|
146
|
+
mustNotDo: [
|
|
147
|
+
"Do not switch package managers because another lockfile exists without user approval.",
|
|
148
|
+
"Do not delete lockfiles or rewrite dependency manifests only to satisfy a tool preference.",
|
|
149
|
+
"Do not run automatic fix commands without reviewing the dependency read set first."
|
|
150
|
+
],
|
|
151
|
+
readSetCount: dependencyReadSet.length
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function dependencyPreflightReadSet(manifest = {}) {
|
|
156
|
+
const hints = manifest.lightSbom?.dependencyChangeHints || [];
|
|
157
|
+
const readSet = hints.map((hint) => ({
|
|
158
|
+
manifest: hint.manifest,
|
|
159
|
+
ecosystem: hint.ecosystem || "unknown",
|
|
160
|
+
manager: hint.manager || "unknown",
|
|
161
|
+
groups: hint.groups || [],
|
|
162
|
+
lockfiles: (hint.lockfiles || []).map((item) => item.file).filter(Boolean),
|
|
163
|
+
riskPackages: (hint.riskPackages || []).map((item) => item.name).filter(Boolean).slice(0, 5),
|
|
164
|
+
reason: hint.riskPackages?.length
|
|
165
|
+
? "Read before dependency or security remediation; vulnerable packages are linked to this manifest."
|
|
166
|
+
: "Read before dependency changes; this manifest defines project packages."
|
|
167
|
+
}));
|
|
168
|
+
if (readSet.length) return readSet.slice(0, 8);
|
|
169
|
+
|
|
170
|
+
const manifests = manifest.lightSbom?.summary?.manifests || manifest.dependencySnapshot?.manifests || [];
|
|
171
|
+
const lockfiles = (manifest.lightSbom?.summary?.lockfiles || manifest.dependencySnapshot?.lockfiles || [])
|
|
172
|
+
.map((item) => item.file || item)
|
|
173
|
+
.filter(Boolean);
|
|
174
|
+
if (!manifests.length && !lockfiles.length) return [];
|
|
175
|
+
return [{
|
|
176
|
+
manifest: manifests[0] || "",
|
|
177
|
+
ecosystem: "unknown",
|
|
178
|
+
manager: "unknown",
|
|
179
|
+
groups: [],
|
|
180
|
+
lockfiles,
|
|
181
|
+
riskPackages: [],
|
|
182
|
+
reason: "Read detected dependency files before package changes."
|
|
183
|
+
}];
|
|
184
|
+
}
|
|
185
|
+
|
|
71
186
|
function recommendedIntentTargets(manifest = {}, warnings = [], intents = []) {
|
|
72
187
|
const targets = [];
|
|
73
188
|
for (const warning of warnings) {
|
package/src/render.js
CHANGED
|
@@ -92,6 +92,25 @@ function preflightLines(preflight = {}) {
|
|
|
92
92
|
} else {
|
|
93
93
|
lines.push("- environment: `aienvmp intent --actor agent:id --action planned-change --target environment`");
|
|
94
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
|
+
}
|
|
95
114
|
return lines;
|
|
96
115
|
}
|
|
97
116
|
|
|
@@ -186,6 +205,9 @@ export function renderHandoff(handoff) {
|
|
|
186
205
|
"Recommended actions:",
|
|
187
206
|
...(handoff.recommendedActions?.length ? handoff.recommendedActions.map((item) => `- [${item.priority}] ${item.summary}${item.command ? ` (${item.command})` : ""}`) : ["- none"]),
|
|
188
207
|
"",
|
|
208
|
+
"Dependency handoff:",
|
|
209
|
+
...dependencyHandoffLines(handoff.dependencyHandoff),
|
|
210
|
+
"",
|
|
189
211
|
"Recent changes:",
|
|
190
212
|
...(handoff.recentChanges.length ? handoff.recentChanges.map((t) => `- ${formatTimeline(t)}`) : ["- none"]),
|
|
191
213
|
"",
|
|
@@ -198,6 +220,24 @@ export function renderHandoff(handoff) {
|
|
|
198
220
|
return lines.join("\n");
|
|
199
221
|
}
|
|
200
222
|
|
|
223
|
+
function dependencyHandoffLines(dependencyHandoff = {}) {
|
|
224
|
+
const readSet = dependencyHandoff.readSet || [];
|
|
225
|
+
const protocol = dependencyHandoff.protocol || {};
|
|
226
|
+
const lines = [];
|
|
227
|
+
if (readSet.length) {
|
|
228
|
+
for (const item of readSet.slice(0, 3)) {
|
|
229
|
+
const files = [item.manifest, ...(item.lockfiles || [])].filter(Boolean).join(", ");
|
|
230
|
+
lines.push(`- Read: ${files || "dependency files"} (${item.ecosystem || "deps"}/${item.manager || "unknown"})`);
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
lines.push("- Read: no dependency files detected");
|
|
234
|
+
}
|
|
235
|
+
lines.push(`- Intent: ${protocol.recordIntent || "aienvmp intent --actor agent:id --action planned-change --target dependency"}`);
|
|
236
|
+
lines.push(`- After change: ${protocol.recordAfterChange || "aienvmp record --actor agent:id --summary dependency-change --target dependency"}`);
|
|
237
|
+
lines.push(`- Handoff: ${protocol.handoff || "aienvmp handoff --record --actor agent:id"}`);
|
|
238
|
+
return lines;
|
|
239
|
+
}
|
|
240
|
+
|
|
201
241
|
export function renderPlan(plan) {
|
|
202
242
|
const lines = [
|
|
203
243
|
"# AI Environment Plan",
|
|
@@ -218,6 +258,9 @@ export function renderPlan(plan) {
|
|
|
218
258
|
"Review gates:",
|
|
219
259
|
...plan.reviewGates.map((item) => `- ${item}`),
|
|
220
260
|
"",
|
|
261
|
+
"Dependency protocol:",
|
|
262
|
+
...dependencyProtocolPlanLines(plan.preflight?.dependencyChangeProtocol),
|
|
263
|
+
"",
|
|
221
264
|
"Remediation steps:",
|
|
222
265
|
...(plan.remediationSteps?.length ? plan.remediationSteps.slice(0, 5).flatMap(remediationLines) : ["- none"]),
|
|
223
266
|
"",
|
|
@@ -231,6 +274,17 @@ export function renderPlan(plan) {
|
|
|
231
274
|
return lines.join("\n");
|
|
232
275
|
}
|
|
233
276
|
|
|
277
|
+
function dependencyProtocolPlanLines(protocol = {}) {
|
|
278
|
+
if (!protocol.commands) return ["- none"];
|
|
279
|
+
return [
|
|
280
|
+
`- Mode: ${protocol.mode || "advisory"}`,
|
|
281
|
+
`- Package manager policy: ${protocol.packageManagerPolicy || "not-detected"}`,
|
|
282
|
+
`- Intent: ${protocol.commands.recordIntent}`,
|
|
283
|
+
`- After change: ${protocol.commands.refreshAfterChange}; ${protocol.commands.recordAfterChange}`,
|
|
284
|
+
...(protocol.mustNotDo || []).slice(0, 3).map((item) => `- Must not: ${item}`)
|
|
285
|
+
];
|
|
286
|
+
}
|
|
287
|
+
|
|
234
288
|
function environmentLines(item) {
|
|
235
289
|
return [
|
|
236
290
|
`- ${item.category}: ${item.summary}`,
|
|
@@ -354,6 +408,10 @@ const strictCommands=enforcementProfile.strictCommands||[];
|
|
|
354
408
|
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>\`;
|
|
355
409
|
const intentTargets=manifest.preflight?.intentTargets||[];
|
|
356
410
|
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>';
|
|
411
|
+
const dependencyReadSet=manifest.preflight?.dependencyReadSet||[];
|
|
412
|
+
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>';
|
|
413
|
+
const dependencyProtocol=manifest.preflight?.dependencyChangeProtocol||{};
|
|
414
|
+
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>';
|
|
357
415
|
const card=(title,badge,body)=>\`<section class="card"><div class="card-head"><h2>\${title}</h2>\${badge||''}</div>\${body}</section>\`;
|
|
358
416
|
const reviewRequired=warnings.length>0||intents.length>0;
|
|
359
417
|
const recentChanges=timeline.slice(-8).length;
|
|
@@ -361,7 +419,12 @@ const trustState=manifest.trust?.state||'observed';
|
|
|
361
419
|
const nextAction=reviewRequired?'Review before environment changes':'Proceed with project-local work';
|
|
362
420
|
const auditItem=(key,value,hint,klass='')=>\`<div class="audit-item \${klass}"><div class="audit-k">\${key}</div><div class="audit-v">\${value}</div><div class="audit-hint">\${hint}</div></div>\`;
|
|
363
421
|
const driftLabel=warnings.length?'detected':'none';
|
|
364
|
-
const
|
|
422
|
+
const nextAgent=manifest.preflight?.nextAgent||{};
|
|
423
|
+
const coordination=manifest.preflight?.coordination||{};
|
|
424
|
+
const conflictTargets=coordination.conflictTargets||[];
|
|
425
|
+
const handoffFiles=nextAgent.dependencyFiles?.length?nextAgent.dependencyFiles:(dependencyReadSet[0]?[dependencyReadSet[0].manifest,...(dependencyReadSet[0].lockfiles||[])].filter(Boolean):[]);
|
|
426
|
+
const handoffNext=nextAgent.rule||(reviewRequired?'Review warnings and open intents':'Continue project-local work');
|
|
427
|
+
const handoffHtml=\`<table><tr><th>Status</th><td>\${reviewRequired?'review-required':'clear'}</td></tr><tr><th>Trust</th><td><code>\${esc(trustState)}</code></td></tr><tr><th>Read first</th><td><code>\${esc(nextAgent.readFirst||'.aienvmp/status.json')}</code></td></tr><tr><th>Dependency files</th><td>\${handoffFiles.length?'<code>'+esc(handoffFiles.join(', '))+'</code>':'none'}</td></tr><tr><th>Conflicts</th><td>\${conflictTargets.length?'<code>'+esc(conflictTargets.join(', '))+'</code>':'none'}</td></tr><tr><th>Next</th><td>\${esc(handoffNext)}</td></tr></table>\`;
|
|
365
428
|
document.getElementById('app').innerHTML=\`
|
|
366
429
|
<header>
|
|
367
430
|
<div>
|
|
@@ -399,6 +462,10 @@ document.getElementById('app').innerHTML=\`
|
|
|
399
462
|
<div style="height:14px"></div>
|
|
400
463
|
\${card('AI Intent Targets','<span class="pill">'+intentTargets.length+' targets</span>',intentTargetsHtml)}
|
|
401
464
|
<div style="height:14px"></div>
|
|
465
|
+
\${card('Dependency Read Set','<span class="pill">'+dependencyReadSet.length+' files</span>',dependencyReadSetHtml)}
|
|
466
|
+
<div style="height:14px"></div>
|
|
467
|
+
\${card('Dependency Protocol','<span class="pill">'+(dependencyProtocol.mode||'advisory')+'</span>',dependencyProtocolHtml)}
|
|
468
|
+
<div style="height:14px"></div>
|
|
402
469
|
\${card('AI Plan Artifacts',plan.markdown||plan.json?'<span class="pill">written</span>':'<span class="pill off">not written</span>',planHtml)}
|
|
403
470
|
<div style="height:14px"></div>
|
|
404
471
|
\${card('Remediation Steps',remediation.length?'<span class="pill warn">'+remediation.length+' items</span>':'<span class="pill off">none</span>',remediationHtml)}
|