aienvmp 0.1.40 → 0.1.41

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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.41
4
+
5
+ - Added preflight contract metadata so AI and CI consumers can rely on stable entry fields while ignoring additive changes.
6
+ - Added `aienvmp schema --json` to print the stable AI-readable output contract without scanning a workspace.
7
+ - Exposed coordination summaries at the root of `context --json` and `handoff --json`, with a human-readable handoff section.
8
+ - Added light SBOM source, confidence, and limitation hints so agents know what the lightweight snapshot does and does not prove.
9
+ - Updated the README with the schema command and light SBOM verification boundary while keeping the quick-start compact.
10
+ - Added an AI Contract dashboard card so humans can review the same stable fields that agents consume.
11
+
3
12
  ## 0.1.40
4
13
 
5
14
  - Added dependency handoff summaries so the next AI receives dependency read-set and protocol guidance directly in `handoff`.
package/README.md CHANGED
@@ -16,6 +16,7 @@ It helps Codex, Claude, Gemini, and humans avoid silent runtime, package manager
16
16
  npx aienvmp sync
17
17
  npx aienvmp status
18
18
  npx aienvmp context --json
19
+ npx aienvmp schema --json
19
20
  ```
20
21
 
21
22
  Before environment changes:
@@ -43,10 +44,12 @@ AIENV.md # Markdown env map for AI agents
43
44
  ## AI Contract
44
45
 
45
46
  - `status`, `context`, `plan`, and `handoff` share one preflight contract.
47
+ - `schema --json` prints the stable AI-readable output contract without scanning.
46
48
  - `status.json.nextAgent` tells the next AI what to read and whether to review first.
47
49
  - `dependencyReadSet` lists manifests and lockfiles before package or security changes.
48
50
  - `coordination.conflictTargets` shows where multiple agents are planning changes.
49
51
  - `handoff` carries dependency read-set and protocol guidance for the next AI.
52
+ - Light SBOM includes source/confidence hints; verify security claims with dedicated scanners.
50
53
  - Everything is advisory by default; strict failure is opt-in with `doctor --strict`.
51
54
 
52
55
  ## Agent Files
@@ -65,6 +68,7 @@ npx aienvmp snippet gemini
65
68
  aienvmp sync # update env map, status, ledger, dashboard
66
69
  aienvmp status --write # refresh compact AI status
67
70
  aienvmp context --json # AI decision contract
71
+ aienvmp schema --json # stable output contract for AI/CI consumers
68
72
  aienvmp plan --write # read-only action plan
69
73
  aienvmp handoff --record # next-agent summary
70
74
  aienvmp intent # record planned env change
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aienvmp",
3
- "version": "0.1.40",
3
+ "version": "0.1.41",
4
4
  "description": "AI-first environment maps and lightweight runtime SBOMs for shared development machines.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -13,6 +13,7 @@ import { snippetWorkspace } from "./commands/snippet.js";
13
13
  import { handoffWorkspace } from "./commands/handoff.js";
14
14
  import { planWorkspace } from "./commands/plan.js";
15
15
  import { statusWorkspace } from "./commands/status.js";
16
+ import { schemaWorkspace } from "./commands/schema.js";
16
17
  import { readFileSync } from "node:fs";
17
18
 
18
19
  const commands = new Map([
@@ -30,7 +31,8 @@ const commands = new Map([
30
31
  ["snippet", snippetWorkspace],
31
32
  ["handoff", handoffWorkspace],
32
33
  ["plan", planWorkspace],
33
- ["status", statusWorkspace]
34
+ ["status", statusWorkspace],
35
+ ["schema", schemaWorkspace]
34
36
  ]);
35
37
 
36
38
  const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
@@ -107,6 +109,7 @@ Usage:
107
109
  aienvmp status [--dir .] [--json] [--write] [--quiet]
108
110
  aienvmp handoff [--dir .] [--json] [--record --actor agent:id]
109
111
  aienvmp plan [--dir .] [--json] [--write]
112
+ aienvmp schema [--json]
110
113
 
111
114
  Common:
112
115
  aienvmp sync update AIENV.md, manifest, status, ledger, intents, and dashboard
@@ -114,6 +117,7 @@ Common:
114
117
  aienvmp context print the AI preflight brief
115
118
  aienvmp handoff print the next-agent handoff summary
116
119
  aienvmp plan print a read-only AI environment action plan
120
+ aienvmp schema print the stable AI-readable output contract
117
121
  aienvmp snippet print an AGENTS.md pointer snippet
118
122
  aienvmp dash regenerate/open the lightweight dashboard
119
123
 
@@ -26,6 +26,7 @@ export async function contextWorkspace(args) {
26
26
  console.log(JSON.stringify({
27
27
  status: warnings.length ? "review-required" : "clear",
28
28
  preflight,
29
+ coordination: preflight.coordination,
29
30
  decision,
30
31
  enforcement: enforcementAdvice(warnings),
31
32
  recommendedActions: actions,
@@ -73,6 +74,9 @@ function lightSbomSummary(lightSbom = {}) {
73
74
  guidance: "No lockfile policy detected."
74
75
  },
75
76
  dependencyChangeHints: (lightSbom.dependencyChangeHints || []).slice(0, 8),
77
+ source: lightSbom.source || {},
78
+ confidence: lightSbom.confidence || {},
79
+ limitations: lightSbom.limitations || [],
76
80
  aiUse: lightSbom.aiUse || {}
77
81
  };
78
82
  }
@@ -62,6 +62,7 @@ export function buildHandoff(manifest, timeline = [], warnings = [], intents = [
62
62
  inventory: inventorySummary(manifest.inventory),
63
63
  security: securitySummary(manifest.security),
64
64
  dependencyHandoff: dependencyHandoffSummary(preflight),
65
+ coordination: preflight.coordination,
65
66
  policy: {
66
67
  node: policy.node || "not set",
67
68
  python: policy.python || "not set",
@@ -0,0 +1,16 @@
1
+ import { schemaContract } from "../contract.js";
2
+
3
+ export async function schemaWorkspace(args = {}) {
4
+ const schema = schemaContract();
5
+ if (args.json) {
6
+ console.log(JSON.stringify(schema, null, 2));
7
+ } else {
8
+ console.log("aienvmp contract");
9
+ console.log(`schema: ${schema.name} v${schema.schemaVersion}`);
10
+ console.log(`status: ${schema.outputs.status.command}`);
11
+ console.log(`context: ${schema.outputs.context.command}`);
12
+ console.log(`handoff: ${schema.outputs.handoff.command}`);
13
+ console.log(`rule: ${schema.compatibility.consumerRule}`);
14
+ }
15
+ return schema;
16
+ }
@@ -0,0 +1,42 @@
1
+ export function preflightContract() {
2
+ return {
3
+ name: "aienvmp-preflight",
4
+ version: 1,
5
+ stability: "additive",
6
+ requiredFields: ["schemaVersion", "state", "decision", "quickstart", "commands", "artifacts"],
7
+ aiEntryFields: ["state", "summary", "nextAgent", "coordination", "dependencyReadSet", "dependencyChangeProtocol"],
8
+ rule: "Consumers should ignore unknown fields and treat missing optional fields as unavailable."
9
+ };
10
+ }
11
+
12
+ export function schemaContract() {
13
+ return {
14
+ schemaVersion: 1,
15
+ name: "aienvmp-contract",
16
+ purpose: "Stable AI-readable contract for aienvmp outputs.",
17
+ outputs: {
18
+ status: {
19
+ file: ".aienvmp/status.json",
20
+ command: "aienvmp status --json",
21
+ contract: preflightContract()
22
+ },
23
+ context: {
24
+ command: "aienvmp context --json",
25
+ rootFields: ["status", "preflight", "coordination", "decision", "recommendedActions", "workspace", "dependencySnapshot", "lightSbom", "warnings"]
26
+ },
27
+ handoff: {
28
+ command: "aienvmp handoff --json",
29
+ rootFields: ["status", "decision", "preflight", "coordination", "dependencyHandoff", "openIntents", "warnings", "recommendedActions", "recentChanges"]
30
+ },
31
+ manifest: {
32
+ file: ".aienvmp/manifest.json",
33
+ rootFields: ["schemaVersion", "workspace", "runtimes", "packageManagers", "dependencySnapshot", "lightSbom", "security", "trust"]
34
+ }
35
+ },
36
+ compatibility: {
37
+ stability: "additive",
38
+ consumerRule: "Ignore unknown fields. Do not require optional fields unless listed in requiredFields.",
39
+ localBehavior: "read-only; this command does not scan, install, update, or lock anything."
40
+ }
41
+ };
42
+ }
@@ -79,6 +79,23 @@ export function buildLightSbom(snapshot = {}, security = {}) {
79
79
  schemaVersion: 1,
80
80
  mode: "light-sbom",
81
81
  note: "AI-ready package and vulnerability summary from read-only project files and optional scanners.",
82
+ source: {
83
+ dependencies: "project manifests",
84
+ lockfiles: "file presence only",
85
+ vulnerabilities: security.enabled ? "optional scanner summary" : "not scanned",
86
+ resolver: "not run"
87
+ },
88
+ confidence: {
89
+ directDependencies: "high",
90
+ lockfileManager: snapshot.lockfiles?.length ? "medium" : "none",
91
+ transitiveDependencies: "not-resolved",
92
+ vulnerabilities: security.enabled ? "scanner-provided" : "not-scanned"
93
+ },
94
+ limitations: [
95
+ "Does not install packages.",
96
+ "Does not resolve full transitive dependency graphs.",
97
+ "Does not replace CycloneDX, SPDX, Syft, Trivy, npm audit, or pip-audit outputs."
98
+ ],
82
99
  summary: {
83
100
  ecosystems: countBy(packages, "ecosystem"),
84
101
  managers: countBy(packages, "manager"),
@@ -96,7 +113,8 @@ export function buildLightSbom(snapshot = {}, security = {}) {
96
113
  aiUse: {
97
114
  beforeDependencyChanges: "Read lightSbom.summary and lightSbom.topRisk before changing dependencies.",
98
115
  securityMode: security.enabled ? "scanner-summary" : "scanner-off",
99
- dependencySource: "project manifests only; no install or resolver is run"
116
+ dependencySource: "project manifests only; no install or resolver is run",
117
+ trustRule: "Use lightSbom as a fast AI planning map; verify with dedicated scanners before security claims."
100
118
  }
101
119
  };
102
120
  }
package/src/preflight.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { recommendedActions } from "./actions.js";
2
2
  import { aiDecision } from "./decision.js";
3
3
  import { enforcementAdvice } from "./enforcement.js";
4
+ import { preflightContract } from "./contract.js";
4
5
 
5
6
  export function buildPreflight(manifest = {}, warnings = [], intents = []) {
6
7
  const decision = aiDecision(warnings, intents);
@@ -14,6 +15,7 @@ export function buildPreflight(manifest = {}, warnings = [], intents = []) {
14
15
  const coordination = coordinationSummary(intents);
15
16
  return {
16
17
  schemaVersion: 1,
18
+ contract: preflightContract(),
17
19
  state,
18
20
  summary: state === "clear"
19
21
  ? "Project-local work can continue. Record intent before environment changes."
package/src/render.js CHANGED
@@ -199,6 +199,9 @@ export function renderHandoff(handoff) {
199
199
  "Open intents:",
200
200
  ...(handoff.openIntents.length ? handoff.openIntents.map((i) => `- ${i.actor}: ${i.action}${i.target ? ` (${i.target})` : ""}`) : ["- none"]),
201
201
  "",
202
+ "Coordination:",
203
+ ...coordinationHandoffLines(handoff.coordination),
204
+ "",
202
205
  "Warnings:",
203
206
  ...(handoff.warnings.length ? handoff.warnings.map((w) => `- ${w.message}`) : ["- none"]),
204
207
  "",
@@ -220,6 +223,12 @@ export function renderHandoff(handoff) {
220
223
  return lines.join("\n");
221
224
  }
222
225
 
226
+ function coordinationHandoffLines(coordination = {}) {
227
+ const conflicts = coordination.conflictTargets || [];
228
+ if (conflicts.length) return [`- Conflicts: ${conflicts.join(", ")}`, `- Next: ${coordination.next || "review open intents"}`];
229
+ return [`- Open intents: ${coordination.openIntentCount || 0}`, `- Next: ${coordination.next || "no open environment intents"}`];
230
+ }
231
+
223
232
  function dependencyHandoffLines(dependencyHandoff = {}) {
224
233
  const readSet = dependencyHandoff.readSet || [];
225
234
  const protocol = dependencyHandoff.protocol || {};
@@ -406,6 +415,8 @@ const ciReadinessHtml=ciReadiness.length?'<table>'+ciReadiness.map(s=>\`<tr><th>
406
415
  const enforcementProfile=manifest.preflight?.enforcementProfile||{};
407
416
  const strictCommands=enforcementProfile.strictCommands||[];
408
417
  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>\`;
418
+ const contract=manifest.preflight?.contract||{};
419
+ const contractHtml=contract.name?\`<table><tr><th>Name</th><td><code>\${esc(contract.name)}</code></td></tr><tr><th>Version</th><td><code>\${esc(contract.version||1)}</code></td></tr><tr><th>Stability</th><td><code>\${esc(contract.stability||'additive')}</code></td></tr><tr><th>AI fields</th><td>\${esc((contract.aiEntryFields||[]).join(', ')||'none')}</td></tr></table><div class="path">\${esc(contract.rule||'Ignore unknown fields.')}</div>\`:'<div class="okline">Run <code>aienvmp status --write</code> to include AI contract metadata.</div>';
409
420
  const intentTargets=manifest.preflight?.intentTargets||[];
410
421
  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
422
  const dependencyReadSet=manifest.preflight?.dependencyReadSet||[];
@@ -462,6 +473,8 @@ document.getElementById('app').innerHTML=\`
462
473
  <div style="height:14px"></div>
463
474
  \${card('AI Intent Targets','<span class="pill">'+intentTargets.length+' targets</span>',intentTargetsHtml)}
464
475
  <div style="height:14px"></div>
476
+ \${card('AI Contract','<span class="pill">'+(contract.stability||'additive')+'</span>',contractHtml)}
477
+ <div style="height:14px"></div>
465
478
  \${card('Dependency Read Set','<span class="pill">'+dependencyReadSet.length+' files</span>',dependencyReadSetHtml)}
466
479
  <div style="height:14px"></div>
467
480
  \${card('Dependency Protocol','<span class="pill">'+(dependencyProtocol.mode||'advisory')+'</span>',dependencyProtocolHtml)}
@@ -569,6 +582,10 @@ function lightSbomLines(lightSbom = {}) {
569
582
  `- Transitive or unmatched vulnerable packages: ${summary.transitiveOrUnmatchedVulnerablePackages || 0}`,
570
583
  `- Lockfiles: ${(summary.lockfiles || []).map((item) => item.file).join(", ") || "none"}`
571
584
  ];
585
+ if (lightSbom.source || lightSbom.confidence) {
586
+ lines.push(`- Source: ${lightSbom.source?.dependencies || "project manifests"}; vulnerabilities: ${lightSbom.source?.vulnerabilities || "not scanned"}`);
587
+ lines.push(`- Confidence: direct ${lightSbom.confidence?.directDependencies || "unknown"}; transitive ${lightSbom.confidence?.transitiveDependencies || "unknown"}`);
588
+ }
572
589
  if (lightSbom.packageManagerPolicy) {
573
590
  lines.push(`- Package manager policy: ${lightSbom.packageManagerPolicy.status}`);
574
591
  lines.push(`- Package manager guidance: ${lightSbom.packageManagerPolicy.guidance}`);