aienvmp 0.1.3 → 0.1.4

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.4
4
+
5
+ - Added `aienvmp handoff` for next-agent environment handoff summaries.
6
+ - Added an AI Handoff card to the dashboard.
7
+ - Added handoff test coverage.
8
+
3
9
  ## 0.1.3
4
10
 
5
11
  - Strengthened the dashboard audit summary with AI decision, open intents, warnings, and recent changes.
package/README.md CHANGED
@@ -48,6 +48,7 @@ npx aienvmp snippet agents
48
48
  aienvmp sync # update env map, light SBOM, ledger, dashboard
49
49
  aienvmp context # AI preflight brief
50
50
  aienvmp context --json # machine-readable AI decision context
51
+ aienvmp handoff # next-agent handoff summary
51
52
  aienvmp intent # record a planned env change
52
53
  aienvmp record # record what changed
53
54
  aienvmp doctor --ci # strict CI check
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aienvmp",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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
@@ -10,6 +10,7 @@ import { intentWorkspace } from "./commands/intent.js";
10
10
  import { resolveWorkspace } from "./commands/resolve.js";
11
11
  import { syncWorkspace } from "./commands/sync.js";
12
12
  import { snippetWorkspace } from "./commands/snippet.js";
13
+ import { handoffWorkspace } from "./commands/handoff.js";
13
14
  import { readFileSync } from "node:fs";
14
15
 
15
16
  const commands = new Map([
@@ -24,7 +25,8 @@ const commands = new Map([
24
25
  ["intent", intentWorkspace],
25
26
  ["resolve", resolveWorkspace],
26
27
  ["sync", syncWorkspace],
27
- ["snippet", snippetWorkspace]
28
+ ["snippet", snippetWorkspace],
29
+ ["handoff", handoffWorkspace]
28
30
  ]);
29
31
 
30
32
  const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
@@ -74,10 +76,12 @@ function printUsage() {
74
76
  Usage:
75
77
  aienvmp sync [--dir .] [--json] [--quiet]
76
78
  aienvmp context [--dir .] [--json]
79
+ aienvmp handoff [--dir .] [--json]
77
80
 
78
81
  Common:
79
82
  aienvmp sync update AIENV.md, manifest, ledger, intents, and dashboard
80
83
  aienvmp context print the AI preflight brief
84
+ aienvmp handoff print the next-agent handoff summary
81
85
  aienvmp snippet print an AGENTS.md pointer snippet
82
86
  aienvmp dash regenerate/open the lightweight dashboard
83
87
 
@@ -0,0 +1,55 @@
1
+ import { diagnose } from "../doctor.js";
2
+ import { readJson } from "../fsutil.js";
3
+ import { loadPolicy, policyWarnings } from "../policy.js";
4
+ import { intentsPath, manifestPath, timelinePath, workspaceDir } from "../paths.js";
5
+ import { renderHandoff } from "../render.js";
6
+ import { openIntents, readJsonl, readTimeline } from "../timeline.js";
7
+
8
+ export async function handoffWorkspace(args) {
9
+ const dir = workspaceDir(args);
10
+ const manifest = await readJson(manifestPath(dir));
11
+ if (!manifest) throw new Error("missing manifest; run `aienvmp sync` first");
12
+ const timeline = await readTimeline(timelinePath(dir));
13
+ const intents = openIntents(await readJsonl(intentsPath(dir)));
14
+ const policy = await loadPolicy(dir);
15
+ const warnings = [...diagnose(manifest), ...policyWarnings(manifest, policy)];
16
+ const handoff = buildHandoff(manifest, timeline, warnings, intents, policy);
17
+
18
+ if (args.json) {
19
+ console.log(JSON.stringify(handoff, null, 2));
20
+ } else {
21
+ console.log(renderHandoff(handoff));
22
+ }
23
+ return handoff;
24
+ }
25
+
26
+ export function buildHandoff(manifest, timeline = [], warnings = [], intents = [], policy = {}) {
27
+ const reviewRequired = warnings.length > 0 || intents.length > 0;
28
+ return {
29
+ status: reviewRequired ? "review-required" : "clear",
30
+ workspace: manifest.workspace,
31
+ safeRuntime: {
32
+ node: manifest.runtimes?.node || "not detected",
33
+ python: manifest.runtimes?.python || manifest.runtimes?.python3 || "not detected",
34
+ docker: manifest.containers?.docker ? "available" : "not detected"
35
+ },
36
+ policy: {
37
+ node: policy.node || "not set",
38
+ python: policy.python || "not set",
39
+ packageManager: policy.packageManager || "not set",
40
+ runtimeChanges: policy.runtimeChanges || "ask-first",
41
+ globalInstalls: policy.globalInstalls || "ask-first"
42
+ },
43
+ openIntents: intents.slice(-5).reverse(),
44
+ warnings,
45
+ recentChanges: timeline.slice(-5).reverse(),
46
+ mustNotDo: [
47
+ "do not change global runtimes without user approval",
48
+ "do not install or remove global package managers without user approval",
49
+ "do not change Docker daemon/context assumptions without user approval"
50
+ ],
51
+ recommendedNext: reviewRequired
52
+ ? "review warnings and open intents before environment changes"
53
+ : "continue with project-local work; record intent before environment changes"
54
+ };
55
+ }
package/src/render.js CHANGED
@@ -111,6 +111,36 @@ export function renderContext(manifest, timeline = [], warnings = [], intents =
111
111
  ].join("\n");
112
112
  }
113
113
 
114
+ export function renderHandoff(handoff) {
115
+ const lines = [
116
+ "# AI Handoff",
117
+ "",
118
+ `Status: ${handoff.status}`,
119
+ `Workspace: ${handoff.workspace?.path || "unknown"}`,
120
+ "",
121
+ "Safe runtime:",
122
+ `- Node: ${handoff.safeRuntime.node}`,
123
+ `- Python: ${handoff.safeRuntime.python}`,
124
+ `- Docker: ${handoff.safeRuntime.docker}`,
125
+ "",
126
+ "Open intents:",
127
+ ...(handoff.openIntents.length ? handoff.openIntents.map((i) => `- ${i.actor}: ${i.action}${i.target ? ` (${i.target})` : ""}`) : ["- none"]),
128
+ "",
129
+ "Warnings:",
130
+ ...(handoff.warnings.length ? handoff.warnings.map((w) => `- ${w.message}`) : ["- none"]),
131
+ "",
132
+ "Recent changes:",
133
+ ...(handoff.recentChanges.length ? handoff.recentChanges.map((t) => `- ${formatTimeline(t)}`) : ["- none"]),
134
+ "",
135
+ "Must not do:",
136
+ ...handoff.mustNotDo.map((item) => `- ${item}`),
137
+ "",
138
+ `Recommended next: ${handoff.recommendedNext}`,
139
+ ""
140
+ ];
141
+ return lines.join("\n");
142
+ }
143
+
114
144
  export function renderDashboard(manifest, timeline = [], warnings = [], intents = [], policy = {}) {
115
145
  const data = JSON.stringify({ manifest, timeline, warnings, intents, policy });
116
146
  return `<!doctype html>
@@ -180,6 +210,7 @@ const reviewRequired=warnings.length>0||intents.length>0;
180
210
  const recentChanges=timeline.slice(-8).length;
181
211
  const nextAction=reviewRequired?'Review before environment changes':'Proceed with project-local work';
182
212
  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>\`;
213
+ const handoffHtml=\`<table><tr><th>Status</th><td>\${reviewRequired?'review-required':'clear'}</td></tr><tr><th>Node</th><td><code>\${esc(manifest.runtimes.node||'not detected')}</code></td></tr><tr><th>Python</th><td><code>\${esc(manifest.runtimes.python||manifest.runtimes.python3||'not detected')}</code></td></tr><tr><th>Docker</th><td>\${manifest.containers?.docker?'available':'not detected'}</td></tr><tr><th>Next</th><td>\${reviewRequired?'Review warnings and open intents':'Continue project-local work'}</td></tr></table>\`;
183
214
  document.getElementById('app').innerHTML=\`
184
215
  <header>
185
216
  <div>
@@ -215,6 +246,8 @@ document.getElementById('app').innerHTML=\`
215
246
  <div style="height:14px"></div>
216
247
  \${card('Agent Intents','<span class="pill">'+intents.length+' open</span>',intentsHtml)}
217
248
  <div style="height:14px"></div>
249
+ \${card('AI Handoff',reviewRequired?'<span class="pill warn">review</span>':'<span class="pill">ready</span>',handoffHtml)}
250
+ <div style="height:14px"></div>
218
251
  \${card('Agent Pointers','<span class="pill">'+entries(manifest.agentFiles).filter(([,v])=>v).length+' detected</span>','<div class="agents">'+agentCards+'</div>')}
219
252
  <div style="height:14px"></div>
220
253
  \${card('Snapshot','',\`<table><tr><th>OS</th><td>\${esc(manifest.os.platform)} \${esc(manifest.os.release)} \${esc(manifest.os.arch)}</td></tr><tr><th>Shell</th><td>\${esc(manifest.os.shell||'unknown')}</td></tr><tr><th>Workspace</th><td><div class="path">\${esc(manifest.workspace.path)}</div></td></tr></table>\`)}