@aliou/pi-guardrails 0.13.1 → 0.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -198,8 +198,14 @@ export const COMMAND_EXAMPLES: Array<{
198
198
  },
199
199
  {
200
200
  label: "git push --force",
201
- description: "Prompts before force-pushing Git history.",
202
- pattern: { pattern: "git push --force", description: "Git force push" },
201
+ description:
202
+ "Prompts before force-pushing Git history. Uses regex so the flag is caught regardless of its position in the command (e.g. `git push origin main --force` or `-f` at the end).",
203
+ pattern: {
204
+ pattern: "git push .*(-f\\b|--force(?!-with-lease)|--force-with-lease)",
205
+ regex: true,
206
+ description:
207
+ "Git force push (any variant: --force, --force-with-lease, -f)",
208
+ },
203
209
  },
204
210
  {
205
211
  label: "npm publish",
@@ -441,13 +441,15 @@ export function registerGuardrailsSettings(
441
441
 
442
442
  if (scope === "global") {
443
443
  featureItems.push({
444
- id: "onboarding.run",
444
+ id: "onboarding.completed",
445
445
  label: "Onboarding status",
446
- description: "Use /guardrails:onboarding to run onboarding",
446
+ description:
447
+ "Reset to pending to re-run onboarding (takes effect after reload)",
447
448
  currentValue:
448
449
  scopedConfig.onboarding?.completed === true
449
450
  ? "completed"
450
451
  : "pending",
452
+ values: ["completed", "pending"],
451
453
  });
452
454
  }
453
455
 
@@ -592,5 +594,43 @@ export function registerGuardrailsSettings(
592
594
  },
593
595
  ];
594
596
  },
597
+
598
+ onSettingChange: (id, newValue, config) => {
599
+ const updated = structuredClone(config);
600
+
601
+ if (id.startsWith("features.")) {
602
+ const featureKey = id.slice("features.".length);
603
+ updated.features = {
604
+ ...updated.features,
605
+ [featureKey]: newValue === "enabled",
606
+ };
607
+ return updated;
608
+ }
609
+
610
+ if (id === "permissionGate.requireConfirmation") {
611
+ updated.permissionGate = {
612
+ ...updated.permissionGate,
613
+ requireConfirmation: newValue === "on",
614
+ };
615
+ return updated;
616
+ }
617
+
618
+ if (id === "onboarding.completed") {
619
+ updated.onboarding = {
620
+ ...updated.onboarding,
621
+ completed: newValue === "completed",
622
+ completedAt:
623
+ newValue === "completed"
624
+ ? (updated.onboarding?.completedAt ?? new Date().toISOString())
625
+ : undefined,
626
+ version:
627
+ newValue === "completed" ? updated.onboarding?.version : undefined,
628
+ };
629
+ return updated;
630
+ }
631
+
632
+ // Fall through to default string storage for enums (pathAccess.mode, etc.)
633
+ return null;
634
+ },
595
635
  });
596
636
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aliou/pi-guardrails",
3
- "version": "0.13.1",
3
+ "version": "0.13.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "private": false,
@@ -56,14 +56,6 @@
56
56
  "typescript": "^5.9.3",
57
57
  "vitest": "^4.1.4"
58
58
  },
59
- "peerDependenciesMeta": {
60
- "@earendil-works/pi-coding-agent": {
61
- "optional": true
62
- },
63
- "@earendil-works/pi-tui": {
64
- "optional": true
65
- }
66
- },
67
59
  "scripts": {
68
60
  "typecheck": "tsc --noEmit",
69
61
  "gen:schema": "ts-json-schema-generator --path src/shared/config/types.ts --type GuardrailsConfig --no-type-check -o schema.json",
@@ -73,8 +65,18 @@
73
65
  "lint": "biome check",
74
66
  "format": "biome check --write",
75
67
  "check:lockfile": "pnpm install --frozen-lockfile --ignore-scripts",
68
+ "prepare": "[ -d .git ] && husky || true",
76
69
  "changeset": "changeset",
77
70
  "version": "changeset version",
78
71
  "release": "pnpm changeset publish"
72
+ },
73
+ "packageManager": "pnpm@10.26.1",
74
+ "peerDependenciesMeta": {
75
+ "@earendil-works/pi-coding-agent": {
76
+ "optional": true
77
+ },
78
+ "@earendil-works/pi-tui": {
79
+ "optional": true
80
+ }
79
81
  }
80
- }
82
+ }
@@ -1,13 +1,31 @@
1
- import { buildSchemaUrl, ConfigLoader } from "@aliou/pi-utils-settings";
1
+ import {
2
+ buildSchemaUrl,
3
+ ConfigLoader,
4
+ type Scope,
5
+ } from "@aliou/pi-utils-settings";
2
6
  import pkg from "../../../package.json" with { type: "json" };
3
7
  import { DEFAULT_CONFIG } from "./defaults";
4
8
  import { migrations } from "./migration";
5
9
  import type { GuardrailsConfig, PolicyRule, ResolvedConfig } from "./types";
6
10
 
7
- export const configLoader = new ConfigLoader<GuardrailsConfig, ResolvedConfig>(
8
- "guardrails",
9
- DEFAULT_CONFIG,
10
- {
11
+ class GuardrailsConfigLoader extends ConfigLoader<
12
+ GuardrailsConfig,
13
+ ResolvedConfig
14
+ > {
15
+ override async save(scope: Scope, config: GuardrailsConfig): Promise<void> {
16
+ await super.save(scope, ensureConfigVersion(config));
17
+ }
18
+ }
19
+
20
+ function ensureConfigVersion(config: GuardrailsConfig): GuardrailsConfig {
21
+ if (typeof config.version === "string" && config.version.trim()) {
22
+ return config;
23
+ }
24
+ return { ...config, version: DEFAULT_CONFIG.version };
25
+ }
26
+
27
+ export function createGuardrailsConfigLoader(): GuardrailsConfigLoader {
28
+ return new GuardrailsConfigLoader("guardrails", DEFAULT_CONFIG, {
11
29
  scopes: ["global", "local", "memory"],
12
30
  migrations,
13
31
  schemaUrl: buildSchemaUrl(pkg.name, pkg.version),
@@ -60,5 +78,7 @@ export const configLoader = new ConfigLoader<GuardrailsConfig, ResolvedConfig>(
60
78
 
61
79
  return resolved;
62
80
  },
63
- },
64
- );
81
+ });
82
+ }
83
+
84
+ export const configLoader = createGuardrailsConfigLoader();
@@ -0,0 +1,57 @@
1
+ import { addPendingWarning } from "../../warnings";
2
+ import type { GuardrailsConfig } from "../types";
3
+ import { CURRENT_VERSION } from "./version";
4
+
5
+ export function shouldRun(config: GuardrailsConfig): boolean {
6
+ const features = config.features as Record<string, unknown> | undefined;
7
+ if (features) {
8
+ for (const value of Object.values(features)) {
9
+ if (value === "enabled" || value === "disabled") return true;
10
+ }
11
+ }
12
+
13
+ const requiresConfirmation = config.permissionGate
14
+ ?.requireConfirmation as unknown;
15
+ if (requiresConfirmation === "on" || requiresConfirmation === "off") {
16
+ return true;
17
+ }
18
+
19
+ return false;
20
+ }
21
+
22
+ export function run(config: GuardrailsConfig): GuardrailsConfig {
23
+ const migrated = structuredClone(config) as Record<string, unknown>;
24
+ let changed = false;
25
+
26
+ const features = migrated.features as Record<string, unknown> | undefined;
27
+ if (features) {
28
+ for (const [key, value] of Object.entries(features)) {
29
+ if (value === "enabled" || value === "disabled") {
30
+ features[key] = value === "enabled";
31
+ changed = true;
32
+ }
33
+ }
34
+ }
35
+
36
+ const permissionGate = migrated.permissionGate as
37
+ | Record<string, unknown>
38
+ | undefined;
39
+ if (
40
+ permissionGate &&
41
+ (permissionGate.requireConfirmation === "on" ||
42
+ permissionGate.requireConfirmation === "off")
43
+ ) {
44
+ permissionGate.requireConfirmation =
45
+ permissionGate.requireConfirmation === "on";
46
+ changed = true;
47
+ }
48
+
49
+ if (changed) {
50
+ migrated.version = CURRENT_VERSION;
51
+ addPendingWarning(
52
+ "[guardrails] Config migrated: boolean settings stored as strings were converted to true/false.",
53
+ );
54
+ }
55
+
56
+ return migrated as GuardrailsConfig;
57
+ }
@@ -7,6 +7,7 @@ import * as envFilesToPolicies from "./004-env-files-to-policies";
7
7
  import * as normalizeAllowedPaths from "./005-normalize-allowed-paths";
8
8
  import * as applyBuiltinDefaults from "./006-apply-builtin-defaults";
9
9
  import * as markOnboardingDone from "./007-mark-onboarding-done";
10
+ import * as normalizeStringBooleans from "./008-normalize-string-booleans";
10
11
 
11
12
  export { CURRENT_VERSION } from "./version";
12
13
 
@@ -36,6 +37,11 @@ export const migrations: Migration<GuardrailsConfig>[] = [
36
37
  shouldRun: normalizeAllowedPaths.shouldRun,
37
38
  run: normalizeAllowedPaths.run,
38
39
  },
40
+ {
41
+ name: "normalize-string-booleans",
42
+ shouldRun: normalizeStringBooleans.shouldRun,
43
+ run: normalizeStringBooleans.run,
44
+ },
39
45
  ];
40
46
 
41
47
  export const globalConfigMigrations = [