aienvmp 0.1.39 → 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 +18 -0
- package/README.md +52 -81
- package/package.json +1 -1
- package/src/cli.js +5 -1
- package/src/commands/context.js +4 -0
- package/src/commands/handoff.js +18 -1
- package/src/commands/schema.js +16 -0
- package/src/commands/status.js +1 -0
- package/src/contract.js +42 -0
- package/src/dependencies.js +19 -1
- package/src/preflight.js +55 -0
- package/src/render.js +44 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
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
|
+
|
|
12
|
+
## 0.1.40
|
|
13
|
+
|
|
14
|
+
- Added dependency handoff summaries so the next AI receives dependency read-set and protocol guidance directly in `handoff`.
|
|
15
|
+
- Added a compact `nextAgent` hint to status/preflight JSON for safer AI-to-AI continuation.
|
|
16
|
+
- Added target-level open intent coordination summaries so agents can detect dependency conflicts without parsing logs.
|
|
17
|
+
- Shortened the README around the 10-second AI flow, generated outputs, and advisory contract.
|
|
18
|
+
- Updated the dashboard handoff card with next-agent read hints, dependency files, and conflict targets.
|
|
19
|
+
- Printed the next-agent handoff command in the plain `status` output.
|
|
20
|
+
|
|
3
21
|
## 0.1.39
|
|
4
22
|
|
|
5
23
|
- Added a dependency read set to preflight, `AIENV.md`, and the dashboard so agents know which manifests and lockfiles to read before package or security changes.
|
package/README.md
CHANGED
|
@@ -6,123 +6,96 @@
|
|
|
6
6
|
|
|
7
7
|
**AI Environment Map.**
|
|
8
8
|
|
|
9
|
-
`aienvmp` is
|
|
9
|
+
`aienvmp` is a lightweight env map and light SBOM for shared AI coding machines.
|
|
10
10
|
|
|
11
|
-
It helps Codex, Claude, Gemini, and humans avoid silent
|
|
11
|
+
It helps Codex, Claude, Gemini, and humans avoid silent runtime, package manager, dependency, Docker, and security drift.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## Quick Start
|
|
13
|
+
## 10-Second Use
|
|
16
14
|
|
|
17
15
|
```bash
|
|
18
16
|
npx aienvmp sync
|
|
19
17
|
npx aienvmp status
|
|
20
|
-
npx aienvmp context
|
|
21
|
-
npx aienvmp
|
|
22
|
-
npx aienvmp handoff
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
10-second AI flow: `aienvmp status --write` -> `aienvmp context --json` -> intent before environment changes.
|
|
26
|
-
Use `--dir <workspace>` before or after the command when an AI or CI process runs outside the target project.
|
|
27
|
-
|
|
28
|
-
Optional deeper read-only checks:
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npx aienvmp sync --deep
|
|
32
|
-
npx aienvmp sync --security
|
|
18
|
+
npx aienvmp context --json
|
|
19
|
+
npx aienvmp schema --json
|
|
33
20
|
```
|
|
34
21
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Tell each agent to read `aienvmp context --json` before environment changes.
|
|
22
|
+
Before environment changes:
|
|
38
23
|
|
|
39
24
|
```bash
|
|
40
|
-
npx aienvmp
|
|
41
|
-
npx aienvmp
|
|
42
|
-
npx aienvmp record --actor agent:codex --summary "updated .nvmrc" --target node
|
|
25
|
+
npx aienvmp intent --actor agent:codex --action "change dependency" --target dependency
|
|
26
|
+
npx aienvmp record --actor agent:codex --summary "dependency-change" --target dependency
|
|
43
27
|
npx aienvmp handoff --record --actor agent:codex
|
|
44
28
|
```
|
|
45
29
|
|
|
46
|
-
|
|
30
|
+
Use `--dir <workspace>` when AI or CI runs outside the target project.
|
|
31
|
+
|
|
32
|
+
## What It Creates
|
|
47
33
|
|
|
48
34
|
```text
|
|
49
|
-
AIENV.md
|
|
50
|
-
.aienvmp/
|
|
51
|
-
.aienvmp/
|
|
52
|
-
.aienvmp/intents.jsonl
|
|
53
|
-
.aienvmp/timeline.jsonl
|
|
54
|
-
.aienvmp/plan.
|
|
55
|
-
.aienvmp/
|
|
56
|
-
.aienvmp/dashboard.html # includes dependencies, plan, remediation, and environment cards
|
|
35
|
+
AIENV.md # Markdown env map for AI agents
|
|
36
|
+
.aienvmp/status.json # first AI read: clear/review, next command, nextAgent hint
|
|
37
|
+
.aienvmp/manifest.json # runtime map + light SBOM
|
|
38
|
+
.aienvmp/intents.jsonl # planned env changes
|
|
39
|
+
.aienvmp/timeline.jsonl # append-only change ledger
|
|
40
|
+
.aienvmp/plan.md # read-only action plan
|
|
41
|
+
.aienvmp/dashboard.html # human dashboard
|
|
57
42
|
```
|
|
58
43
|
|
|
59
|
-
|
|
44
|
+
## AI Contract
|
|
60
45
|
|
|
61
|
-
|
|
62
|
-
`
|
|
63
|
-
`status
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
AI agents can observe, plan, and record. Only a human or CI should mark environment facts as verified.
|
|
46
|
+
- `status`, `context`, `plan`, and `handoff` share one preflight contract.
|
|
47
|
+
- `schema --json` prints the stable AI-readable output contract without scanning.
|
|
48
|
+
- `status.json.nextAgent` tells the next AI what to read and whether to review first.
|
|
49
|
+
- `dependencyReadSet` lists manifests and lockfiles before package or security changes.
|
|
50
|
+
- `coordination.conflictTargets` shows where multiple agents are planning changes.
|
|
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.
|
|
53
|
+
- Everything is advisory by default; strict failure is opt-in with `doctor --strict`.
|
|
71
54
|
|
|
72
55
|
## Agent Files
|
|
73
56
|
|
|
74
|
-
`aienvmp` does not replace AGENTS.md. It gives
|
|
57
|
+
`aienvmp` does not replace AGENTS.md, CLAUDE.md, or GEMINI.md. It gives them a live environment source of truth.
|
|
75
58
|
|
|
76
59
|
```bash
|
|
77
60
|
npx aienvmp snippet agents
|
|
61
|
+
npx aienvmp snippet claude
|
|
62
|
+
npx aienvmp snippet gemini
|
|
78
63
|
```
|
|
79
64
|
|
|
80
|
-
##
|
|
65
|
+
## Commands
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
aienvmp sync # update env map, status, ledger, dashboard
|
|
69
|
+
aienvmp status --write # refresh compact AI status
|
|
70
|
+
aienvmp context --json # AI decision contract
|
|
71
|
+
aienvmp schema --json # stable output contract for AI/CI consumers
|
|
72
|
+
aienvmp plan --write # read-only action plan
|
|
73
|
+
aienvmp handoff --record # next-agent summary
|
|
74
|
+
aienvmp intent # record planned env change
|
|
75
|
+
aienvmp record # record what changed
|
|
76
|
+
aienvmp doctor --strict security|policy|coordination|all
|
|
77
|
+
```
|
|
81
78
|
|
|
82
|
-
|
|
83
|
-
CI also writes `.aienvmp/status.json` for a compact AI-readable result.
|
|
84
|
-
The dashboard shows which strict scopes are CI-ready before you enforce them.
|
|
79
|
+
## CI
|
|
85
80
|
|
|
86
81
|
```yaml
|
|
87
82
|
- uses: soovwv/aienvmp@main
|
|
88
83
|
with:
|
|
89
84
|
write-status: "true"
|
|
90
85
|
write-plan: "true"
|
|
91
|
-
strict: "off"
|
|
86
|
+
strict: "off"
|
|
92
87
|
```
|
|
93
88
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
aienvmp sync # update env map, light SBOM, status, ledger, dashboard
|
|
98
|
-
aienvmp status --write # refresh .aienvmp/status.json only
|
|
99
|
-
aienvmp context # AI preflight brief
|
|
100
|
-
aienvmp context --json # AI decision contract + actions + compact step summary
|
|
101
|
-
aienvmp plan # read-only AI action plan using the same decision contract
|
|
102
|
-
aienvmp handoff # next-agent handoff summary using the same decision contract
|
|
103
|
-
aienvmp intent # record a planned env change
|
|
104
|
-
aienvmp record # record what changed
|
|
105
|
-
aienvmp doctor --ci # strict CI check for all warnings
|
|
106
|
-
aienvmp doctor --strict security # fail only scoped warnings
|
|
107
|
-
```
|
|
89
|
+
See [examples/github-action.yml](examples/github-action.yml).
|
|
108
90
|
|
|
109
91
|
## Principles
|
|
110
92
|
|
|
111
|
-
- simple
|
|
112
|
-
- AI-first
|
|
113
|
-
- lightweight
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
-
|
|
117
|
-
- context and plan expose suggested strict scopes for CI
|
|
118
|
-
- dashboard and preflight explain advisory default vs optional strict mode
|
|
119
|
-
- non-blocking unless strict mode is explicitly requested
|
|
120
|
-
- security checks are opt-in and read-only
|
|
121
|
-
- light SBOM is generated from project files and optional scanner summaries
|
|
122
|
-
- dependency change hints point AI agents to the relevant manifest before edits
|
|
123
|
-
- lockfiles are detected read-only and shown before dependency edits
|
|
124
|
-
- package manager policy is inferred from lockfiles to avoid accidental npm/pnpm/yarn drift
|
|
125
|
-
- dashboard mirrors AI-facing SBOM hints so humans can review the same dependency context
|
|
93
|
+
- simple commands
|
|
94
|
+
- AI-first outputs
|
|
95
|
+
- lightweight, read-only scanning
|
|
96
|
+
- light SBOM from project files
|
|
97
|
+
- advisory locally, strict only when requested
|
|
98
|
+
- humans or CI verify; AI agents observe, plan, record, and hand off
|
|
126
99
|
|
|
127
100
|
## Development
|
|
128
101
|
|
|
@@ -132,8 +105,6 @@ npm run smoke
|
|
|
132
105
|
npm pack --dry-run
|
|
133
106
|
```
|
|
134
107
|
|
|
135
|
-
## Links
|
|
136
|
-
|
|
137
108
|
[Roadmap](ROADMAP.md) / [Security](SECURITY.md) / [Troubleshooting](TROUBLESHOOTING.md) / [Bugfix Log](BUGFIXES.md) / [Contributing](CONTRIBUTING.md)
|
|
138
109
|
|
|
139
110
|
Apache-2.0
|
package/package.json
CHANGED
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
|
|
package/src/commands/context.js
CHANGED
|
@@ -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
|
}
|
package/src/commands/handoff.js
CHANGED
|
@@ -46,11 +46,12 @@ async function recordHandoff(file, handoff, actor) {
|
|
|
46
46
|
export function buildHandoff(manifest, timeline = [], warnings = [], intents = [], policy = {}) {
|
|
47
47
|
const reviewRequired = warnings.length > 0 || intents.length > 0;
|
|
48
48
|
const actions = recommendedActions(manifest, { warnings, intents });
|
|
49
|
+
const preflight = buildPreflight(manifest, warnings, intents);
|
|
49
50
|
return {
|
|
50
51
|
status: reviewRequired ? "review-required" : "clear",
|
|
51
52
|
trust: manifest.trust || {},
|
|
52
53
|
schemaVersion: manifest.schemaVersion || 1,
|
|
53
|
-
preflight
|
|
54
|
+
preflight,
|
|
54
55
|
decision: aiDecision(warnings, intents),
|
|
55
56
|
workspace: manifest.workspace,
|
|
56
57
|
safeRuntime: {
|
|
@@ -60,6 +61,8 @@ export function buildHandoff(manifest, timeline = [], warnings = [], intents = [
|
|
|
60
61
|
},
|
|
61
62
|
inventory: inventorySummary(manifest.inventory),
|
|
62
63
|
security: securitySummary(manifest.security),
|
|
64
|
+
dependencyHandoff: dependencyHandoffSummary(preflight),
|
|
65
|
+
coordination: preflight.coordination,
|
|
63
66
|
policy: {
|
|
64
67
|
node: policy.node || "not set",
|
|
65
68
|
python: policy.python || "not set",
|
|
@@ -82,6 +85,20 @@ export function buildHandoff(manifest, timeline = [], warnings = [], intents = [
|
|
|
82
85
|
};
|
|
83
86
|
}
|
|
84
87
|
|
|
88
|
+
function dependencyHandoffSummary(preflight = {}) {
|
|
89
|
+
const protocol = preflight.dependencyChangeProtocol || {};
|
|
90
|
+
return {
|
|
91
|
+
readSet: (preflight.dependencyReadSet || []).slice(0, 5),
|
|
92
|
+
protocol: {
|
|
93
|
+
mode: protocol.mode || "advisory",
|
|
94
|
+
packageManagerPolicy: protocol.packageManagerPolicy || "not-detected",
|
|
95
|
+
recordIntent: protocol.commands?.recordIntent || "aienvmp intent --actor agent:id --action planned-change --target dependency",
|
|
96
|
+
recordAfterChange: protocol.commands?.recordAfterChange || "aienvmp record --actor agent:id --summary dependency-change --target dependency",
|
|
97
|
+
handoff: protocol.commands?.handoff || "aienvmp handoff --record --actor agent:id"
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
85
102
|
function securitySummary(security = {}) {
|
|
86
103
|
return {
|
|
87
104
|
mode: security.mode || "basic",
|
|
@@ -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
|
+
}
|
package/src/commands/status.js
CHANGED
|
@@ -25,6 +25,7 @@ export async function statusWorkspace(args) {
|
|
|
25
25
|
console.log(`next: ${output.nextCommand}`);
|
|
26
26
|
console.log(`ai: ${output.quickstart.readFirst} -> ${output.quickstart.detailCommand}`);
|
|
27
27
|
console.log(`intent: ${output.intentTargets[0]?.command || output.commands.recordIntent}`);
|
|
28
|
+
console.log(`handoff: ${output.nextAgent.handoffCommand}`);
|
|
28
29
|
console.log(`strict: ${output.enforcement.recommendedCommand}`);
|
|
29
30
|
if (artifact) console.log(`status: ${artifact}`);
|
|
30
31
|
}
|
package/src/contract.js
ADDED
|
@@ -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
|
+
}
|
package/src/dependencies.js
CHANGED
|
@@ -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);
|
|
@@ -11,8 +12,10 @@ export function buildPreflight(manifest = {}, warnings = [], intents = []) {
|
|
|
11
12
|
const intentTargets = recommendedIntentTargets(manifest, warnings, intents);
|
|
12
13
|
const dependencyReadSet = dependencyPreflightReadSet(manifest);
|
|
13
14
|
const dependencyChangeProtocol = dependencyProtocol(manifest, dependencyReadSet);
|
|
15
|
+
const coordination = coordinationSummary(intents);
|
|
14
16
|
return {
|
|
15
17
|
schemaVersion: 1,
|
|
18
|
+
contract: preflightContract(),
|
|
16
19
|
state,
|
|
17
20
|
summary: state === "clear"
|
|
18
21
|
? "Project-local work can continue. Record intent before environment changes."
|
|
@@ -47,6 +50,8 @@ export function buildPreflight(manifest = {}, warnings = [], intents = []) {
|
|
|
47
50
|
environmentChanges: decision.canChangeEnvironmentWithoutReview ? "allowed" : "intent-and-review-first"
|
|
48
51
|
},
|
|
49
52
|
quickstart: agentQuickstart(decision.reviewRequired),
|
|
53
|
+
nextAgent: nextAgentHint(state, dependencyReadSet, dependencyChangeProtocol),
|
|
54
|
+
coordination,
|
|
50
55
|
intentTargets,
|
|
51
56
|
dependencyReadSet,
|
|
52
57
|
dependencyChangeProtocol,
|
|
@@ -72,6 +77,56 @@ export function buildPreflight(manifest = {}, warnings = [], intents = []) {
|
|
|
72
77
|
};
|
|
73
78
|
}
|
|
74
79
|
|
|
80
|
+
function coordinationSummary(intents = []) {
|
|
81
|
+
const byTarget = new Map();
|
|
82
|
+
for (const intent of intents) {
|
|
83
|
+
const target = normalizeTarget(intent.target || targetFromText(intent.action) || "environment") || "environment";
|
|
84
|
+
const item = byTarget.get(target) || { target, count: 0, actors: [], actions: [], conflict: false };
|
|
85
|
+
item.count += 1;
|
|
86
|
+
if (intent.actor && !item.actors.includes(intent.actor)) item.actors.push(intent.actor);
|
|
87
|
+
if (intent.action) item.actions.push(intent.action);
|
|
88
|
+
byTarget.set(target, item);
|
|
89
|
+
}
|
|
90
|
+
const targets = [...byTarget.values()].map((item) => ({
|
|
91
|
+
target: item.target,
|
|
92
|
+
count: item.count,
|
|
93
|
+
actors: item.actors.slice(0, 5),
|
|
94
|
+
actions: item.actions.slice(-3),
|
|
95
|
+
conflict: item.count > 1 && item.actors.length > 1
|
|
96
|
+
}));
|
|
97
|
+
const conflictTargets = targets.filter((item) => item.conflict).map((item) => item.target);
|
|
98
|
+
return {
|
|
99
|
+
openIntentCount: intents.length,
|
|
100
|
+
targets,
|
|
101
|
+
conflictTargets,
|
|
102
|
+
next: conflictTargets.length
|
|
103
|
+
? "Review or resolve conflicting intents before environment changes."
|
|
104
|
+
: intents.length
|
|
105
|
+
? "Check open intents before environment changes."
|
|
106
|
+
: "No open environment intents."
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function nextAgentHint(state, dependencyReadSet = [], dependencyChangeProtocol = {}) {
|
|
111
|
+
const firstDependency = dependencyReadSet[0];
|
|
112
|
+
const dependencyFiles = firstDependency
|
|
113
|
+
? [firstDependency.manifest, ...(firstDependency.lockfiles || [])].filter(Boolean)
|
|
114
|
+
: [];
|
|
115
|
+
return {
|
|
116
|
+
handoffCommand: dependencyChangeProtocol.commands?.handoff || "aienvmp handoff --record --actor agent:id",
|
|
117
|
+
readFirst: ".aienvmp/status.json",
|
|
118
|
+
readNext: "aienvmp context --json",
|
|
119
|
+
reviewState: state,
|
|
120
|
+
dependencyFiles,
|
|
121
|
+
dependencyProtocol: dependencyChangeProtocol.commands?.recordIntent
|
|
122
|
+
? "record dependency intent, sync after change, then record dependency-change"
|
|
123
|
+
: "no dependency protocol detected",
|
|
124
|
+
rule: state === "clear"
|
|
125
|
+
? "Next AI may continue project-local work; record intent before environment changes."
|
|
126
|
+
: "Next AI should review warnings or open intents before environment changes."
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
75
130
|
function dependencyProtocol(manifest = {}, dependencyReadSet = []) {
|
|
76
131
|
const pmPolicy = manifest.lightSbom?.packageManagerPolicy || {};
|
|
77
132
|
return {
|
package/src/render.js
CHANGED
|
@@ -199,12 +199,18 @@ 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
|
"",
|
|
205
208
|
"Recommended actions:",
|
|
206
209
|
...(handoff.recommendedActions?.length ? handoff.recommendedActions.map((item) => `- [${item.priority}] ${item.summary}${item.command ? ` (${item.command})` : ""}`) : ["- none"]),
|
|
207
210
|
"",
|
|
211
|
+
"Dependency handoff:",
|
|
212
|
+
...dependencyHandoffLines(handoff.dependencyHandoff),
|
|
213
|
+
"",
|
|
208
214
|
"Recent changes:",
|
|
209
215
|
...(handoff.recentChanges.length ? handoff.recentChanges.map((t) => `- ${formatTimeline(t)}`) : ["- none"]),
|
|
210
216
|
"",
|
|
@@ -217,6 +223,30 @@ export function renderHandoff(handoff) {
|
|
|
217
223
|
return lines.join("\n");
|
|
218
224
|
}
|
|
219
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
|
+
|
|
232
|
+
function dependencyHandoffLines(dependencyHandoff = {}) {
|
|
233
|
+
const readSet = dependencyHandoff.readSet || [];
|
|
234
|
+
const protocol = dependencyHandoff.protocol || {};
|
|
235
|
+
const lines = [];
|
|
236
|
+
if (readSet.length) {
|
|
237
|
+
for (const item of readSet.slice(0, 3)) {
|
|
238
|
+
const files = [item.manifest, ...(item.lockfiles || [])].filter(Boolean).join(", ");
|
|
239
|
+
lines.push(`- Read: ${files || "dependency files"} (${item.ecosystem || "deps"}/${item.manager || "unknown"})`);
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
lines.push("- Read: no dependency files detected");
|
|
243
|
+
}
|
|
244
|
+
lines.push(`- Intent: ${protocol.recordIntent || "aienvmp intent --actor agent:id --action planned-change --target dependency"}`);
|
|
245
|
+
lines.push(`- After change: ${protocol.recordAfterChange || "aienvmp record --actor agent:id --summary dependency-change --target dependency"}`);
|
|
246
|
+
lines.push(`- Handoff: ${protocol.handoff || "aienvmp handoff --record --actor agent:id"}`);
|
|
247
|
+
return lines;
|
|
248
|
+
}
|
|
249
|
+
|
|
220
250
|
export function renderPlan(plan) {
|
|
221
251
|
const lines = [
|
|
222
252
|
"# AI Environment Plan",
|
|
@@ -385,6 +415,8 @@ const ciReadinessHtml=ciReadiness.length?'<table>'+ciReadiness.map(s=>\`<tr><th>
|
|
|
385
415
|
const enforcementProfile=manifest.preflight?.enforcementProfile||{};
|
|
386
416
|
const strictCommands=enforcementProfile.strictCommands||[];
|
|
387
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>';
|
|
388
420
|
const intentTargets=manifest.preflight?.intentTargets||[];
|
|
389
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>';
|
|
390
422
|
const dependencyReadSet=manifest.preflight?.dependencyReadSet||[];
|
|
@@ -398,7 +430,12 @@ const trustState=manifest.trust?.state||'observed';
|
|
|
398
430
|
const nextAction=reviewRequired?'Review before environment changes':'Proceed with project-local work';
|
|
399
431
|
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>\`;
|
|
400
432
|
const driftLabel=warnings.length?'detected':'none';
|
|
401
|
-
const
|
|
433
|
+
const nextAgent=manifest.preflight?.nextAgent||{};
|
|
434
|
+
const coordination=manifest.preflight?.coordination||{};
|
|
435
|
+
const conflictTargets=coordination.conflictTargets||[];
|
|
436
|
+
const handoffFiles=nextAgent.dependencyFiles?.length?nextAgent.dependencyFiles:(dependencyReadSet[0]?[dependencyReadSet[0].manifest,...(dependencyReadSet[0].lockfiles||[])].filter(Boolean):[]);
|
|
437
|
+
const handoffNext=nextAgent.rule||(reviewRequired?'Review warnings and open intents':'Continue project-local work');
|
|
438
|
+
const handoffHtml=\`<table><tr><th>Status</th><td>\${reviewRequired?'review-required':'clear'}</td></tr><tr><th>Trust</th><td><code>\${esc(trustState)}</code></td></tr><tr><th>Read first</th><td><code>\${esc(nextAgent.readFirst||'.aienvmp/status.json')}</code></td></tr><tr><th>Dependency files</th><td>\${handoffFiles.length?'<code>'+esc(handoffFiles.join(', '))+'</code>':'none'}</td></tr><tr><th>Conflicts</th><td>\${conflictTargets.length?'<code>'+esc(conflictTargets.join(', '))+'</code>':'none'}</td></tr><tr><th>Next</th><td>\${esc(handoffNext)}</td></tr></table>\`;
|
|
402
439
|
document.getElementById('app').innerHTML=\`
|
|
403
440
|
<header>
|
|
404
441
|
<div>
|
|
@@ -436,6 +473,8 @@ document.getElementById('app').innerHTML=\`
|
|
|
436
473
|
<div style="height:14px"></div>
|
|
437
474
|
\${card('AI Intent Targets','<span class="pill">'+intentTargets.length+' targets</span>',intentTargetsHtml)}
|
|
438
475
|
<div style="height:14px"></div>
|
|
476
|
+
\${card('AI Contract','<span class="pill">'+(contract.stability||'additive')+'</span>',contractHtml)}
|
|
477
|
+
<div style="height:14px"></div>
|
|
439
478
|
\${card('Dependency Read Set','<span class="pill">'+dependencyReadSet.length+' files</span>',dependencyReadSetHtml)}
|
|
440
479
|
<div style="height:14px"></div>
|
|
441
480
|
\${card('Dependency Protocol','<span class="pill">'+(dependencyProtocol.mode||'advisory')+'</span>',dependencyProtocolHtml)}
|
|
@@ -543,6 +582,10 @@ function lightSbomLines(lightSbom = {}) {
|
|
|
543
582
|
`- Transitive or unmatched vulnerable packages: ${summary.transitiveOrUnmatchedVulnerablePackages || 0}`,
|
|
544
583
|
`- Lockfiles: ${(summary.lockfiles || []).map((item) => item.file).join(", ") || "none"}`
|
|
545
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
|
+
}
|
|
546
589
|
if (lightSbom.packageManagerPolicy) {
|
|
547
590
|
lines.push(`- Package manager policy: ${lightSbom.packageManagerPolicy.status}`);
|
|
548
591
|
lines.push(`- Package manager guidance: ${lightSbom.packageManagerPolicy.guidance}`);
|