aienvmp 0.1.67 → 0.1.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.1.69
6
+
7
+ - Added `aiBootstrap` to the shared preflight surfaces so AI agents can read the shortest first-read, next-command, and local-mode hint.
8
+ - Mirrored `aiBootstrap` in the dashboard first-read area so humans see the same AI entry hint.
9
+ - Added the same bootstrap hint to `summary.md` so Markdown-first AI agents can start from the compact artifact.
10
+ - Added the bootstrap hint to `AIENV.md` so the main environment map matches the JSON, summary, and dashboard entry point.
11
+ - Added `aiBootstrap` and `nextSafeCommand` to plan JSON and the same bootstrap hint to `plan.md`.
12
+ - Added `aiBootstrap` and `nextSafeCommand` to the standalone light SBOM artifact so dependency review starts from the same AI loop.
13
+ - Added `aienvmp:aiBootstrap:*` properties to CycloneDX-lite output so external SBOM consumers can find the same next-step hint.
14
+
15
+ ## 0.1.68
16
+
17
+ - Added an `aienvmp AI loop` block to the GitHub Action Step Summary using `schema.aiLoop`.
18
+ - Kept the Action summary advisory by default while showing the same loop documented in README and `schema --json`.
19
+ - Updated the GitHub Action example and README CI notes to mention Step Summary strict-plan and AI-loop blocks.
20
+ - Added `doctor --json` `nextSafeCommand` so AI agents can pick one advisory next command without parsing every warning.
21
+ - Aligned the dashboard next-command fallback with `maintenanceLoop.nextCommand` so the visual surface follows the same advisory next-step chain.
22
+ - Added `context --json` `nextSafeCommand` so AI agents get the same one-step advisory next command in the preflight payload.
23
+ - Added `handoff --json` `nextSafeCommand` so the next AI can resume from one root-level advisory command.
24
+ - Added `status --json` and `.aienvmp/status.json` `nextSafeCommand` as a stable alias for the preflight next command.
25
+
3
26
  ## 0.1.67
4
27
 
5
28
  - Added a dashboard `First read` strip with status, first file, review targets, and local operation mode.
package/README.md CHANGED
@@ -73,10 +73,15 @@ AIENV.md # Markdown env map for AI agents
73
73
  ## AI Contract
74
74
 
75
75
  - `status`, `context`, `plan`, and `handoff` share one additive preflight contract.
76
+ - `aiBootstrap` gives AI the shortest read-first, next-command, and local-mode hint.
77
+ - `AIENV.md`, `summary.md`, and `plan.md` start with the same bootstrap hint for Markdown-first agents.
78
+ - `sbom.json` and CycloneDX-lite properties include the same bootstrap hint for dependency and security review loops.
76
79
  - `maintenanceLoop` gives AI the recurring env-management loop.
77
80
  - `sbomRisk` and `sbomReview` connect light SBOM risk to safe dependency-change steps.
78
81
  - `collaboration`, `coordination`, and `agentActivity` show multi-agent conflicts and shared targets.
79
82
  - `strictDecision` separates local warn-only checks from optional CI strict gates.
83
+ - `status --json`, `context --json`, `handoff --json`, and `doctor --json` include `nextSafeCommand` for one advisory next step.
84
+ - The dashboard mirrors `aiBootstrap` so humans and AI see the same first-read and next-command hint.
80
85
  - `schema --json` prints the stable machine-readable contract without scanning.
81
86
 
82
87
  ## Agent Files
@@ -118,7 +123,7 @@ aienvmp doctor --strict security|policy|coordination|all
118
123
  ## CI
119
124
 
120
125
  The GitHub Action writes status, summary, schema, doctor, plan, SBOM, and dashboard artifacts. `strict: "off"` reports warnings without failing the job.
121
- The Step Summary includes an `aienvmp strict plan` block so humans and AI agents can pick the narrowest CI strict scope without parsing the full artifacts.
126
+ 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.
122
127
 
123
128
  ```yaml
124
129
  - 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aienvmp",
3
- "version": "0.1.67",
3
+ "version": "0.1.69",
4
4
  "description": "AI-first environment maps and lightweight runtime SBOMs for shared development machines.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,9 +22,12 @@ 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,
30
+ aiBootstrap: preflight.aiBootstrap,
28
31
  preflight,
29
32
  aiReadiness: preflight.aiReadiness,
30
33
  collaboration: preflight.collaboration,
@@ -59,6 +62,14 @@ export async function contextWorkspace(args) {
59
62
  console.log(renderContext({ ...manifest, preflight }, timeline, warnings, intents, policy, actions));
60
63
  }
61
64
 
65
+ function contextNextSafeCommand(actions = [], warnings = [], preflight = {}) {
66
+ const actionCommand = actions.find((item) => item.command)?.command;
67
+ return preflight.nextCommand
68
+ || preflight.maintenanceLoop?.nextCommand
69
+ || actionCommand
70
+ || (warnings.length ? "aienvmp plan --write" : "aienvmp status --json");
71
+ }
72
+
62
73
  function lightSbomSummary(lightSbom = {}) {
63
74
  return {
64
75
  mode: lightSbom.mode || "light-sbom",
@@ -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
+ }
@@ -49,6 +49,8 @@ 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",
53
+ aiBootstrap: preflight.aiBootstrap,
52
54
  trust: manifest.trust || {},
53
55
  schemaVersion: manifest.schemaVersion || 1,
54
56
  preflight,
@@ -36,11 +36,14 @@ export async function planWorkspace(args) {
36
36
  export function buildPlan(manifest, warnings = [], intents = [], policy = {}, timeline = []) {
37
37
  const actions = recommendedActions(manifest, { warnings, intents });
38
38
  const status = warnings.length || intents.length ? "review-required" : "clear";
39
+ const preflight = buildPreflight(manifest, warnings, intents, timeline);
39
40
  return {
40
41
  schemaVersion: 1,
41
42
  generatedAt: new Date().toISOString(),
42
43
  status,
43
- preflight: buildPreflight(manifest, warnings, intents, timeline),
44
+ aiBootstrap: preflight.aiBootstrap,
45
+ nextSafeCommand: preflight.nextSafeCommand || preflight.nextCommand,
46
+ preflight,
44
47
  workspace: manifest.workspace || {},
45
48
  trust: manifest.trust || {},
46
49
  decision: aiDecision(warnings, intents),
@@ -22,6 +22,11 @@ export async function sbomWorkspace(args = {}) {
22
22
 
23
23
  export function buildSbomArtifact(manifest = {}) {
24
24
  const lightSbom = manifest.lightSbom || {};
25
+ const dependencyReview = lightSbom.aiDependencyReview || aiDependencyReview(lightSbom);
26
+ const nextSafeCommand = dependencyReview.beforeDependencyChange?.[0]
27
+ || lightSbom.riskSummary?.commands?.[0]
28
+ || "aienvmp context --json";
29
+ const aiBootstrap = sbomBootstrap(nextSafeCommand, dependencyReview);
25
30
  return {
26
31
  schemaVersion: 1,
27
32
  schemaName: "aienvmp.light-sbom",
@@ -36,16 +41,31 @@ export function buildSbomArtifact(manifest = {}) {
36
41
  topRisk: (lightSbom.topRisk || []).slice(0, 20),
37
42
  packageManagerPolicy: lightSbom.packageManagerPolicy || {},
38
43
  dependencyChangeHints: (lightSbom.dependencyChangeHints || []).slice(0, 20),
39
- aiDependencyReview: lightSbom.aiDependencyReview || aiDependencyReview(lightSbom),
44
+ aiBootstrap,
45
+ nextSafeCommand,
46
+ aiDependencyReview: dependencyReview,
40
47
  aiUse: {
41
48
  purpose: "Standalone AI-readable light SBOM artifact.",
42
49
  readBefore: "Dependency changes, vulnerability remediation, release review, or shared AI handoff.",
43
- nextCommand: lightSbom.riskSummary?.commands?.[0] || "aienvmp context --json",
50
+ nextCommand: nextSafeCommand,
44
51
  rule: "Use as a lightweight planning map; verify security claims with dedicated scanners."
45
52
  }
46
53
  };
47
54
  }
48
55
 
56
+ function sbomBootstrap(nextSafeCommand, review = {}) {
57
+ return {
58
+ purpose: "Shortest AI entry point for dependency and SBOM review.",
59
+ readFirst: ".aienvmp/sbom.json",
60
+ detailCommand: "aienvmp context --json",
61
+ nextSafeCommand,
62
+ localMode: "advisory",
63
+ projectLocalWork: "allowed",
64
+ environmentChanges: review.status === "review" ? "review-first" : "intent-first",
65
+ rule: review.rule || "Read SBOM risk first; record intent before dependency or lockfile changes."
66
+ };
67
+ }
68
+
49
69
  function aiDependencyReview(lightSbom = {}) {
50
70
  const risk = lightSbom.riskSummary || {};
51
71
  const hints = lightSbom.dependencyChangeHints || [];
@@ -97,6 +117,11 @@ export function buildCycloneDxLite(manifest = {}) {
97
117
  const snapshot = manifest.dependencySnapshot || {};
98
118
  const packages = snapshot.packages || [];
99
119
  const lightSbom = manifest.lightSbom || {};
120
+ const dependencyReview = lightSbom.aiDependencyReview || aiDependencyReview(lightSbom);
121
+ const nextSafeCommand = dependencyReview.beforeDependencyChange?.[0]
122
+ || lightSbom.riskSummary?.commands?.[0]
123
+ || "aienvmp context --json";
124
+ const aiBootstrap = sbomBootstrap(nextSafeCommand, dependencyReview);
100
125
  return {
101
126
  bomFormat: "CycloneDX",
102
127
  specVersion: "1.6",
@@ -121,14 +146,20 @@ export function buildCycloneDxLite(manifest = {}) {
121
146
  { name: "aienvmp:source", value: lightSbom.source?.dependencies || "project manifests" },
122
147
  { name: "aienvmp:confidence:transitiveDependencies", value: lightSbom.confidence?.transitiveDependencies || "not-resolved" },
123
148
  { name: "aienvmp:risk:level", value: lightSbom.riskSummary?.level || "clear" },
124
- { name: "aienvmp:risk:score", value: String(lightSbom.riskSummary?.score || 0) }
149
+ { name: "aienvmp:risk:score", value: String(lightSbom.riskSummary?.score || 0) },
150
+ { name: "aienvmp:aiBootstrap:readFirst", value: aiBootstrap.readFirst },
151
+ { name: "aienvmp:aiBootstrap:detailCommand", value: aiBootstrap.detailCommand },
152
+ { name: "aienvmp:aiBootstrap:nextSafeCommand", value: aiBootstrap.nextSafeCommand },
153
+ { name: "aienvmp:aiBootstrap:localMode", value: aiBootstrap.localMode },
154
+ { name: "aienvmp:aiBootstrap:environmentChanges", value: aiBootstrap.environmentChanges }
125
155
  ]
126
156
  },
127
157
  components: packages.slice(0, 200).map(cycloneComponent),
128
158
  vulnerabilities: (lightSbom.topRisk || []).slice(0, 50).map(cycloneVulnerability),
129
159
  properties: [
130
160
  { name: "aienvmp:limitation", value: "Light SBOM from project manifests only; no install or dependency resolver was run." },
131
- { name: "aienvmp:verifyWith", value: "CycloneDX, Syft, Trivy, npm audit, pip-audit, or another dedicated scanner before security claims." }
161
+ { name: "aienvmp:verifyWith", value: "CycloneDX, Syft, Trivy, npm audit, pip-audit, or another dedicated scanner before security claims." },
162
+ { name: "aienvmp:aiBootstrap:rule", value: aiBootstrap.rule }
132
163
  ]
133
164
  };
134
165
  }
@@ -40,10 +40,11 @@ export function renderSummary(status = {}, manifest = {}) {
40
40
  const collaboration = status.collaboration || {};
41
41
  const maintenanceLoop = status.maintenanceLoop || {};
42
42
  const sbomReview = maintenanceLoop.sbomReview || {};
43
+ const aiBootstrap = status.aiBootstrap || {};
43
44
  const workspace = manifest.workspace?.root || manifest.workspace?.name || ".";
44
- const next = status.nextCommand || status.decision?.nextCommand || "aienvmp status";
45
- const readFirst = status.nextAgent?.readFirst || ".aienvmp/status.json";
46
- const detail = status.quickstart?.detailCommand || "aienvmp context --json";
45
+ const next = aiBootstrap.nextSafeCommand || status.nextSafeCommand || status.nextCommand || status.decision?.nextCommand || "aienvmp status";
46
+ const readFirst = aiBootstrap.readFirst || status.nextAgent?.readFirst || ".aienvmp/status.json";
47
+ const detail = aiBootstrap.detailCommand || status.quickstart?.detailCommand || "aienvmp context --json";
47
48
  const strict = status.enforcement?.recommendedCommand || "aienvmp doctor --strict all";
48
49
  const riskLevel = sbomRisk.level || "unknown";
49
50
  const riskScore = valueOrZero(sbomRisk.score);
@@ -67,11 +68,13 @@ export function renderSummary(status = {}, manifest = {}) {
67
68
  "",
68
69
  `- AI readiness: ${aiReadiness.level || "unknown"}`,
69
70
  `- AI signals: ${aiSignals.length ? aiSignals.join("; ") : "none"}`,
70
- `- AI next: ${aiNext}`,
71
+ `- AI bootstrap: ${aiBootstrap.projectLocalWork || "allowed"} / ${aiBootstrap.environmentChanges || status.agentUse?.environmentChanges || "intent-first"} / ${aiBootstrap.localMode || "advisory"}`,
72
+ `- AI next: ${next} (${aiNext})`,
71
73
  `- AI collaboration: ${collaboration.status || "unknown"} / ${toList(collaboration.activeTargets).join(", ") || "none"} / ${collaboration.nextCommand || "aienvmp status --json"}`,
72
74
  `- AI maintenance loop: ${maintenanceLoop.nextCommand || next}`,
73
75
  `- AI safe local work: ${toList(aiReadiness.safeProjectLocalActions)[0] || "read artifacts and avoid environment changes until reviewed"}`,
74
76
  `- AI read first: ${readFirst}, then ${detail}`,
77
+ `- AI bootstrap rule: ${aiBootstrap.rule || "Read status first, use context for details, and keep local checks advisory."}`,
75
78
  `- mode: advisory by default; strict is opt-in with ${strict}`,
76
79
  `- local check: ${strictDecision.localCommand || "aienvmp doctor --json"} (${strictDecision.local || "warn-only"})`,
77
80
  `- CI strict: ${strictPlan.ciCommand || `${strict} --json`}`,
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", "maintenanceLoop", "nextAgent", "coordination", "agentActivity", "agentPointers", "sbomRisk", "followUps", "dependencyReadSet", "dependencyChangeProtocol"],
7
+ aiEntryFields: ["state", "summary", "aiBootstrap", "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
  }
@@ -32,6 +32,7 @@ export function schemaContract() {
32
32
  status: {
33
33
  file: ".aienvmp/status.json",
34
34
  command: "aienvmp status --json",
35
+ rootFields: ["state", "aiBootstrap", "nextCommand", "nextSafeCommand", "decision", "counts", "aiReadiness", "collaboration", "maintenanceLoop", "coordination", "agentPointers", "sbomRisk"],
35
36
  contract: preflightContract()
36
37
  },
37
38
  summary: {
@@ -41,13 +42,18 @@ export function schemaContract() {
41
42
  purpose: "Compact AI and CI step summary for quick review.",
42
43
  startsWith: ["AI readiness", "AI signals", "AI next"]
43
44
  },
45
+ plan: {
46
+ file: ".aienvmp/plan.json",
47
+ command: "aienvmp plan --json",
48
+ rootFields: ["schemaVersion", "status", "aiBootstrap", "nextSafeCommand", "preflight", "decision", "enforcement", "recommendedActions", "reviewGates", "remediationSteps", "environmentSteps"]
49
+ },
44
50
  context: {
45
51
  command: "aienvmp context --json",
46
- rootFields: ["status", "preflight", "aiReadiness", "collaboration", "maintenanceLoop", "coordination", "agentPointers", "decision", "enforcement", "recommendedActions", "workspace", "dependencySnapshot", "lightSbom", "warnings"]
52
+ rootFields: ["status", "aiBootstrap", "nextSafeCommand", "preflight", "aiReadiness", "collaboration", "maintenanceLoop", "coordination", "agentPointers", "decision", "enforcement", "recommendedActions", "workspace", "dependencySnapshot", "lightSbom", "warnings"]
47
53
  },
48
54
  handoff: {
49
55
  command: "aienvmp handoff --json",
50
- rootFields: ["status", "decision", "preflight", "continuation", "coordination", "dependencyHandoff", "openIntents", "warnings", "recommendedActions", "recentChanges"]
56
+ rootFields: ["status", "aiBootstrap", "nextSafeCommand", "decision", "preflight", "continuation", "coordination", "dependencyHandoff", "openIntents", "warnings", "recommendedActions", "recentChanges"]
51
57
  },
52
58
  manifest: {
53
59
  file: ".aienvmp/manifest.json",
@@ -56,7 +62,7 @@ export function schemaContract() {
56
62
  sbom: {
57
63
  file: ".aienvmp/sbom.json",
58
64
  command: "aienvmp sbom --json",
59
- rootFields: ["schemaVersion", "schemaName", "workspace", "summary", "riskSummary", "topRisk", "packageManagerPolicy", "dependencyChangeHints", "aiDependencyReview"],
65
+ rootFields: ["schemaVersion", "schemaName", "workspace", "aiBootstrap", "nextSafeCommand", "summary", "riskSummary", "topRisk", "packageManagerPolicy", "dependencyChangeHints", "aiDependencyReview"],
60
66
  aiDependencyReviewFields: ["status", "statusReason", "securityConfidence", "readFirst", "reviewTargets", "beforeDependencyChange", "afterDependencyChange", "rule"]
61
67
  },
62
68
  cyclonedxLite: {
package/src/preflight.js CHANGED
@@ -30,6 +30,8 @@ export function buildPreflight(manifest = {}, warnings = [], intents = [], timel
30
30
  dependencyReadSet,
31
31
  collaboration
32
32
  });
33
+ const nextCommand = maintenanceLoop.nextCommand || topAction?.command || decision.nextCommand;
34
+ const aiBootstrap = aiBootstrapSummary({ state, decision, nextCommand });
33
35
  return {
34
36
  schemaVersion: 1,
35
37
  contract: preflightContract(),
@@ -69,6 +71,7 @@ export function buildPreflight(manifest = {}, warnings = [], intents = [], timel
69
71
  projectLocalWork: decision.canContinueProjectLocalWork ? "allowed" : "review-first",
70
72
  environmentChanges: decision.canChangeEnvironmentWithoutReview ? "allowed" : "intent-and-review-first"
71
73
  },
74
+ aiBootstrap,
72
75
  quickstart: agentQuickstart(decision.reviewRequired),
73
76
  nextAgent: nextAgentHint(state, dependencyReadSet, dependencyChangeProtocol),
74
77
  coordination,
@@ -102,7 +105,23 @@ export function buildPreflight(manifest = {}, warnings = [], intents = [], timel
102
105
  recordIntent: intentTargets[0]?.command || "aienvmp intent --actor agent:id --action planned-change"
103
106
  },
104
107
  topAction,
105
- nextCommand: maintenanceLoop.nextCommand || topAction?.command || decision.nextCommand
108
+ nextCommand,
109
+ nextSafeCommand: nextCommand
110
+ };
111
+ }
112
+
113
+ function aiBootstrapSummary({ state, decision, nextCommand }) {
114
+ return {
115
+ purpose: "Shortest AI entry point for this workspace environment.",
116
+ readFirst: ".aienvmp/status.json",
117
+ detailCommand: "aienvmp context --json",
118
+ nextSafeCommand: nextCommand || "aienvmp status --json",
119
+ localMode: "advisory",
120
+ projectLocalWork: decision.canContinueProjectLocalWork ? "allowed" : "review-first",
121
+ environmentChanges: decision.canChangeEnvironmentWithoutReview ? "intent-first" : "review-first",
122
+ rule: state === "clear"
123
+ ? "Continue project-local work; record intent before shared environment changes."
124
+ : "Review context before shared environment changes; local checks remain non-blocking."
106
125
  };
107
126
  }
108
127
 
package/src/render.js CHANGED
@@ -70,10 +70,17 @@ export function renderAIEnv(manifest, timeline = [], warnings = [], intents = []
70
70
 
71
71
  function preflightLines(preflight = {}) {
72
72
  const quickstart = preflight.quickstart;
73
+ const aiBootstrap = preflight.aiBootstrap || {};
73
74
  const targets = preflight.intentTargets || [];
74
75
  const maintenanceLoop = preflight.maintenanceLoop || {};
75
76
  const lines = ["## 10-Second AI Flow", ""];
76
- if (quickstart) {
77
+ if (aiBootstrap.nextSafeCommand || aiBootstrap.readFirst) {
78
+ lines.push(`- AI bootstrap: ${aiBootstrap.projectLocalWork || "allowed"} / ${aiBootstrap.environmentChanges || "intent-first"} / ${aiBootstrap.localMode || "advisory"}`);
79
+ lines.push(`- Next safe command: \`${aiBootstrap.nextSafeCommand || preflight.nextSafeCommand || preflight.nextCommand || "aienvmp status --json"}\``);
80
+ lines.push(`- Read first: \`${aiBootstrap.readFirst || ".aienvmp/status.json"}\``);
81
+ lines.push(`- Detail: \`${aiBootstrap.detailCommand || "aienvmp context --json"}\``);
82
+ lines.push(`- Rule: ${aiBootstrap.rule || "Read status first, use context for details, and keep local checks advisory."}`);
83
+ } else if (quickstart) {
77
84
  lines.push(`- Read first: \`${quickstart.readFirst}\``);
78
85
  lines.push(`- Detail: \`${quickstart.detailCommand}\``);
79
86
  lines.push(`- Before env change: \`${quickstart.beforeEnvironmentChange}\``);
@@ -288,10 +295,16 @@ function dependencyHandoffLines(dependencyHandoff = {}) {
288
295
  }
289
296
 
290
297
  export function renderPlan(plan) {
298
+ const aiBootstrap = plan.aiBootstrap || plan.preflight?.aiBootstrap || {};
299
+ const nextSafeCommand = plan.nextSafeCommand || aiBootstrap.nextSafeCommand || plan.preflight?.nextSafeCommand || plan.preflight?.nextCommand || "aienvmp status --json";
291
300
  const lines = [
292
301
  "# AI Environment Plan",
293
302
  "",
294
303
  `Status: ${plan.status}`,
304
+ `AI bootstrap: ${aiBootstrap.projectLocalWork || "allowed"} / ${aiBootstrap.environmentChanges || "intent-first"} / ${aiBootstrap.localMode || "advisory"}`,
305
+ `Next safe command: ${nextSafeCommand}`,
306
+ `Read first: ${aiBootstrap.readFirst || ".aienvmp/status.json"} -> ${aiBootstrap.detailCommand || "aienvmp context --json"}`,
307
+ `Bootstrap rule: ${aiBootstrap.rule || "Read status first, use context for details, and keep local checks advisory."}`,
295
308
  `Decision: ${plan.decision?.mode || plan.status}`,
296
309
  `Enforcement: ${plan.enforcement?.mode || "advisory-by-default"} (${plan.enforcement?.localBehavior || "non-blocking"})`,
297
310
  `Generated: ${plan.generatedAt}`,
@@ -413,7 +426,7 @@ h1,h2,h3,p{margin:0}h1{font-size:clamp(28px,4vw,46px);line-height:1.02;margin-to
413
426
  .nextbar b{color:var(--green);font-size:12px;text-transform:uppercase;letter-spacing:.08em}
414
427
  .nextbar code{display:inline-block;max-width:100%;white-space:normal;overflow-wrap:anywhere}
415
428
  .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}
429
+ .brief{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px;margin:0 0 14px}
417
430
  .brief-item{border:1px solid var(--line2);background:rgba(9,19,16,.9);border-radius:8px;padding:10px;min-width:0}
418
431
  .brief-k{color:var(--muted);font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.08em}
419
432
  .brief-v{margin-top:5px;font-size:13px;font-weight:700;color:var(--text);overflow-wrap:anywhere}
@@ -528,6 +541,7 @@ const activityTargets=agentActivity.targets||[];
528
541
  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>';
529
542
  const collaboration=manifest.preflight?.collaboration||{};
530
543
  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>\`;
544
+ const maintenanceLoop=manifest.preflight?.maintenanceLoop||{};
531
545
  const dependencyReadSet=manifest.preflight?.dependencyReadSet||[];
532
546
  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>';
533
547
  const dependencyProtocol=manifest.preflight?.dependencyChangeProtocol||{};
@@ -540,6 +554,7 @@ const nextAction=reviewRequired?'Review before environment changes':'Proceed wit
540
554
  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>\`;
541
555
  const controlCard=(label,value,next,klass='')=>\`<div class="control-card \${klass}"><div class="control-label">\${label}</div><div class="control-value">\${esc(value)}</div><div class="control-next">\${esc(next)}</div></div>\`;
542
556
  const driftLabel=warnings.length?'detected':'none';
557
+ const aiBootstrap=manifest.preflight?.aiBootstrap||{};
543
558
  const nextAgent=manifest.preflight?.nextAgent||{};
544
559
  const aiReadiness=manifest.preflight?.aiReadiness||{};
545
560
  const aiReadinessSignals=(aiReadiness.signals||[]).slice(0,3);
@@ -552,17 +567,18 @@ const sbomRiskValue=riskSummary.level||'unknown';
552
567
  const sbomRiskClass=['urgent','high','medium'].includes(sbomRiskValue)?'review':'ready';
553
568
  const sbomRiskScore=riskSummary.score!==undefined?' ('+riskSummary.score+')':'';
554
569
  const sbomRiskNext=riskSummary.next||aiDependencyReview.beforeDependencyChange?.[0]||'Run aienvmp sbom --json for dependency context.';
555
- const nextCommand=manifest.preflight?.nextCommand||topAction.command||collaboration.nextCommand||'aienvmp status --json';
556
- const nextReason=topAction.summary||collaboration.rule||riskSummary.next||'Read status/context before changing shared environment state.';
570
+ const nextCommand=aiBootstrap.nextSafeCommand||manifest.preflight?.nextSafeCommand||manifest.preflight?.nextCommand||maintenanceLoop.nextCommand||topAction.command||collaboration.nextCommand||'aienvmp status --json';
571
+ const nextReason=topAction.summary||aiBootstrap.rule||maintenanceLoop.rule||collaboration.rule||riskSummary.next||'Read status/context before changing shared environment state.';
557
572
  const coordination=manifest.preflight?.coordination||{};
558
573
  const conflictTargets=coordination.conflictTargets||[];
559
574
  const handoffFiles=nextAgent.dependencyFiles?.length?nextAgent.dependencyFiles:(dependencyReadSet[0]?[dependencyReadSet[0].manifest,...(dependencyReadSet[0].lockfiles||[])].filter(Boolean):[]);
560
575
  const handoffNext=nextAgent.rule||(reviewRequired?'Review warnings and open intents':'Continue project-local work');
561
- const firstRead=nextAgent.readFirst||'.aienvmp/status.json';
576
+ const firstRead=aiBootstrap.readFirst||nextAgent.readFirst||'.aienvmp/status.json';
562
577
  const reviewTargets=[...new Set([...(conflictTargets||[]),...(collaboration.activeTargets||[]),...(riskSummary.reviewTargets||[])].filter(Boolean))];
563
- const safeMode=enforcementProfile.gate?.localDefault||enforcementProfile.localOperation||'warn-only';
578
+ const safeMode=aiBootstrap.localMode||enforcementProfile.gate?.localDefault||enforcementProfile.localOperation||'warn-only';
579
+ const bootstrapState=[aiBootstrap.projectLocalWork||'allowed',aiBootstrap.environmentChanges||'intent-first'].join(' / ');
564
580
  const briefItem=(key,value)=>\`<div class="brief-item"><div class="brief-k">\${key}</div><div class="brief-v">\${esc(value)}</div></div>\`;
565
- 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>\`;
581
+ 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(firstRead)}</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>\`;
566
582
  document.getElementById('app').innerHTML=\`
567
583
  <header>
568
584
  <div>
@@ -583,6 +599,7 @@ document.getElementById('app').innerHTML=\`
583
599
  <span>\${esc(nextReason)}</span>
584
600
  </section>
585
601
  <section class="brief" aria-label="First read">
602
+ \${briefItem('AI bootstrap',bootstrapState)}
586
603
  \${briefItem('Status',reviewRequired?'review required':'clear')}
587
604
  \${briefItem('Read first',firstRead)}
588
605
  \${briefItem('Review targets',reviewTargets.length?reviewTargets.slice(0,4).join(', '):'none')}
@@ -687,9 +704,13 @@ function formatTimeline(item) {
687
704
  }
688
705
 
689
706
  function contextLines(manifest, warnings, intents) {
707
+ const preflight = manifest.preflight || {};
708
+ const aiBootstrap = preflight.aiBootstrap || {};
690
709
  return [
691
710
  `- Status: ${warnings.length ? "review-required" : "clear"}`,
692
- `- Next: ${warnings.length ? "review warnings before environment changes" : "continue with project-local work"}`,
711
+ `- Next: ${aiBootstrap.nextSafeCommand || preflight.nextSafeCommand || preflight.nextCommand || (warnings.length ? "review warnings before environment changes" : "continue with project-local work")}`,
712
+ `- Bootstrap: ${aiBootstrap.projectLocalWork || "allowed"} / ${aiBootstrap.environmentChanges || "intent-first"} / ${aiBootstrap.localMode || "advisory"}`,
713
+ `- Read first: ${aiBootstrap.readFirst || ".aienvmp/status.json"} -> ${aiBootstrap.detailCommand || "aienvmp context --json"}`,
693
714
  `- Node: ${manifest.runtimes.node || "not detected"}`,
694
715
  `- Python: ${manifest.runtimes.python || manifest.runtimes.python3 || "not detected"}`,
695
716
  `- Docker: ${manifest.containers.docker ? "available" : "not detected"}`,