Compare commits

..

2 Commits

Author SHA1 Message Date
6a8cb62903 style(pi): format guardrails module with alejandra 2026-04-29 19:51:35 +02:00
a3e247e5af feat(pi): add guardrails config option for pi-guardrails integration
Adds a guardrails submodule option to coding.agents.pi that:
- Generates ~/.pi/agent/extensions/guardrails.json when enabled
- Automatically injects @aliou/pi-guardrails package into settings.packages
- Provides structured options for policies, pathAccess, and permissionGate

The module generates the JSON config that pi-guardrails reads for
its security hooks (policies, permission-gate, path-access).

Limitations documented in option descriptions:
- Path access checks are lexical (not symlink-safe)
- Local project guardrails.json can override global rule IDs
2026-04-29 19:48:10 +02:00

View File

@@ -202,6 +202,38 @@
See pi docs/settings.md for all options. See pi docs/settings.md for all options.
''; '';
}; };
# ── Pi Guardrails ─────────────────────────────────────────────
guardrails = mkOption {
type = types.nullOr (types.submodule {
options = {
enable =
mkEnableOption
"Generate ~/.pi/agent/extensions/guardrails.json for pi-guardrails. "
+ "Adds @aliou/pi-guardrails to packages automatically.";
config = mkOption {
type = types.attrsOf types.anything;
default = {};
description = ''
Guardrails configuration written to ~/.pi/agent/extensions/guardrails.json.
See https://github.com/aliou/pi-guardrails for config schema.
IMPORTANT: Path access checks are lexical (not symlink-safe).
Local project .pi/extensions/guardrails.json can override same rule IDs
(memory > local > global > defaults). For immutable global policies,
consider a wrapper or upstream patch.
'';
};
};
});
default = null;
description = ''
Pi Guardrails security configuration.
Generates ~/.pi/agent/extensions/guardrails.json when enabled.
The @aliou/pi-guardrails package is added to settings.packages automatically.
'';
};
}; };
config = with lib; let config = with lib; let
@@ -224,8 +256,37 @@
attrs attrs
); );
# Base settings (already filtered)
piSettings = filterNulls cfg.settings; piSettings = filterNulls cfg.settings;
# Guardrails package to inject when guardrails is enabled
guardrailsPackage = "npm:@aliou/pi-guardrails@0.11.1";
# Guardrails config (only when guardrails is enabled)
guardrailsJson =
if (cfg.guardrails != null && cfg.guardrails.enable)
then builtins.toJSON cfg.guardrails.config
else null;
# Merge guardrails package into settings.packages when guardrails is enabled
piSettingsWithGuardrails = let
baseSettings = cfg.settings;
basePackages = baseSettings.packages or [];
hasGuardrailsPackage =
lib.any
(p:
lib.hasPrefix "npm:@aliou/pi-guardrails" p
|| (lib.hasPrefix "git:" p && lib.hasSuffix "/pi-guardrails" p))
basePackages;
packagesWithGuardrails =
if (cfg.guardrails != null && cfg.guardrails.enable && !hasGuardrailsPackage)
then basePackages ++ [guardrailsPackage]
else basePackages;
in
if packagesWithGuardrails != basePackages
then filterNulls (baseSettings // {packages = packagesWithGuardrails;})
else piSettings;
# Coding rules config for renderForPi (only when both agentsInput and codingRules are set) # Coding rules config for renderForPi (only when both agentsInput and codingRules are set)
piCodingRules = piCodingRules =
if cfg.agentsInput != null && cfg.codingRules != null if cfg.agentsInput != null && cfg.codingRules != null
@@ -269,10 +330,16 @@
# ── ~/.pi/agent/settings.json ────────────────────────────────── # ── ~/.pi/agent/settings.json ──────────────────────────────────
{ {
".pi/agent/settings.json".text = builtins.toJSON piSettings; ".pi/agent/settings.json".text = builtins.toJSON piSettingsWithGuardrails;
".pi/agent/settings.json".force = true; ".pi/agent/settings.json".force = true;
} }
# ── pi-guardrails config ─────────────────────────────────────
(mkIf (guardrailsJson != null) {
".pi/agent/extensions/guardrails.json".text = guardrailsJson;
".pi/agent/extensions/guardrails.json".force = true;
})
# ── AGENTS.md — agent descriptions and specialist listing ────── # ── AGENTS.md — agent descriptions and specialist listing ──────
(mkIf (cfg.agentsInput != null) { (mkIf (cfg.agentsInput != null) {
".pi/agent/AGENTS.md".source = "${rendered}/AGENTS.md"; ".pi/agent/AGENTS.md".source = "${rendered}/AGENTS.md";