aienvmp 0.1.1 → 0.1.3

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.
@@ -45,8 +45,7 @@ Use this for changes such as:
45
45
  Refresh the environment map:
46
46
 
47
47
  ```bash
48
- npx aienvmp scan
49
- npx aienvmp compile
48
+ npx aienvmp sync
50
49
  ```
51
50
 
52
51
  Record what changed:
package/BUGFIXES.md ADDED
@@ -0,0 +1,38 @@
1
+ # Bugfix Log
2
+
3
+ Short record of bugs, fixes, and follow-up checks.
4
+
5
+ ## 2026-07-08
6
+
7
+ ### CLI version was hardcoded
8
+
9
+ - Issue: `npm run smoke` printed `0.1.0` after the package version changed.
10
+ - Fix: CLI now reads the version from `package.json`.
11
+ - Verification: `npm run smoke` prints the package version.
12
+
13
+ ### AGENTS.md default generation conflicted with product focus
14
+
15
+ - Issue: default agent instruction file generation made `aienvmp` look like an AGENTS.md generator.
16
+ - Fix: `sync` now focuses on `AIENV.md`, manifest, intent log, timeline, and dashboard. Agent instruction integration is explicit through `aienvmp snippet`.
17
+ - Verification: `node --test` confirms `sync` does not create `AGENTS.md` by default and `snippet --write` only updates the marker block.
18
+
19
+ ### npm publish metadata appeared stale
20
+
21
+ - Issue: npm publish returned success for `0.1.2`, but `npm view aienvmp version` briefly returned `0.1.1`.
22
+ - Fix: verified with `npm view aienvmp versions --json` and `npm dist-tag ls aienvmp`; registry later reported `0.1.2`.
23
+ - Verification: `npm view aienvmp version` returns `0.1.2`.
24
+
25
+ ### macOS SSH PATH did not expose Node/npm
26
+
27
+ - Issue: remote macOS non-interactive SSH command did not find `node` or `npm`.
28
+ - Fix: test command used `/bin/zsh -lc` to load login shell PATH.
29
+ - Verification: remote test detected Node `v25.3.0`, npm `11.11.0`, and completed `aienvmp sync`.
30
+
31
+ ## Template
32
+
33
+ ### Title
34
+
35
+ - Issue:
36
+ - Fix:
37
+ - Verification:
38
+ - Follow-up:
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.3
4
+
5
+ - Strengthened the dashboard audit summary with AI decision, open intents, warnings, and recent changes.
6
+ - Added dashboard render coverage for the audit summary surface.
7
+
8
+ ## 0.1.2
9
+
10
+ - Added `aienvmp sync` as the simple one-step command for init, scan, `AIENV.md`, manifest, ledger, and dashboard generation.
11
+ - Improved AI preflight context with an explicit next action.
12
+ - Repositioned AGENTS.md integration as an optional snippet instead of default file generation.
13
+ - Added machine-readable sync/context improvements for AI and CI integrations.
14
+ - Simplified README for faster first-time understanding.
15
+ - Kept AGENTS.md, CLAUDE.md, and GEMINI.md integration explicit through `aienvmp snippet`.
16
+ - Added troubleshooting and bugfix logs for operational issue tracking.
17
+
18
+ ## 0.1.1
19
+
20
+ - Added repo-scoped Codex skill wrapper.
21
+ - Normalized npm package metadata and published the initial npm package.
22
+
3
23
  ## 0.1.0
4
24
 
5
25
  - Initial AI-first env map prototype.
package/README.md CHANGED
@@ -4,303 +4,72 @@
4
4
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
5
5
  [![Node](https://img.shields.io/badge/node-%3E%3D18-339933.svg)](package.json)
6
6
 
7
- **aienvmp = AI Environment Map**
7
+ **AI Environment Map.**
8
8
 
9
- `aienvmp` is an AI-first environment map and change ledger for shared AI coding workspaces.
9
+ `aienvmp` gives AI agents a shared view of the current dev environment:
10
10
 
11
- It helps multiple AI coding agents read the same runtime state, avoid installing or using conflicting software versions, declare environment-changing intent, record what changed, and keep humans informed through a lightweight dashboard.
11
+ - runtime versions
12
+ - package managers
13
+ - Docker state
14
+ - project version hints
15
+ - planned env changes
16
+ - recent env changes
12
17
 
13
- It is intentionally small: use it beside SBOM scanners, vulnerability scanners, version managers, and CI systems.
18
+ So multiple AI agents do not silently use or install different software versions.
14
19
 
15
- ## Why
16
-
17
- Shared development machines drift.
18
-
19
- One person upgrades Node. Another AI agent installs a global Python tool. A different agent assumes Docker is available. A third agent uses the wrong package manager because it did not see the project lockfile.
20
-
21
- After a few sessions, nobody is quite sure which runtime is safe to use.
22
-
23
- `aienvmp` is designed to prevent that kind of AI-driven version drift.
24
-
25
- It is also designed to stay out of the operator's way. By default, `aienvmp` reports warnings and review-required states instead of taking locks, blocking commands, or changing the machine.
26
-
27
- `aienvmp` turns that machine state into:
28
-
29
- - `AIENV.md`: an AI-readable environment protocol
30
- - `.aienvmp/manifest.json`: a lightweight runtime SBOM
31
- - `.aienvmp/timeline.jsonl`: an append-only environment change ledger
32
- - `.aienvmp/intents.jsonl`: planned and resolved environment-changing actions
33
- - `.aienvmp/dashboard.html`: a small human-readable dashboard
34
-
35
- ## Core Idea
36
-
37
- Before an AI changes the environment, it should know the environment.
38
-
39
- Before an AI installs or uses a runtime, it should check the shared policy.
40
-
41
- The policy is advisory by default. It should guide AI agents and humans, not unexpectedly interrupt production or shared workspace operations.
20
+ ## Use
42
21
 
43
22
  ```bash
44
- aienvmp context
45
- aienvmp intent --actor agent:codex --action "install pnpm" --target pnpm
46
- # make the change
47
- aienvmp scan
48
- aienvmp compile
49
- aienvmp record --actor agent:codex --summary "installed pnpm" --target pnpm --evidence "pnpm --version"
50
- aienvmp resolve --actor human:you --id int_abc123
51
- ```
52
-
53
- ## Quickstart
54
-
55
- Install with npm:
56
-
57
- ```bash
58
- npm install -g aienvmp
59
- ```
60
-
61
- Or run without installing:
62
-
63
- ```bash
64
- npx aienvmp init
65
- npx aienvmp scan
23
+ npx aienvmp sync
66
24
  npx aienvmp context
67
- npx aienvmp compile --agents all
68
- npx aienvmp dash
69
- ```
70
-
71
- Run locally from this repository while developing:
72
-
73
- ```bash
74
- node bin/aienvmp.js init
75
- node bin/aienvmp.js scan
76
- node bin/aienvmp.js context
77
- node bin/aienvmp.js compile --agents all
78
- node bin/aienvmp.js dash
79
25
  ```
80
26
 
81
- Try the included sample app:
82
-
83
- ```bash
84
- node bin/aienvmp.js scan --dir sample-app
85
- node bin/aienvmp.js context --dir sample-app
86
- node bin/aienvmp.js dash --dir sample-app
87
- ```
88
-
89
- ## AI Agent Workflow
90
-
91
- 1. Read `AIENV.md` or run `aienvmp context`.
92
- 2. Prefer project-local version files such as `.nvmrc`, `.python-version`, `mise.toml`, and `.tool-versions`.
93
- 3. Do not install or switch to a different runtime version when it conflicts with `.aienvmp/policy.yml`.
94
- 4. Ask the user before changing global runtimes, global packages, Docker settings, or package managers.
95
- 5. Record planned environment changes with `aienvmp intent`.
96
- 6. After changes, run `aienvmp scan && aienvmp compile`.
97
- 7. Record what changed with `aienvmp record`.
98
- 8. Resolve the intent with `aienvmp resolve`.
99
-
100
- Agent files can be injected automatically:
101
-
102
- ```bash
103
- aienvmp compile --agents codex,claude,gemini
104
- ```
105
-
106
- Generated agent targets:
107
-
108
- - `AGENTS.md`
109
- - `CLAUDE.md`
110
- - `GEMINI.md`
111
-
112
- Codex can also use the repo-scoped skill:
27
+ ## Output
113
28
 
114
29
  ```text
115
- .agents/skills/aienvmp/SKILL.md
30
+ AIENV.md
31
+ .aienvmp/manifest.json
32
+ .aienvmp/intents.jsonl
33
+ .aienvmp/timeline.jsonl
34
+ .aienvmp/dashboard.html
116
35
  ```
117
36
 
118
- Invoke it explicitly as `$aienvmp`, or let Codex select it when the task involves runtime, package manager, Docker, global package, or environment policy changes.
119
-
120
- ## What AI Agents See
121
-
122
- `aienvmp context` returns a compact preflight brief:
123
-
124
- ```text
125
- # AI Preflight Context
126
-
127
- Status: clear
128
- Node: 24.14.1
129
- Python: 3.11.0
130
- Docker: available
37
+ ## For AGENTS.md
131
38
 
132
- Must follow:
133
- - Ask the user before global runtime, package manager, Docker, or global package changes.
134
- - Prefer project-local version files and local environments.
135
- - Before planned env changes, run `aienvmp intent --actor <agent:id> --action <planned-change>`.
136
- - After env changes, run `aienvmp scan && aienvmp compile`.
137
- - Then run `aienvmp record --actor <agent:id> --summary <what-changed>`.
138
- ```
139
-
140
- For tool integrations:
39
+ `aienvmp` does not replace AGENTS.md. It gives AGENTS.md a live environment source of truth.
141
40
 
142
41
  ```bash
143
- aienvmp context --json
144
- aienvmp doctor --json
145
- aienvmp doctor --ci
42
+ npx aienvmp snippet agents
146
43
  ```
147
44
 
148
- `doctor --ci` is the explicit strict path. Normal `doctor` output is advisory and exits successfully so it does not disrupt shared operations.
149
-
150
45
  ## Commands
151
46
 
152
- | Command | Purpose |
153
- | --- | --- |
154
- | `init` | Create `.aienvmp/` |
155
- | `scan` | Collect runtime/tooling versions and write `.aienvmp/manifest.json` |
156
- | `context` | Print short AI preflight context |
157
- | `intent` | Record a planned environment-impacting action |
158
- | `resolve` | Mark an intent as resolved or cancelled |
159
- | `record` | Append an agent/human environment change to the ledger |
160
- | `compile` | Write `AIENV.md` and optionally inject agent files |
161
- | `diff` | Compare current and previous manifests |
162
- | `doctor` | Report environment warnings |
163
- | `dash` | Generate `.aienvmp/dashboard.html` |
164
-
165
- Intent IDs are short and prefix-matchable, so `aienvmp resolve --id int_mabc` works when it matches one open intent.
166
-
167
- ## What It Scans Today
168
-
169
- - OS: platform, release, arch, hostname, shell
170
- - Runtimes: Node.js, Python, Go, Java, Rust
171
- - Package managers and version managers: npm, pnpm, yarn, uv, pip, pipx, mise, asdf, pyenv, nvm, fnm, volta
172
- - Containers: Docker and Docker Compose
173
- - Project hints: `.nvmrc`, `.python-version`, `mise.toml`, `.tool-versions`, lockfiles, `package.json`, `pyproject.toml`, `Dockerfile`
174
- - Agent integration files: Codex, Claude, Gemini, Cursor, Copilot targets
175
-
176
- ## Version Drift Prevention
177
-
178
- The highest-priority use case is preventing multiple AI agents from installing or using different software versions in the same workspace.
179
-
180
- `aienvmp` checks project hints and policy against the currently detected environment.
181
-
182
- Example `.aienvmp/policy.yml`:
183
-
184
- ```yaml
185
- node: 24
186
- python: 3.11
187
- packageManager: npm
188
- globalInstalls: ask-first
189
- runtimeChanges: ask-first
190
- ```
191
-
192
- If an AI tries to use Node 22 when policy requires Node 24, `aienvmp doctor` and `aienvmp context` should surface that mismatch before work continues.
193
-
194
- Example warning:
195
-
196
- ```text
197
- .aienvmp/policy.yml requires node 24, but detected 22.11.0.
198
- ```
199
-
200
- Normal commands remain non-blocking. Use CI/strict mode only when you explicitly want a warning to fail automation.
201
-
202
- ## Output Layout
203
-
204
- ```text
205
- .aienvmp/
206
- manifest.json
207
- timeline.jsonl
208
- intents.jsonl
209
- dashboard.html
210
- AIENV.md
211
- AGENTS.md
212
- CLAUDE.md
213
- GEMINI.md
214
- ```
215
-
216
- ## Dashboard
217
-
218
- The dashboard is a static HTML file. It is derived from the manifest, warnings, intents, and ledger.
219
-
220
47
  ```bash
221
- aienvmp dash
222
- ```
223
-
224
- Open:
225
-
226
- ```text
227
- .aienvmp/dashboard.html
48
+ aienvmp sync # update env map, light SBOM, ledger, dashboard
49
+ aienvmp context # AI preflight brief
50
+ aienvmp context --json # machine-readable AI decision context
51
+ aienvmp intent # record a planned env change
52
+ aienvmp record # record what changed
53
+ aienvmp doctor --ci # strict CI check
228
54
  ```
229
55
 
230
- ## GitHub Action
56
+ ## Principles
231
57
 
232
- Use `aienvmp` in CI without making it disruptive by default:
233
-
234
- ```yaml
235
- - uses: soovwv/aienvmp@main
236
- with:
237
- directory: "."
238
- agents: "codex,claude,gemini"
239
- strict: "false"
240
- ```
241
-
242
- Set `strict: "true"` only when you want policy warnings to fail automation.
243
-
244
- See [examples/github-action.yml](examples/github-action.yml).
245
-
246
- ## How This Differs From SBOM Tools
247
-
248
- `aienvmp` is not trying to replace full SBOM or security scanners.
249
-
250
- Tools like Syft, Trivy, Dependency-Track, and osquery are excellent for component inventory, vulnerability scanning, and system instrumentation.
251
-
252
- `aienvmp` focuses on a narrower AI workflow:
253
-
254
- - What environment should this AI agent assume?
255
- - What must it avoid changing globally?
256
- - What changed recently?
257
- - Which agent planned or performed the change?
258
- - What should the next agent read before continuing?
259
-
260
- Think of it as an **AI coordination layer over a lightweight runtime SBOM**.
261
-
262
- ## Why It Is Different
263
-
264
- - **AI-first**: the primary consumer is an AI coding agent, not only a human operator.
265
- - **Version drift prevention**: policy warnings are designed to stop agents from silently using different Node, Python, Docker, or package manager assumptions.
266
- - **Change intent before change**: agents can declare intent before touching shared environment state.
267
- - **Append-only ledger**: environment-impacting changes are recorded for the next agent.
268
- - **Non-blocking operations**: normal warnings do not lock or break the machine; strict mode is opt-in.
269
- - **Small surface area**: no daemon, no database, no hosted service required.
270
-
271
- ## Project Links
272
-
273
- - [Roadmap](ROADMAP.md)
274
- - [Security policy](SECURITY.md)
275
- - [Contributing](CONTRIBUTING.md)
276
-
277
- ## Status
278
-
279
- This is an early prototype.
280
-
281
- Good fit today:
282
-
283
- - shared development servers
284
- - AI coding workspaces
285
- - local projects using Codex, Claude, Gemini, Cursor, or Copilot
286
- - self-hosted runner experiments
287
-
288
- Planned next:
289
-
290
- - deeper policy enforcement
291
- - deeper nvm/fnm/volta/pyenv/mise/asdf scans
292
- - global npm, pipx, and uv tool inventory
293
-
294
- See [ROADMAP.md](ROADMAP.md) for the longer plan.
58
+ - simple by default
59
+ - AI-first
60
+ - lightweight
61
+ - non-blocking unless strict mode is requested
295
62
 
296
63
  ## Development
297
64
 
298
65
  ```bash
299
66
  node --test
300
- npm pack --dry-run
301
67
  npm run smoke
68
+ npm pack --dry-run
302
69
  ```
303
70
 
304
- ## License
71
+ ## Links
72
+
73
+ [Roadmap](ROADMAP.md) / [Security](SECURITY.md) / [Troubleshooting](TROUBLESHOOTING.md) / [Bugfix Log](BUGFIXES.md) / [Contributing](CONTRIBUTING.md)
305
74
 
306
75
  Apache-2.0
@@ -0,0 +1,103 @@
1
+ # Troubleshooting
2
+
3
+ Known operational issues and quick fixes for `aienvmp`.
4
+
5
+ ## npm publish succeeds but `npm view` still shows the old version
6
+
7
+ Symptom:
8
+
9
+ ```text
10
+ npm publish
11
+ + aienvmp@0.1.2
12
+ npm view aienvmp version
13
+ 0.1.1
14
+ ```
15
+
16
+ Cause:
17
+
18
+ - npm registry metadata can lag briefly after publish.
19
+
20
+ Check:
21
+
22
+ ```bash
23
+ npm view aienvmp versions --json
24
+ npm dist-tag ls aienvmp
25
+ ```
26
+
27
+ Fix:
28
+
29
+ - Wait briefly and re-check `npm view aienvmp version`.
30
+
31
+ ## Windows `npm exec --package ... -- aienvmp --help` cannot find the command
32
+
33
+ Symptom:
34
+
35
+ ```text
36
+ 'aienvmp' is not recognized as an internal or external command
37
+ ```
38
+
39
+ Notes:
40
+
41
+ - The package bin metadata can still be valid.
42
+ - A direct temporary install creates the expected `.bin/aienvmp.cmd` shim.
43
+
44
+ Check:
45
+
46
+ ```bash
47
+ npm view aienvmp bin --json
48
+ npm install aienvmp@latest --prefix ./tmp-aienvmp-check
49
+ ```
50
+
51
+ Workaround:
52
+
53
+ ```bash
54
+ npx aienvmp@latest --version
55
+ ```
56
+
57
+ or install first:
58
+
59
+ ```bash
60
+ npm install -g aienvmp
61
+ aienvmp --version
62
+ ```
63
+
64
+ ## macOS SSH session cannot find `node` or `npm`
65
+
66
+ Symptom:
67
+
68
+ ```text
69
+ command -v node
70
+ # empty in non-interactive SSH command
71
+ ```
72
+
73
+ Cause:
74
+
75
+ - Node may be configured only in the login shell environment.
76
+
77
+ Fix:
78
+
79
+ ```bash
80
+ /bin/zsh -lc 'node --version && npm --version'
81
+ ```
82
+
83
+ For automation, make sure the shell PATH includes the Node/npm location.
84
+
85
+ ## Missing manifest
86
+
87
+ Symptom:
88
+
89
+ ```text
90
+ aienvmp: missing manifest; run `aienvmp sync` first
91
+ ```
92
+
93
+ Fix:
94
+
95
+ ```bash
96
+ npx aienvmp sync
97
+ ```
98
+
99
+ Then retry:
100
+
101
+ ```bash
102
+ npx aienvmp context
103
+ ```
package/action.yml CHANGED
@@ -7,10 +7,6 @@ inputs:
7
7
  description: Workspace directory to scan
8
8
  required: false
9
9
  default: "."
10
- agents:
11
- description: Agent files to inject during compile
12
- required: false
13
- default: ""
14
10
  strict:
15
11
  description: Fail when doctor reports warnings
16
12
  required: false
@@ -19,18 +15,9 @@ inputs:
19
15
  runs:
20
16
  using: composite
21
17
  steps:
22
- - name: Scan environment
23
- shell: bash
24
- run: node "$GITHUB_ACTION_PATH/bin/aienvmp.js" scan --dir "${{ inputs.directory }}"
25
-
26
- - name: Compile agent context
18
+ - name: Sync environment map
27
19
  shell: bash
28
- run: |
29
- if [ -n "${{ inputs.agents }}" ]; then
30
- node "$GITHUB_ACTION_PATH/bin/aienvmp.js" compile --dir "${{ inputs.directory }}" --agents "${{ inputs.agents }}"
31
- else
32
- node "$GITHUB_ACTION_PATH/bin/aienvmp.js" compile --dir "${{ inputs.directory }}"
33
- fi
20
+ run: node "$GITHUB_ACTION_PATH/bin/aienvmp.js" sync --dir "${{ inputs.directory }}" --quiet
34
21
 
35
22
  - name: Doctor
36
23
  shell: bash
@@ -13,5 +13,4 @@ jobs:
13
13
  - uses: soovwv/aienvmp@main
14
14
  with:
15
15
  directory: "."
16
- agents: "codex,claude,gemini"
17
16
  strict: "false"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aienvmp",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "AI-first environment maps and lightweight runtime SBOMs for shared development machines.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,8 +20,10 @@
20
20
  "README.md",
21
21
  "LICENSE",
22
22
  "CHANGELOG.md",
23
+ "BUGFIXES.md",
23
24
  "CONTRIBUTING.md",
24
25
  "SECURITY.md",
26
+ "TROUBLESHOOTING.md",
25
27
  "ROADMAP.md",
26
28
  "action.yml",
27
29
  "examples",
package/src/cli.js CHANGED
@@ -8,6 +8,8 @@ import { contextWorkspace } from "./commands/context.js";
8
8
  import { recordWorkspace } from "./commands/record.js";
9
9
  import { intentWorkspace } from "./commands/intent.js";
10
10
  import { resolveWorkspace } from "./commands/resolve.js";
11
+ import { syncWorkspace } from "./commands/sync.js";
12
+ import { snippetWorkspace } from "./commands/snippet.js";
11
13
  import { readFileSync } from "node:fs";
12
14
 
13
15
  const commands = new Map([
@@ -20,7 +22,9 @@ const commands = new Map([
20
22
  ["context", contextWorkspace],
21
23
  ["record", recordWorkspace],
22
24
  ["intent", intentWorkspace],
23
- ["resolve", resolveWorkspace]
25
+ ["resolve", resolveWorkspace],
26
+ ["sync", syncWorkspace],
27
+ ["snippet", snippetWorkspace]
24
28
  ]);
25
29
 
26
30
  const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
@@ -68,13 +72,23 @@ function printUsage() {
68
72
  console.log(`aienvmp - AI-first env map + lightweight runtime SBOM
69
73
 
70
74
  Usage:
75
+ aienvmp sync [--dir .] [--json] [--quiet]
76
+ aienvmp context [--dir .] [--json]
77
+
78
+ Common:
79
+ aienvmp sync update AIENV.md, manifest, ledger, intents, and dashboard
80
+ aienvmp context print the AI preflight brief
81
+ aienvmp snippet print an AGENTS.md pointer snippet
82
+ aienvmp dash regenerate/open the lightweight dashboard
83
+
84
+ Advanced:
71
85
  aienvmp init [--dir .]
72
86
  aienvmp scan [--dir .]
73
- aienvmp context [--dir .] [--json]
74
87
  aienvmp intent [--dir .] --actor agent:codex --action "install pnpm"
75
88
  aienvmp resolve [--dir .] --actor human:you --id <intent-id> [--status resolved|cancelled]
76
89
  aienvmp record [--dir .] --actor agent:codex --summary "updated .nvmrc" [--target node] [--before 20] [--after 24]
77
- aienvmp compile [--dir .] [--agents all|codex,claude,gemini]
90
+ aienvmp snippet [agents|claude|gemini] [--write AGENTS.md]
91
+ aienvmp compile [--dir .]
78
92
  aienvmp diff [--dir .]
79
93
  aienvmp doctor [--dir .] [--json] [--ci]
80
94
  aienvmp dash [--dir .] [--open]
@@ -1,41 +1,25 @@
1
1
  import fs from "node:fs/promises";
2
- import path from "node:path";
3
2
  import { diagnose } from "../doctor.js";
4
- import { readJson, replaceMarkerBlock } from "../fsutil.js";
3
+ import { readJson } from "../fsutil.js";
5
4
  import { openIntents, readJsonl, readTimeline } from "../timeline.js";
6
5
  import { aiEnvPath, intentsPath, manifestPath, timelinePath, workspaceDir } from "../paths.js";
7
- import { markerBegin, markerEnd, renderAgentBlock, renderAIEnv } from "../render.js";
6
+ import { renderAIEnv } from "../render.js";
8
7
  import { loadPolicy, policyWarnings } from "../policy.js";
9
8
 
10
- const agentFiles = {
11
- codex: "AGENTS.md",
12
- claude: "CLAUDE.md",
13
- gemini: "GEMINI.md"
14
- };
15
-
16
9
  export async function compileWorkspace(args) {
17
10
  const dir = workspaceDir(args);
18
11
  const manifest = await readJson(manifestPath(dir));
19
- if (!manifest) throw new Error("missing manifest; run `aienvmp scan` first");
12
+ if (!manifest) throw new Error("missing manifest; run `aienvmp sync` first");
20
13
  const timeline = await readTimeline(timelinePath(dir));
21
14
  const intents = openIntents(await readJsonl(intentsPath(dir)));
22
15
  const policy = await loadPolicy(dir);
23
16
  const warnings = [...diagnose(manifest), ...policyWarnings(manifest, policy)];
24
17
  const rendered = renderAIEnv(manifest, timeline, warnings, intents, policy);
25
18
  await fs.writeFile(aiEnvPath(dir), rendered, "utf8");
26
- const agents = parseAgents(args.agents);
27
- const block = renderAgentBlock(manifest);
28
- for (const agent of agents) {
29
- const rel = agentFiles[agent];
30
- if (!rel) continue;
31
- await replaceMarkerBlock(path.join(dir, rel), markerBegin, markerEnd, block);
19
+ if (!args.quiet) {
20
+ console.log(`compiled ${aiEnvPath(dir)}`);
32
21
  }
33
- console.log(`compiled ${aiEnvPath(dir)}`);
34
- if (agents.length) console.log(`injected: ${agents.join(", ")}`);
35
- }
36
-
37
- function parseAgents(value) {
38
- if (!value) return [];
39
- if (value === true || value === "all") return Object.keys(agentFiles);
40
- return String(value).split(",").map((v) => v.trim()).filter(Boolean);
22
+ return {
23
+ aiEnv: aiEnvPath(dir)
24
+ };
41
25
  }
@@ -8,14 +8,17 @@ import { loadPolicy, policyWarnings } from "../policy.js";
8
8
  export async function contextWorkspace(args) {
9
9
  const dir = workspaceDir(args);
10
10
  const manifest = await readJson(manifestPath(dir));
11
- if (!manifest) throw new Error("missing manifest; run `aienvmp scan` first");
11
+ if (!manifest) throw new Error("missing manifest; run `aienvmp sync` first");
12
12
  const timeline = await readTimeline(timelinePath(dir));
13
13
  const intents = openIntents(await readJsonl(intentsPath(dir)));
14
14
  const policy = await loadPolicy(dir);
15
15
  const warnings = [...diagnose(manifest), ...policyWarnings(manifest, policy)];
16
+ const decision = contextDecision(warnings, intents);
16
17
  if (args.json) {
17
18
  console.log(JSON.stringify({
18
19
  status: warnings.length ? "review-required" : "clear",
20
+ decision,
21
+ guidance: decision,
19
22
  workspace: manifest.workspace,
20
23
  runtimes: manifest.runtimes,
21
24
  packageManagers: manifest.packageManagers,
@@ -31,3 +34,25 @@ export async function contextWorkspace(args) {
31
34
  }
32
35
  console.log(renderContext(manifest, timeline, warnings, intents, policy));
33
36
  }
37
+
38
+ function contextDecision(warnings, intents) {
39
+ const reviewRequired = warnings.length > 0 || intents.length > 0;
40
+ return {
41
+ canProceed: !reviewRequired,
42
+ safeForProjectLocalWork: warnings.length === 0,
43
+ reviewRequired,
44
+ environmentChangeRequiresIntent: true,
45
+ globalEnvironmentChangesRequireUserApproval: true,
46
+ pendingIntentCount: intents.length,
47
+ mustNotDo: [
48
+ "do not change global runtimes without user approval",
49
+ "do not install or remove global package managers without user approval",
50
+ "do not change Docker daemon/context assumptions without user approval",
51
+ "do not ignore open intents or review-required warnings"
52
+ ],
53
+ recommendedNextActions: warnings.length
54
+ ? ["review warnings", "ask the user before environment changes", "record intent before changes"]
55
+ : ["continue with project-local work", "run aienvmp intent before environment changes"],
56
+ nextCommand: warnings.length ? "review warnings before changing environment" : "continue with project-local work"
57
+ };
58
+ }
@@ -11,7 +11,7 @@ import { loadPolicy, policyWarnings } from "../policy.js";
11
11
  export async function dashWorkspace(args) {
12
12
  const dir = workspaceDir(args);
13
13
  const manifest = await readJson(manifestPath(dir));
14
- if (!manifest) throw new Error("missing manifest; run `aienvmp scan` first");
14
+ if (!manifest) throw new Error("missing manifest; run `aienvmp sync` first");
15
15
  const timeline = await readTimeline(timelinePath(dir));
16
16
  const intents = openIntents(await readJsonl(intentsPath(dir)));
17
17
  const policy = await loadPolicy(dir);
@@ -20,8 +20,9 @@ export async function dashWorkspace(args) {
20
20
  const out = dashboardPath(dir);
21
21
  await fs.mkdir(path.dirname(out), { recursive: true });
22
22
  await fs.writeFile(out, html, "utf8");
23
- console.log(`dashboard: ${out}`);
23
+ if (!args.quiet) console.log(`dashboard: ${out}`);
24
24
  if (args.open) openFile(out);
25
+ return { dashboard: out };
25
26
  }
26
27
 
27
28
  function openFile(file) {
@@ -6,7 +6,7 @@ export async function diffWorkspace(args) {
6
6
  const dir = workspaceDir(args);
7
7
  const previous = await readJson(previousManifestPath(dir));
8
8
  const current = await readJson(manifestPath(dir));
9
- if (!current) throw new Error("missing manifest; run `aienvmp scan` first");
9
+ if (!current) throw new Error("missing manifest; run `aienvmp sync` first");
10
10
  const changes = diffManifests(previous, current);
11
11
  if (!changes.length) {
12
12
  console.log("no environment changes detected");
@@ -6,7 +6,7 @@ import { loadPolicy, policyWarnings } from "../policy.js";
6
6
  export async function doctorWorkspace(args) {
7
7
  const dir = workspaceDir(args);
8
8
  const manifest = await readJson(manifestPath(dir));
9
- if (!manifest) throw new Error("missing manifest; run `aienvmp scan` first");
9
+ if (!manifest) throw new Error("missing manifest; run `aienvmp sync` first");
10
10
  const policy = await loadPolicy(dir);
11
11
  const warnings = [...diagnose(manifest), ...policyWarnings(manifest, policy)];
12
12
  if (args.json) {
@@ -15,5 +15,6 @@ export async function initWorkspace(args) {
15
15
  "runtimeChanges: ask-first",
16
16
  ""
17
17
  ].join("\n"), { flag: "wx" }).catch(() => {});
18
- console.log(`initialized ${stateDir(dir)}`);
18
+ if (!args.quiet) console.log(`initialized ${stateDir(dir)}`);
19
+ return { stateDir: stateDir(dir) };
19
20
  }
@@ -22,7 +22,15 @@ export async function scanWorkspace(args) {
22
22
  change
23
23
  });
24
24
  }
25
- console.log(`scanned ${dir}`);
26
- console.log(`manifest: ${currentPath}`);
27
- console.log(`changes: ${changes.length}`);
25
+ if (!args.quiet) {
26
+ console.log(`scanned ${dir}`);
27
+ console.log(`manifest: ${currentPath}`);
28
+ console.log(`changes: ${changes.length}`);
29
+ }
30
+ return {
31
+ dir,
32
+ manifest: currentPath,
33
+ timeline: timelinePath(dir),
34
+ changes: changes.length
35
+ };
28
36
  }
@@ -0,0 +1,27 @@
1
+ import path from "node:path";
2
+ import { markerBegin, markerEnd, renderAgentPointer } from "../render.js";
3
+ import { replaceMarkerBlock } from "../fsutil.js";
4
+ import { workspaceDir } from "../paths.js";
5
+
6
+ const defaultTargets = {
7
+ agents: "AGENTS.md",
8
+ codex: "AGENTS.md",
9
+ claude: "CLAUDE.md",
10
+ gemini: "GEMINI.md"
11
+ };
12
+
13
+ export async function snippetWorkspace(args) {
14
+ const target = String(args._?.[0] || args.agent || "agents").toLowerCase();
15
+ const block = renderAgentPointer(target);
16
+
17
+ if (args.write) {
18
+ const dir = workspaceDir(args);
19
+ const rel = args.write === true ? defaultTargets[target] || "AGENTS.md" : String(args.write);
20
+ await replaceMarkerBlock(path.join(dir, rel), markerBegin, markerEnd, block);
21
+ if (!args.quiet) console.log(`snippet written: ${rel}`);
22
+ return { file: rel, target };
23
+ }
24
+
25
+ console.log(block);
26
+ return { target };
27
+ }
@@ -0,0 +1,34 @@
1
+ import { initWorkspace } from "./init.js";
2
+ import { scanWorkspace } from "./scan.js";
3
+ import { compileWorkspace } from "./compile.js";
4
+ import { dashWorkspace } from "./dash.js";
5
+
6
+ export async function syncWorkspace(args) {
7
+ const quiet = args.quiet || args.json;
8
+ const next = { ...args, quiet };
9
+
10
+ const initialized = await initWorkspace(next);
11
+ const scanned = await scanWorkspace(next);
12
+ const compiled = await compileWorkspace(next);
13
+ const dashboard = await dashWorkspace(next);
14
+
15
+ const result = {
16
+ status: "ok",
17
+ outputs: {
18
+ aiEnv: compiled.aiEnv,
19
+ manifest: scanned.manifest,
20
+ timeline: scanned.timeline,
21
+ dashboard: dashboard.dashboard
22
+ },
23
+ changes: scanned.changes,
24
+ initialized: initialized.stateDir
25
+ };
26
+
27
+ if (args.json) {
28
+ console.log(JSON.stringify(result, null, 2));
29
+ } else if (!quiet) {
30
+ console.log("sync complete: AIENV.md, manifest, ledger, intents, and dashboard are up to date");
31
+ }
32
+
33
+ return result;
34
+ }
package/src/manifest.js CHANGED
@@ -7,8 +7,13 @@ import { exists } from "./fsutil.js";
7
7
  export async function buildManifest(dir) {
8
8
  const now = new Date().toISOString();
9
9
  const manifest = {
10
+ schemaName: "aienvmp.runtime-sbom",
10
11
  schemaVersion: 1,
11
12
  generatedAt: now,
13
+ generatedBy: {
14
+ name: "aienvmp",
15
+ command: "aienvmp sync"
16
+ },
12
17
  workspace: {
13
18
  path: dir,
14
19
  name: path.basename(dir)
@@ -24,10 +29,51 @@ export async function buildManifest(dir) {
24
29
  preflightCommand: "aienvmp context",
25
30
  intentCommand: "aienvmp intent --actor <agent:id> --action <planned-change>",
26
31
  recordCommand: "aienvmp record --actor <agent:id> --summary <what-changed>",
27
- afterEnvironmentChange: ["aienvmp scan", "aienvmp compile"],
32
+ afterEnvironmentChange: ["aienvmp sync"],
28
33
  globalRuntimeChangeRequiresUserApproval: true,
29
34
  globalInstallPolicy: "ask-first",
30
35
  projectLocalChanges: "allowed-when-task-requires"
36
+ },
37
+ evidence: {
38
+ runtimes: {
39
+ node: "node --version",
40
+ python: "python --version",
41
+ python3: "python3 --version",
42
+ go: "go version",
43
+ java: "java -version",
44
+ rustc: "rustc --version"
45
+ },
46
+ packageManagers: {
47
+ npm: "npm --version",
48
+ pnpm: "pnpm --version",
49
+ yarn: "yarn --version",
50
+ uv: "uv --version",
51
+ pip: "pip --version",
52
+ pipx: "pipx --version",
53
+ mise: "mise --version",
54
+ asdf: "asdf --version",
55
+ pyenv: "pyenv --version",
56
+ nvm: "nvm --version",
57
+ fnm: "fnm --version",
58
+ volta: "volta --version"
59
+ },
60
+ containers: {
61
+ docker: "docker --version",
62
+ compose: "docker compose version or docker-compose --version"
63
+ },
64
+ projectHints: [
65
+ ".nvmrc",
66
+ ".python-version",
67
+ "mise.toml",
68
+ ".tool-versions",
69
+ "package.json",
70
+ "pyproject.toml",
71
+ "requirements.txt",
72
+ "Dockerfile",
73
+ "package-lock.json",
74
+ "pnpm-lock.yaml",
75
+ "yarn.lock"
76
+ ]
31
77
  }
32
78
  };
33
79
  return manifest;
package/src/render.js CHANGED
@@ -13,7 +13,7 @@ export function renderAIEnv(manifest, timeline = [], warnings = [], intents = []
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
15
  lines.push("4. Record planned environment changes with `aienvmp intent --actor <agent:id> --action <planned-change>`.");
16
- lines.push("5. After environment changes, run `aienvmp scan && aienvmp compile`.");
16
+ lines.push("5. After environment changes, run `aienvmp sync`.");
17
17
  lines.push("6. Record what changed with `aienvmp record --actor <agent:id> --summary <what-changed>`.", "");
18
18
  lines.push("## Current Policy", "");
19
19
  lines.push(...policyLines(policy));
@@ -57,31 +57,32 @@ export function renderAIEnv(manifest, timeline = [], warnings = [], intents = []
57
57
  return lines.join("\n");
58
58
  }
59
59
 
60
- export function renderAgentBlock(manifest) {
61
- const node = manifest.runtimes.node || "not detected";
62
- const python = manifest.runtimes.python || manifest.runtimes.python3 || "not detected";
63
- const docker = manifest.containers.docker ? "available" : "not detected";
64
- return `AI environment map is maintained by aienvmp.
60
+ export function renderAgentPointer(target = "agents") {
61
+ const label = target === "claude" ? "Claude" : target === "gemini" ? "Gemini" : "AI agents";
62
+ return `## aienvmp Environment Map
65
63
 
66
- Before changing runtimes, package managers, Docker, or global packages:
67
- 1. Read AIENV.md.
68
- 2. Run \`aienvmp context\`.
69
- 3. Ask the user before global environment changes.
70
- 4. Record planned changes with \`aienvmp intent\`.
71
- 5. After changes, run \`aienvmp scan && aienvmp compile\`.
64
+ ${label} should use \`aienvmp\` as the workspace environment source of truth.
65
+
66
+ Before changing runtimes, package managers, Docker settings, global packages, or environment policy:
67
+
68
+ 1. Run \`aienvmp context\`.
69
+ 2. Read \`AIENV.md\`.
70
+ 3. If the context says \`review-required\`, ask the user before changing the environment.
71
+ 4. Record planned environment changes with \`aienvmp intent\`.
72
+ 5. After environment changes, run \`aienvmp sync\`.
72
73
  6. Record what changed with \`aienvmp record\`.
73
74
 
74
- Default detected tools:
75
- - Node.js: ${node}
76
- - Python: ${python}
77
- - Docker: ${docker}`;
75
+ \`aienvmp\` does not replace this instruction file. It provides the live env map, lightweight runtime SBOM, intent log, timeline, and dashboard.`;
78
76
  }
79
77
 
80
78
  export function renderContext(manifest, timeline = [], warnings = [], intents = [], policy = {}) {
79
+ const status = warnings.length ? "review-required" : "clear";
80
+ const next = warnings.length ? "Review warnings before changing the environment." : "Continue with project-local work. Record intent before environment changes.";
81
81
  return [
82
82
  "# AI Preflight Context",
83
83
  "",
84
- `Status: ${warnings.length ? "review-required" : "clear"}`,
84
+ `Status: ${status}`,
85
+ `Next: ${next}`,
85
86
  `Workspace: ${manifest.workspace.path}`,
86
87
  `Node: ${manifest.runtimes.node || "not detected"}`,
87
88
  `Python: ${manifest.runtimes.python || manifest.runtimes.python3 || "not detected"}`,
@@ -95,7 +96,7 @@ export function renderContext(manifest, timeline = [], warnings = [], intents =
95
96
  "- Treat policy mismatches as review-required, not as permission to break ongoing operations.",
96
97
  "- Prefer project-local version files and local environments.",
97
98
  "- Before planned env changes, run `aienvmp intent --actor <agent:id> --action <planned-change>`.",
98
- "- After env changes, run `aienvmp scan && aienvmp compile`.",
99
+ "- After env changes, run `aienvmp sync`.",
99
100
  "- Then run `aienvmp record --actor <agent:id> --summary <what-changed>`.",
100
101
  "",
101
102
  "Warnings:",
@@ -130,6 +131,13 @@ h1,h2,h3,p{margin:0}h1{font-size:clamp(28px,4vw,46px);line-height:1.02;margin-to
130
131
  .sub{color:var(--muted);margin-top:12px;max-width:680px;line-height:1.55}
131
132
  .stamp{min-width:220px;border:1px solid var(--line2);background:#091310;border-radius:8px;padding:14px}
132
133
  .stamp b{display:block;color:var(--green);font-size:24px;margin-bottom:3px}.stamp span{display:block;color:var(--muted);font-size:12px;overflow-wrap:anywhere}
134
+ .audit{display:grid;grid-template-columns:1.2fr repeat(3,minmax(0,.8fr));gap:12px;margin:14px 0}
135
+ .audit-item{border:1px solid var(--line);background:rgba(13,24,21,.92);border-radius:8px;padding:14px;min-width:0}
136
+ .audit-item.primary{background:linear-gradient(135deg,rgba(19,61,42,.88),rgba(9,19,16,.95))}
137
+ .audit-item.review{background:linear-gradient(135deg,rgba(81,53,17,.88),rgba(9,19,16,.95));border-color:rgba(244,191,95,.42)}
138
+ .audit-k{color:var(--muted);font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.08em}
139
+ .audit-v{margin-top:7px;font-size:20px;font-weight:800;color:var(--text);overflow-wrap:anywhere}
140
+ .audit-hint{margin-top:6px;color:var(--muted);font-size:12px;line-height:1.4}
133
141
  .metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin:14px 0 18px}
134
142
  .metric,.card{border:1px solid var(--line);background:rgba(13,24,21,.9);border-radius:8px}
135
143
  .metric{padding:14px}.metric .num{font-size:28px;font-weight:800;color:var(--green);line-height:1}.metric .label{margin-top:7px;color:var(--muted);font-size:12px}
@@ -146,7 +154,9 @@ code{color:var(--code);background:#0a2017;border:1px solid #17462f;padding:2px 6
146
154
  .timeline{display:grid;gap:10px}.event{display:grid;grid-template-columns:108px 1fr;gap:12px;border-top:1px solid var(--line2);padding-top:10px}.event time{color:var(--muted);font-size:12px}.event b{color:var(--green)}
147
155
  .path{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--muted);font-size:12px;overflow-wrap:anywhere}
148
156
  @media (max-width:860px){header,.layout{grid-template-columns:1fr}.metrics{grid-template-columns:repeat(2,1fr)}.grid{grid-template-columns:1fr}.agents{grid-template-columns:1fr}}
157
+ @media (max-width:860px){.audit{grid-template-columns:1fr 1fr}}
149
158
  @media (max-width:520px){.shell{padding:14px}.metrics{grid-template-columns:1fr}.event{grid-template-columns:1fr}h1{font-size:32px}}
159
+ @media (max-width:520px){.audit{grid-template-columns:1fr}}
150
160
  </style>
151
161
  </head>
152
162
  <body>
@@ -159,14 +169,17 @@ const entries=o=>Object.entries(o||{});
159
169
  const rows=o=>entries(o).map(([k,v])=>\`<tr><th>\${esc(k)}</th><td><code>\${esc(String(v))}</code></td></tr>\`).join('')||'<tr><td colspan="2">None detected</td></tr>';
160
170
  const change=c=>c.type==='changed'?\`\${c.scope} \${c.key}: \${c.before} -> \${c.after}\`:\`\${c.scope} \${c.key}: \${c.type} \${c.after||c.before}\`;
161
171
  const timelineLabel=t=>t.change?change(t.change):(t.summary||t.action||t.type||'recorded change');
162
- const totalTools=entries(manifest.runtimes).length+entries(manifest.packageManagers).length+entries(manifest.containers).length;
163
172
  const agentNames={agents:'Codex',claude:'Claude',gemini:'Gemini'};
164
- const agentCards=Object.entries(agentNames).map(([key,label])=>\`<div class="agent"><strong>\${label}</strong><span>\${manifest.agentFiles?.[key]?'connected':'not connected'}</span></div>\`).join('');
173
+ const agentCards=Object.entries(agentNames).map(([key,label])=>\`<div class="agent"><strong>\${label}</strong><span>\${manifest.agentFiles?.[key]?'instruction file detected':'not detected'}</span></div>\`).join('');
165
174
  const warnHtml=warnings.length?'<div class="warnings">'+warnings.map(w=>\`<div class="warning">\${esc(w.message)}</div>\`).join('')+'</div>':'<div class="okline">No blocking environment warnings detected.</div>';
166
175
  const timelineHtml=timeline.length?'<div class="timeline">'+timeline.slice(-8).reverse().map(t=>\`<div class="event"><time>\${esc(t.at.replace('T',' ').slice(0,16))}</time><div><b>\${esc(t.actor||'system')}</b> \${esc(timelineLabel(t))}</div></div>\`).join('')+'</div>':'<div class="okline">No previous environment changes recorded.</div>';
167
176
  const intentsHtml=intents.length?'<div class="timeline">'+intents.slice(-6).reverse().map(i=>\`<div class="event"><time>\${esc(i.at.replace('T',' ').slice(0,16))}</time><div><b>\${esc(i.actor)}</b> plans \${esc(i.action)}</div></div>\`).join('')+'</div>':'<div class="okline">No pending agent intents recorded.</div>';
168
177
  const policyHtml=entries(policy).length?\`<table>\${rows(policy)}</table>\`:'<div class="okline">No explicit version policy set.</div>';
169
178
  const card=(title,badge,body)=>\`<section class="card"><div class="card-head"><h2>\${title}</h2>\${badge||''}</div>\${body}</section>\`;
179
+ const reviewRequired=warnings.length>0||intents.length>0;
180
+ const recentChanges=timeline.slice(-8).length;
181
+ const nextAction=reviewRequired?'Review before environment changes':'Proceed with project-local work';
182
+ 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>\`;
170
183
  document.getElementById('app').innerHTML=\`
171
184
  <header>
172
185
  <div>
@@ -176,6 +189,12 @@ document.getElementById('app').innerHTML=\`
176
189
  </div>
177
190
  <div class="stamp"><b>\${warnings.length?'review':'clear'}</b><span>\${esc(manifest.workspace.name)}</span><span>\${esc(manifest.generatedAt)}</span></div>
178
191
  </header>
192
+ <section class="audit" aria-label="Audit summary">
193
+ \${auditItem('AI decision',reviewRequired?'review required':'can proceed',nextAction,reviewRequired?'review':'primary')}
194
+ \${auditItem('Open intents',String(intents.length),intents.length?'Resolve or coordinate before changes':'No pending env changes')}
195
+ \${auditItem('Warnings',String(warnings.length),warnings.length?'Policy or drift needs attention':'No warnings detected')}
196
+ \${auditItem('Recent changes',String(recentChanges),recentChanges?'Check ledger before continuing':'No recent env ledger entries')}
197
+ </section>
179
198
  <section class="metrics">
180
199
  <div class="metric"><div class="num">\${entries(manifest.runtimes).length}</div><div class="label">runtimes</div></div>
181
200
  <div class="metric"><div class="num">\${entries(manifest.packageManagers).length}</div><div class="label">package managers</div></div>
@@ -196,7 +215,7 @@ document.getElementById('app').innerHTML=\`
196
215
  <div style="height:14px"></div>
197
216
  \${card('Agent Intents','<span class="pill">'+intents.length+' open</span>',intentsHtml)}
198
217
  <div style="height:14px"></div>
199
- \${card('Agent Integration','<span class="pill">'+totalTools+' tools</span>','<div class="agents">'+agentCards+'</div>')}
218
+ \${card('Agent Pointers','<span class="pill">'+entries(manifest.agentFiles).filter(([,v])=>v).length+' detected</span>','<div class="agents">'+agentCards+'</div>')}
200
219
  <div style="height:14px"></div>
201
220
  \${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>\`)}
202
221
  </aside>
@@ -236,6 +255,7 @@ function formatTimeline(item) {
236
255
  function contextLines(manifest, warnings, intents) {
237
256
  return [
238
257
  `- Status: ${warnings.length ? "review-required" : "clear"}`,
258
+ `- Next: ${warnings.length ? "review warnings before environment changes" : "continue with project-local work"}`,
239
259
  `- Node: ${manifest.runtimes.node || "not detected"}`,
240
260
  `- Python: ${manifest.runtimes.python || manifest.runtimes.python3 || "not detected"}`,
241
261
  `- Docker: ${manifest.containers.docker ? "available" : "not detected"}`,