aienvmp 0.1.26 → 0.1.27

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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.27
4
+
5
+ - Added a read-only dependency snapshot to the manifest, context, AIENV.md, and dashboard.
6
+ - Captured npm and Python project dependencies from `package.json`, `requirements.txt`, and `pyproject.toml` without installing or resolving packages.
7
+ - Linked the env map and light SBOM story so AI agents can see runtime, dependency, and vulnerability context together.
8
+
3
9
  ## 0.1.26
4
10
 
5
11
  - Shared one AI decision contract across `context --json`, `plan`, and `handoff`.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  `aienvmp` is an AI-first environment map for shared coding machines.
10
10
 
11
- It helps Codex, Claude, Gemini, and humans avoid silent Node, Python, package manager, Docker, and security drift.
11
+ It helps Codex, Claude, Gemini, and humans avoid silent Node, Python, package manager, dependency, Docker, and security drift.
12
12
 
13
13
  Core loop: scan once, give AI a shared decision contract, write a read-only action plan, and hand off safe next steps.
14
14
 
@@ -48,7 +48,7 @@ AIENV.md
48
48
  .aienvmp/timeline.jsonl
49
49
  .aienvmp/plan.json
50
50
  .aienvmp/plan.md
51
- .aienvmp/dashboard.html # includes plan, remediation, and environment cards
51
+ .aienvmp/dashboard.html # includes dependencies, plan, remediation, and environment cards
52
52
  ```
53
53
 
54
54
  Trust states are machine-readable: `observed`, `planned`, `changed`, `review`, `verified`, `stale`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aienvmp",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "description": "AI-first environment maps and lightweight runtime SBOMs for shared development machines.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,6 +32,7 @@ export async function contextWorkspace(args) {
32
32
  packageManagers: manifest.packageManagers,
33
33
  containers: manifest.containers,
34
34
  inventory: inventorySummary(manifest.inventory),
35
+ dependencySnapshot: dependencySummary(manifest.dependencySnapshot),
35
36
  security: securitySummary(manifest.security),
36
37
  projectHints: manifest.projectHints,
37
38
  warnings,
@@ -45,6 +46,15 @@ export async function contextWorkspace(args) {
45
46
  console.log(renderContext(manifest, timeline, warnings, intents, policy, actions));
46
47
  }
47
48
 
49
+ function dependencySummary(snapshot = {}) {
50
+ return {
51
+ mode: snapshot.mode || "snapshot",
52
+ enabled: snapshot.enabled === true,
53
+ summary: snapshot.summary || { ecosystems: [], manifests: 0, packages: 0 },
54
+ packages: (snapshot.packages || []).slice(0, 12)
55
+ };
56
+ }
57
+
48
58
  function securitySummary(security = {}) {
49
59
  return {
50
60
  mode: security.mode || "basic",
@@ -0,0 +1,114 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { exists, readJson } from "./fsutil.js";
4
+
5
+ const NODE_GROUPS = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
6
+
7
+ export async function scanDependencySnapshot(dir) {
8
+ const node = await scanNodeDependencies(dir);
9
+ const python = await scanPythonDependencies(dir);
10
+ const packages = [...node.packages, ...python.packages];
11
+ const manifests = [...node.manifests, ...python.manifests];
12
+ return {
13
+ mode: "snapshot",
14
+ enabled: true,
15
+ note: "Read-only dependency snapshot from project files. It does not install, update, or resolve packages.",
16
+ manifests,
17
+ summary: {
18
+ ecosystems: [...new Set(packages.map((pkg) => pkg.ecosystem))],
19
+ manifests: manifests.length,
20
+ packages: packages.length
21
+ },
22
+ packages: packages.slice(0, 80)
23
+ };
24
+ }
25
+
26
+ async function scanNodeDependencies(dir) {
27
+ const file = path.join(dir, "package.json");
28
+ if (!(await exists(file))) return { manifests: [], packages: [] };
29
+ const json = await readJson(file, {});
30
+ const packages = [];
31
+ for (const group of NODE_GROUPS) {
32
+ for (const [name, version] of Object.entries(json[group] || {})) {
33
+ packages.push({
34
+ ecosystem: "npm",
35
+ manager: "npm",
36
+ manifest: "package.json",
37
+ group,
38
+ name,
39
+ version: String(version)
40
+ });
41
+ }
42
+ }
43
+ return { manifests: ["package.json"], packages };
44
+ }
45
+
46
+ async function scanPythonDependencies(dir) {
47
+ const requirements = await scanRequirementsTxt(dir);
48
+ const pyproject = await scanPyproject(dir);
49
+ return {
50
+ manifests: [...requirements.manifests, ...pyproject.manifests],
51
+ packages: [...requirements.packages, ...pyproject.packages]
52
+ };
53
+ }
54
+
55
+ async function scanRequirementsTxt(dir) {
56
+ const file = path.join(dir, "requirements.txt");
57
+ if (!(await exists(file))) return { manifests: [], packages: [] };
58
+ const raw = await fs.readFile(file, "utf8");
59
+ const packages = raw
60
+ .split(/\r?\n/)
61
+ .map((line) => line.trim())
62
+ .filter((line) => line && !line.startsWith("#") && !line.startsWith("-"))
63
+ .slice(0, 80)
64
+ .map((line) => {
65
+ const parsed = parseRequirementLine(line);
66
+ return {
67
+ ecosystem: "python",
68
+ manager: "pip",
69
+ manifest: "requirements.txt",
70
+ group: "requirements",
71
+ name: parsed.name,
72
+ version: parsed.version
73
+ };
74
+ });
75
+ return { manifests: ["requirements.txt"], packages };
76
+ }
77
+
78
+ async function scanPyproject(dir) {
79
+ const file = path.join(dir, "pyproject.toml");
80
+ if (!(await exists(file))) return { manifests: [], packages: [] };
81
+ const raw = await fs.readFile(file, "utf8");
82
+ const packages = parsePyprojectDependencies(raw).slice(0, 80).map((line) => {
83
+ const parsed = parseRequirementLine(line);
84
+ return {
85
+ ecosystem: "python",
86
+ manager: "pyproject",
87
+ manifest: "pyproject.toml",
88
+ group: "project.dependencies",
89
+ name: parsed.name,
90
+ version: parsed.version
91
+ };
92
+ });
93
+ return { manifests: ["pyproject.toml"], packages };
94
+ }
95
+
96
+ export function parseRequirementLine(line) {
97
+ const cleaned = String(line).split("#")[0].trim();
98
+ const match = cleaned.match(/^([A-Za-z0-9_.-]+)\s*(.*)$/);
99
+ return {
100
+ name: match?.[1] || cleaned,
101
+ version: (match?.[2] || "unspecified").trim() || "unspecified"
102
+ };
103
+ }
104
+
105
+ export function parsePyprojectDependencies(raw) {
106
+ const lines = [];
107
+ const match = String(raw).match(/dependencies\s*=\s*\[([\s\S]*?)\]/m);
108
+ if (!match) return lines;
109
+ for (const item of match[1].split(/\r?\n/)) {
110
+ const cleaned = item.trim().replace(/,$/, "").replace(/^["']|["']$/g, "");
111
+ if (cleaned && !cleaned.startsWith("#")) lines.push(cleaned);
112
+ }
113
+ return lines;
114
+ }
package/src/manifest.js CHANGED
@@ -6,6 +6,7 @@ import { exists } from "./fsutil.js";
6
6
  import { observedTrust } from "./trust.js";
7
7
  import { scanGlobalInventory } from "./inventory.js";
8
8
  import { scanSecurity } from "./security.js";
9
+ import { scanDependencySnapshot } from "./dependencies.js";
9
10
 
10
11
  export async function buildManifest(dir, options = {}) {
11
12
  const now = new Date().toISOString();
@@ -27,6 +28,7 @@ export async function buildManifest(dir, options = {}) {
27
28
  packageManagers: await scanPackageManagers(),
28
29
  containers: await scanContainers(),
29
30
  projectHints: await scanProjectHints(dir),
31
+ dependencySnapshot: await scanDependencySnapshot(dir),
30
32
  inventory: await scanGlobalInventory({ deep: options.deep }),
31
33
  security: await scanSecurity(dir, { security: options.security }),
32
34
  agentFiles: await scanAgentFiles(dir),
@@ -84,6 +86,11 @@ export async function buildManifest(dir, options = {}) {
84
86
  mode: "basic by default; vulnerability summaries only when requested",
85
87
  npmAudit: "npm audit --json"
86
88
  },
89
+ dependencySnapshot: {
90
+ mode: "read-only project file snapshot",
91
+ node: "package.json dependencies",
92
+ python: "requirements.txt and pyproject.toml dependencies"
93
+ },
87
94
  projectHints: [
88
95
  ".nvmrc",
89
96
  ".python-version",
package/src/render.js CHANGED
@@ -31,6 +31,8 @@ export function renderAIEnv(manifest, timeline = [], warnings = [], intents = []
31
31
  pushMap(lines, "Containers", manifest.containers);
32
32
  lines.push("## Global Tool Inventory", "");
33
33
  lines.push(...inventoryLines(manifest.inventory), "");
34
+ lines.push("## Dependency Snapshot", "");
35
+ lines.push(...dependencyLines(manifest.dependencySnapshot), "");
34
36
  lines.push("## Security Summary", "");
35
37
  lines.push(...securityLines(manifest.security), "");
36
38
  lines.push("## Project Requirements And Hints", "");
@@ -99,6 +101,7 @@ export function renderContext(manifest, timeline = [], warnings = [], intents =
99
101
  `Python: ${manifest.runtimes.python || manifest.runtimes.python3 || "not detected"}`,
100
102
  `Docker: ${manifest.containers.docker ? "available" : "not detected"}`,
101
103
  `Inventory: ${manifest.inventory?.mode || "basic"}${manifest.inventory?.enabled ? " enabled" : " disabled"}`,
104
+ `Dependencies: ${manifest.dependencySnapshot?.summary?.packages || 0} packages across ${(manifest.dependencySnapshot?.summary?.ecosystems || []).join(", ") || "no ecosystems"}`,
102
105
  `Security: ${manifest.security?.mode || "basic"}${manifest.security?.enabled ? ` enabled (${manifest.security.summary?.total || 0} vulnerabilities)` : " disabled"}`,
103
106
  `Policy Node: ${policy.node || "not set"}`,
104
107
  `Policy Python: ${policy.python || "not set"}`,
@@ -273,6 +276,10 @@ const rows=o=>entries(o).map(([k,v])=>\`<tr><th>\${esc(k)}</th><td><code>\${esc(
273
276
  const inventoryGroups=manifest.inventory?.tools||{};
274
277
  const inventoryCount=Object.values(inventoryGroups).reduce((sum,items)=>sum+(Array.isArray(items)?items.length:0),0);
275
278
  const inventoryHtml=manifest.inventory?.enabled?('<table>'+Object.entries(inventoryGroups).map(([k,v])=>\`<tr><th>\${esc(k)}</th><td><code>\${Array.isArray(v)?v.length:0} tools</code></td></tr>\`).join('')+'</table>'):'<div class="okline">Deep global inventory is off. Run <code>aienvmp sync --deep</code> when an AI needs global tool awareness.</div>';
279
+ const deps=manifest.dependencySnapshot||{};
280
+ const depSummary=deps.summary||{ecosystems:[],manifests:0,packages:0};
281
+ const depPackages=deps.packages||[];
282
+ const depHtml=depPackages.length?'<table><tr><th>Packages</th><td><code>'+esc(depSummary.packages||0)+'</code></td></tr><tr><th>Ecosystems</th><td><code>'+esc((depSummary.ecosystems||[]).join(', ')||'none')+'</code></td></tr><tr><th>Manifests</th><td><code>'+esc((deps.manifests||[]).join(', ')||'none')+'</code></td></tr></table><div class="timeline">'+depPackages.slice(0,8).map(p=>\`<div class="event"><time>\${esc(p.ecosystem)}</time><div><b>\${esc(p.name)}</b> <code>\${esc(p.version)}</code><div class="path">\${esc(p.manifest)} / \${esc(p.group)}</div></div></div>\`).join('')+'</div>':'<div class="okline">No project dependency manifests detected.</div>';
276
283
  const sec=manifest.security||{};
277
284
  const secSummary=sec.summary||{total:0,critical:0,high:0,moderate:0,low:0,info:0};
278
285
  const secPackages=sec.topPackages||[];
@@ -336,6 +343,7 @@ document.getElementById('app').innerHTML=\`
336
343
  \${card('Containers',manifest.containers?.docker?'<span class="pill">available</span>':'<span class="pill off">not detected</span>',\`<table>\${rows(manifest.containers)}</table>\`)}
337
344
  \${card('Project Hints',\`<span class="pill">\${entries(manifest.projectHints).length} hints</span>\`,\`<table>\${rows(manifest.projectHints)}</table>\`)}
338
345
  \${card('Global Inventory',manifest.inventory?.enabled?'<span class="pill">deep</span>':'<span class="pill off">basic</span>',inventoryHtml)}
346
+ \${card('Dependency Snapshot','<span class="pill">'+(depSummary.packages||0)+' packages</span>',depHtml)}
339
347
  \${card('Security Summary',sec.enabled?'<span class="pill warn">security</span>':'<span class="pill off">basic</span>',securityHtml)}
340
348
  </div>
341
349
  <aside>
@@ -416,6 +424,22 @@ function inventoryLines(inventory = {}) {
416
424
  return lines;
417
425
  }
418
426
 
427
+ function dependencyLines(snapshot = {}) {
428
+ const summary = snapshot.summary || {};
429
+ const packages = snapshot.packages || [];
430
+ const ecosystems = summary.ecosystems?.length ? summary.ecosystems.join(", ") : "none";
431
+ const lines = [
432
+ `- Mode: ${snapshot.mode || "snapshot"}`,
433
+ `- Manifests: ${(snapshot.manifests || []).join(", ") || "none"}`,
434
+ `- Ecosystems: ${ecosystems}`,
435
+ `- Packages: ${summary.packages || 0}`
436
+ ];
437
+ for (const pkg of packages.slice(0, 10)) {
438
+ lines.push(`- ${pkg.ecosystem}/${pkg.name}: ${pkg.version} (${pkg.manifest})`);
439
+ }
440
+ return lines;
441
+ }
442
+
419
443
  function securityLines(security = {}) {
420
444
  if (!security.enabled) return ["- Mode: basic", "- Security scan is disabled. Run `aienvmp sync --security` when vulnerability context is needed."];
421
445
  const summary = security.summary || {};