aienvmp 0.1.29 → 0.1.31

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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.31
4
+
5
+ - Added `aienvmp status` as a compact human and AI entrypoint.
6
+ - Summarized clear/review state, next command, counts, top action, and enforcement advice in one output.
7
+ - Kept `context --json` as the richer preflight while making first checks simpler.
8
+
9
+ ## 0.1.30
10
+
11
+ - Added shared enforcement advice for AI and CI surfaces.
12
+ - Exposed advisory-by-default behavior, suggested strict scopes, and scoped CI commands in context and plan outputs.
13
+ - Moved strict scope logic into a reusable enforcement module while keeping existing `doctor --strict` behavior compatible.
14
+
3
15
  ## 0.1.29
4
16
 
5
17
  - Added lightweight remediation priority scoring for vulnerable packages.
package/README.md CHANGED
@@ -16,6 +16,7 @@ Core loop: scan once, link runtime/dependency/security context, give AI a shared
16
16
 
17
17
  ```bash
18
18
  npx aienvmp sync
19
+ npx aienvmp status
19
20
  npx aienvmp context
20
21
  npx aienvmp plan
21
22
  npx aienvmp handoff
@@ -79,6 +80,7 @@ The dashboard shows which strict scopes are CI-ready before you enforce them.
79
80
 
80
81
  ```bash
81
82
  aienvmp sync # update env map, light SBOM, ledger, dashboard
83
+ aienvmp status # one compact clear/review decision
82
84
  aienvmp context # AI preflight brief
83
85
  aienvmp context --json # AI decision contract + actions + compact step summary
84
86
  aienvmp plan # read-only AI action plan using the same decision contract
@@ -97,6 +99,7 @@ aienvmp doctor --strict security # fail only scoped warnings
97
99
  - read-only planning, no automatic fixes
98
100
  - one advisory engine, optional enforcement with `doctor --ci`
99
101
  - scoped enforcement with `doctor --strict security|policy|coordination|all`
102
+ - context and plan expose suggested strict scopes for CI
100
103
  - non-blocking unless strict mode is explicitly requested
101
104
  - security checks are opt-in and read-only
102
105
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aienvmp",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "description": "AI-first environment maps and lightweight runtime SBOMs for shared development machines.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -12,6 +12,7 @@ import { syncWorkspace } from "./commands/sync.js";
12
12
  import { snippetWorkspace } from "./commands/snippet.js";
13
13
  import { handoffWorkspace } from "./commands/handoff.js";
14
14
  import { planWorkspace } from "./commands/plan.js";
15
+ import { statusWorkspace } from "./commands/status.js";
15
16
  import { readFileSync } from "node:fs";
16
17
 
17
18
  const commands = new Map([
@@ -28,7 +29,8 @@ const commands = new Map([
28
29
  ["sync", syncWorkspace],
29
30
  ["snippet", snippetWorkspace],
30
31
  ["handoff", handoffWorkspace],
31
- ["plan", planWorkspace]
32
+ ["plan", planWorkspace],
33
+ ["status", statusWorkspace]
32
34
  ]);
33
35
 
34
36
  const version = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
@@ -78,11 +80,13 @@ function printUsage() {
78
80
  Usage:
79
81
  aienvmp sync [--dir .] [--json] [--quiet] [--deep] [--security]
80
82
  aienvmp context [--dir .] [--json]
83
+ aienvmp status [--dir .] [--json]
81
84
  aienvmp handoff [--dir .] [--json] [--record --actor agent:id]
82
85
  aienvmp plan [--dir .] [--json] [--write]
83
86
 
84
87
  Common:
85
88
  aienvmp sync update AIENV.md, manifest, ledger, intents, and dashboard
89
+ aienvmp status print one simple environment decision
86
90
  aienvmp context print the AI preflight brief
87
91
  aienvmp handoff print the next-agent handoff summary
88
92
  aienvmp plan print a read-only AI environment action plan
@@ -7,6 +7,7 @@ import { loadPolicy, policyWarnings } from "../policy.js";
7
7
  import { recommendedActions } from "../actions.js";
8
8
  import { buildPlan, compactStepSummary } from "./plan.js";
9
9
  import { aiDecision } from "../decision.js";
10
+ import { enforcementAdvice } from "../enforcement.js";
10
11
 
11
12
  export async function contextWorkspace(args) {
12
13
  const dir = workspaceDir(args);
@@ -23,6 +24,7 @@ export async function contextWorkspace(args) {
23
24
  console.log(JSON.stringify({
24
25
  status: warnings.length ? "review-required" : "clear",
25
26
  decision,
27
+ enforcement: enforcementAdvice(warnings),
26
28
  recommendedActions: actions,
27
29
  stepSummary,
28
30
  trust: manifest.trust || {},
@@ -8,7 +8,7 @@ import { dashboardPath, intentsPath, manifestPath, planJsonPath, planMdPath, tim
8
8
  import { renderDashboard } from "../render.js";
9
9
  import { loadPolicy, policyWarnings } from "../policy.js";
10
10
  import { recommendedActions } from "../actions.js";
11
- import { strictResult } from "./doctor.js";
11
+ import { strictResult } from "../enforcement.js";
12
12
 
13
13
  export async function dashWorkspace(args) {
14
14
  const dir = workspaceDir(args);
@@ -4,6 +4,9 @@ import { openIntents, readJsonl, readTimeline } from "../timeline.js";
4
4
  import { intentsPath, manifestPath, timelinePath, workspaceDir } from "../paths.js";
5
5
  import { loadPolicy, policyWarnings } from "../policy.js";
6
6
  import { recommendedActions } from "../actions.js";
7
+ import { enforcementAdvice, strictResult } from "../enforcement.js";
8
+
9
+ export { strictResult } from "../enforcement.js";
7
10
 
8
11
  export async function doctorWorkspace(args) {
9
12
  const dir = workspaceDir(args);
@@ -23,6 +26,7 @@ export async function doctorWorkspace(args) {
23
26
  openIntentCount: intents.length,
24
27
  warnings,
25
28
  recommendedActions: actions,
29
+ enforcement: enforcementAdvice(warnings),
26
30
  strict
27
31
  }, null, 2));
28
32
  if (strict.fail) {
@@ -46,34 +50,3 @@ export async function doctorWorkspace(args) {
46
50
  process.exitCode = 1;
47
51
  }
48
52
  }
49
-
50
- export function strictResult(warnings = [], args = {}) {
51
- const scope = normalizeStrictScope(args.strict || (args.ci ? "all" : ""));
52
- const matchedWarnings = scope ? warnings.filter((warning) => warningMatchesScope(warning, scope)) : [];
53
- return {
54
- enabled: Boolean(scope),
55
- scope: scope || "off",
56
- fail: matchedWarnings.length > 0,
57
- matchedWarningCodes: matchedWarnings.map((warning) => warning.code),
58
- availableScopes: ["security", "policy", "coordination", "all"]
59
- };
60
- }
61
-
62
- function normalizeStrictScope(value) {
63
- if (value === true) return "all";
64
- const scope = String(value || "").trim().toLowerCase();
65
- if (!scope || scope === "false" || scope === "off") return "";
66
- if (["security", "policy", "coordination", "all"].includes(scope)) return scope;
67
- return "all";
68
- }
69
-
70
- function warningMatchesScope(warning, scope) {
71
- if (scope === "all") return true;
72
- return warningScope(warning.code) === scope;
73
- }
74
-
75
- function warningScope(code = "") {
76
- if (code === "security-vulnerabilities") return "security";
77
- if (["conflicting-open-intents", "stale-open-intent", "handoff-stale"].includes(code)) return "coordination";
78
- return "policy";
79
- }
@@ -7,6 +7,7 @@ import { renderPlan } from "../render.js";
7
7
  import { recommendedActions } from "../actions.js";
8
8
  import { openIntents, readJsonl, readTimeline } from "../timeline.js";
9
9
  import { aiDecision } from "../decision.js";
10
+ import { enforcementAdvice } from "../enforcement.js";
10
11
 
11
12
  export async function planWorkspace(args) {
12
13
  const dir = workspaceDir(args);
@@ -41,6 +42,7 @@ export function buildPlan(manifest, warnings = [], intents = [], policy = {}) {
41
42
  workspace: manifest.workspace || {},
42
43
  trust: manifest.trust || {},
43
44
  decision: aiDecision(warnings, intents),
45
+ enforcement: enforcementAdvice(warnings),
44
46
  policy: {
45
47
  node: policy.node || "not set",
46
48
  python: policy.python || "not set",
@@ -0,0 +1,53 @@
1
+ import { diagnose } from "../doctor.js";
2
+ import { readJson } from "../fsutil.js";
3
+ import { loadPolicy, policyWarnings } from "../policy.js";
4
+ import { intentsPath, manifestPath, timelinePath, workspaceDir } from "../paths.js";
5
+ import { openIntents, readJsonl, readTimeline } from "../timeline.js";
6
+ import { recommendedActions } from "../actions.js";
7
+ import { aiDecision } from "../decision.js";
8
+ import { enforcementAdvice } from "../enforcement.js";
9
+
10
+ export async function statusWorkspace(args) {
11
+ const dir = workspaceDir(args);
12
+ const manifest = await readJson(manifestPath(dir));
13
+ if (!manifest) throw new Error("missing manifest; run `aienvmp sync` first");
14
+ const policy = await loadPolicy(dir);
15
+ const timeline = await readTimeline(timelinePath(dir));
16
+ const intents = openIntents(await readJsonl(intentsPath(dir)));
17
+ const warnings = [...diagnose(manifest, { timeline, intents }), ...policyWarnings(manifest, policy)];
18
+ const status = buildStatus(manifest, warnings, intents);
19
+ if (args.json) {
20
+ console.log(JSON.stringify(status, null, 2));
21
+ } else {
22
+ console.log(`${status.state}: ${status.summary}`);
23
+ console.log(`next: ${status.nextCommand}`);
24
+ console.log(`strict: ${status.enforcement.recommendedCommand}`);
25
+ }
26
+ return status;
27
+ }
28
+
29
+ export function buildStatus(manifest = {}, warnings = [], intents = []) {
30
+ const decision = aiDecision(warnings, intents);
31
+ const enforcement = enforcementAdvice(warnings);
32
+ const actions = recommendedActions(manifest, { warnings, intents });
33
+ const state = decision.reviewRequired ? "review-required" : "clear";
34
+ const topAction = actions[0] || null;
35
+ return {
36
+ schemaVersion: 1,
37
+ state,
38
+ summary: state === "clear"
39
+ ? "Project-local work can continue. Record intent before environment changes."
40
+ : "Review warnings or open intents before environment changes.",
41
+ decision,
42
+ enforcement,
43
+ counts: {
44
+ warnings: warnings.length,
45
+ openIntents: intents.length,
46
+ runtimes: Object.keys(manifest.runtimes || {}).length,
47
+ dependencies: Number(manifest.dependencySnapshot?.summary?.packages || 0),
48
+ vulnerabilities: Number(manifest.security?.summary?.total || 0)
49
+ },
50
+ topAction,
51
+ nextCommand: topAction?.command || decision.nextCommand
52
+ };
53
+ }
@@ -0,0 +1,57 @@
1
+ const STRICT_SCOPES = ["security", "policy", "coordination", "all"];
2
+
3
+ export function strictResult(warnings = [], args = {}) {
4
+ const scope = normalizeStrictScope(args.strict || (args.ci ? "all" : ""));
5
+ const matchedWarnings = scope ? warnings.filter((warning) => warningMatchesScope(warning, scope)) : [];
6
+ return {
7
+ enabled: Boolean(scope),
8
+ scope: scope || "off",
9
+ fail: matchedWarnings.length > 0,
10
+ matchedWarningCodes: matchedWarnings.map((warning) => warning.code),
11
+ availableScopes: STRICT_SCOPES
12
+ };
13
+ }
14
+
15
+ export function enforcementAdvice(warnings = []) {
16
+ const scopeResults = STRICT_SCOPES.map((scope) => {
17
+ const result = strictResult(warnings, { strict: scope });
18
+ return {
19
+ scope,
20
+ status: result.fail ? "fail" : "pass",
21
+ matchedWarningCodes: result.matchedWarningCodes
22
+ };
23
+ });
24
+ const suggestedStrictScopes = scopeResults
25
+ .filter((item) => item.scope !== "all" && item.status === "fail")
26
+ .map((item) => item.scope);
27
+ return {
28
+ mode: "advisory-by-default",
29
+ localBehavior: "non-blocking",
30
+ ciBehavior: "strict-only-when-requested",
31
+ suggestedStrictScopes,
32
+ scopes: scopeResults,
33
+ recommendedCommand: suggestedStrictScopes.length
34
+ ? `aienvmp doctor --strict ${suggestedStrictScopes[0]}`
35
+ : "aienvmp doctor --strict all",
36
+ note: "Use strict mode in CI or explicit checks; do not block local operation unless the user requests it."
37
+ };
38
+ }
39
+
40
+ function normalizeStrictScope(value) {
41
+ if (value === true) return "all";
42
+ const scope = String(value || "").trim().toLowerCase();
43
+ if (!scope || scope === "false" || scope === "off") return "";
44
+ if (STRICT_SCOPES.includes(scope)) return scope;
45
+ return "all";
46
+ }
47
+
48
+ function warningMatchesScope(warning, scope) {
49
+ if (scope === "all") return true;
50
+ return warningScope(warning.code) === scope;
51
+ }
52
+
53
+ function warningScope(code = "") {
54
+ if (code === "security-vulnerabilities") return "security";
55
+ if (["conflicting-open-intents", "stale-open-intent", "handoff-stale"].includes(code)) return "coordination";
56
+ return "policy";
57
+ }
package/src/render.js CHANGED
@@ -95,6 +95,7 @@ export function renderContext(manifest, timeline = [], warnings = [], intents =
95
95
  `Status: ${status}`,
96
96
  `Next: ${next}`,
97
97
  "Project-local work: allowed; environment changes require intent and review when warnings or open intents exist.",
98
+ "Enforcement: advisory by default; use `aienvmp doctor --strict <scope>` only when explicit CI failure is wanted.",
98
99
  `Trust: ${manifest.trust?.state || "observed"} (verified requires human or CI)`,
99
100
  `Workspace: ${manifest.workspace.path}`,
100
101
  `Node: ${manifest.runtimes.node || "not detected"}`,
@@ -175,6 +176,7 @@ export function renderPlan(plan) {
175
176
  "",
176
177
  `Status: ${plan.status}`,
177
178
  `Decision: ${plan.decision?.mode || plan.status}`,
179
+ `Enforcement: ${plan.enforcement?.mode || "advisory-by-default"} (${plan.enforcement?.localBehavior || "non-blocking"})`,
178
180
  `Generated: ${plan.generatedAt}`,
179
181
  `Workspace: ${plan.workspace?.path || "unknown"}`,
180
182
  "",