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 +6 -0
- package/README.md +3 -1
- package/ROADMAP.md +2 -2
- package/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/commands/handoff.js +18 -1
- package/src/doctor.js +47 -0
- package/src/manifest.js +2 -2
- package/src/render.js +7 -4
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
|
-
-
|
|
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
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
|
package/src/commands/handoff.js
CHANGED
|
@@ -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
|
|
36
|
-
recordCommand: "aienvmp record --actor
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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"]),
|