aienvmp 0.1.66 → 0.1.68
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 +28 -0
- package/README.md +41 -25
- package/action.yml +30 -0
- package/examples/github-action.yml +1 -1
- package/package.json +1 -1
- package/src/actions.js +8 -2
- package/src/commands/context.js +11 -0
- package/src/commands/doctor.js +9 -0
- package/src/commands/handoff.js +33 -0
- package/src/commands/schema.js +1 -0
- package/src/commands/summary.js +7 -0
- package/src/contract.js +20 -3
- package/src/enforcement.js +24 -0
- package/src/preflight.js +89 -1
- package/src/render.js +42 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.1.68
|
|
6
|
+
|
|
7
|
+
- Added an `aienvmp AI loop` block to the GitHub Action Step Summary using `schema.aiLoop`.
|
|
8
|
+
- Kept the Action summary advisory by default while showing the same loop documented in README and `schema --json`.
|
|
9
|
+
- Updated the GitHub Action example and README CI notes to mention Step Summary strict-plan and AI-loop blocks.
|
|
10
|
+
- Added `doctor --json` `nextSafeCommand` so AI agents can pick one advisory next command without parsing every warning.
|
|
11
|
+
- Aligned the dashboard next-command fallback with `maintenanceLoop.nextCommand` so the visual surface follows the same advisory next-step chain.
|
|
12
|
+
- Added `context --json` `nextSafeCommand` so AI agents get the same one-step advisory next command in the preflight payload.
|
|
13
|
+
- Added `handoff --json` `nextSafeCommand` so the next AI can resume from one root-level advisory command.
|
|
14
|
+
- Added `status --json` and `.aienvmp/status.json` `nextSafeCommand` as a stable alias for the preflight next command.
|
|
15
|
+
|
|
16
|
+
## 0.1.67
|
|
17
|
+
|
|
18
|
+
- Added a dashboard `First read` strip with status, first file, review targets, and local operation mode.
|
|
19
|
+
- Kept the detailed dashboard cards below the top decision area so humans get a faster scan without losing AI context.
|
|
20
|
+
- Added `maintenanceLoop` to the AI preflight contract so agents can repeat refresh, decide, inspect, plan, intent, checkpoint, and handoff steps.
|
|
21
|
+
- Surfaced the maintenance loop in `context --json`, `summary.md`, `AIENV.md`, schema metadata, and regression tests.
|
|
22
|
+
- Added `maintenanceLoop.sbomReview` so AI agents can connect SBOM risk, scanner confidence, review targets, and dependency-change commands.
|
|
23
|
+
- Added medium light-SBOM risk planning guidance to recommended actions.
|
|
24
|
+
- Added `strictDecision` so AI agents and CI can distinguish local warn-only checks from optional strict gates.
|
|
25
|
+
- Surfaced local `doctor --json` and scoped CI strict commands in summary, dashboard, status/context JSON, and tests.
|
|
26
|
+
- Added `handoff.continuation` so the next AI receives maintenance-loop, SBOM-review, and strict-decision guidance without parsing the full preflight.
|
|
27
|
+
- Added the continuation summary to plain Markdown handoff output and schema metadata.
|
|
28
|
+
- Reworked the README first-read flow around shared AI workspaces, the 10-second command path, and the AI maintenance loop.
|
|
29
|
+
- Added `aiLoop` to `schema --json` and plain schema output so AI consumers see the same loop described in the README.
|
|
30
|
+
|
|
3
31
|
## 0.1.66
|
|
4
32
|
|
|
5
33
|
- Added `aienvmp resolve --target <target>` to resolve all open intents for one environment target.
|
package/README.md
CHANGED
|
@@ -6,28 +6,54 @@
|
|
|
6
6
|
|
|
7
7
|
**AI Environment Map.**
|
|
8
8
|
|
|
9
|
-
`aienvmp` is a lightweight env map and light SBOM for
|
|
9
|
+
`aienvmp` is a lightweight env map and light SBOM for AI coding workspaces.
|
|
10
10
|
|
|
11
|
-
It helps Codex, Claude, Gemini, and humans avoid silent runtime, package manager, dependency, Docker, and security drift.
|
|
11
|
+
It helps Codex, Claude, Gemini, and humans avoid silent runtime, package manager, dependency, Docker, and security drift on shared machines.
|
|
12
|
+
|
|
13
|
+
**Use it when multiple AI agents or people touch the same server, repo, laptop, or CI workspace.**
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
- one AI-readable environment map
|
|
18
|
+
- one lightweight SBOM view
|
|
19
|
+
- one append-only intent and change timeline
|
|
20
|
+
- one human dashboard
|
|
21
|
+
- advisory by default, strict only when requested
|
|
12
22
|
|
|
13
23
|
## 10-Second Use
|
|
14
24
|
|
|
15
25
|
```bash
|
|
16
26
|
npx aienvmp sync
|
|
17
27
|
npx aienvmp status
|
|
28
|
+
npx aienvmp handoff
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
For AI details:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
18
34
|
npx aienvmp context --json
|
|
19
35
|
npx aienvmp schema --json
|
|
20
36
|
```
|
|
21
37
|
|
|
22
|
-
Before environment
|
|
38
|
+
Before an environment-affecting change:
|
|
23
39
|
|
|
24
40
|
```bash
|
|
25
|
-
npx aienvmp intent --actor agent:
|
|
26
|
-
npx aienvmp checkpoint --actor agent:
|
|
41
|
+
npx aienvmp intent --actor agent:id --action "planned-change" --target dependency
|
|
42
|
+
npx aienvmp checkpoint --actor agent:id --summary "dependency-change" --target dependency
|
|
27
43
|
```
|
|
28
44
|
|
|
29
|
-
Use `--dir <workspace>` when AI or CI runs outside the target project.
|
|
30
|
-
|
|
45
|
+
Use `--dir <workspace>` when AI or CI runs outside the target project. Warnings do not block local work by default.
|
|
46
|
+
|
|
47
|
+
## AI Loop
|
|
48
|
+
|
|
49
|
+
1. `sync` refreshes `AIENV.md`, status, summary, SBOM, ledger, and dashboard.
|
|
50
|
+
2. `status` gives the 5-line clear/review decision.
|
|
51
|
+
3. `context --json` gives AI the full preflight contract.
|
|
52
|
+
4. `intent` records planned env changes before touching dependencies, runtimes, package managers, Docker, or global tools.
|
|
53
|
+
5. `checkpoint` records the accepted change, refreshes outputs, and writes a handoff.
|
|
54
|
+
6. `handoff` tells the next AI what to read, what to avoid, and whether SBOM/strict review is needed.
|
|
55
|
+
|
|
56
|
+
Local mode is warn-only. Use `doctor --strict security|policy|coordination|all` only for CI or explicit human-requested gates.
|
|
31
57
|
|
|
32
58
|
## What It Creates
|
|
33
59
|
|
|
@@ -46,23 +72,13 @@ AIENV.md # Markdown env map for AI agents
|
|
|
46
72
|
|
|
47
73
|
## AI Contract
|
|
48
74
|
|
|
49
|
-
- `status`, `context`, `plan`, and `handoff` share one preflight contract.
|
|
50
|
-
- `
|
|
51
|
-
- `
|
|
52
|
-
- `
|
|
53
|
-
- `
|
|
54
|
-
- `
|
|
55
|
-
- `
|
|
56
|
-
- `dependencyReadSet` lists manifests and lockfiles before package or security changes.
|
|
57
|
-
- `sbomRisk` gives AI a compact light-SBOM risk level, signals, and next command.
|
|
58
|
-
- `sbom.json.aiDependencyReview` connects SBOM risk, scanner confidence, and safe dependency-change steps.
|
|
59
|
-
- `coordination.conflictTargets` shows where multiple agents are planning changes.
|
|
60
|
-
- `agentActivity.multiActorTargets` shows where multiple agents actually recorded env changes.
|
|
61
|
-
- `followUps` shows records that still need `sync`, `status`, or `handoff`.
|
|
62
|
-
- `handoff` carries dependency read-set and protocol guidance for the next AI.
|
|
63
|
-
- Light SBOM includes source/confidence hints; verify security claims with dedicated scanners.
|
|
64
|
-
- `enforcementProfile.gate` explains when checks warn, fail, and set exit codes.
|
|
65
|
-
- Everything is advisory by default; strict failure is opt-in with `doctor --strict` or `--ci`.
|
|
75
|
+
- `status`, `context`, `plan`, and `handoff` share one additive preflight contract.
|
|
76
|
+
- `maintenanceLoop` gives AI the recurring env-management loop.
|
|
77
|
+
- `sbomRisk` and `sbomReview` connect light SBOM risk to safe dependency-change steps.
|
|
78
|
+
- `collaboration`, `coordination`, and `agentActivity` show multi-agent conflicts and shared targets.
|
|
79
|
+
- `strictDecision` separates local warn-only checks from optional CI strict gates.
|
|
80
|
+
- `status --json`, `context --json`, `handoff --json`, and `doctor --json` include `nextSafeCommand` for one advisory next step.
|
|
81
|
+
- `schema --json` prints the stable machine-readable contract without scanning.
|
|
66
82
|
|
|
67
83
|
## Agent Files
|
|
68
84
|
|
|
@@ -103,7 +119,7 @@ aienvmp doctor --strict security|policy|coordination|all
|
|
|
103
119
|
## CI
|
|
104
120
|
|
|
105
121
|
The GitHub Action writes status, summary, schema, doctor, plan, SBOM, and dashboard artifacts. `strict: "off"` reports warnings without failing the job.
|
|
106
|
-
The Step Summary includes
|
|
122
|
+
The Step Summary includes `aienvmp strict plan` and `aienvmp AI loop` blocks so humans and AI agents can choose CI gates and continue the same workflow without parsing full artifacts.
|
|
107
123
|
|
|
108
124
|
```yaml
|
|
109
125
|
- uses: soovwv/aienvmp@main
|
package/action.yml
CHANGED
|
@@ -123,6 +123,36 @@ runs:
|
|
|
123
123
|
NODE
|
|
124
124
|
fi
|
|
125
125
|
|
|
126
|
+
- name: Append AI loop summary
|
|
127
|
+
shell: bash
|
|
128
|
+
run: |
|
|
129
|
+
if [ "${{ inputs.write-summary }}" = "true" ] && [ -n "$GITHUB_STEP_SUMMARY" ]; then
|
|
130
|
+
mkdir -p "${{ inputs.directory }}/.aienvmp"
|
|
131
|
+
schema_source="${{ inputs.directory }}/.aienvmp/schema.json"
|
|
132
|
+
if [ ! -f "$schema_source" ]; then
|
|
133
|
+
schema_source="$(mktemp)"
|
|
134
|
+
node "$GITHUB_ACTION_PATH/bin/aienvmp.js" schema --json > "$schema_source"
|
|
135
|
+
fi
|
|
136
|
+
node - "$schema_source" "$GITHUB_STEP_SUMMARY" <<'NODE'
|
|
137
|
+
const fs = require("node:fs");
|
|
138
|
+
|
|
139
|
+
const [schemaPath, summaryPath] = process.argv.slice(2);
|
|
140
|
+
const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8"));
|
|
141
|
+
const loop = schema.aiLoop || {};
|
|
142
|
+
const steps = (loop.steps || []).slice(0, 6).map((item) => item.command).join(" -> ");
|
|
143
|
+
|
|
144
|
+
fs.appendFileSync(summaryPath, [
|
|
145
|
+
"",
|
|
146
|
+
"## aienvmp AI loop",
|
|
147
|
+
"",
|
|
148
|
+
`- mode: ${loop.localMode || "warn-only"}`,
|
|
149
|
+
`- steps: \`${steps || "aienvmp sync -> aienvmp status -> aienvmp handoff"}\``,
|
|
150
|
+
`- rule: ${loop.strictRule || "Local checks are warn-only; strict gates are explicit."}`,
|
|
151
|
+
""
|
|
152
|
+
].join("\n"));
|
|
153
|
+
NODE
|
|
154
|
+
fi
|
|
155
|
+
|
|
126
156
|
- name: Doctor
|
|
127
157
|
shell: bash
|
|
128
158
|
run: |
|
|
@@ -17,7 +17,7 @@ jobs:
|
|
|
17
17
|
write-schema: "true"
|
|
18
18
|
write-doctor-json: "true"
|
|
19
19
|
write-sbom: "true"
|
|
20
|
-
write-summary: "true"
|
|
20
|
+
write-summary: "true" # appends summary, strict plan, and AI loop to GitHub Step Summary
|
|
21
21
|
strict: "off" # security, policy, coordination, all, or off
|
|
22
22
|
|
|
23
23
|
- uses: actions/upload-artifact@v4
|
package/package.json
CHANGED
package/src/actions.js
CHANGED
|
@@ -44,8 +44,14 @@ function sbomRiskActions(risk = {}) {
|
|
|
44
44
|
if (risk.scanner === "off" && risk.vulnerabilityCount === 0) {
|
|
45
45
|
actions.push(action("scan-sbom-risk", "medium", "security", "Run a read-only security scan before dependency or release decisions.", "aienvmp sync --security"));
|
|
46
46
|
}
|
|
47
|
-
if (["urgent", "high"].includes(risk.level)) {
|
|
48
|
-
actions.push(action(
|
|
47
|
+
if (["urgent", "high", "medium"].includes(risk.level)) {
|
|
48
|
+
actions.push(action(
|
|
49
|
+
"review-sbom-risk",
|
|
50
|
+
["urgent", "high"].includes(risk.level) ? "high" : "medium",
|
|
51
|
+
"security",
|
|
52
|
+
`Review light SBOM risk summary before dependency changes: ${(risk.signals || []).slice(0, 2).join("; ") || risk.level}.`,
|
|
53
|
+
"aienvmp plan --write"
|
|
54
|
+
));
|
|
49
55
|
}
|
|
50
56
|
return actions;
|
|
51
57
|
}
|
package/src/commands/context.js
CHANGED
|
@@ -22,12 +22,15 @@ export async function contextWorkspace(args) {
|
|
|
22
22
|
const actions = recommendedActions(manifest, { warnings, intents });
|
|
23
23
|
const stepSummary = compactStepSummary(buildPlan(manifest, warnings, intents, policy));
|
|
24
24
|
const preflight = buildPreflight(manifest, warnings, intents, timeline);
|
|
25
|
+
const nextSafeCommand = contextNextSafeCommand(actions, warnings, preflight);
|
|
25
26
|
if (args.json) {
|
|
26
27
|
console.log(JSON.stringify({
|
|
27
28
|
status: warnings.length ? "review-required" : "clear",
|
|
29
|
+
nextSafeCommand,
|
|
28
30
|
preflight,
|
|
29
31
|
aiReadiness: preflight.aiReadiness,
|
|
30
32
|
collaboration: preflight.collaboration,
|
|
33
|
+
maintenanceLoop: preflight.maintenanceLoop,
|
|
31
34
|
coordination: preflight.coordination,
|
|
32
35
|
agentPointers: preflight.agentPointers,
|
|
33
36
|
sbomRisk: preflight.sbomRisk,
|
|
@@ -58,6 +61,14 @@ export async function contextWorkspace(args) {
|
|
|
58
61
|
console.log(renderContext({ ...manifest, preflight }, timeline, warnings, intents, policy, actions));
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
function contextNextSafeCommand(actions = [], warnings = [], preflight = {}) {
|
|
65
|
+
const actionCommand = actions.find((item) => item.command)?.command;
|
|
66
|
+
return preflight.nextCommand
|
|
67
|
+
|| preflight.maintenanceLoop?.nextCommand
|
|
68
|
+
|| actionCommand
|
|
69
|
+
|| (warnings.length ? "aienvmp plan --write" : "aienvmp status --json");
|
|
70
|
+
}
|
|
71
|
+
|
|
61
72
|
function lightSbomSummary(lightSbom = {}) {
|
|
62
73
|
return {
|
|
63
74
|
mode: lightSbom.mode || "light-sbom",
|
package/src/commands/doctor.js
CHANGED
|
@@ -20,6 +20,7 @@ export async function doctorWorkspace(args) {
|
|
|
20
20
|
const actions = recommendedActions(manifest, { warnings, intents });
|
|
21
21
|
const preflight = buildPreflight(manifest, warnings, intents, timeline);
|
|
22
22
|
const strict = strictResult(warnings, args);
|
|
23
|
+
const nextSafeCommand = doctorNextSafeCommand(actions, warnings);
|
|
23
24
|
const exitBehavior = {
|
|
24
25
|
mode: strict.enabled ? "strict" : "advisory",
|
|
25
26
|
willSetFailureExitCode: strict.fail,
|
|
@@ -37,6 +38,7 @@ export async function doctorWorkspace(args) {
|
|
|
37
38
|
aiReadiness: preflight.aiReadiness,
|
|
38
39
|
agentPointers: preflight.agentPointers,
|
|
39
40
|
openIntentCount: intents.length,
|
|
41
|
+
nextSafeCommand,
|
|
40
42
|
warnings,
|
|
41
43
|
recommendedActions: actions,
|
|
42
44
|
enforcement: enforcementAdvice(warnings),
|
|
@@ -63,3 +65,10 @@ export async function doctorWorkspace(args) {
|
|
|
63
65
|
process.exitCode = 1;
|
|
64
66
|
}
|
|
65
67
|
}
|
|
68
|
+
|
|
69
|
+
function doctorNextSafeCommand(actions = [], warnings = []) {
|
|
70
|
+
const command = actions.find((item) => item.command)?.command;
|
|
71
|
+
if (command) return command;
|
|
72
|
+
if (warnings.length) return "aienvmp plan --write";
|
|
73
|
+
return "aienvmp status --json";
|
|
74
|
+
}
|
package/src/commands/handoff.js
CHANGED
|
@@ -49,6 +49,7 @@ export function buildHandoff(manifest, timeline = [], warnings = [], intents = [
|
|
|
49
49
|
const preflight = buildPreflight(manifest, warnings, intents, timeline);
|
|
50
50
|
return {
|
|
51
51
|
status: reviewRequired ? "review-required" : "clear",
|
|
52
|
+
nextSafeCommand: preflight.nextCommand || preflight.maintenanceLoop?.nextCommand || "aienvmp status --json",
|
|
52
53
|
trust: manifest.trust || {},
|
|
53
54
|
schemaVersion: manifest.schemaVersion || 1,
|
|
54
55
|
preflight,
|
|
@@ -62,6 +63,7 @@ export function buildHandoff(manifest, timeline = [], warnings = [], intents = [
|
|
|
62
63
|
inventory: inventorySummary(manifest.inventory),
|
|
63
64
|
security: securitySummary(manifest.security),
|
|
64
65
|
dependencyHandoff: dependencyHandoffSummary(preflight),
|
|
66
|
+
continuation: continuationSummary(preflight),
|
|
65
67
|
coordination: preflight.coordination,
|
|
66
68
|
agentActivity: preflight.agentActivity,
|
|
67
69
|
policy: {
|
|
@@ -86,6 +88,37 @@ export function buildHandoff(manifest, timeline = [], warnings = [], intents = [
|
|
|
86
88
|
};
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
function continuationSummary(preflight = {}) {
|
|
92
|
+
const maintenanceLoop = preflight.maintenanceLoop || {};
|
|
93
|
+
const strictDecision = preflight.enforcementProfile?.strictDecision || preflight.enforcement?.strictDecision || {};
|
|
94
|
+
const sbomReview = maintenanceLoop.sbomReview || {};
|
|
95
|
+
return {
|
|
96
|
+
status: preflight.state || "unknown",
|
|
97
|
+
nextCommand: maintenanceLoop.nextCommand || preflight.nextCommand || "aienvmp status --json",
|
|
98
|
+
readOrder: (maintenanceLoop.readOrder || preflight.readOrder || []).slice(0, 4),
|
|
99
|
+
maintenance: {
|
|
100
|
+
mode: maintenanceLoop.mode || "advisory",
|
|
101
|
+
localImpact: maintenanceLoop.localImpact || "read-only until an AI or human records a change",
|
|
102
|
+
sbomCommand: maintenanceLoop.sbomCommand || "aienvmp sbom --json",
|
|
103
|
+
rule: maintenanceLoop.rule || "Keep local operation advisory and lightweight."
|
|
104
|
+
},
|
|
105
|
+
sbomReview: {
|
|
106
|
+
status: sbomReview.status || "unknown",
|
|
107
|
+
riskLevel: sbomReview.riskLevel || "unknown",
|
|
108
|
+
securityConfidence: sbomReview.securityConfidence || "unknown",
|
|
109
|
+
nextCommand: sbomReview.nextCommand || maintenanceLoop.sbomCommand || "aienvmp sbom --json",
|
|
110
|
+
reviewTargets: (sbomReview.reviewTargets || []).slice(0, 5)
|
|
111
|
+
},
|
|
112
|
+
strict: {
|
|
113
|
+
localCommand: strictDecision.localCommand || "aienvmp doctor --json",
|
|
114
|
+
local: strictDecision.local || "warn-only",
|
|
115
|
+
shouldFailLocal: strictDecision.shouldFailLocal === true,
|
|
116
|
+
ciCommand: strictDecision.ciCommand || "aienvmp doctor --strict all --json",
|
|
117
|
+
rule: strictDecision.rule || "Local checks stay advisory; strict failure is opt-in."
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
89
122
|
function dependencyHandoffSummary(preflight = {}) {
|
|
90
123
|
const protocol = preflight.dependencyChangeProtocol || {};
|
|
91
124
|
return {
|
package/src/commands/schema.js
CHANGED
|
@@ -10,6 +10,7 @@ export async function schemaWorkspace(args = {}) {
|
|
|
10
10
|
console.log(`status: ${schema.outputs.status.command}`);
|
|
11
11
|
console.log(`context: ${schema.outputs.context.command}`);
|
|
12
12
|
console.log(`handoff: ${schema.outputs.handoff.command}`);
|
|
13
|
+
console.log(`loop: ${schema.aiLoop.steps.map((item) => item.command).join(" -> ")}`);
|
|
13
14
|
console.log(`rule: ${schema.compatibility.consumerRule}`);
|
|
14
15
|
}
|
|
15
16
|
return schema;
|
package/src/commands/summary.js
CHANGED
|
@@ -38,6 +38,8 @@ export function renderSummary(status = {}, manifest = {}) {
|
|
|
38
38
|
const coordination = status.coordination || {};
|
|
39
39
|
const activity = status.agentActivity || {};
|
|
40
40
|
const collaboration = status.collaboration || {};
|
|
41
|
+
const maintenanceLoop = status.maintenanceLoop || {};
|
|
42
|
+
const sbomReview = maintenanceLoop.sbomReview || {};
|
|
41
43
|
const workspace = manifest.workspace?.root || manifest.workspace?.name || ".";
|
|
42
44
|
const next = status.nextCommand || status.decision?.nextCommand || "aienvmp status";
|
|
43
45
|
const readFirst = status.nextAgent?.readFirst || ".aienvmp/status.json";
|
|
@@ -58,6 +60,7 @@ export function renderSummary(status = {}, manifest = {}) {
|
|
|
58
60
|
const aiNext = aiReadiness.next || "Run aienvmp context --json for details.";
|
|
59
61
|
const aiDependencyReview = manifest.lightSbom?.aiDependencyReview || {};
|
|
60
62
|
const strictPlan = status.enforcementProfile?.strictPlan || status.enforcement?.strictPlan || {};
|
|
63
|
+
const strictDecision = status.enforcementProfile?.strictDecision || status.enforcement?.strictDecision || {};
|
|
61
64
|
|
|
62
65
|
return [
|
|
63
66
|
"# aienvmp summary",
|
|
@@ -66,9 +69,11 @@ export function renderSummary(status = {}, manifest = {}) {
|
|
|
66
69
|
`- AI signals: ${aiSignals.length ? aiSignals.join("; ") : "none"}`,
|
|
67
70
|
`- AI next: ${aiNext}`,
|
|
68
71
|
`- AI collaboration: ${collaboration.status || "unknown"} / ${toList(collaboration.activeTargets).join(", ") || "none"} / ${collaboration.nextCommand || "aienvmp status --json"}`,
|
|
72
|
+
`- AI maintenance loop: ${maintenanceLoop.nextCommand || next}`,
|
|
69
73
|
`- AI safe local work: ${toList(aiReadiness.safeProjectLocalActions)[0] || "read artifacts and avoid environment changes until reviewed"}`,
|
|
70
74
|
`- AI read first: ${readFirst}, then ${detail}`,
|
|
71
75
|
`- mode: advisory by default; strict is opt-in with ${strict}`,
|
|
76
|
+
`- local check: ${strictDecision.localCommand || "aienvmp doctor --json"} (${strictDecision.local || "warn-only"})`,
|
|
72
77
|
`- CI strict: ${strictPlan.ciCommand || `${strict} --json`}`,
|
|
73
78
|
"",
|
|
74
79
|
`- state: ${status.state || "unknown"}`,
|
|
@@ -84,6 +89,7 @@ export function renderSummary(status = {}, manifest = {}) {
|
|
|
84
89
|
`- collaboration rule: ${collaboration.rule || "Record intent before shared environment changes."}`,
|
|
85
90
|
`- coordination: ${coordination.next || "No open environment intents."}`,
|
|
86
91
|
`- recent agent activity: ${activity.next || "No environment records need coordination."}`,
|
|
92
|
+
`- maintenance rule: ${maintenanceLoop.rule || "Refresh, inspect, record intent, checkpoint, and hand off without blocking local operation."}`,
|
|
87
93
|
`- conflict targets: ${conflictTargets.length ? conflictTargets.join(", ") : "none"}`,
|
|
88
94
|
`- multi-actor targets: ${multiActorTargets.length ? multiActorTargets.join(", ") : "none"}`,
|
|
89
95
|
"",
|
|
@@ -91,6 +97,7 @@ export function renderSummary(status = {}, manifest = {}) {
|
|
|
91
97
|
"",
|
|
92
98
|
`- source: ${manifest.lightSbom?.source?.dependencies || "project manifests"}`,
|
|
93
99
|
`- confidence: transitive ${manifest.lightSbom?.confidence?.transitiveDependencies || "not-resolved"}`,
|
|
100
|
+
`- maintenance SBOM review: ${sbomReview.status || "unknown"} / ${sbomReview.securityConfidence || "unknown"} / ${sbomReview.nextCommand || maintenanceLoop.sbomCommand || "aienvmp sbom --json"}`,
|
|
94
101
|
`- AI dependency review: ${aiDependencyReview.status || "unknown"} / ${aiDependencyReview.securityConfidence || "unknown"} / ${aiDependencyReview.beforeDependencyChange?.[0] || "aienvmp sbom --json"}`,
|
|
95
102
|
`- signals: ${riskSignals.length ? riskSignals.join("; ") : "none"}`,
|
|
96
103
|
`- verify: ${sbomRisk.next || "Use a dedicated scanner for security decisions."}`,
|
package/src/contract.js
CHANGED
|
@@ -4,7 +4,7 @@ export function preflightContract() {
|
|
|
4
4
|
version: 1,
|
|
5
5
|
stability: "additive",
|
|
6
6
|
requiredFields: ["schemaVersion", "state", "decision", "quickstart", "commands", "artifacts"],
|
|
7
|
-
aiEntryFields: ["state", "summary", "aiReadiness", "collaboration", "nextAgent", "coordination", "agentActivity", "agentPointers", "sbomRisk", "followUps", "dependencyReadSet", "dependencyChangeProtocol"],
|
|
7
|
+
aiEntryFields: ["state", "summary", "nextSafeCommand", "aiReadiness", "collaboration", "maintenanceLoop", "nextAgent", "coordination", "agentActivity", "agentPointers", "sbomRisk", "followUps", "dependencyReadSet", "dependencyChangeProtocol"],
|
|
8
8
|
rule: "Consumers should ignore unknown fields and treat missing optional fields as unavailable."
|
|
9
9
|
};
|
|
10
10
|
}
|
|
@@ -14,10 +14,25 @@ export function schemaContract() {
|
|
|
14
14
|
schemaVersion: 1,
|
|
15
15
|
name: "aienvmp-contract",
|
|
16
16
|
purpose: "Stable AI-readable contract for aienvmp outputs.",
|
|
17
|
+
aiLoop: {
|
|
18
|
+
name: "AI maintenance loop",
|
|
19
|
+
purpose: "Shared lightweight workflow for AI agents that maintain one workspace environment.",
|
|
20
|
+
localMode: "warn-only",
|
|
21
|
+
steps: [
|
|
22
|
+
{ step: "sync", command: "aienvmp sync", purpose: "refresh AIENV.md, status, summary, SBOM, ledger, and dashboard" },
|
|
23
|
+
{ step: "status", command: "aienvmp status", purpose: "read the 5-line clear/review decision" },
|
|
24
|
+
{ step: "context", command: "aienvmp context --json", purpose: "read the full AI preflight contract when details are needed" },
|
|
25
|
+
{ step: "intent", command: "aienvmp intent --actor agent:id --action planned-change --target dependency", purpose: "record planned environment-affecting changes before touching shared state" },
|
|
26
|
+
{ step: "checkpoint", command: "aienvmp checkpoint --actor agent:id --summary dependency-change --target dependency", purpose: "record accepted changes, refresh outputs, and write handoff context" },
|
|
27
|
+
{ step: "handoff", command: "aienvmp handoff", purpose: "tell the next AI what to read, avoid, and review" }
|
|
28
|
+
],
|
|
29
|
+
strictRule: "Local checks are warn-only; use doctor --strict only for CI or explicit human-requested gates."
|
|
30
|
+
},
|
|
17
31
|
outputs: {
|
|
18
32
|
status: {
|
|
19
33
|
file: ".aienvmp/status.json",
|
|
20
34
|
command: "aienvmp status --json",
|
|
35
|
+
rootFields: ["state", "nextCommand", "nextSafeCommand", "decision", "counts", "aiReadiness", "collaboration", "maintenanceLoop", "coordination", "agentPointers", "sbomRisk"],
|
|
21
36
|
contract: preflightContract()
|
|
22
37
|
},
|
|
23
38
|
summary: {
|
|
@@ -29,11 +44,11 @@ export function schemaContract() {
|
|
|
29
44
|
},
|
|
30
45
|
context: {
|
|
31
46
|
command: "aienvmp context --json",
|
|
32
|
-
rootFields: ["status", "preflight", "aiReadiness", "collaboration", "coordination", "agentPointers", "decision", "enforcement", "recommendedActions", "workspace", "dependencySnapshot", "lightSbom", "warnings"]
|
|
47
|
+
rootFields: ["status", "nextSafeCommand", "preflight", "aiReadiness", "collaboration", "maintenanceLoop", "coordination", "agentPointers", "decision", "enforcement", "recommendedActions", "workspace", "dependencySnapshot", "lightSbom", "warnings"]
|
|
33
48
|
},
|
|
34
49
|
handoff: {
|
|
35
50
|
command: "aienvmp handoff --json",
|
|
36
|
-
rootFields: ["status", "decision", "preflight", "coordination", "dependencyHandoff", "openIntents", "warnings", "recommendedActions", "recentChanges"]
|
|
51
|
+
rootFields: ["status", "nextSafeCommand", "decision", "preflight", "continuation", "coordination", "dependencyHandoff", "openIntents", "warnings", "recommendedActions", "recentChanges"]
|
|
37
52
|
},
|
|
38
53
|
manifest: {
|
|
39
54
|
file: ".aienvmp/manifest.json",
|
|
@@ -57,6 +72,8 @@ export function schemaContract() {
|
|
|
57
72
|
localBehavior: "read-only; this command does not scan, install, update, or lock anything.",
|
|
58
73
|
aiReadinessRule: "When aiReadiness.level is review, project-local code work may still continue if aiReadiness.projectLocalWork is allowed; environment changes should follow intent/review guidance.",
|
|
59
74
|
collaborationRule: "Use collaboration.status, activeTargets, and nextCommand as the shortest multi-agent environment coordination hint.",
|
|
75
|
+
maintenanceLoopRule: "Use maintenanceLoop as the short recurring AI workflow: refresh, decide, inspect, plan, intent, checkpoint, and handoff without blocking local operation.",
|
|
76
|
+
strictDecisionRule: "Use enforcement.strictDecision or preflight.enforcementProfile.strictDecision for the shortest local warn-only vs CI strict decision.",
|
|
60
77
|
strictPlanRule: "Use enforcement.strictPlan or preflight.enforcementProfile.strictPlan to choose the narrowest explicit strict scope for CI."
|
|
61
78
|
}
|
|
62
79
|
};
|
package/src/enforcement.js
CHANGED
|
@@ -34,6 +34,7 @@ export function enforcementAdvice(warnings = []) {
|
|
|
34
34
|
suggestedStrictScopes,
|
|
35
35
|
scopes: scopeResults,
|
|
36
36
|
strictPlan: strictScopePlan(suggestedStrictScopes, scopeResults),
|
|
37
|
+
strictDecision: strictDecision(suggestedStrictScopes, scopeResults),
|
|
37
38
|
recommendedCommand: suggestedStrictScopes.length
|
|
38
39
|
? `aienvmp doctor --strict ${suggestedStrictScopes[0]}`
|
|
39
40
|
: "aienvmp doctor --strict all",
|
|
@@ -41,6 +42,29 @@ export function enforcementAdvice(warnings = []) {
|
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
export function strictDecision(suggestedStrictScopes = [], scopeResults = []) {
|
|
46
|
+
const recommendedScope = suggestedStrictScopes[0] || "all";
|
|
47
|
+
const failingScopes = suggestedStrictScopes.slice();
|
|
48
|
+
const passingScopes = scopeResults
|
|
49
|
+
.filter((item) => item.scope !== "all" && item.status === "pass")
|
|
50
|
+
.map((item) => item.scope);
|
|
51
|
+
return {
|
|
52
|
+
local: "warn-only",
|
|
53
|
+
localCommand: "aienvmp doctor --json",
|
|
54
|
+
shouldFailLocal: false,
|
|
55
|
+
ci: failingScopes.length ? "fail-on-recommended-scope" : "optional-health-check",
|
|
56
|
+
recommendedScope,
|
|
57
|
+
recommendedCommand: `aienvmp doctor --strict ${recommendedScope}`,
|
|
58
|
+
ciCommand: `aienvmp doctor --strict ${recommendedScope} --json`,
|
|
59
|
+
failingScopes,
|
|
60
|
+
passingScopes,
|
|
61
|
+
rule: failingScopes.length
|
|
62
|
+
? "Keep local operation advisory; use the first failing scope only when CI or the user wants a gate."
|
|
63
|
+
: "Keep local operation advisory; use --strict all only for explicit CI health checks.",
|
|
64
|
+
whenToUseStrict: "CI, release checks, or explicit human-requested verification."
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
44
68
|
export function strictScopePlan(suggestedStrictScopes = [], scopeResults = []) {
|
|
45
69
|
const firstScope = suggestedStrictScopes[0] || "all";
|
|
46
70
|
return {
|
package/src/preflight.js
CHANGED
|
@@ -20,6 +20,17 @@ export function buildPreflight(manifest = {}, warnings = [], intents = [], timel
|
|
|
20
20
|
const agentPointers = agentPointerSummary(manifest.agentFiles);
|
|
21
21
|
const aiReadiness = aiReadinessSummary({ state, decision, coordination, agentActivity, agentPointers, sbomRisk, followUps });
|
|
22
22
|
const collaboration = collaborationSummary({ state, decision, coordination, agentActivity, followUps, aiReadiness });
|
|
23
|
+
const maintenanceLoop = maintenanceLoopSummary({
|
|
24
|
+
state,
|
|
25
|
+
decision,
|
|
26
|
+
topAction,
|
|
27
|
+
followUps,
|
|
28
|
+
sbomRisk,
|
|
29
|
+
aiDependencyReview: manifest.lightSbom?.aiDependencyReview,
|
|
30
|
+
dependencyReadSet,
|
|
31
|
+
collaboration
|
|
32
|
+
});
|
|
33
|
+
const nextCommand = maintenanceLoop.nextCommand || topAction?.command || decision.nextCommand;
|
|
23
34
|
return {
|
|
24
35
|
schemaVersion: 1,
|
|
25
36
|
contract: preflightContract(),
|
|
@@ -38,6 +49,7 @@ export function buildPreflight(manifest = {}, warnings = [], intents = [], timel
|
|
|
38
49
|
reason: "Avoid disrupting shared servers or developer machines while still making drift visible.",
|
|
39
50
|
recommendedStrictCommand: enforcement.recommendedCommand,
|
|
40
51
|
strictPlan: enforcement.strictPlan,
|
|
52
|
+
strictDecision: enforcement.strictDecision,
|
|
41
53
|
strictCommands: [
|
|
42
54
|
"aienvmp doctor --strict security",
|
|
43
55
|
"aienvmp doctor --strict policy",
|
|
@@ -63,6 +75,7 @@ export function buildPreflight(manifest = {}, warnings = [], intents = [], timel
|
|
|
63
75
|
coordination,
|
|
64
76
|
agentActivity,
|
|
65
77
|
collaboration,
|
|
78
|
+
maintenanceLoop,
|
|
66
79
|
agentPointers,
|
|
67
80
|
aiReadiness,
|
|
68
81
|
sbomRisk,
|
|
@@ -90,7 +103,82 @@ export function buildPreflight(manifest = {}, warnings = [], intents = [], timel
|
|
|
90
103
|
recordIntent: intentTargets[0]?.command || "aienvmp intent --actor agent:id --action planned-change"
|
|
91
104
|
},
|
|
92
105
|
topAction,
|
|
93
|
-
nextCommand
|
|
106
|
+
nextCommand,
|
|
107
|
+
nextSafeCommand: nextCommand
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function maintenanceLoopSummary({ state, decision, topAction, followUps, sbomRisk, aiDependencyReview = {}, dependencyReadSet, collaboration }) {
|
|
112
|
+
const followUpCommand = firstFollowUpCommand(followUps);
|
|
113
|
+
const dependencyAware = dependencyReadSet.length > 0;
|
|
114
|
+
const securityReview = ["urgent", "high", "medium"].includes(sbomRisk?.level || "");
|
|
115
|
+
const scannerOff = sbomRisk?.scanner === "off" || aiDependencyReview.securityConfidence === "scanner-off";
|
|
116
|
+
const nextCommand = followUpCommand
|
|
117
|
+
|| collaboration?.nextCommand
|
|
118
|
+
|| topAction?.command
|
|
119
|
+
|| decision?.nextCommand
|
|
120
|
+
|| "aienvmp status --json";
|
|
121
|
+
const triggers = [
|
|
122
|
+
"start of an AI coding session",
|
|
123
|
+
"before runtime, dependency, package manager, Docker, or global tool changes",
|
|
124
|
+
"after accepted environment changes",
|
|
125
|
+
"before handing work to another AI or human"
|
|
126
|
+
];
|
|
127
|
+
if (securityReview) triggers.push("before dependency or release decisions when SBOM risk is not clear");
|
|
128
|
+
return {
|
|
129
|
+
mode: "advisory",
|
|
130
|
+
localImpact: "read-only until an AI or human intentionally records intent, checkpoint, or handoff",
|
|
131
|
+
state,
|
|
132
|
+
nextCommand,
|
|
133
|
+
triggers,
|
|
134
|
+
readOrder: [
|
|
135
|
+
".aienvmp/status.json",
|
|
136
|
+
".aienvmp/summary.md",
|
|
137
|
+
"aienvmp context --json",
|
|
138
|
+
"AIENV.md"
|
|
139
|
+
],
|
|
140
|
+
cycle: [
|
|
141
|
+
{ step: "refresh", command: "aienvmp sync", when: "workspace env map may be stale" },
|
|
142
|
+
{ step: "decide", command: "aienvmp status --json", when: "first AI read or quick gate" },
|
|
143
|
+
{ step: "inspect", command: "aienvmp context --json", when: "status is review-required or details are needed" },
|
|
144
|
+
{ step: "plan", command: "aienvmp plan --write", when: "warnings, SBOM risk, or multi-agent activity need a read-only plan" },
|
|
145
|
+
{ step: "intent", command: dependencyAware ? "aienvmp intent --actor agent:id --action planned-change --target dependency" : "aienvmp intent --actor agent:id --action planned-change --target environment", when: "before environment-affecting changes" },
|
|
146
|
+
{ step: "checkpoint", command: dependencyAware ? "aienvmp checkpoint --actor agent:id --summary dependency-change --target dependency" : "aienvmp checkpoint --actor agent:id --summary what-changed --target environment", when: "after accepted environment-affecting changes" },
|
|
147
|
+
{ step: "handoff", command: "aienvmp handoff --record --actor agent:id", when: "before another AI continues environment work" }
|
|
148
|
+
],
|
|
149
|
+
sbomCommand: securityReview || scannerOff ? "aienvmp sync --security" : "aienvmp sbom --json",
|
|
150
|
+
sbomReview: sbomReviewSummary({ sbomRisk, aiDependencyReview, securityReview, scannerOff }),
|
|
151
|
+
rule: "Keep local operation advisory and lightweight; use strict checks only when CI or the user explicitly asks."
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function sbomReviewSummary({ sbomRisk = {}, aiDependencyReview = {}, securityReview = false, scannerOff = false }) {
|
|
156
|
+
const status = securityReview || aiDependencyReview.status === "review" ? "review" : "ready";
|
|
157
|
+
const before = aiDependencyReview.beforeDependencyChange?.length
|
|
158
|
+
? aiDependencyReview.beforeDependencyChange
|
|
159
|
+
: [
|
|
160
|
+
scannerOff ? "aienvmp sync --security" : "aienvmp sbom --json",
|
|
161
|
+
"aienvmp intent --actor agent:id --action dependency-review --target dependency",
|
|
162
|
+
"aienvmp plan --write"
|
|
163
|
+
];
|
|
164
|
+
const after = aiDependencyReview.afterDependencyChange?.length
|
|
165
|
+
? aiDependencyReview.afterDependencyChange
|
|
166
|
+
: [
|
|
167
|
+
"run the narrowest relevant project validation",
|
|
168
|
+
"aienvmp checkpoint --actor agent:id --summary dependency-change --target dependency"
|
|
169
|
+
];
|
|
170
|
+
return {
|
|
171
|
+
status,
|
|
172
|
+
riskLevel: sbomRisk.level || "unknown",
|
|
173
|
+
score: Number(sbomRisk.score || 0),
|
|
174
|
+
securityConfidence: aiDependencyReview.securityConfidence || (scannerOff ? "scanner-off" : "unknown"),
|
|
175
|
+
reviewTargets: aiDependencyReview.reviewTargets || sbomRisk.reviewTargets || [],
|
|
176
|
+
nextCommand: before[0] || "aienvmp sbom --json",
|
|
177
|
+
beforeDependencyChange: before,
|
|
178
|
+
afterDependencyChange: after,
|
|
179
|
+
rule: status === "review"
|
|
180
|
+
? "Review SBOM risk, package manager policy, and dependency read set before dependency changes."
|
|
181
|
+
: "Keep SBOM review lightweight; run security scan before dependency or release decisions when scanner confidence is low."
|
|
94
182
|
};
|
|
95
183
|
}
|
|
96
184
|
|
package/src/render.js
CHANGED
|
@@ -71,6 +71,7 @@ export function renderAIEnv(manifest, timeline = [], warnings = [], intents = []
|
|
|
71
71
|
function preflightLines(preflight = {}) {
|
|
72
72
|
const quickstart = preflight.quickstart;
|
|
73
73
|
const targets = preflight.intentTargets || [];
|
|
74
|
+
const maintenanceLoop = preflight.maintenanceLoop || {};
|
|
74
75
|
const lines = ["## 10-Second AI Flow", ""];
|
|
75
76
|
if (quickstart) {
|
|
76
77
|
lines.push(`- Read first: \`${quickstart.readFirst}\``);
|
|
@@ -82,6 +83,9 @@ function preflightLines(preflight = {}) {
|
|
|
82
83
|
} else {
|
|
83
84
|
lines.push("- Run `aienvmp status --write`, then `aienvmp context --json` before environment changes.");
|
|
84
85
|
}
|
|
86
|
+
if (maintenanceLoop.nextCommand) {
|
|
87
|
+
lines.push(`- Maintenance loop: \`${maintenanceLoop.nextCommand}\` - ${maintenanceLoop.rule || "refresh, inspect, record intent, checkpoint, and hand off"}`);
|
|
88
|
+
}
|
|
85
89
|
lines.push("", "## Recommended Intent Targets", "");
|
|
86
90
|
if (targets.length) {
|
|
87
91
|
for (const target of targets.slice(0, 5)) {
|
|
@@ -212,6 +216,9 @@ export function renderHandoff(handoff) {
|
|
|
212
216
|
`- Inventory: ${handoff.inventory?.mode || "basic"}${handoff.inventory?.enabled ? " enabled" : " disabled"}`,
|
|
213
217
|
`- Security: ${handoff.security?.mode || "basic"}${handoff.security?.enabled ? ` enabled (${handoff.security.summary?.total || 0} vulnerabilities)` : " disabled"}`,
|
|
214
218
|
"",
|
|
219
|
+
"AI continuation:",
|
|
220
|
+
...continuationHandoffLines(handoff.continuation),
|
|
221
|
+
"",
|
|
215
222
|
"Open intents:",
|
|
216
223
|
...(handoff.openIntents.length ? handoff.openIntents.map((i) => `- ${i.actor}: ${i.action}${i.target ? ` (${i.target})` : ""}`) : ["- none"]),
|
|
217
224
|
"",
|
|
@@ -242,6 +249,20 @@ export function renderHandoff(handoff) {
|
|
|
242
249
|
return lines.join("\n");
|
|
243
250
|
}
|
|
244
251
|
|
|
252
|
+
function continuationHandoffLines(continuation = {}) {
|
|
253
|
+
const strict = continuation.strict || {};
|
|
254
|
+
const sbomReview = continuation.sbomReview || {};
|
|
255
|
+
const maintenance = continuation.maintenance || {};
|
|
256
|
+
return [
|
|
257
|
+
`- Next: ${continuation.nextCommand || "aienvmp status --json"}`,
|
|
258
|
+
`- Read: ${(continuation.readOrder || []).join(", ") || ".aienvmp/status.json"}`,
|
|
259
|
+
`- Local check: ${strict.localCommand || "aienvmp doctor --json"} (${strict.local || "warn-only"})`,
|
|
260
|
+
`- CI strict: ${strict.ciCommand || "aienvmp doctor --strict all --json"}`,
|
|
261
|
+
`- SBOM review: ${sbomReview.status || "unknown"} / ${sbomReview.riskLevel || "unknown"} / ${sbomReview.nextCommand || maintenance.sbomCommand || "aienvmp sbom --json"}`,
|
|
262
|
+
`- Rule: ${maintenance.rule || strict.rule || "Keep local operation advisory and lightweight."}`
|
|
263
|
+
];
|
|
264
|
+
}
|
|
265
|
+
|
|
245
266
|
function coordinationHandoffLines(coordination = {}) {
|
|
246
267
|
const conflicts = coordination.conflictTargets || [];
|
|
247
268
|
if (conflicts.length) return [`- Conflicts: ${conflicts.join(", ")}`, `- Next: ${coordination.next || "review open intents"}`];
|
|
@@ -392,6 +413,10 @@ h1,h2,h3,p{margin:0}h1{font-size:clamp(28px,4vw,46px);line-height:1.02;margin-to
|
|
|
392
413
|
.nextbar b{color:var(--green);font-size:12px;text-transform:uppercase;letter-spacing:.08em}
|
|
393
414
|
.nextbar code{display:inline-block;max-width:100%;white-space:normal;overflow-wrap:anywhere}
|
|
394
415
|
.nextbar span{color:var(--muted);font-size:12px;overflow-wrap:anywhere}
|
|
416
|
+
.brief{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:8px;margin:0 0 14px}
|
|
417
|
+
.brief-item{border:1px solid var(--line2);background:rgba(9,19,16,.9);border-radius:8px;padding:10px;min-width:0}
|
|
418
|
+
.brief-k{color:var(--muted);font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.08em}
|
|
419
|
+
.brief-v{margin-top:5px;font-size:13px;font-weight:700;color:var(--text);overflow-wrap:anywhere}
|
|
395
420
|
.audit{display:grid;grid-template-columns:1.2fr repeat(3,minmax(0,.8fr));gap:12px;margin:14px 0}
|
|
396
421
|
.audit-item{border:1px solid var(--line);background:rgba(13,24,21,.92);border-radius:8px;padding:14px;min-width:0}
|
|
397
422
|
.audit-item.primary{background:linear-gradient(135deg,rgba(19,61,42,.88),rgba(9,19,16,.95))}
|
|
@@ -417,8 +442,10 @@ code{color:var(--code);background:#0a2017;border:1px solid #17462f;padding:2px 6
|
|
|
417
442
|
@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}}
|
|
418
443
|
@media (max-width:860px){.control{grid-template-columns:1fr}}
|
|
419
444
|
@media (max-width:860px){.nextbar{grid-template-columns:1fr}}
|
|
445
|
+
@media (max-width:860px){.brief{grid-template-columns:1fr 1fr}}
|
|
420
446
|
@media (max-width:860px){.audit{grid-template-columns:1fr 1fr}}
|
|
421
447
|
@media (max-width:520px){.shell{padding:14px}.metrics{grid-template-columns:1fr}.event{grid-template-columns:1fr}h1{font-size:32px}}
|
|
448
|
+
@media (max-width:520px){.brief{grid-template-columns:1fr}}
|
|
422
449
|
@media (max-width:520px){.audit{grid-template-columns:1fr}}
|
|
423
450
|
</style>
|
|
424
451
|
</head>
|
|
@@ -488,7 +515,8 @@ const enforcementProfile=manifest.preflight?.enforcementProfile||{};
|
|
|
488
515
|
const strictCommands=enforcementProfile.strictCommands||[];
|
|
489
516
|
const gate=enforcementProfile.gate||{};
|
|
490
517
|
const strictPlan=enforcementProfile.strictPlan||{};
|
|
491
|
-
const
|
|
518
|
+
const strictDecision=enforcementProfile.strictDecision||{};
|
|
519
|
+
const enforcementHtml=\`<table><tr><th>Default</th><td><code>\${esc(gate.defaultMode||enforcementProfile.defaultMode||'advisory')}</code> \${esc(gate.localDefault||'warn-only')}</td></tr><tr><th>Local</th><td><code>\${esc(strictDecision.localCommand||'aienvmp doctor --json')}</code> \${esc(strictDecision.local||'warn-only')}</td></tr><tr><th>Strict</th><td><code>\${esc(gate.strictMode||'off')}</code></td></tr><tr><th>Fail</th><td>\${esc(gate.failCondition||'never in default mode')}</td></tr><tr><th>Exit</th><td>\${esc(gate.exitCode||'0 unless the command itself errors')}</td></tr><tr><th>Recommended</th><td><code>\${esc(strictDecision.recommendedCommand||strictPlan.recommendedStrictCommand||enforcementProfile.recommendedStrictCommand||'aienvmp doctor --strict all')}</code></td></tr><tr><th>CI</th><td><code>\${esc(strictDecision.ciCommand||strictPlan.ciCommand||'aienvmp doctor --strict all --json')}</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(strictDecision.rule||strictPlan.rule||gate.rule||enforcementProfile.reason||'Warnings stay advisory unless strict mode is requested.')}</div>\`;
|
|
492
520
|
const contract=manifest.preflight?.contract||{};
|
|
493
521
|
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>';
|
|
494
522
|
const intentTargets=manifest.preflight?.intentTargets||[];
|
|
@@ -500,6 +528,7 @@ const activityTargets=agentActivity.targets||[];
|
|
|
500
528
|
const activityHtml=activityTargets.length?'<div class="timeline">'+activityTargets.slice(0,5).map(a=>\`<div class="event"><time>\${esc(a.target||'env')}</time><div><b>\${esc((a.actors||[]).join(', ')||'unknown')}</b> \${esc(a.count||0)} record(s) \${a.multiActor?'<code>multi-agent</code>':'<code>single-agent</code>'}\${a.latestSummary?\`<div class="path">\${esc(a.latestSummary)}</div>\`:''}</div></div>\`).join('')+'</div><div class="path">'+esc(agentActivity.next||'Review activity before environment changes.')+'</div>':'<div class="okline">No recorded environment activity needs coordination.</div>';
|
|
501
529
|
const collaboration=manifest.preflight?.collaboration||{};
|
|
502
530
|
const collaborationHtml=\`<table><tr><th>Status</th><td><code>\${esc(collaboration.status||'unknown')}</code> \${esc(collaboration.mode||'advisory')}</td></tr><tr><th>Targets</th><td>\${esc((collaboration.activeTargets||[]).join(', ')||'none')}</td></tr><tr><th>Project work</th><td><code>\${esc(collaboration.projectLocalWork||'allowed')}</code></td></tr><tr><th>Env changes</th><td><code>\${esc(collaboration.environmentChanges||'intent-first')}</code></td></tr><tr><th>Next</th><td><code>\${esc(collaboration.nextCommand||'aienvmp status --json')}</code></td></tr></table><div class="timeline">\${(collaboration.reviewSignals||[]).slice(0,4).map(signal=>\`<div class="event"><time>review</time><div>\${esc(signal)}</div></div>\`).join('')}</div><div class="path">\${esc(collaboration.rule||'Record intent before shared environment changes.')}</div>\`;
|
|
531
|
+
const maintenanceLoop=manifest.preflight?.maintenanceLoop||{};
|
|
503
532
|
const dependencyReadSet=manifest.preflight?.dependencyReadSet||[];
|
|
504
533
|
const dependencyReadSetHtml=dependencyReadSet.length?'<div class="timeline">'+dependencyReadSet.slice(0,5).map(d=>\`<div class="event"><time>\${esc(d.ecosystem||'deps')}</time><div><b>\${esc(d.manifest||'dependency files')}</b> <code>\${esc(d.manager||'unknown')}</code><div class="path">\${esc([d.manifest,...(d.lockfiles||[])].filter(Boolean).join(', '))}</div>\${d.riskPackages?.length?\`<div class="path">risk: \${esc(d.riskPackages.join(', '))}</div>\`:''}</div></div>\`).join('')+'</div>':'<div class="okline">No dependency files detected.</div>';
|
|
505
534
|
const dependencyProtocol=manifest.preflight?.dependencyChangeProtocol||{};
|
|
@@ -524,12 +553,16 @@ const sbomRiskValue=riskSummary.level||'unknown';
|
|
|
524
553
|
const sbomRiskClass=['urgent','high','medium'].includes(sbomRiskValue)?'review':'ready';
|
|
525
554
|
const sbomRiskScore=riskSummary.score!==undefined?' ('+riskSummary.score+')':'';
|
|
526
555
|
const sbomRiskNext=riskSummary.next||aiDependencyReview.beforeDependencyChange?.[0]||'Run aienvmp sbom --json for dependency context.';
|
|
527
|
-
const nextCommand=manifest.preflight?.nextCommand||topAction.command||collaboration.nextCommand||'aienvmp status --json';
|
|
528
|
-
const nextReason=topAction.summary||collaboration.rule||riskSummary.next||'Read status/context before changing shared environment state.';
|
|
556
|
+
const nextCommand=manifest.preflight?.nextCommand||maintenanceLoop.nextCommand||topAction.command||collaboration.nextCommand||'aienvmp status --json';
|
|
557
|
+
const nextReason=topAction.summary||maintenanceLoop.rule||collaboration.rule||riskSummary.next||'Read status/context before changing shared environment state.';
|
|
529
558
|
const coordination=manifest.preflight?.coordination||{};
|
|
530
559
|
const conflictTargets=coordination.conflictTargets||[];
|
|
531
560
|
const handoffFiles=nextAgent.dependencyFiles?.length?nextAgent.dependencyFiles:(dependencyReadSet[0]?[dependencyReadSet[0].manifest,...(dependencyReadSet[0].lockfiles||[])].filter(Boolean):[]);
|
|
532
561
|
const handoffNext=nextAgent.rule||(reviewRequired?'Review warnings and open intents':'Continue project-local work');
|
|
562
|
+
const firstRead=nextAgent.readFirst||'.aienvmp/status.json';
|
|
563
|
+
const reviewTargets=[...new Set([...(conflictTargets||[]),...(collaboration.activeTargets||[]),...(riskSummary.reviewTargets||[])].filter(Boolean))];
|
|
564
|
+
const safeMode=enforcementProfile.gate?.localDefault||enforcementProfile.localOperation||'warn-only';
|
|
565
|
+
const briefItem=(key,value)=>\`<div class="brief-item"><div class="brief-k">\${key}</div><div class="brief-v">\${esc(value)}</div></div>\`;
|
|
533
566
|
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>\`;
|
|
534
567
|
document.getElementById('app').innerHTML=\`
|
|
535
568
|
<header>
|
|
@@ -550,6 +583,12 @@ document.getElementById('app').innerHTML=\`
|
|
|
550
583
|
<code>\${esc(nextCommand)}</code>
|
|
551
584
|
<span>\${esc(nextReason)}</span>
|
|
552
585
|
</section>
|
|
586
|
+
<section class="brief" aria-label="First read">
|
|
587
|
+
\${briefItem('Status',reviewRequired?'review required':'clear')}
|
|
588
|
+
\${briefItem('Read first',firstRead)}
|
|
589
|
+
\${briefItem('Review targets',reviewTargets.length?reviewTargets.slice(0,4).join(', '):'none')}
|
|
590
|
+
\${briefItem('Local mode',safeMode)}
|
|
591
|
+
</section>
|
|
553
592
|
<section class="audit" aria-label="Audit summary">
|
|
554
593
|
\${auditItem('AI decision',reviewRequired?'review required':'can proceed',nextAction,reviewRequired?'review':'primary')}
|
|
555
594
|
\${auditItem('AI readiness',aiReadiness.level||'unknown',aiReadinessHint,aiReadiness.level==='ready'?'primary':'review')}
|