aienvmp 0.1.6 → 0.1.7

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.7
4
+
5
+ - Added stale open intent warnings for long-running environment change plans.
6
+ - Added stale handoff warnings when environment changes happen after the last recorded AI handoff.
7
+ - Added optional `aienvmp handoff --record --actor <agent:id>` timeline entries.
8
+
3
9
  ## 0.1.6
4
10
 
5
11
  - Added optional `sync --deep` / `scan --deep` read-only global tool inventory.
package/README.md CHANGED
@@ -18,6 +18,7 @@ Core loop: scan the env, give AI a preflight context, and hand off safe next ste
18
18
  npx aienvmp sync
19
19
  npx aienvmp context
20
20
  npx aienvmp handoff
21
+ npx aienvmp handoff --record --actor agent:codex
21
22
  ```
22
23
 
23
24
  Optional deeper read-only inventory:
@@ -65,7 +66,8 @@ aienvmp doctor --ci # strict CI check
65
66
  - simple by default
66
67
  - AI-first
67
68
  - lightweight
68
- - non-blocking unless strict mode is requested
69
+ - one advisory engine, optional enforcement with `doctor --ci`
70
+ - non-blocking unless strict mode is explicitly requested
69
71
 
70
72
  ## Development
71
73
 
package/ROADMAP.md CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  - Strengthen trust states: observed, planned, changed, review, verified, stale
8
8
  - Detect multi-agent environment intent conflicts
9
+ - Keep one advisory decision engine with optional strict enforcement
9
10
  - Stabilize `.aienvmp/manifest.json` and JSON command schemas
10
11
  - Keep `sync`, `context`, and `handoff` as the simple core flow
11
12
  - Improve the dashboard for 10-second human review
@@ -20,9 +21,8 @@
20
21
  - richer summaries for `npm -g`, `pipx list`, `uv tool list`, and Homebrew
21
22
  - optional `--deep` scanners for more toolchains
22
23
  - Conflict detection:
23
- - stale unresolved intents
24
- - recent runtime changes without a fresh handoff
25
24
  - package manager policy vs lockfile mismatch
25
+ - monorepo/project boundary aware intent targets
26
26
  - CI mode:
27
27
  - stable exit codes
28
28
  - GitHub Action example
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aienvmp",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
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
@@ -76,7 +76,7 @@ function printUsage() {
76
76
  Usage:
77
77
  aienvmp sync [--dir .] [--json] [--quiet] [--deep]
78
78
  aienvmp context [--dir .] [--json]
79
- aienvmp handoff [--dir .] [--json]
79
+ aienvmp handoff [--dir .] [--json] [--record --actor agent:id]
80
80
 
81
81
  Common:
82
82
  aienvmp sync update AIENV.md, manifest, ledger, intents, and dashboard
@@ -1,9 +1,10 @@
1
1
  import { diagnose } from "../doctor.js";
2
- import { readJson } from "../fsutil.js";
2
+ import { appendJsonLine, readJson } from "../fsutil.js";
3
3
  import { loadPolicy, policyWarnings } from "../policy.js";
4
4
  import { intentsPath, manifestPath, timelinePath, workspaceDir } from "../paths.js";
5
5
  import { renderHandoff } from "../render.js";
6
6
  import { openIntents, readJsonl, readTimeline } from "../timeline.js";
7
+ import { changedTrust } from "../trust.js";
7
8
 
8
9
  export async function handoffWorkspace(args) {
9
10
  const dir = workspaceDir(args);
@@ -14,6 +15,9 @@ export async function handoffWorkspace(args) {
14
15
  const policy = await loadPolicy(dir);
15
16
  const warnings = [...diagnose(manifest, { timeline, intents }), ...policyWarnings(manifest, policy)];
16
17
  const handoff = buildHandoff(manifest, timeline, warnings, intents, policy);
18
+ if (args.record) {
19
+ await recordHandoff(timelinePath(dir), handoff, args.actor || "agent:unknown");
20
+ }
17
21
 
18
22
  if (args.json) {
19
23
  console.log(JSON.stringify(handoff, null, 2));
@@ -23,6 +27,19 @@ export async function handoffWorkspace(args) {
23
27
  return handoff;
24
28
  }
25
29
 
30
+ async function recordHandoff(file, handoff, actor) {
31
+ const now = new Date();
32
+ await appendJsonLine(file, {
33
+ at: now.toISOString(),
34
+ actor,
35
+ type: "agent-handoff",
36
+ summary: `handoff ${handoff.status}`,
37
+ status: handoff.status,
38
+ warnings: handoff.warnings.map((warning) => warning.code),
39
+ trust: changedTrust(now, handoff.status !== "clear")
40
+ });
41
+ }
42
+
26
43
  export function buildHandoff(manifest, timeline = [], warnings = [], intents = [], policy = {}) {
27
44
  const reviewRequired = warnings.length > 0 || intents.length > 0;
28
45
  return {
package/src/doctor.js CHANGED
@@ -43,6 +43,8 @@ export function diagnose(manifest, context = {}) {
43
43
  });
44
44
  }
45
45
  warnings.push(...coordinationWarnings(context.intents || []));
46
+ warnings.push(...staleIntentWarnings(context.intents || []));
47
+ warnings.push(...handoffWarnings(context.timeline || []));
46
48
  return warnings;
47
49
  }
48
50
 
@@ -69,6 +71,30 @@ export function coordinationWarnings(intents = []) {
69
71
  return warnings;
70
72
  }
71
73
 
74
+ export function staleIntentWarnings(intents = [], now = new Date(), maxAgeHours = 4) {
75
+ const warnings = [];
76
+ for (const intent of intents) {
77
+ if (!isStaleTimestamp(intent.at, now, maxAgeHours)) continue;
78
+ warnings.push({
79
+ code: "stale-open-intent",
80
+ target: intent.target || inferTarget(intent.action) || "",
81
+ message: `Open intent ${intent.id || ""} from ${intent.actor || "unknown"} is older than ${maxAgeHours} hours. Resolve it or confirm it before environment changes.`.replace(/\s+/g, " ").trim()
82
+ });
83
+ }
84
+ return warnings;
85
+ }
86
+
87
+ export function handoffWarnings(timeline = []) {
88
+ const lastEnvChange = [...timeline].reverse().find(isEnvironmentChange);
89
+ if (!lastEnvChange) return [];
90
+ const lastHandoff = [...timeline].reverse().find((item) => item.type === "agent-handoff");
91
+ if (lastHandoff && new Date(lastHandoff.at).getTime() >= new Date(lastEnvChange.at).getTime()) return [];
92
+ return [{
93
+ code: "handoff-stale",
94
+ message: "Environment changes were recorded after the last AI handoff. Run `aienvmp handoff --record --actor agent:id` before the next agent continues."
95
+ }];
96
+ }
97
+
72
98
  function inferTarget(action = "") {
73
99
  const normalized = String(action).toLowerCase();
74
100
  for (const target of ["node", "python", "docker", "npm", "pnpm", "yarn", "uv", "pip", "pipx"]) {
@@ -76,3 +102,24 @@ function inferTarget(action = "") {
76
102
  }
77
103
  return "";
78
104
  }
105
+
106
+ function isEnvironmentChange(item = {}) {
107
+ if (["runtime", "package-manager", "container"].includes(item.change?.scope)) return true;
108
+ if (item.type === "detected-change") return false;
109
+ const text = `${item.type || ""} ${item.target || ""} ${item.summary || ""} ${item.action || ""} ${item.change?.scope || ""} ${item.change?.key || ""}`.toLowerCase();
110
+ return [
111
+ "runtime",
112
+ "node",
113
+ "python",
114
+ "docker",
115
+ "package manager",
116
+ "package-manager",
117
+ "npm",
118
+ "pnpm",
119
+ "yarn",
120
+ "uv",
121
+ "pip",
122
+ "pipx",
123
+ "global"
124
+ ].some((token) => text.includes(token));
125
+ }
package/src/manifest.js CHANGED
@@ -32,8 +32,8 @@ export async function buildManifest(dir, options = {}) {
32
32
  sourceOfTruth: "AIENV.md",
33
33
  preflightCommand: "aienvmp context",
34
34
  handoffCommand: "aienvmp handoff",
35
- intentCommand: "aienvmp intent --actor <agent:id> --action <planned-change>",
36
- recordCommand: "aienvmp record --actor <agent:id> --summary <what-changed>",
35
+ intentCommand: "aienvmp intent --actor agent:id --action planned-change",
36
+ recordCommand: "aienvmp record --actor agent:id --summary what-changed",
37
37
  afterEnvironmentChange: ["aienvmp sync"],
38
38
  trustModel: {
39
39
  agentWritable: ["observed", "planned", "changed", "review", "stale"],
package/src/render.js CHANGED
@@ -12,9 +12,10 @@ export function renderAIEnv(manifest, timeline = [], warnings = [], intents = []
12
12
  lines.push("1. Run `aienvmp context`.");
13
13
  lines.push("2. Prefer project-local version files such as `.nvmrc`, `.python-version`, `mise.toml`, and `.tool-versions`.");
14
14
  lines.push("3. Ask the user before changing global environment state.");
15
- lines.push("4. Record planned environment changes with `aienvmp intent --actor <agent:id> --action <planned-change>`.");
15
+ lines.push("4. Record planned environment changes with `aienvmp intent --actor agent:id --action planned-change`.");
16
16
  lines.push("5. After environment changes, run `aienvmp sync`.");
17
- lines.push("6. Record what changed with `aienvmp record --actor <agent:id> --summary <what-changed>`.", "");
17
+ lines.push("6. Record what changed with `aienvmp record --actor agent:id --summary what-changed`.");
18
+ lines.push("7. At handoff, run `aienvmp handoff --record --actor agent:id`.", "");
18
19
  lines.push("## Current Policy", "");
19
20
  lines.push(...policyLines(policy));
20
21
  lines.push("- Enforcement: non-blocking by default; warnings require review but do not lock the machine.");
@@ -76,6 +77,7 @@ Before changing runtimes, package managers, Docker settings, global packages, or
76
77
  4. Record planned environment changes with \`aienvmp intent\`.
77
78
  5. After environment changes, run \`aienvmp sync\`.
78
79
  6. Record what changed with \`aienvmp record\`.
80
+ 7. At handoff, run \`aienvmp handoff --record --actor agent:id\`.
79
81
 
80
82
  \`aienvmp\` does not replace this instruction file. It provides the live env map, lightweight runtime SBOM, intent log, timeline, and dashboard.`;
81
83
  }
@@ -102,9 +104,10 @@ export function renderContext(manifest, timeline = [], warnings = [], intents =
102
104
  "- Ask the user before global runtime, package manager, Docker, or global package changes.",
103
105
  "- Treat policy mismatches as review-required, not as permission to break ongoing operations.",
104
106
  "- Prefer project-local version files and local environments.",
105
- "- Before planned env changes, run `aienvmp intent --actor <agent:id> --action <planned-change>`.",
107
+ "- Before planned env changes, run `aienvmp intent --actor agent:id --action planned-change`.",
106
108
  "- After env changes, run `aienvmp sync`.",
107
- "- Then run `aienvmp record --actor <agent:id> --summary <what-changed>`.",
109
+ "- Then run `aienvmp record --actor agent:id --summary what-changed`.",
110
+ "- Before handing work to another AI, run `aienvmp handoff --record --actor agent:id`.",
108
111
  "",
109
112
  "Warnings:",
110
113
  ...(warnings.length ? warnings.map((w) => `- ${w.message}`) : ["- none"]),