aienvmp 0.1.38 → 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 CHANGED
@@ -1,5 +1,15 @@
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
+
3
13
  ## 0.1.38
4
14
 
5
15
  - Added a 10-second AI quickstart flow to the shared preflight contract and status output.
package/README.md CHANGED
@@ -23,6 +23,7 @@ npx aienvmp handoff
23
23
  ```
24
24
 
25
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.
26
27
 
27
28
  Optional deeper read-only checks:
28
29
 
@@ -51,7 +52,7 @@ AIENV.md
51
52
  .aienvmp/intents.jsonl
52
53
  .aienvmp/timeline.jsonl
53
54
  .aienvmp/plan.json
54
- .aienvmp/plan.md
55
+ .aienvmp/plan.md # read-only plan with dependency protocol
55
56
  .aienvmp/dashboard.html # includes dependencies, plan, remediation, and environment cards
56
57
  ```
57
58
 
@@ -61,6 +62,9 @@ Trust states are machine-readable: `observed`, `planned`, `changed`, `review`, `
61
62
  `status.json` also lists AI read order, artifact paths, and safe commands.
62
63
  `status`, `context`, `plan`, and `handoff` share the same AI preflight contract.
63
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.
64
68
  The dashboard shows the same intent target guidance for human review.
65
69
 
66
70
  AI agents can observe, plan, and record. Only a human or CI should mark environment facts as verified.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aienvmp",
3
- "version": "0.1.38",
3
+ "version": "0.1.39",
4
4
  "description": "AI-first environment maps and lightweight runtime SBOMs for shared development machines.",
5
5
  "type": "module",
6
6
  "bin": {
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 vulnerable packages before dependency or deployment changes: ${packageHints.join("; ")}.`
55
- : "Review vulnerability summary before dependency or deployment changes.",
56
- "aienvmp context --json"
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 [command, ...rest] = argv;
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/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,8 @@ 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);
12
14
  return {
13
15
  schemaVersion: 1,
14
16
  state,
@@ -46,6 +48,8 @@ export function buildPreflight(manifest = {}, warnings = [], intents = []) {
46
48
  },
47
49
  quickstart: agentQuickstart(decision.reviewRequired),
48
50
  intentTargets,
51
+ dependencyReadSet,
52
+ dependencyChangeProtocol,
49
53
  artifacts: preflightArtifacts(),
50
54
  readOrder: [
51
55
  ".aienvmp/status.json",
@@ -68,6 +72,64 @@ export function buildPreflight(manifest = {}, warnings = [], intents = []) {
68
72
  };
69
73
  }
70
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
+
71
133
  function recommendedIntentTargets(manifest = {}, warnings = [], intents = []) {
72
134
  const targets = [];
73
135
  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
 
@@ -218,6 +237,9 @@ export function renderPlan(plan) {
218
237
  "Review gates:",
219
238
  ...plan.reviewGates.map((item) => `- ${item}`),
220
239
  "",
240
+ "Dependency protocol:",
241
+ ...dependencyProtocolPlanLines(plan.preflight?.dependencyChangeProtocol),
242
+ "",
221
243
  "Remediation steps:",
222
244
  ...(plan.remediationSteps?.length ? plan.remediationSteps.slice(0, 5).flatMap(remediationLines) : ["- none"]),
223
245
  "",
@@ -231,6 +253,17 @@ export function renderPlan(plan) {
231
253
  return lines.join("\n");
232
254
  }
233
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
+
234
267
  function environmentLines(item) {
235
268
  return [
236
269
  `- ${item.category}: ${item.summary}`,
@@ -354,6 +387,10 @@ const strictCommands=enforcementProfile.strictCommands||[];
354
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>\`;
355
388
  const intentTargets=manifest.preflight?.intentTargets||[];
356
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>';
357
394
  const card=(title,badge,body)=>\`<section class="card"><div class="card-head"><h2>\${title}</h2>\${badge||''}</div>\${body}</section>\`;
358
395
  const reviewRequired=warnings.length>0||intents.length>0;
359
396
  const recentChanges=timeline.slice(-8).length;
@@ -399,6 +436,10 @@ document.getElementById('app').innerHTML=\`
399
436
  <div style="height:14px"></div>
400
437
  \${card('AI Intent Targets','<span class="pill">'+intentTargets.length+' targets</span>',intentTargetsHtml)}
401
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>
402
443
  \${card('AI Plan Artifacts',plan.markdown||plan.json?'<span class="pill">written</span>':'<span class="pill off">not written</span>',planHtml)}
403
444
  <div style="height:14px"></div>
404
445
  \${card('Remediation Steps',remediation.length?'<span class="pill warn">'+remediation.length+' items</span>':'<span class="pill off">none</span>',remediationHtml)}