From af9fd67e2399d8dcb2d0798103a7db51e9be9ede Mon Sep 17 00:00:00 2001 From: captjt Date: Mon, 8 Dec 2025 11:39:05 -0500 Subject: [PATCH 1/7] feat: slim down catalog bundle structure --- cupcake-cli/src/catalog_cli.rs | 166 ++++++++++++----- cupcake-core/src/engine/catalog_overlay.rs | 81 ++++++++- docs/docs/catalog/authoring/index.md | 86 ++++++--- docs/docs/catalog/authoring/manifest.md | 8 +- docs/docs/catalog/authoring/policies.md | 149 +++++++++------- docs/docs/catalog/authoring/publishing.md | 8 + docs/docs/catalog/reference/cli.md | 8 +- .../reference/namespace-conventions.md | 168 +++++++++++------- 8 files changed, 465 insertions(+), 209 deletions(-) diff --git a/cupcake-cli/src/catalog_cli.rs b/cupcake-cli/src/catalog_cli.rs index eb028e4f..c72fd56c 100644 --- a/cupcake-cli/src/catalog_cli.rs +++ b/cupcake-cli/src/catalog_cli.rs @@ -684,6 +684,12 @@ async fn execute_lint(path: &std::path::Path) -> Result<()> { errors.push(format!("Manifest validation failed: {}", e)); } + // Check for system/evaluate.rego at rulebook root (shared entrypoint) + let system_eval = path.join("system").join("evaluate.rego"); + if !system_eval.exists() { + errors.push("Missing system/evaluate.rego at rulebook root".to_string()); + } + // Check policies exist for declared harnesses let policies_dir = path.join("policies"); if !policies_dir.exists() { @@ -699,28 +705,17 @@ async fn execute_lint(path: &std::path::Path) -> Result<()> { continue; } - // Check for system/evaluate.rego - let system_eval = harness_dir.join("system").join("evaluate.rego"); - if !system_eval.exists() { - errors.push(format!( - "Missing system/evaluate.rego for harness: {}", - harness - )); - } - - // Check that at least some .rego files exist - let rego_files = count_rego_files(&harness_dir); + // Check that harness directory has at least one .rego file directly + let rego_files = count_rego_files_direct(&harness_dir); if rego_files == 0 { - errors.push(format!("No .rego files found for harness: {}", harness)); + errors.push(format!("No .rego policy files in policies/{}/", harness)); } } } - // Validate Rego namespaces - if policies_dir.exists() { - if let Err(e) = validate_rego_namespaces(path, &manifest.metadata.name) { - errors.push(format!("Namespace validation failed: {}", e)); - } + // Validate Rego namespaces (policies, helpers, and system) + if let Err(e) = validate_rego_namespaces(path, &manifest.metadata.name) { + errors.push(format!("Namespace validation failed: {}", e)); } // Check for README @@ -753,25 +748,89 @@ async fn execute_lint(path: &std::path::Path) -> Result<()> { Ok(()) } -fn count_rego_files(dir: &std::path::Path) -> usize { - walkdir::WalkDir::new(dir) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| { - e.path().is_file() - && e.path() - .extension() - .map(|ext| ext == "rego") - .unwrap_or(false) +/// Count .rego files directly in a directory (non-recursive) +fn count_rego_files_direct(dir: &std::path::Path) -> usize { + std::fs::read_dir(dir) + .map(|entries| { + entries + .filter_map(|e| e.ok()) + .filter(|e| { + e.path().is_file() + && e.path() + .extension() + .map(|ext| ext == "rego") + .unwrap_or(false) + }) + .count() }) - .count() + .unwrap_or(0) } fn validate_rego_namespaces(rulebook_path: &std::path::Path, rulebook_name: &str) -> Result<()> { + let normalized_name = rulebook_name.replace('-', "_"); + let base_prefix = format!("cupcake.catalog.{}", normalized_name); + + // Validate policies/ directory (policies namespace) let policies_dir = rulebook_path.join("policies"); - let expected_prefix = format!("cupcake.catalog.{}", rulebook_name.replace('-', "_")); + if policies_dir.exists() { + let expected_prefix = format!("{}.policies", base_prefix); + validate_rego_files_in_dir(&policies_dir, &expected_prefix, rulebook_path)?; + } + + // Validate helpers/ directory (helpers namespace) + let helpers_dir = rulebook_path.join("helpers"); + if helpers_dir.exists() { + let expected_prefix = format!("{}.helpers", base_prefix); + validate_rego_files_in_dir(&helpers_dir, &expected_prefix, rulebook_path)?; + } + + // Validate system/ directory (exact system namespace) + let system_dir = rulebook_path.join("system"); + if system_dir.exists() { + let expected_package = format!("{}.system", base_prefix); + for entry in walkdir::WalkDir::new(&system_dir) { + let entry = entry?; + if !entry.path().is_file() { + continue; + } + if entry + .path() + .extension() + .map(|ext| ext != "rego") + .unwrap_or(true) + { + continue; + } + + let content = std::fs::read_to_string(entry.path())?; + let package_name = extract_package_name(&content); - for entry in walkdir::WalkDir::new(&policies_dir) { + if let Some(pkg) = package_name { + if pkg != expected_package { + anyhow::bail!( + "System file at {:?} has invalid namespace '{}'. Expected exactly '{}'", + entry + .path() + .strip_prefix(rulebook_path) + .unwrap_or(entry.path()), + pkg, + expected_package + ); + } + } + } + } + + Ok(()) +} + +/// Validate .rego files in a directory have the expected namespace prefix +fn validate_rego_files_in_dir( + dir: &std::path::Path, + expected_prefix: &str, + rulebook_path: &std::path::Path, +) -> Result<()> { + for entry in walkdir::WalkDir::new(dir) { let entry = entry?; if !entry.path().is_file() { continue; @@ -787,23 +846,19 @@ fn validate_rego_namespaces(rulebook_path: &std::path::Path, rulebook_name: &str } let content = std::fs::read_to_string(entry.path())?; - - // Find package declaration - for line in content.lines() { - let trimmed = line.trim(); - if trimmed.starts_with("package ") { - let package_name = trimmed.strip_prefix("package ").unwrap_or("").trim(); - - // Check namespace prefix - if !package_name.starts_with(&expected_prefix) { - anyhow::bail!( - "Policy at {:?} has invalid namespace '{}'. Expected prefix '{}'", - entry.path(), - package_name, - expected_prefix - ); - } - break; + let package_name = extract_package_name(&content); + + if let Some(pkg) = package_name { + if !pkg.starts_with(expected_prefix) { + anyhow::bail!( + "File at {:?} has invalid namespace '{}'. Expected prefix '{}'", + entry + .path() + .strip_prefix(rulebook_path) + .unwrap_or(entry.path()), + pkg, + expected_prefix + ); } } } @@ -811,6 +866,23 @@ fn validate_rego_namespaces(rulebook_path: &std::path::Path, rulebook_name: &str Ok(()) } +/// Extract package name from Rego content +fn extract_package_name(content: &str) -> Option { + for line in content.lines() { + let trimmed = line.trim(); + if trimmed.starts_with("package ") { + return Some( + trimmed + .strip_prefix("package ") + .unwrap_or("") + .trim() + .to_string(), + ); + } + } + None +} + fn print_validation_results(errors: &[String], warnings: &[String]) { if errors.is_empty() && warnings.is_empty() { println!("Rulebook is valid."); diff --git a/cupcake-core/src/engine/catalog_overlay.rs b/cupcake-core/src/engine/catalog_overlay.rs index 699ae358..1a5127fa 100644 --- a/cupcake-core/src/engine/catalog_overlay.rs +++ b/cupcake-core/src/engine/catalog_overlay.rs @@ -137,7 +137,7 @@ pub async fn scan_catalog_policies( overlay.name ); - // Also scan for helper files in the helpers/ directory at rulebook root + // Scan for helper files in the helpers/ directory at rulebook root let helpers_dir = overlay.rulebook_root.join("helpers"); let helper_files = if helpers_dir.exists() { match scanner::scan_policies(&helpers_dir).await { @@ -162,6 +162,31 @@ pub async fn scan_catalog_policies( Vec::new() }; + // Scan for system files (evaluate.rego) in the system/ directory at rulebook root + let system_dir = overlay.rulebook_root.join("system"); + let system_files = if system_dir.exists() { + match scanner::scan_policies(&system_dir).await { + Ok(files) => { + info!( + "Found {} system files in catalog overlay {}", + files.len(), + overlay.name + ); + files + } + Err(e) => { + warn!( + "Failed to scan system dir for catalog overlay {}: {}", + overlay.name, e + ); + Vec::new() + } + } + } else { + debug!("No system directory for catalog overlay {}", overlay.name); + Vec::new() + }; + // Parse each policy file for path in policy_files { match parse_catalog_policy(&path, &overlay.namespace).await { @@ -193,6 +218,22 @@ pub async fn scan_catalog_policies( } } } + + // Parse each system file (evaluate.rego entrypoint) + for path in system_files { + match parse_catalog_system(&path, &overlay.namespace).await { + Ok(unit) => { + debug!( + "Parsed catalog system file: {} from {:?}", + unit.package_name, path + ); + overlay.policies.push(unit); + } + Err(e) => { + warn!("Failed to parse catalog system file {:?}: {}", path, e); + } + } + } } Ok(()) @@ -291,6 +332,44 @@ async fn parse_catalog_helper(path: &Path, expected_namespace: &str) -> Result

.system +async fn parse_catalog_system(path: &Path, expected_namespace: &str) -> Result { + use super::metadata::{self, RoutingDirective}; + + let content = tokio::fs::read_to_string(path) + .await + .context("Failed to read system file")?; + + // Extract package name + let package_name = + metadata::extract_package_name(&content).context("Failed to extract package name")?; + + // Validate namespace - system files MUST use the system namespace + let expected_system = format!("{}.system", expected_namespace); + + if package_name != expected_system { + return Err(anyhow::anyhow!( + "Catalog system file {} has invalid namespace. Expected '{}'", + package_name, + expected_system + )); + } + + // Parse OPA metadata (optional for system files) + let policy_metadata = metadata::parse_metadata(&content).ok().flatten(); + + // System files don't need routing - they're entrypoints + let routing = RoutingDirective::default(); + + Ok(PolicyUnit { + path: path.to_path_buf(), + package_name, + routing, + metadata: policy_metadata, + }) +} + /// Compile catalog overlay policies to WASM pub async fn compile_catalog_overlay( overlay: &CatalogOverlay, diff --git a/docs/docs/catalog/authoring/index.md b/docs/docs/catalog/authoring/index.md index 90776c74..2b4cc95c 100644 --- a/docs/docs/catalog/authoring/index.md +++ b/docs/docs/catalog/authoring/index.md @@ -22,26 +22,25 @@ my-rulebook/ ├── manifest.yaml # Required: rulebook metadata ├── README.md # Recommended: documentation ├── CHANGELOG.md # Recommended: version history +├── system/ +│ └── evaluate.rego # Required: shared aggregation entrypoint ├── helpers/ # Optional: shared Rego helpers │ └── utils.rego └── policies/ # Required: policies by harness ├── claude/ - │ ├── system/ - │ │ └── evaluate.rego # Required: aggregation policy - │ └── builtins/ - │ └── my_policy.rego + │ └── my_policy.rego # Policy files directly in harness dir ├── cursor/ - │ ├── system/ - │ │ └── evaluate.rego - │ └── builtins/ - │ └── my_policy.rego + │ └── my_policy.rego └── opencode/ - ├── system/ - │ └── evaluate.rego - └── builtins/ - └── my_policy.rego + └── my_policy.rego ``` +Key points: + +- `system/evaluate.rego` is at the **rulebook root**, shared across all harnesses +- `helpers/` contains shared functions that can be imported by any policy +- Policy files go **directly** in `policies//` (no subdirectories) + ## Quick Start ### 1. Create the Manifest @@ -70,40 +69,71 @@ metadata: Create a policy for each harness you support: ```rego -# policies/claude/builtins/my_policy.rego -package cupcake.catalog.my_rulebook.policies.claude.builtins.my_policy +# policies/claude/my_policy.rego +package cupcake.catalog.my_rulebook.policies.my_policy + +import rego.v1 # METADATA +# scope: package # title: My Policy # description: Blocks something dangerous -# scope: rule # custom: -# decision: deny # severity: high # routing: -# events: [pre_tool_use] -# tools: [bash] +# required_events: ["PreToolUse"] +# required_tools: ["Bash"] -default deny := false - -deny { +deny contains decision if { + input.hook_event_name == "PreToolUse" + input.tool_name == "Bash" input.tool_input.command == "something_dangerous" + + decision := { + "rule_id": "MY-001", + "reason": "This command is blocked", + "severity": "HIGH", + } } ``` -### 3. Create the Aggregation Policy +### 3. Create the Aggregation Entrypoint -Each harness needs a `system/evaluate.rego`: +Create a single `system/evaluate.rego` at the rulebook root: ```rego -# policies/claude/system/evaluate.rego -package cupcake.catalog.my_rulebook.policies.claude.system +# system/evaluate.rego +package cupcake.catalog.my_rulebook.system -import data.cupcake.catalog.my_rulebook.policies.claude.builtins +import rego.v1 -decisions[decision] { - decision := builtins.my_policy.deny +# METADATA +# scope: package +# custom: +# entrypoint: true + +evaluate := { + "halts": collect_verbs("halt"), + "denials": collect_verbs("deny"), + "blocks": collect_verbs("block"), + "asks": collect_verbs("ask"), + "allow_overrides": collect_verbs("allow_override"), + "add_context": collect_verbs("add_context"), +} + +collect_verbs(verb_name) := result if { + verb_sets := [value | + walk(data.cupcake.catalog.my_rulebook.policies, [path, value]) + path[count(path) - 1] == verb_name + ] + all_decisions := [decision | + some verb_set in verb_sets + some decision in verb_set + ] + result := all_decisions } + +default collect_verbs(_) := [] ``` ### 4. Validate diff --git a/docs/docs/catalog/authoring/manifest.md b/docs/docs/catalog/authoring/manifest.md index 01563268..b760fe0e 100644 --- a/docs/docs/catalog/authoring/manifest.md +++ b/docs/docs/catalog/authoring/manifest.md @@ -161,5 +161,9 @@ This checks: - Required fields are present - Name format is valid - Version is valid semver -- Listed harnesses have corresponding policy directories -- Policy namespaces follow the `cupcake.catalog..*` pattern +- `system/evaluate.rego` exists at rulebook root +- Listed harnesses have corresponding policy directories with `.rego` files +- Namespaces follow the required patterns: + - Policies: `cupcake.catalog..policies.*` + - Helpers: `cupcake.catalog..helpers.*` + - System: `cupcake.catalog..system` diff --git a/docs/docs/catalog/authoring/policies.md b/docs/docs/catalog/authoring/policies.md index 56c3a469..9786cbcc 100644 --- a/docs/docs/catalog/authoring/policies.md +++ b/docs/docs/catalog/authoring/policies.md @@ -9,52 +9,46 @@ This guide covers how to write effective policies for your rulebook. ## Namespace Convention -All catalog policies **must** use the namespace pattern: +All catalog files **must** use these namespace patterns: -``` -cupcake.catalog..policies... -``` - -For example: - -```rego -package cupcake.catalog.security_hardened.policies.claude.builtins.dangerous_commands -``` +| Directory | Pattern | Example | +|-----------|---------|---------| +| `policies//` | `cupcake.catalog..policies.` | `cupcake.catalog.security_hardened.policies.dangerous_commands` | +| `helpers/` | `cupcake.catalog..helpers.` | `cupcake.catalog.security_hardened.helpers.commands` | +| `system/` | `cupcake.catalog..system` | `cupcake.catalog.security_hardened.system` | !!! warning "Namespace Validation" -`cupcake catalog lint` will fail if your policies don't follow this pattern. + `cupcake catalog lint` will fail if your policies don't follow these patterns. ## Policy Structure ### Basic Policy ```rego -# policies/claude/builtins/example.rego -package cupcake.catalog.my_rulebook.policies.claude.builtins.example +# policies/claude/example.rego +package cupcake.catalog.my_rulebook.policies.example + +import rego.v1 # METADATA +# scope: package # title: Example Policy # description: Demonstrates policy structure -# scope: rule # custom: -# decision: deny # severity: medium # routing: -# events: [pre_tool_use] -# tools: [bash] - -default deny := false +# required_events: ["PreToolUse"] +# required_tools: ["Bash"] -deny { - # Your condition here +deny contains decision if { + input.hook_event_name == "PreToolUse" + input.tool_name == "Bash" input.tool_input.command == "bad_command" -} - -reasons[reason] { - deny - reason := { - "message": "This command is not allowed", - "severity": "medium" + + decision := { + "rule_id": "EXAMPLE-001", + "reason": "This command is not allowed", + "severity": "MEDIUM", } } ``` @@ -62,10 +56,9 @@ reasons[reason] { ### Key Components 1. **Package declaration** - Must follow namespace convention -2. **METADATA block** - Routing and documentation -3. **Default rule** - Set safe default (usually `false`) -4. **Decision rule** - The actual policy logic -5. **Reasons** - Human-readable explanation +2. **`import rego.v1`** - Use modern Rego syntax +3. **METADATA block** - Routing and documentation +4. **Decision rule** - Returns decision objects with rule_id, reason, severity ## Routing Metadata @@ -118,63 +111,93 @@ Specify which tools trigger evaluation: | `low` | Minor issue | | `info` | Informational only | -## Aggregation Policy +## Aggregation Entrypoint -Each harness needs a `system/evaluate.rego` that aggregates decisions: +Each rulebook needs a single `system/evaluate.rego` at the **root level** (not per-harness): ```rego -# policies/claude/system/evaluate.rego -package cupcake.catalog.my_rulebook.policies.claude.system +# system/evaluate.rego +package cupcake.catalog.my_rulebook.system -import data.cupcake.catalog.my_rulebook.policies.claude.builtins +import rego.v1 -# Aggregate all deny decisions -decisions[decision] { - decision := builtins.dangerous_commands.deny -} - -decisions[decision] { - decision := builtins.dangerous_flags.deny +# METADATA +# scope: package +# custom: +# entrypoint: true + +evaluate := { + "halts": collect_verbs("halt"), + "denials": collect_verbs("deny"), + "blocks": collect_verbs("block"), + "asks": collect_verbs("ask"), + "allow_overrides": collect_verbs("allow_override"), + "add_context": collect_verbs("add_context"), } -# Aggregate all reasons -all_reasons[reason] { - reason := builtins.dangerous_commands.reasons[_] +collect_verbs(verb_name) := result if { + verb_sets := [value | + walk(data.cupcake.catalog.my_rulebook.policies, [path, value]) + path[count(path) - 1] == verb_name + ] + all_decisions := [decision | + some verb_set in verb_sets + some decision in verb_set + ] + result := all_decisions } -all_reasons[reason] { - reason := builtins.dangerous_flags.reasons[_] -} +default collect_verbs(_) := [] ``` +This entrypoint uses `walk()` to automatically discover all decision verbs across all policies, so you don't need to manually import each policy. + ## Helper Functions -Place shared helpers in `helpers/`: +Place shared helpers in `helpers/` at the rulebook root: ```rego # helpers/commands.rego package cupcake.catalog.my_rulebook.helpers.commands -# Extract command name from shell input -get_command(input) = cmd { - parts := split(input.tool_input.command, " ") - cmd := parts[0] +import rego.v1 + +# Check if command contains a verb with word boundaries +has_verb(command, verb) if { + pattern := concat("", ["(^|\\s)", verb, "(\\s|$)"]) + regex.match(pattern, command) } -# Check if command is in a list -command_in_list(cmd, list) { - list[_] == cmd +# Check if command has any of the specified flags +has_any_flag(command, flag_set) if { + some flag in flag_set + pattern := concat("", ["(^|\\s)", flag, "(\\s|$|=)"]) + regex.match(pattern, command) } ``` -Use in policies: +Import and use helpers in your policies: ```rego -import data.cupcake.catalog.my_rulebook.helpers.commands +# policies/claude/dangerous_flags.rego +package cupcake.catalog.my_rulebook.policies.dangerous_flags -deny { - cmd := commands.get_command(input) - commands.command_in_list(cmd, dangerous_commands) +import data.cupcake.catalog.my_rulebook.helpers.commands +import rego.v1 + +deny contains decision if { + input.hook_event_name == "PreToolUse" + input.tool_name == "Bash" + + cmd := lower(input.tool_input.command) + commands.has_verb(cmd, "git") + commands.has_any_flag(cmd, {"--no-verify", "-n"}) + + decision := { + "rule_id": "GIT-001", + "reason": "Blocked --no-verify flag on git command", + "severity": "HIGH", + } } ``` diff --git a/docs/docs/catalog/authoring/publishing.md b/docs/docs/catalog/authoring/publishing.md index 5aa74d1e..e1e5f78c 100644 --- a/docs/docs/catalog/authoring/publishing.md +++ b/docs/docs/catalog/authoring/publishing.md @@ -77,7 +77,15 @@ This creates: ├── manifest.yaml ├── README.md ├── CHANGELOG.md + ├── system/ + │ └── evaluate.rego + ├── helpers/ # Optional + │ └── utils.rego └── policies/ + ├── claude/ + │ └── *.rego + ├── cursor/ + │ └── *.rego └── ... ``` diff --git a/docs/docs/catalog/reference/cli.md b/docs/docs/catalog/reference/cli.md index 87db65c7..9c083cd4 100644 --- a/docs/docs/catalog/reference/cli.md +++ b/docs/docs/catalog/reference/cli.md @@ -224,9 +224,11 @@ cupcake catalog lint - `manifest.yaml` exists and is valid - Required metadata fields present - Name format is valid -- Harness directories exist in `policies/` -- Each harness has `system/evaluate.rego` -- Policy namespaces follow `cupcake.catalog..*` pattern +- `system/evaluate.rego` exists at rulebook root +- Harness directories exist in `policies/` with `.rego` files +- Policy namespaces follow `cupcake.catalog..policies.*` pattern +- Helper namespaces follow `cupcake.catalog..helpers.*` pattern +- System namespace is exactly `cupcake.catalog..system` - README.md exists (warning if missing) - CHANGELOG.md exists (warning if missing) diff --git a/docs/docs/catalog/reference/namespace-conventions.md b/docs/docs/catalog/reference/namespace-conventions.md index e0d0ea67..be559275 100644 --- a/docs/docs/catalog/reference/namespace-conventions.md +++ b/docs/docs/catalog/reference/namespace-conventions.md @@ -7,54 +7,34 @@ description: "Rego namespace conventions for catalog rulebooks" Catalog rulebooks must follow specific namespace conventions to ensure isolation and prevent conflicts with project policies. -## Required Namespace Pattern +## Required Namespace Patterns -All policies in a catalog rulebook must use this namespace pattern: +All files in a catalog rulebook must use one of these namespace patterns: -``` -cupcake.catalog..policies... -``` +| Directory | Pattern | Example | +|-----------|---------|---------| +| `policies//` | `cupcake.catalog..policies.` | `cupcake.catalog.security_hardened.policies.dangerous_commands` | +| `helpers/` | `cupcake.catalog..helpers.` | `cupcake.catalog.security_hardened.helpers.commands` | +| `system/` | `cupcake.catalog..system` | `cupcake.catalog.security_hardened.system` | ### Components -| Component | Description | Example | -| ----------------- | ------------------------------------------ | -------------------- | -| `cupcake.catalog` | Fixed prefix for all catalog policies | - | -| `` | Name from manifest (hyphens → underscores) | `security_hardened` | -| `policies` | Fixed separator | - | -| `` | Harness name | `claude`, `cursor` | -| `` | Policy type | `builtins`, `system` | -| `` | Policy identifier | `dangerous_commands` | - -### Examples - -For a rulebook named `security-hardened`: - -```rego -# Good - follows convention -package cupcake.catalog.security_hardened.policies.claude.builtins.dangerous_commands -package cupcake.catalog.security_hardened.policies.cursor.system.evaluate -package cupcake.catalog.security_hardened.policies.opencode.builtins.risky_flags - -# Bad - missing prefix -package policies.claude.builtins.dangerous_commands - -# Bad - wrong prefix -package cupcake.security_hardened.policies.claude.builtins.dangerous_commands - -# Bad - doesn't match rulebook name -package cupcake.catalog.other_name.policies.claude.builtins.dangerous_commands -``` +| Component | Description | Example | +|-----------|-------------|---------| +| `cupcake.catalog` | Fixed prefix for all catalog files | - | +| `` | Rulebook name (hyphens → underscores) | `security_hardened` | +| `policies` / `helpers` / `system` | Directory type | - | +| `` or `` | File identifier | `dangerous_commands` | ## Name Conversion Rulebook names are converted to Rego-safe identifiers: -| manifest.yaml name | Rego namespace | -| ----------------------- | ----------------------- | -| `security-hardened` | `security_hardened` | +| manifest.yaml name | Rego namespace | +|--------------------|----------------| +| `security-hardened` | `security_hardened` | | `python-best-practices` | `python_best_practices` | -| `my-company-rules` | `my_company_rules` | +| `my-company-rules` | `my_company_rules` | The conversion: @@ -62,9 +42,33 @@ The conversion: - Keeps `_` as-is - Lowercase only +## Policy Namespace + +Policies in `policies//` use: + +``` +cupcake.catalog..policies. +``` + +Example: + +```rego +# policies/claude/dangerous_commands.rego +package cupcake.catalog.security_hardened.policies.dangerous_commands + +import rego.v1 + +deny contains decision if { + # policy logic +} +``` + +!!! note "Same package across harnesses" + Each harness has its own file with the **same package name**. The policies are compiled separately per-harness, so there's no conflict. + ## Helper Namespace -Shared helpers should use: +Shared helpers in `helpers/` use: ``` cupcake.catalog..helpers. @@ -73,34 +77,60 @@ cupcake.catalog..helpers. Example: ```rego +# helpers/commands.rego package cupcake.catalog.security_hardened.helpers.commands -# Helper functions here -get_command_name(input) = name { - # ... +import rego.v1 + +has_verb(command, verb) if { + pattern := concat("", ["(^|\\s)", verb, "(\\s|$)"]) + regex.match(pattern, command) } ``` -## System Policies +## System Namespace -The aggregation policy for each harness must be at: +The aggregation entrypoint in `system/` uses exactly: ``` -cupcake.catalog..policies..system +cupcake.catalog..system ``` Example: ```rego -# policies/claude/system/evaluate.rego -package cupcake.catalog.security_hardened.policies.claude.system - -import data.cupcake.catalog.security_hardened.policies.claude.builtins +# system/evaluate.rego +package cupcake.catalog.security_hardened.system + +import rego.v1 + +# METADATA +# scope: package +# custom: +# entrypoint: true + +evaluate := { + "halts": collect_verbs("halt"), + "denials": collect_verbs("deny"), + "blocks": collect_verbs("block"), + "asks": collect_verbs("ask"), + "allow_overrides": collect_verbs("allow_override"), + "add_context": collect_verbs("add_context"), +} -# Aggregate decisions from all builtin policies -decisions[decision] { - decision := builtins.dangerous_commands.deny +collect_verbs(verb_name) := result if { + verb_sets := [value | + walk(data.cupcake.catalog.security_hardened.policies, [path, value]) + path[count(path) - 1] == verb_name + ] + all_decisions := [decision | + some verb_set in verb_sets + some decision in verb_set + ] + result := all_decisions } + +default collect_verbs(_) := [] ``` ## Why Namespaces Matter @@ -119,11 +149,11 @@ Without namespaces, two rulebooks with a `dangerous_commands` policy would confl ```rego # Without namespaces - CONFLICT! -package policies.claude.builtins.dangerous_commands +package policies.dangerous_commands # With namespaces - No conflict -package cupcake.catalog.security_hardened.policies.claude.builtins.dangerous_commands -package cupcake.catalog.compliance_rules.policies.claude.builtins.dangerous_commands +package cupcake.catalog.security_hardened.policies.dangerous_commands +package cupcake.catalog.compliance_rules.policies.dangerous_commands ``` ### Discovery @@ -132,7 +162,7 @@ The namespace pattern allows Cupcake to: 1. Identify catalog policies automatically 2. Route evaluations to the correct policies -3. Generate proper imports in aggregation policies +3. Collect decisions from all policies via `walk()` ## Validation @@ -145,8 +175,11 @@ cupcake catalog lint ./my-rulebook Error examples: ``` -ERROR: Policy at policies/claude/builtins/example.rego has invalid namespace -'policies.claude.builtins.example'. Expected prefix 'cupcake.catalog.my_rulebook' +ERROR: Policy at policies/claude/example.rego has invalid namespace +'policies.example'. Expected prefix 'cupcake.catalog.my_rulebook.policies' + +ERROR: System file at system/evaluate.rego has invalid namespace +'cupcake.catalog.wrong_name.system'. Expected exactly 'cupcake.catalog.my_rulebook.system' ``` ## Importing Between Files @@ -154,17 +187,22 @@ ERROR: Policy at policies/claude/builtins/example.rego has invalid namespace Within a rulebook, use fully-qualified imports: ```rego -package cupcake.catalog.security_hardened.policies.claude.system - -# Import builtins -import data.cupcake.catalog.security_hardened.policies.claude.builtins +package cupcake.catalog.security_hardened.policies.dangerous_flags # Import helpers import data.cupcake.catalog.security_hardened.helpers.commands -# Use imported packages -decisions[decision] { - cmd := commands.get_command_name(input) - decision := builtins.dangerous_commands.deny +import rego.v1 + +deny contains decision if { + cmd := lower(input.tool_input.command) + commands.has_verb(cmd, "git") + commands.has_any_flag(cmd, {"--no-verify"}) + + decision := { + "rule_id": "SEC-002", + "reason": "Blocked --no-verify flag", + "severity": "HIGH", + } } ``` From 755560b7e58ccb86dcdcaac3f71f8f43828bf4cf Mon Sep 17 00:00:00 2001 From: captjt Date: Mon, 8 Dec 2025 13:26:35 -0500 Subject: [PATCH 2/7] feat: converge rulebook formatting with `init` and engine processing --- cupcake-cli/src/main.rs | 62 ++++++++------- cupcake-cli/tests/init_command_test.rs | 77 ++++++++++++------- cupcake-core/src/engine/compiler.rs | 26 ++++++- cupcake-core/src/engine/mod.rs | 46 ++++++++++- .../{policies => }/system/evaluate.rego | 0 .../claude => }/system/evaluate.rego | 0 fixtures/cursor/system/evaluate.rego | 52 ------------- fixtures/factory/system/evaluate.rego | 52 ------------- fixtures/opencode/system/evaluate.rego | 52 ------------- fixtures/{claude => }/system/evaluate.rego | 0 10 files changed, 152 insertions(+), 215 deletions(-) rename cupcake-py/test-fixtures/.cupcake/{policies => }/system/evaluate.rego (100%) rename cupcake-ts/test-fixtures/.cupcake/{policies/claude => }/system/evaluate.rego (100%) delete mode 100644 fixtures/cursor/system/evaluate.rego delete mode 100644 fixtures/factory/system/evaluate.rego delete mode 100644 fixtures/opencode/system/evaluate.rego rename fixtures/{claude => }/system/evaluate.rego (100%) diff --git a/cupcake-cli/src/main.rs b/cupcake-cli/src/main.rs index 305c93ab..ed8b1d6b 100644 --- a/cupcake-cli/src/main.rs +++ b/cupcake-cli/src/main.rs @@ -1476,19 +1476,28 @@ async fn init_project_config(harness: HarnessType, builtins: Option> harness_name ); - // Create the harness-specific directories - fs::create_dir_all(format!(".cupcake/policies/{harness_name}/system")) - .context("Failed to create harness system directory")?; + // Verify shared directories exist (system/ and helpers/ at root) + let system_dir = cupcake_dir.join("system"); + let helpers_dir = cupcake_dir.join("helpers"); + + if !system_dir.exists() { + eprintln!("Warning: .cupcake/system/ directory not found. Creating it..."); + fs::create_dir_all(&system_dir).context("Failed to create system directory")?; + fs::write(system_dir.join("evaluate.rego"), SYSTEM_EVALUATE_TEMPLATE) + .context("Failed to create system evaluate.rego file")?; + } + + if !helpers_dir.exists() { + eprintln!("Warning: .cupcake/helpers/ directory not found. Creating it..."); + fs::create_dir_all(&helpers_dir).context("Failed to create helpers directory")?; + fs::write(helpers_dir.join("commands.rego"), HELPERS_COMMANDS) + .context("Failed to create helpers/commands.rego file")?; + } + + // Create the harness-specific builtins directory fs::create_dir_all(format!(".cupcake/policies/{harness_name}/builtins")) .context("Failed to create harness builtins directory")?; - // Write the system evaluate policy for this harness - fs::write( - format!(".cupcake/policies/{harness_name}/system/evaluate.rego"), - SYSTEM_EVALUATE_TEMPLATE, - ) - .context("Failed to create system evaluate.rego file")?; - // Deploy builtin policies for this harness deploy_harness_builtins(&harness, harness_name)?; @@ -1499,9 +1508,9 @@ async fn init_project_config(harness: HarnessType, builtins: Option> // Fresh initialization info!("Initializing Cupcake project structure..."); - // Create only the specified harness directory (plus shared directories) - fs::create_dir_all(format!(".cupcake/policies/{harness_name}/system")) - .context("Failed to create harness system directory")?; + // Create shared directories at root level + fs::create_dir_all(".cupcake/system").context("Failed to create system directory")?; + fs::create_dir_all(".cupcake/helpers").context("Failed to create helpers directory")?; // Set Unix permissions on .cupcake directory (TOB-EQTY-LAB-CUPCAKE-4) #[cfg(unix)] @@ -1519,10 +1528,9 @@ async fn init_project_config(harness: HarnessType, builtins: Option> eprintln!("Warning: .cupcake directory permissions should be restricted manually on non-Unix systems"); } + // Create harness-specific builtins directory fs::create_dir_all(format!(".cupcake/policies/{harness_name}/builtins")) .context("Failed to create harness builtins directory")?; - fs::create_dir_all(".cupcake/policies/helpers") - .context("Failed to create helpers directory")?; fs::create_dir_all(".cupcake/signals").context("Failed to create signals directory")?; fs::create_dir_all(".cupcake/actions").context("Failed to create actions directory")?; @@ -1536,27 +1544,29 @@ async fn init_project_config(harness: HarnessType, builtins: Option> fs::write(".cupcake/rulebook.yml", rulebook_content) .context("Failed to create rulebook.yml file")?; - // Write the system evaluate policy for this harness only - fs::write( - format!(".cupcake/policies/{harness_name}/system/evaluate.rego"), - SYSTEM_EVALUATE_TEMPLATE, - ) - .context("Failed to create system evaluate.rego file")?; + // Write the system evaluate policy (shared at root level) + fs::write(".cupcake/system/evaluate.rego", SYSTEM_EVALUATE_TEMPLATE) + .context("Failed to create system evaluate.rego file")?; - // Write helper library (shared by all harnesses) - fs::write(".cupcake/policies/helpers/commands.rego", HELPERS_COMMANDS) + // Write helper library (shared at root level) + fs::write(".cupcake/helpers/commands.rego", HELPERS_COMMANDS) .context("Failed to create helpers/commands.rego file")?; // Deploy builtin policies for this harness only deploy_harness_builtins(&harness, harness_name)?; - // Write a simple example policy - fs::write(".cupcake/policies/example.rego", EXAMPLE_POLICY_TEMPLATE) - .context("Failed to create example policy file")?; + // Write a simple example policy in the harness-specific directory + fs::write( + format!(".cupcake/policies/{harness_name}/example.rego"), + EXAMPLE_POLICY_TEMPLATE, + ) + .context("Failed to create example policy file")?; println!("✅ Initialized Cupcake project in .cupcake/"); println!(" Harness: {harness_name}"); println!(" Configuration: .cupcake/rulebook.yml"); + println!(" System: .cupcake/system/"); + println!(" Helpers: .cupcake/helpers/"); println!(" Policies: .cupcake/policies/{harness_name}/"); println!(" Signals: .cupcake/signals/"); println!(" Actions: .cupcake/actions/"); diff --git a/cupcake-cli/tests/init_command_test.rs b/cupcake-cli/tests/init_command_test.rs index 26024dd7..4f4192b5 100644 --- a/cupcake-cli/tests/init_command_test.rs +++ b/cupcake-cli/tests/init_command_test.rs @@ -69,12 +69,13 @@ fn test_init_creates_correct_directory_structure() -> Result<()> { assert!(cupcake_dir.is_dir(), ".cupcake should be a directory"); // Verify all required subdirectories exist (only claude harness) + // New structure: system/ and helpers/ at root level, not per-harness let expected_dirs = vec![ + "system", + "helpers", "policies", "policies/claude", - "policies/claude/system", "policies/claude/builtins", - "policies/helpers", "signals", "actions", ]; @@ -115,12 +116,13 @@ fn test_init_creates_all_required_files() -> Result<()> { let cupcake_dir = project_path.join(".cupcake"); // List of all files that should be created (Claude harness only) + // New structure: system/ and helpers/ at root level, example.rego in harness dir let expected_files = vec![ "rulebook.yml", - "policies/example.rego", - "policies/helpers/commands.rego", - // Claude harness files - "policies/claude/system/evaluate.rego", + "system/evaluate.rego", + "helpers/commands.rego", + // Claude harness files (example + builtins) + "policies/claude/example.rego", "policies/claude/builtins/claude_code_always_inject_on_prompt.rego", "policies/claude/builtins/claude_code_enforce_full_file_read.rego", "policies/claude/builtins/git_block_no_verify.rego", @@ -199,8 +201,8 @@ fn test_rulebook_yml_content() -> Result<()> { #[test] fn test_system_evaluate_policy_content() -> Result<()> { let (_temp_dir, project_path) = run_init_in_temp_dir()?; - // Check Claude harness evaluate.rego - let evaluate_path = project_path.join(".cupcake/policies/claude/system/evaluate.rego"); + // Check evaluate.rego at root level (new structure) + let evaluate_path = project_path.join(".cupcake/system/evaluate.rego"); let content = fs::read_to_string(&evaluate_path)?; @@ -450,8 +452,8 @@ fn test_init_file_permissions() -> Result<()> { ".cupcake directory should be readable/writable/executable by owner" ); - // Check a policy file permissions - let policy_path = cupcake_dir.join("policies/claude/system/evaluate.rego"); + // Check a policy file permissions (system/evaluate.rego at root level) + let policy_path = cupcake_dir.join("system/evaluate.rego"); let file_metadata = fs::metadata(&policy_path)?; let file_perms = file_metadata.permissions(); let file_mode = file_perms.mode() & 0o777; @@ -544,20 +546,20 @@ fn test_correct_number_of_files_created() -> Result<()> { count_entries(&cupcake_dir, &mut file_count, &mut dir_count)?; - // For Claude harness only: + // For Claude harness only (new structure): // - 1 rulebook.yml // - 1 example.rego - // - 1 helper (commands.rego) - // - Claude: 1 evaluate.rego + 7 builtins = 8 files - // Total: 1 + 1 + 1 + 8 = 11 files + // - 1 helper (commands.rego) at root helpers/ + // - 1 evaluate.rego at root system/ + // - 7 Claude builtins + // Total: 1 + 1 + 1 + 1 + 7 = 11 files assert_eq!( file_count, 11, "Should have exactly 11 files (1 rulebook + 1 example + 1 helper + 1 evaluate + 7 builtins)" ); - // We should have exactly 7 directories: - // actions, signals, policies, policies/helpers, - // policies/claude, policies/claude/system, policies/claude/builtins + // We should have exactly 6 directories (new structure): + // system, helpers, policies, policies/claude, policies/claude/builtins, signals, actions assert_eq!(dir_count, 7, "Should have exactly 7 directories"); Ok(()) @@ -569,8 +571,11 @@ fn test_init_cursor_creates_cursor_only() -> Result<()> { let (_temp_dir, project_path) = run_init_with_harness("cursor")?; let cupcake_dir = project_path.join(".cupcake"); - // Cursor directory should exist - assert!(cupcake_dir.join("policies/cursor/system").exists()); + // Shared directories at root should exist + assert!(cupcake_dir.join("system").exists()); + assert!(cupcake_dir.join("helpers").exists()); + + // Cursor harness builtins directory should exist assert!(cupcake_dir.join("policies/cursor/builtins").exists()); // Other harness directories should NOT exist @@ -601,8 +606,11 @@ fn test_init_opencode_creates_opencode_only() -> Result<()> { let (_temp_dir, project_path) = run_init_with_harness("opencode")?; let cupcake_dir = project_path.join(".cupcake"); - // OpenCode directory should exist - assert!(cupcake_dir.join("policies/opencode/system").exists()); + // Shared directories at root should exist + assert!(cupcake_dir.join("system").exists()); + assert!(cupcake_dir.join("helpers").exists()); + + // OpenCode harness builtins directory should exist assert!(cupcake_dir.join("policies/opencode/builtins").exists()); // Other harness directories should NOT exist @@ -633,8 +641,11 @@ fn test_init_factory_creates_factory_only() -> Result<()> { let (_temp_dir, project_path) = run_init_with_harness("factory")?; let cupcake_dir = project_path.join(".cupcake"); - // Factory directory should exist - assert!(cupcake_dir.join("policies/factory/system").exists()); + // Shared directories at root should exist + assert!(cupcake_dir.join("system").exists()); + assert!(cupcake_dir.join("helpers").exists()); + + // Factory harness builtins directory should exist assert!(cupcake_dir.join("policies/factory/builtins").exists()); // Other harness directories should NOT exist @@ -697,18 +708,28 @@ fn test_init_can_add_second_harness() -> Result<()> { "Should indicate adding cursor harness to existing project" ); - // Both should now exist + // Shared directories should exist at root + assert!( + project_path.join(".cupcake/system").exists(), + "System directory should exist at root" + ); + assert!( + project_path.join(".cupcake/helpers").exists(), + "Helpers directory should exist at root" + ); + + // Both harness builtins should now exist assert!( project_path - .join(".cupcake/policies/claude/system") + .join(".cupcake/policies/claude/builtins") .exists(), - "Claude should still exist after adding cursor" + "Claude builtins should still exist after adding cursor" ); assert!( project_path - .join(".cupcake/policies/cursor/system") + .join(".cupcake/policies/cursor/builtins") .exists(), - "Cursor should exist after being added" + "Cursor builtins should exist after being added" ); // Verify cursor has correct builtins diff --git a/cupcake-core/src/engine/compiler.rs b/cupcake-core/src/engine/compiler.rs index 0a42b134..0d29fea6 100644 --- a/cupcake-core/src/engine/compiler.rs +++ b/cupcake-core/src/engine/compiler.rs @@ -113,14 +113,21 @@ pub async fn compile_policies( policies: &[PolicyUnit], opa_path_override: Option, ) -> Result> { - compile_policies_with_namespace(policies, "cupcake.system", opa_path_override).await + compile_policies_with_namespace(policies, "cupcake.system", opa_path_override, None).await } /// Compile policies with a specific namespace for the entrypoint +/// +/// # Arguments +/// * `policies` - List of policy units to compile +/// * `namespace` - Namespace for the entrypoint (e.g., "cupcake.system") +/// * `opa_path_override` - Optional path to OPA binary +/// * `cupcake_dir` - Optional path to .cupcake directory for locating helpers at root level pub async fn compile_policies_with_namespace( policies: &[PolicyUnit], namespace: &str, opa_path_override: Option, + cupcake_dir: Option<&Path>, ) -> Result> { if policies.is_empty() { bail!("No policies to compile"); @@ -161,7 +168,22 @@ pub async fn compile_policies_with_namespace( debug!("Policies root: {:?}", policies_root); // Copy helpers directory if it exists (required by refactored builtins) - let helpers_src = policies_root.join("helpers"); + // New structure: helpers at .cupcake/helpers/ (cupcake_dir/helpers) + // Fallback: helpers at .cupcake/policies/helpers/ (policies_root/helpers) for catalog overlays + let helpers_src = if let Some(cupcake) = cupcake_dir { + let root_helpers = cupcake.join("helpers"); + if root_helpers.exists() && root_helpers.is_dir() { + debug!("Using helpers from cupcake root: {:?}", root_helpers); + Some(root_helpers) + } else { + debug!("No helpers at cupcake root, checking policies root"); + None + } + } else { + None + } + .unwrap_or_else(|| policies_root.join("helpers")); + if helpers_src.exists() && helpers_src.is_dir() { debug!("Copying helpers directory: {:?}", helpers_src); let helpers_dest = temp_path.join("helpers"); diff --git a/cupcake-core/src/engine/mod.rs b/cupcake-core/src/engine/mod.rs index d6ad6f5a..e24a6cb2 100644 --- a/cupcake-core/src/engine/mod.rs +++ b/cupcake-core/src/engine/mod.rs @@ -492,7 +492,23 @@ impl Engine { harness_subdir ); - // Step 2: Parse selectors and build policy units + // Step 1b: Scan system directory at cupcake root for shared system entrypoint + let system_dir = self.paths.cupcake_dir.join("system"); + let system_files = if system_dir.exists() && system_dir.is_dir() { + info!("Scanning system directory: {:?}", system_dir); + scanner::scan_policies(&system_dir) + .await + .unwrap_or_else(|e| { + warn!("Failed to scan system directory: {}", e); + Vec::new() + }) + } else { + debug!("No system directory found at {:?}", system_dir); + Vec::new() + }; + info!("Found {} system policy files", system_files.len()); + + // Step 2: Parse selectors and build policy units from harness policies for path in policy_files { match self.parse_policy(&path).await { Ok(unit) => { @@ -509,6 +525,22 @@ impl Engine { } } + // Step 2b: Parse system policies + for path in system_files { + match self.parse_policy(&path).await { + Ok(unit) => { + info!( + "Successfully parsed system policy: {} from {:?}", + unit.package_name, path + ); + self.policies.push(unit); + } + Err(e) => { + error!("Failed to parse system policy at {:?}: {}", path, e); + } + } + } + if self.policies.is_empty() { warn!("No valid policies found in directory"); return Ok(()); @@ -521,8 +553,14 @@ impl Engine { // No entrypoint mapping needed - Hybrid Model uses single aggregation entrypoint // Step 4: Compile unified WASM module with OPA path from CLI - let wasm_bytes = - compiler::compile_policies(&self.policies, self.config.opa_path.clone()).await?; + // Pass cupcake_dir for helpers resolution at root level + let wasm_bytes = compiler::compile_policies_with_namespace( + &self.policies, + "cupcake.system", + self.config.opa_path.clone(), + Some(&self.paths.cupcake_dir), + ) + .await?; info!( "Successfully compiled unified WASM module ({} bytes)", wasm_bytes.len() @@ -669,10 +707,12 @@ impl Engine { ); // Compile global policies to WASM with OPA path from CLI + // Pass global_root for helpers resolution at root level let global_wasm_bytes = compiler::compile_policies_with_namespace( &self.global_policies, "cupcake.global.system", self.config.opa_path.clone(), + self.paths.global_root.as_deref(), ) .await?; info!( diff --git a/cupcake-py/test-fixtures/.cupcake/policies/system/evaluate.rego b/cupcake-py/test-fixtures/.cupcake/system/evaluate.rego similarity index 100% rename from cupcake-py/test-fixtures/.cupcake/policies/system/evaluate.rego rename to cupcake-py/test-fixtures/.cupcake/system/evaluate.rego diff --git a/cupcake-ts/test-fixtures/.cupcake/policies/claude/system/evaluate.rego b/cupcake-ts/test-fixtures/.cupcake/system/evaluate.rego similarity index 100% rename from cupcake-ts/test-fixtures/.cupcake/policies/claude/system/evaluate.rego rename to cupcake-ts/test-fixtures/.cupcake/system/evaluate.rego diff --git a/fixtures/cursor/system/evaluate.rego b/fixtures/cursor/system/evaluate.rego deleted file mode 100644 index cfba615b..00000000 --- a/fixtures/cursor/system/evaluate.rego +++ /dev/null @@ -1,52 +0,0 @@ -package cupcake.system - -import rego.v1 - -# METADATA -# scope: document -# title: System Aggregation Entrypoint for Hybrid Model -# authors: ["Cupcake Engine"] -# custom: -# description: "Aggregates all decision verbs from policies into a DecisionSet" -# entrypoint: true -# routing: -# required_events: [] -# required_tools: [] - -# The single entrypoint for the Hybrid Model. -# This uses the `walk()` built-in to recursively traverse data.cupcake.policies, -# automatically discovering and aggregating all decision verbs from all loaded -# policies, regardless of their package name or nesting depth. -evaluate := decision_set if { - decision_set := { - "halts": collect_verbs("halt"), - "denials": collect_verbs("deny"), - "blocks": collect_verbs("block"), - "asks": collect_verbs("ask"), - "modifications": collect_verbs("modify"), - "add_context": collect_verbs("add_context") - } -} - -# Helper function to collect all decisions for a specific verb type. -# Uses walk() to recursively find all instances of the verb across -# the entire policy hierarchy under data.cupcake.policies. -collect_verbs(verb_name) := result if { - # Collect all matching verb sets from the policy tree - verb_sets := [value | - walk(data.cupcake.policies, [path, value]) - path[count(path) - 1] == verb_name - ] - - # Flatten all sets into a single array - # Since Rego v1 decision verbs are sets, we need to convert to arrays - all_decisions := [decision | - some verb_set in verb_sets - some decision in verb_set - ] - - result := all_decisions -} - -# Default to empty arrays if no decisions found -default collect_verbs(_) := [] diff --git a/fixtures/factory/system/evaluate.rego b/fixtures/factory/system/evaluate.rego deleted file mode 100644 index 3a6d5152..00000000 --- a/fixtures/factory/system/evaluate.rego +++ /dev/null @@ -1,52 +0,0 @@ -package cupcake.system - -import rego.v1 - -# METADATA -# scope: document -# title: System Aggregation Entrypoint for Hybrid Model -# authors: ["Cupcake Engine"] -# custom: -# description: "Aggregates all decision verbs from policies into a DecisionSet" -# entrypoint: true -# routing: -# required_events: [] -# required_tools: [] - -# The single entrypoint for the Hybrid Model. -# This uses the `walk()` built-in to recursively traverse data.cupcake.policies, -# automatically discovering and aggregating all decision verbs from all loaded -# policies, regardless of their package name or nesting depth. -evaluate := decision_set if { - decision_set := { - "halts": collect_verbs("halt"), - "denials": collect_verbs("deny"), - "blocks": collect_verbs("block"), - "asks": collect_verbs("ask"), - "modifications": collect_verbs("modify"), - "add_context": collect_verbs("add_context"), - } -} - -# Helper function to collect all decisions for a specific verb type. -# Uses walk() to recursively find all instances of the verb across -# the entire policy hierarchy under data.cupcake.policies. -collect_verbs(verb_name) := result if { - # Collect all matching verb sets from the policy tree - verb_sets := [value | - walk(data.cupcake.policies, [path, value]) - path[count(path) - 1] == verb_name - ] - - # Flatten all sets into a single array - # Since Rego v1 decision verbs are sets, we need to convert to arrays - all_decisions := [decision | - some verb_set in verb_sets - some decision in verb_set - ] - - result := all_decisions -} - -# Default to empty arrays if no decisions found -default collect_verbs(_) := [] diff --git a/fixtures/opencode/system/evaluate.rego b/fixtures/opencode/system/evaluate.rego deleted file mode 100644 index 3a6d5152..00000000 --- a/fixtures/opencode/system/evaluate.rego +++ /dev/null @@ -1,52 +0,0 @@ -package cupcake.system - -import rego.v1 - -# METADATA -# scope: document -# title: System Aggregation Entrypoint for Hybrid Model -# authors: ["Cupcake Engine"] -# custom: -# description: "Aggregates all decision verbs from policies into a DecisionSet" -# entrypoint: true -# routing: -# required_events: [] -# required_tools: [] - -# The single entrypoint for the Hybrid Model. -# This uses the `walk()` built-in to recursively traverse data.cupcake.policies, -# automatically discovering and aggregating all decision verbs from all loaded -# policies, regardless of their package name or nesting depth. -evaluate := decision_set if { - decision_set := { - "halts": collect_verbs("halt"), - "denials": collect_verbs("deny"), - "blocks": collect_verbs("block"), - "asks": collect_verbs("ask"), - "modifications": collect_verbs("modify"), - "add_context": collect_verbs("add_context"), - } -} - -# Helper function to collect all decisions for a specific verb type. -# Uses walk() to recursively find all instances of the verb across -# the entire policy hierarchy under data.cupcake.policies. -collect_verbs(verb_name) := result if { - # Collect all matching verb sets from the policy tree - verb_sets := [value | - walk(data.cupcake.policies, [path, value]) - path[count(path) - 1] == verb_name - ] - - # Flatten all sets into a single array - # Since Rego v1 decision verbs are sets, we need to convert to arrays - all_decisions := [decision | - some verb_set in verb_sets - some decision in verb_set - ] - - result := all_decisions -} - -# Default to empty arrays if no decisions found -default collect_verbs(_) := [] diff --git a/fixtures/claude/system/evaluate.rego b/fixtures/system/evaluate.rego similarity index 100% rename from fixtures/claude/system/evaluate.rego rename to fixtures/system/evaluate.rego From 7569d2db3206fe5f7226406d6c8e175f92e5627a Mon Sep 17 00:00:00 2001 From: captjt Date: Mon, 8 Dec 2025 13:26:56 -0500 Subject: [PATCH 3/7] docs: include standardized format for rulebooks to mirror catalog structure --- docs/agents/opencode/installation.md | 63 +++++-------------- docs/agents/opencode/quickstart.md | 51 +++++---------- .../getting-started/using-rulebooks.md | 2 +- docs/docs/enterprise/global-config.md | 14 ++--- docs/docs/reference/policies/custom.md | 49 ++++++++------- docs/docs/tutorials/claude-code.md | 11 ++-- docs/docs/tutorials/cursor.md | 12 ++-- docs/docs/tutorials/opencode.md | 4 +- 8 files changed, 84 insertions(+), 122 deletions(-) diff --git a/docs/agents/opencode/installation.md b/docs/agents/opencode/installation.md index f65d50eb..1bdf2d9e 100644 --- a/docs/agents/opencode/installation.md +++ b/docs/agents/opencode/installation.md @@ -100,51 +100,22 @@ cupcake init --harness opencode This creates: -- `.cupcake/rulebook.yml` - Configuration file -- `.cupcake/policies/` - Policy directory -- `.cupcake/signals/` - Signal definitions -- `.cupcake/actions/` - Action definitions - -## Creating the System Evaluator - -The system evaluator is required for policy compilation: - -```bash -mkdir -p .cupcake/policies/opencode/system - -cat > .cupcake/policies/opencode/system/evaluate.rego << 'EOF' -package cupcake.system - -import rego.v1 - -evaluate := decision_set if { - decision_set := { - "halts": collect_verbs("halt"), - "denials": collect_verbs("deny"), - "blocks": collect_verbs("block"), - "asks": collect_verbs("ask"), - "modifications": collect_verbs("modify"), - "add_context": collect_verbs("add_context") - } -} - -collect_verbs(verb_name) := result if { - verb_sets := [value | - walk(data.cupcake.policies, [path, value]) - path[count(path) - 1] == verb_name - ] - - all_decisions := [decision | - some verb_set in verb_sets - some decision in verb_set - ] - - result := all_decisions -} - -default collect_verbs(_) := [] -EOF ``` +.cupcake/ +├── rulebook.yml # Configuration file +├── system/ +│ └── evaluate.rego # System entrypoint (auto-generated) +├── helpers/ +│ └── commands.rego # Shared helper functions +└── policies/ + └── opencode/ + └── builtins/ # Built-in policies + └── *.rego +├── signals/ # Signal definitions +└── actions/ # Action definitions +``` + +The system evaluator at `.cupcake/system/evaluate.rego` is automatically created by `cupcake init`. ## Adding Policies @@ -233,8 +204,8 @@ opencode - [ ] Plugin built: `ls cupcake-plugins/opencode/dist/` - [ ] Plugin installed: `ls .opencode/plugins/cupcake/` or `~/.config/opencode/plugins/cupcake/` - [ ] Cupcake initialized: `ls .cupcake/` -- [ ] System evaluator exists: `ls .cupcake/policies/opencode/system/evaluate.rego` -- [ ] Policies exist: `ls .cupcake/policies/opencode/*.rego` +- [ ] System evaluator exists: `ls .cupcake/system/evaluate.rego` +- [ ] Policies exist: `ls .cupcake/policies/opencode/builtins/*.rego` - [ ] CLI test passes (deny for --no-verify) - [ ] OpenCode integration works diff --git a/docs/agents/opencode/quickstart.md b/docs/agents/opencode/quickstart.md index 768b4934..a46e28f4 100644 --- a/docs/agents/opencode/quickstart.md +++ b/docs/agents/opencode/quickstart.md @@ -24,42 +24,21 @@ cd /path/to/your/project # Initialize Cupcake for OpenCode cupcake init --harness opencode +``` -# Create OpenCode policy directory and system evaluator -mkdir -p .cupcake/policies/opencode/system - -cat > .cupcake/policies/opencode/system/evaluate.rego << 'EOF' -package cupcake.system - -import rego.v1 - -evaluate := decision_set if { - decision_set := { - "halts": collect_verbs("halt"), - "denials": collect_verbs("deny"), - "blocks": collect_verbs("block"), - "asks": collect_verbs("ask"), - "modifications": collect_verbs("modify"), - "add_context": collect_verbs("add_context") - } -} - -collect_verbs(verb_name) := result if { - verb_sets := [value | - walk(data.cupcake.policies, [path, value]) - path[count(path) - 1] == verb_name - ] - - all_decisions := [decision | - some verb_set in verb_sets - some decision in verb_set - ] - - result := all_decisions -} +This creates the following structure: -default collect_verbs(_) := [] -EOF +``` +.cupcake/ +├── rulebook.yml # Configuration +├── system/ +│ └── evaluate.rego # System entrypoint (auto-generated) +├── helpers/ +│ └── commands.rego # Shared helper functions +└── policies/ + └── opencode/ + └── builtins/ # Built-in policies + └── *.rego ``` ## Step 3: Add a Policy @@ -214,8 +193,8 @@ Or specify the full path in plugin config (`.cupcake/opencode.json`): Check that: -1. `.cupcake/policies/opencode/` directory exists -2. `system/evaluate.rego` file exists +1. `.cupcake/system/evaluate.rego` file exists (at root level) +2. `.cupcake/policies/opencode/` directory exists 3. Your policy has correct routing metadata Debug with: diff --git a/docs/docs/catalog/getting-started/using-rulebooks.md b/docs/docs/catalog/getting-started/using-rulebooks.md index 22e52047..969c2448 100644 --- a/docs/docs/catalog/getting-started/using-rulebooks.md +++ b/docs/docs/catalog/getting-started/using-rulebooks.md @@ -130,7 +130,7 @@ Catalog rulebooks include policies for multiple harnesses. Only policies matchin 1. Verify the rulebook is installed: `cupcake catalog list` 2. Check that policies exist for your harness: `ls .cupcake/catalog//policies/` -3. Ensure the rulebook has a `system/evaluate.rego` for your harness +3. Ensure the rulebook has a `system/evaluate.rego` at the rulebook root level ### Namespace Conflicts diff --git a/docs/docs/enterprise/global-config.md b/docs/docs/enterprise/global-config.md index df30a5dd..d48470ae 100644 --- a/docs/docs/enterprise/global-config.md +++ b/docs/docs/enterprise/global-config.md @@ -4,23 +4,23 @@ Global configuration enables organization-wide policy enforcement across all pro ## Location -| Platform | Path | -|----------|------| -| Linux | `~/.config/cupcake/` | -| macOS | `~/Library/Application Support/cupcake/` | -| Windows | `%APPDATA%\cupcake\` | +| Platform | Path | +| -------- | ---------------------------------------- | +| Linux | `~/.config/cupcake/` | +| macOS | `~/Library/Application Support/cupcake/` | +| Windows | `%APPDATA%\cupcake\` | ## Directory Structure ``` cupcake/ ├── rulebook.yml +├── system/ # Shared system entrypoint +│ └── evaluate.rego ├── policies/ │ ├── claude/ -│ │ ├── system/ │ │ └── builtins/ │ └── cursor/ -│ ├── system/ │ └── builtins/ ├── signals/ └── actions/ diff --git a/docs/docs/reference/policies/custom.md b/docs/docs/reference/policies/custom.md index 668f6288..7ad91f5c 100644 --- a/docs/docs/reference/policies/custom.md +++ b/docs/docs/reference/policies/custom.md @@ -56,30 +56,30 @@ The metadata tells Cupcake when to evaluate your policy: ### Available Events -| Event | Description | -| ------------------ | ---------------------------------------- | -| `PreToolUse` | Before a tool executes | -| `PostToolUse` | After a tool executes | -| `UserPromptSubmit` | Before sending prompt to LLM | -| `SessionStart` | When session starts or resumes | -| `SessionEnd` | When session ends | -| `Stop` | When agent stops | -| `SubagentStop` | When subagent (Task tool) completes | -| `PreCompact` | Before memory compaction | -| `Notification` | On agent notifications | +| Event | Description | +| ------------------ | ----------------------------------- | +| `PreToolUse` | Before a tool executes | +| `PostToolUse` | After a tool executes | +| `UserPromptSubmit` | Before sending prompt to LLM | +| `SessionStart` | When session starts or resumes | +| `SessionEnd` | When session ends | +| `Stop` | When agent stops | +| `SubagentStop` | When subagent (Task tool) completes | +| `PreCompact` | Before memory compaction | +| `Notification` | On agent notifications | ## Decision Verbs Policies emit decisions using these verbs (in priority order): -| Verb | Priority | Effect | Supported Events | -| ---------------- | -------- | ----------------------------------------- | ---------------- | -| `halt` | Highest | Block and stop the session immediately | All | -| `deny` | High | Block the action (policy violation) | All | -| `block` | High | Block the action (same priority as deny) | All | -| `ask` | Medium | Prompt user for confirmation | Tool events | -| `modify` | Medium | Allow with modified input | PreToolUse only | -| `add_context` | N/A | Inject context into the prompt | Prompt events | +| Verb | Priority | Effect | Supported Events | +| ------------- | -------- | ---------------------------------------- | ---------------- | +| `halt` | Highest | Block and stop the session immediately | All | +| `deny` | High | Block the action (policy violation) | All | +| `block` | High | Block the action (same priority as deny) | All | +| `ask` | Medium | Prompt user for confirmation | Tool events | +| `modify` | Medium | Allow with modified input | PreToolUse only | +| `add_context` | N/A | Inject context into the prompt | Prompt events | ### Deny Example @@ -176,17 +176,22 @@ Place policies in the harness-specific directory: ``` .cupcake/ +├── rulebook.yml +├── system/ # System aggregation entrypoint +│ └── evaluate.rego ├── policies/ │ ├── claude/ # Claude Code policies -│ │ ├── my_policy.rego -│ │ └── another.rego +│ │ ├── builtins/ # Built-in policies +│ │ └── my_policy.rego │ ├── cursor/ # Cursor policies +│ │ ├── builtins/ │ │ └── cursor_rules.rego │ ├── factory/ # Factory AI policies +│ │ ├── builtins/ │ │ └── factory_rules.rego │ └── opencode/ # OpenCode policies +│ ├── builtins/ │ └── opencode_rules.rego -└── rulebook.yml ``` ## Testing Policies diff --git a/docs/docs/tutorials/claude-code.md b/docs/docs/tutorials/claude-code.md index 8c4077f8..e8c1fbe5 100644 --- a/docs/docs/tutorials/claude-code.md +++ b/docs/docs/tutorials/claude-code.md @@ -44,10 +44,13 @@ This runs `cupcake init`, and some scaffolding to create: ``` .cupcake/ ├── rulebook.yml # Default configuration - ├── policies/ # Rego policies - │ └── builtins/ # Built-in policies (developer productivity, security) - ├── signals/ # External data providers - └── actions/ # Automated response scripts + ├── system/ # System aggregation entrypoint + │ └── evaluate.rego + ├── policies/ # Rego policies + │ └── claude/ + │ └── builtins/ # Built-in policies (developer productivity, security) + ├── signals/ # External data providers + └── actions/ # Automated response scripts .claude/settings.json # Claude Code integration (hooks config) ``` diff --git a/docs/docs/tutorials/cursor.md b/docs/docs/tutorials/cursor.md index e5971de2..43836764 100644 --- a/docs/docs/tutorials/cursor.md +++ b/docs/docs/tutorials/cursor.md @@ -43,11 +43,13 @@ This runs `cupcake init --harness cursor`, and some scaffolding to create: ``` .cupcake/ ├── rulebook.yml # Default configuration - ├── policies/ # Rego policies - │ ├── cursor/ # Cursor-specific policies - │ └── builtins/ # Built-in security policies - ├── signals/ # External data providers - └── actions/ # Automated response scripts + ├── system/ # System aggregation entrypoint + │ └── evaluate.rego + ├── policies/ # Rego policies + │ └── cursor/ + │ └── builtins/ # Built-in security policies + ├── signals/ # External data providers + └── actions/ # Automated response scripts ~/.cursor/hooks.json # Cursor hooks integration (global) ``` diff --git a/docs/docs/tutorials/opencode.md b/docs/docs/tutorials/opencode.md index e61f6d1a..fcf8bef7 100644 --- a/docs/docs/tutorials/opencode.md +++ b/docs/docs/tutorials/opencode.md @@ -34,8 +34,10 @@ This creates: ``` .cupcake/ ├── rulebook.yml # Default configuration + ├── system/ # System aggregation entrypoint + │ └── evaluate.rego ├── policies/ # Rego policies - │ └── opencode/ # OpenCode-specific policies + │ └── opencode/ │ └── builtins/ # Built-in security policies ├── signals/ # External data providers └── actions/ # Automated response scripts From c91aa33696c1644c598b27b347e187122a4b3d81 Mon Sep 17 00:00:00 2001 From: captjt Date: Mon, 8 Dec 2025 13:31:06 -0500 Subject: [PATCH 4/7] docs: regen latest cli command assets --- docs/docs/assets/cupcake-help.cast | 4 +- docs/docs/assets/cupcake-init.cast | 42 ++++++------ docs/docs/assets/cupcake-inspect.cast | 26 +++---- docs/docs/assets/cupcake-trust.cast | 12 ++-- docs/docs/assets/cupcake-verify.cast | 99 ++++++++++++++------------- 5 files changed, 92 insertions(+), 91 deletions(-) diff --git a/docs/docs/assets/cupcake-help.cast b/docs/docs/assets/cupcake-help.cast index d63e333e..1b80d371 100644 --- a/docs/docs/assets/cupcake-help.cast +++ b/docs/docs/assets/cupcake-help.cast @@ -1,4 +1,4 @@ -{"version": 3, "term": {"cols": 100, "rows": 30}, "timestamp": 1764772238, "idle_time_limit": 2, "title": "Cupcake CLI Help"} +{"version": 3, "term": {"cols": 100, "rows": 30}, "timestamp": 1765218523, "idle_time_limit": 2, "title": "Cupcake CLI Help"} [1.5, "o", "$ cupcake --help\r\n"] [0.5, "o", "Governance and augmentation orchestrator for agentic AI systems\r\n"] [0.075, "o", "\r\n"] @@ -6,12 +6,14 @@ [0.075, "o", "\r\n"] [0.4, "o", "Commands:\r\n"] [0.15, "o", " eval Evaluate a hook event against policies\r\n"] +[0.15, "o", " watchdog Evaluate an event using Watchdog (LLM-as-judge) directly\r\n"] [0.15, "o", " verify Verify the engine configuration and policies\r\n"] [0.15, "o", " init Initialize a new Cupcake project\r\n"] [0.15, "o", " trust Manage script trust and integrity verification\r\n"] [0.15, "o", " validate Validate policies for Cupcake requirements and best practices\r\n"] [0.15, "o", " inspect Inspect policies to show metadata and routing information\r\n"] [0.15, "o", " onboard Launch the interactive onboarding wizard to convert rule files into Cupcake policies\r\n"] +[0.15, "o", " catalog Browse and manage rulebooks from the Cupcake Catalog\r\n"] [0.15, "o", " help Print this message or the help of the given subcommand(s)\r\n"] [0.075, "o", "\r\n"] [0.4, "o", "Options:\r\n"] diff --git a/docs/docs/assets/cupcake-init.cast b/docs/docs/assets/cupcake-init.cast index cc720fbe..e3c18083 100644 --- a/docs/docs/assets/cupcake-init.cast +++ b/docs/docs/assets/cupcake-init.cast @@ -1,13 +1,15 @@ -{"version": 3, "term": {"cols": 100, "rows": 30}, "timestamp": 1764772238, "idle_time_limit": 2, "title": "Cupcake Init Command"} +{"version": 3, "term": {"cols": 100, "rows": 30}, "timestamp": 1765218524, "idle_time_limit": 2, "title": "Cupcake Init Command"} [1.0, "o", "$ ls -la\r\n"] [0.5, "o", "total 0\r\n"] -[0.4, "o", "drwx------@ 2 jordan staff 64 Dec 3 09:30 .\r\n"] -[0.4, "o", "drwx------@ 2163 jordan staff 69216 Dec 3 09:30 ..\r\n"] +[0.4, "o", "drwx------@ 2 jordan staff 64 Dec 8 13:28 .\r\n"] +[0.4, "o", "drwx------@ 195 jordan staff 6240 Dec 8 13:28 ..\r\n"] [0.075, "o", "\r\n"] [2.5, "o", "$ cupcake init --harness claude\r\n"] [0.5, "o", "\u2705 Initialized Cupcake project in .cupcake/\r\n"] [0.15, "o", " Harness: claude\r\n"] [0.15, "o", " Configuration: .cupcake/rulebook.yml\r\n"] +[0.15, "o", " System: .cupcake/system/\r\n"] +[0.15, "o", " Helpers: .cupcake/helpers/\r\n"] [0.15, "o", " Policies: .cupcake/policies/claude/\r\n"] [0.15, "o", " Signals: .cupcake/signals/\r\n"] [0.15, "o", " Actions: .cupcake/actions/\r\n"] @@ -20,29 +22,29 @@ [0.15, "o", " - Added SessionStart hook for initial context\r\n"] [0.075, "o", "\r\n"] [0.15, "o", " Claude Code will now evaluate all tool uses and prompts against your Cupcake policies.\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.775134Z\u001b[0m \u001b[32m INFO\u001b[0m Initializing Cupcake project structure...\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.775454Z\u001b[0m \u001b[32m INFO\u001b[0m .cupcake directory permissions set to 0o700 (owner-only access)\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.337895Z\u001b[0m \u001b[32m INFO\u001b[0m Initializing Cupcake project structure...\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.338150Z\u001b[0m \u001b[32m INFO\u001b[0m .cupcake directory permissions set to 0o700 (owner-only access)\r\n"] [0.075, "o", "\r\n"] [3.5, "o", "$ tree -a .cupcake\r\n"] [0.5, "o", ".cupcake\r\n"] [0.15, "o", "\u251c\u2500\u2500 actions\r\n"] +[0.15, "o", "\u251c\u2500\u2500 helpers\r\n"] +[0.15, "o", "\u2502\u00a0\u00a0 \u2514\u2500\u2500 commands.rego\r\n"] [0.15, "o", "\u251c\u2500\u2500 policies\r\n"] -[0.15, "o", "\u2502\u00a0\u00a0 \u251c\u2500\u2500 claude\r\n"] -[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 builtins\r\n"] -[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 claude_code_always_inject_on_prompt.rego\r\n"] -[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 claude_code_enforce_full_file_read.rego\r\n"] -[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 git_block_no_verify.rego\r\n"] -[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 git_pre_check.rego\r\n"] -[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 post_edit_check.rego\r\n"] -[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 protected_paths.rego\r\n"] -[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 rulebook_security_guardrails.rego\r\n"] -[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 system\r\n"] -[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 evaluate.rego\r\n"] -[0.15, "o", "\u2502\u00a0\u00a0 \u251c\u2500\u2500 example.rego\r\n"] -[0.15, "o", "\u2502\u00a0\u00a0 \u2514\u2500\u2500 helpers\r\n"] -[0.15, "o", "\u2502\u00a0\u00a0 \u2514\u2500\u2500 commands.rego\r\n"] +[0.15, "o", "\u2502\u00a0\u00a0 \u2514\u2500\u2500 claude\r\n"] +[0.15, "o", "\u2502\u00a0\u00a0 \u251c\u2500\u2500 builtins\r\n"] +[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 claude_code_always_inject_on_prompt.rego\r\n"] +[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 claude_code_enforce_full_file_read.rego\r\n"] +[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 git_block_no_verify.rego\r\n"] +[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 git_pre_check.rego\r\n"] +[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 post_edit_check.rego\r\n"] +[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 protected_paths.rego\r\n"] +[0.15, "o", "\u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 rulebook_security_guardrails.rego\r\n"] +[0.15, "o", "\u2502\u00a0\u00a0 \u2514\u2500\u2500 example.rego\r\n"] [0.15, "o", "\u251c\u2500\u2500 rulebook.yml\r\n"] -[0.15, "o", "\u2514\u2500\u2500 signals\r\n"] +[0.15, "o", "\u251c\u2500\u2500 signals\r\n"] +[0.15, "o", "\u2514\u2500\u2500 system\r\n"] +[0.15, "o", " \u2514\u2500\u2500 evaluate.rego\r\n"] [0.075, "o", "\r\n"] [0.15, "o", "8 directories, 11 files\r\n"] [0.075, "o", "\r\n"] diff --git a/docs/docs/assets/cupcake-inspect.cast b/docs/docs/assets/cupcake-inspect.cast index 8df090f1..f379694c 100644 --- a/docs/docs/assets/cupcake-inspect.cast +++ b/docs/docs/assets/cupcake-inspect.cast @@ -1,6 +1,6 @@ -{"version": 3, "term": {"cols": 100, "rows": 30}, "timestamp": 1764772238, "idle_time_limit": 2, "title": "Cupcake Inspect Command"} +{"version": 3, "term": {"cols": 100, "rows": 30}, "timestamp": 1765218524, "idle_time_limit": 2, "title": "Cupcake Inspect Command"} [1.0, "o", "$ cupcake inspect\r\n"] -[0.5, "o", "Found 10 policies\r\n"] +[0.5, "o", "Found 8 policies\r\n"] [0.075, "o", "\r\n"] [0.15, "o", "Policy: .cupcake/policies/claude/builtins/claude_code_enforce_full_file_read.rego\r\n"] [0.15, "o", " Package: cupcake.policies.builtins.claude_code_enforce_full_file_read\r\n"] @@ -57,25 +57,17 @@ [0.15, "o", " Title: Rulebook Security Guardrails - Builtin Policy\r\n"] [0.15, "o", " Authors: Cupcake Builtins\r\n"] [0.075, "o", "\r\n"] -[0.15, "o", "Policy: .cupcake/policies/claude/system/evaluate.rego\r\n"] -[0.15, "o", " Package: cupcake.system\r\n"] -[0.15, "o", " Title: System Aggregation Entrypoint for Hybrid Model\r\n"] -[0.15, "o", " Authors: Cupcake Engine\r\n"] -[0.075, "o", "\r\n"] -[0.15, "o", "Policy: .cupcake/policies/example.rego\r\n"] +[0.15, "o", "Policy: .cupcake/policies/claude/example.rego\r\n"] [0.15, "o", " Package: cupcake.policies.example\r\n"] [0.15, "o", " Required Events: PreToolUse\r\n"] [0.15, "o", " Required Tools: Bash\r\n"] [0.15, "o", " Title: Example Policy\r\n"] [0.075, "o", "\r\n"] -[0.15, "o", "Policy: .cupcake/policies/helpers/commands.rego\r\n"] -[0.15, "o", " Package: cupcake.helpers.commands\r\n"] -[0.075, "o", "\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.814462Z\u001b[0m \u001b[32m INFO\u001b[0m Inspecting policies in directory: \".cupcake/policies\"\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.814634Z\u001b[0m \u001b[32m INFO\u001b[0m Found 10 policy files\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.379045Z\u001b[0m \u001b[32m INFO\u001b[0m Inspecting policies in directory: \".cupcake/policies\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.379137Z\u001b[0m \u001b[32m INFO\u001b[0m Found 8 policy files\r\n"] [0.075, "o", "\r\n"] [4.5, "o", "$ cupcake inspect --table\r\n"] -[0.5, "o", "Found 10 policies\r\n"] +[0.5, "o", "Found 8 policies\r\n"] [0.075, "o", "\r\n"] [0.15, "o", "\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\r\n"] [0.15, "o", "\u2502 Package \u2502 Events \u2502 Tools \u2502 Title \u2502 Type \u2502\r\n"] @@ -87,11 +79,9 @@ [0.15, "o", "\u2502 policies.builtins.post_edit_check \u2502 PostToolUse \u2502 Edit, Write, MultiEdit, NotebookEdit \u2502 Post Edit Check - Builtin Policy \u2502 builtin \u2502\r\n"] [0.15, "o", "\u2502 policies.builtins.claude_code_always_inject_on_prompt \u2502 UserPromptSubmit \u2502 \u2502 Always Inject On Prompt - Builtin Policy \u2502 builtin \u2502\r\n"] [0.15, "o", "\u2502 policies.builtins.rulebook_security_guardrails \u2502 PreToolUse \u2502 Edit, Write, MultiEdit, NotebookEdit, Read, Grep, Glob, Bash, Task, WebFetch \u2502 Rulebook Security Guardrails - Builti... \u2502 builtin \u2502\r\n"] -[0.15, "o", "\u2502 system \u2502 \u2502 \u2502 System Aggregation Entrypoint for Hyb... \u2502 custom \u2502\r\n"] [0.15, "o", "\u2502 policies.example \u2502 PreToolUse \u2502 Bash \u2502 Example Policy \u2502 custom \u2502\r\n"] -[0.15, "o", "\u2502 helpers.commands \u2502 \u2502 \u2502 - \u2502 custom \u2502\r\n"] [0.15, "o", "\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.822935Z\u001b[0m \u001b[32m INFO\u001b[0m Inspecting policies in directory: \".cupcake/policies\"\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.823049Z\u001b[0m \u001b[32m INFO\u001b[0m Found 10 policy files\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.386918Z\u001b[0m \u001b[32m INFO\u001b[0m Inspecting policies in directory: \".cupcake/policies\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.387003Z\u001b[0m \u001b[32m INFO\u001b[0m Found 8 policy files\r\n"] [0.075, "o", "\r\n"] [4.0, "o", "\r\n$ "] diff --git a/docs/docs/assets/cupcake-trust.cast b/docs/docs/assets/cupcake-trust.cast index a8c22f4e..56328e01 100644 --- a/docs/docs/assets/cupcake-trust.cast +++ b/docs/docs/assets/cupcake-trust.cast @@ -1,4 +1,4 @@ -{"version": 3, "term": {"cols": 100, "rows": 30}, "timestamp": 1764772238, "idle_time_limit": 2, "title": "Cupcake Trust Command"} +{"version": 3, "term": {"cols": 100, "rows": 30}, "timestamp": 1765218524, "idle_time_limit": 2, "title": "Cupcake Trust Command"} [1.0, "o", "$ cupcake trust --help\r\n"] [0.5, "o", "Manage script trust and integrity verification\r\n"] [0.075, "o", "\r\n"] @@ -41,15 +41,15 @@ [0.4, "o", "\u2705 Trust initialized successfully\r\n"] [0.15, "o", " Trust manifest saved to: ./.cupcake/.trust\r\n"] [0.15, "o", " Use 'cupcake trust update' to add more scripts\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.864340Z\u001b[0m \u001b[32m INFO\u001b[0m Loading rulebook from: \"./.cupcake/rulebook.yml\"\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.864677Z\u001b[0m \u001b[32m INFO\u001b[0m Generating signals for enabled builtins: [\"git_pre_check\", \"rulebook_security_guardrails\", \"protected_paths\", \"git_block_no_verify\"]\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.864713Z\u001b[0m \u001b[32m INFO\u001b[0m Generated 1 signals for enabled builtins\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.864715Z\u001b[0m \u001b[32m INFO\u001b[0m Final rulebook: 1 signals, 0 action rules, 4 enabled builtins\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.424428Z\u001b[0m \u001b[32m INFO\u001b[0m Loading rulebook from: \"./.cupcake/rulebook.yml\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.424785Z\u001b[0m \u001b[32m INFO\u001b[0m Generating signals for enabled builtins: [\"git_pre_check\", \"rulebook_security_guardrails\", \"protected_paths\", \"git_block_no_verify\"]\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.424829Z\u001b[0m \u001b[32m INFO\u001b[0m Generated 1 signals for enabled builtins\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.424831Z\u001b[0m \u001b[32m INFO\u001b[0m Final rulebook: 1 signals, 0 action rules, 4 enabled builtins, watchdog=disabled\r\n"] [0.075, "o", "\r\n"] [3.0, "o", "$ cupcake trust list\r\n"] [0.5, "o", "\ud83d\udcdc Trusted Scripts:\r\n"] [0.15, "o", " Manifest: ./.cupcake/.trust\r\n"] -[0.15, "o", " Created: 2025-12-03 14:30:38 UTC\r\n"] +[0.15, "o", " Created: 2025-12-08 18:28:44 UTC\r\n"] [0.075, "o", "\r\n"] [0.4, "o", "\ud83d\udcc1 actions:\r\n"] [0.075, "o", "\r\n"] diff --git a/docs/docs/assets/cupcake-verify.cast b/docs/docs/assets/cupcake-verify.cast index 131888c8..64f7f3d3 100644 --- a/docs/docs/assets/cupcake-verify.cast +++ b/docs/docs/assets/cupcake-verify.cast @@ -1,4 +1,4 @@ -{"version": 3, "term": {"cols": 100, "rows": 30}, "timestamp": 1764772238, "idle_time_limit": 2, "title": "Cupcake Verify Command"} +{"version": 3, "term": {"cols": 100, "rows": 30}, "timestamp": 1765218524, "idle_time_limit": 2, "title": "Cupcake Verify Command"} [1.0, "o", "$ cupcake verify --harness claude\r\n"] [0.5, "o", "\r\n"] [0.4, "o", "=== Global Configuration ===\r\n"] @@ -10,70 +10,77 @@ [0.4, "o", "\u2705 Engine initialized successfully\r\n"] [0.075, "o", "\r\n"] [0.4, "o", "=== Project Routing Map ===\r\n"] -[0.15, "o", " PreToolUse:Write -> 2 policies\r\n"] +[0.15, "o", " PreToolUse:Task -> 1 policies\r\n"] +[0.15, "o", " - cupcake.policies.builtins.rulebook_security_guardrails\r\n"] +[0.15, "o", " PreToolUse:NotebookEdit -> 2 policies\r\n"] [0.15, "o", " - cupcake.policies.builtins.protected_paths\r\n"] [0.15, "o", " - cupcake.policies.builtins.rulebook_security_guardrails\r\n"] [0.15, "o", " PreToolUse:MultiEdit -> 2 policies\r\n"] [0.15, "o", " - cupcake.policies.builtins.protected_paths\r\n"] [0.15, "o", " - cupcake.policies.builtins.rulebook_security_guardrails\r\n"] -[0.15, "o", " PreToolUse:WebFetch -> 1 policies\r\n"] -[0.15, "o", " - cupcake.policies.builtins.rulebook_security_guardrails\r\n"] -[0.15, "o", " PreToolUse:Task -> 1 policies\r\n"] +[0.15, "o", " PreToolUse:Write -> 2 policies\r\n"] +[0.15, "o", " - cupcake.policies.builtins.protected_paths\r\n"] [0.15, "o", " - cupcake.policies.builtins.rulebook_security_guardrails\r\n"] [0.15, "o", " PreToolUse:Read -> 1 policies\r\n"] [0.15, "o", " - cupcake.policies.builtins.rulebook_security_guardrails\r\n"] -[0.15, "o", " PreToolUse:Glob -> 1 policies\r\n"] -[0.15, "o", " - cupcake.policies.builtins.rulebook_security_guardrails\r\n"] -[0.15, "o", " PreToolUse:NotebookEdit -> 2 policies\r\n"] +[0.15, "o", " PreToolUse:Edit -> 2 policies\r\n"] [0.15, "o", " - cupcake.policies.builtins.protected_paths\r\n"] [0.15, "o", " - cupcake.policies.builtins.rulebook_security_guardrails\r\n"] -[0.15, "o", " PreToolUse:Bash -> 4 policies\r\n"] +[0.15, "o", " PreToolUse:Grep -> 1 policies\r\n"] +[0.15, "o", " - cupcake.policies.builtins.rulebook_security_guardrails\r\n"] +[0.15, "o", " PreToolUse:Glob -> 1 policies\r\n"] +[0.15, "o", " - cupcake.policies.builtins.rulebook_security_guardrails\r\n"] +[0.15, "o", " PreToolUse:Bash -> 5 policies\r\n"] [0.15, "o", " - cupcake.policies.builtins.git_pre_check\r\n"] [0.15, "o", " - cupcake.policies.builtins.protected_paths\r\n"] [0.15, "o", " - cupcake.policies.builtins.git_block_no_verify\r\n"] [0.15, "o", " - cupcake.policies.builtins.rulebook_security_guardrails\r\n"] -[0.15, "o", " PreToolUse:Edit -> 2 policies\r\n"] -[0.15, "o", " - cupcake.policies.builtins.protected_paths\r\n"] -[0.15, "o", " - cupcake.policies.builtins.rulebook_security_guardrails\r\n"] -[0.15, "o", " PreToolUse:Grep -> 1 policies\r\n"] +[0.15, "o", " - cupcake.policies.example\r\n"] +[0.15, "o", " PreToolUse:WebFetch -> 1 policies\r\n"] [0.15, "o", " - cupcake.policies.builtins.rulebook_security_guardrails\r\n"] [0.075, "o", "\r\n"] [0.4, "o", "=== WASM Compilation ===\r\n"] -[0.15, "o", " Project WASM: 379375 bytes \u2705\r\n"] +[0.15, "o", " Project WASM: 379828 bytes \u2705\r\n"] [0.15, "o", " Global WASM: Not compiled (no global policies or only system policies)\r\n"] [0.075, "o", "\r\n"] [0.4, "o", "\u2705 Verification complete!\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.968929Z\u001b[0m \u001b[32m INFO\u001b[0m Verifying Cupcake engine configuration...\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.968940Z\u001b[0m \u001b[32m INFO\u001b[0m Harness type: ClaudeCode\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.968941Z\u001b[0m \u001b[32m INFO\u001b[0m Policy directory: \"./policies\"\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.968991Z\u001b[0m \u001b[32m INFO\u001b[0m Initializing Cupcake Engine\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.968995Z\u001b[0m \u001b[32m INFO\u001b[0m Project root: \".\"\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.968996Z\u001b[0m \u001b[32m INFO\u001b[0m Policies directory: \"./.cupcake/policies\"\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.969008Z\u001b[0m \u001b[32m INFO\u001b[0m Starting engine initialization...\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.969013Z\u001b[0m \u001b[32m INFO\u001b[0m Loading rulebook from: \"./.cupcake/rulebook.yml\"\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.969277Z\u001b[0m \u001b[32m INFO\u001b[0m Generating signals for enabled builtins: [\"git_pre_check\", \"rulebook_security_guardrails\", \"protected_paths\", \"git_block_no_verify\"]\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.969289Z\u001b[0m \u001b[32m INFO\u001b[0m Generated 1 signals for enabled builtins\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.969291Z\u001b[0m \u001b[32m INFO\u001b[0m Final rulebook: 1 signals, 0 action rules, 4 enabled builtins\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.969293Z\u001b[0m \u001b[32m INFO\u001b[0m Project rulebook loaded with convention-based discovery\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.969294Z\u001b[0m \u001b[32m INFO\u001b[0m Enabled builtins: [\"git_pre_check\", \"rulebook_security_guardrails\", \"protected_paths\", \"git_block_no_verify\"]\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.969296Z\u001b[0m \u001b[32m INFO\u001b[0m Scanning harness-specific policies for: ClaudeCode at \"./.cupcake/policies/claude\"\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.969313Z\u001b[0m \u001b[32m INFO\u001b[0m Scanning for .rego files in: \"./.cupcake/policies/claude\"\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.969314Z\u001b[0m \u001b[32m INFO\u001b[0m With builtin filter for: [\"git_pre_check\", \"rulebook_security_guardrails\", \"protected_paths\", \"git_block_no_verify\"]\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.969441Z\u001b[0m \u001b[32m INFO\u001b[0m Scan complete: found 5 .rego files\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.969446Z\u001b[0m \u001b[32m INFO\u001b[0m Found 5 policy files in claude harness directory\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.969692Z\u001b[0m \u001b[32m INFO\u001b[0m Successfully parsed policy: cupcake.policies.builtins.git_pre_check from \"./.cupcake/policies/claude/builtins/git_pre_check.rego\"\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.969867Z\u001b[0m \u001b[32m INFO\u001b[0m Successfully parsed policy: cupcake.policies.builtins.protected_paths from \"./.cupcake/policies/claude/builtins/protected_paths.rego\"\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.969968Z\u001b[0m \u001b[32m INFO\u001b[0m Successfully parsed policy: cupcake.policies.builtins.git_block_no_verify from \"./.cupcake/policies/claude/builtins/git_block_no_verify.rego\"\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.970063Z\u001b[0m \u001b[32m INFO\u001b[0m Successfully parsed policy: cupcake.policies.builtins.rulebook_security_guardrails from \"./.cupcake/policies/claude/builtins/rulebook_security_guardrails.rego\"\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.970153Z\u001b[0m \u001b[32m INFO\u001b[0m Successfully parsed policy: cupcake.system from \"./.cupcake/policies/claude/system/evaluate.rego\"\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.970183Z\u001b[0m \u001b[32m INFO\u001b[0m Built routing map with 10 entries\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.970197Z\u001b[0m \u001b[32m INFO\u001b[0m Compiling 5 policies into unified WASM module\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:38.971494Z\u001b[0m \u001b[32m INFO\u001b[0m Executing OPA build command...\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:39.031711Z\u001b[0m \u001b[32m INFO\u001b[0m OPA compilation successful\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:39.042296Z\u001b[0m \u001b[32m INFO\u001b[0m Extracted WASM module: 379375 bytes\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:39.043260Z\u001b[0m \u001b[32m INFO\u001b[0m Successfully compiled unified WASM module (379375 bytes)\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:39.069638Z\u001b[0m \u001b[32m INFO\u001b[0m WASM runtime initialized\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:39.069658Z\u001b[0m \u001b[32m INFO\u001b[0m Trust mode not initialized (optional) - run 'cupcake trust init' to enable\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523355Z\u001b[0m \u001b[32m INFO\u001b[0m Verifying Cupcake engine configuration...\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523363Z\u001b[0m \u001b[32m INFO\u001b[0m Harness type: ClaudeCode\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523364Z\u001b[0m \u001b[32m INFO\u001b[0m Policy directory: \"./policies\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523410Z\u001b[0m \u001b[32m INFO\u001b[0m Initializing Cupcake Engine\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523414Z\u001b[0m \u001b[32m INFO\u001b[0m Project root: \".\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523415Z\u001b[0m \u001b[32m INFO\u001b[0m Policies directory: \"./.cupcake/policies\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523435Z\u001b[0m \u001b[32m INFO\u001b[0m Starting engine initialization...\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523438Z\u001b[0m \u001b[32m INFO\u001b[0m Initializing catalog overlays...\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523464Z\u001b[0m \u001b[32m INFO\u001b[0m Loading rulebook from: \"./.cupcake/rulebook.yml\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523675Z\u001b[0m \u001b[32m INFO\u001b[0m Generating signals for enabled builtins: [\"git_pre_check\", \"rulebook_security_guardrails\", \"protected_paths\", \"git_block_no_verify\"]\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523687Z\u001b[0m \u001b[32m INFO\u001b[0m Generated 1 signals for enabled builtins\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523689Z\u001b[0m \u001b[32m INFO\u001b[0m Final rulebook: 1 signals, 0 action rules, 4 enabled builtins, watchdog=disabled\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523692Z\u001b[0m \u001b[32m INFO\u001b[0m Project rulebook loaded with convention-based discovery\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523693Z\u001b[0m \u001b[32m INFO\u001b[0m Enabled builtins: [\"git_pre_check\", \"rulebook_security_guardrails\", \"protected_paths\", \"git_block_no_verify\"]\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523694Z\u001b[0m \u001b[32m INFO\u001b[0m Scanning harness-specific policies for: ClaudeCode at \"./.cupcake/policies/claude\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523710Z\u001b[0m \u001b[32m INFO\u001b[0m Scanning for .rego files in: \"./.cupcake/policies/claude\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523712Z\u001b[0m \u001b[32m INFO\u001b[0m With builtin filter for: [\"git_pre_check\", \"rulebook_security_guardrails\", \"protected_paths\", \"git_block_no_verify\"]\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523794Z\u001b[0m \u001b[32m INFO\u001b[0m Scan complete: found 5 .rego files\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523796Z\u001b[0m \u001b[32m INFO\u001b[0m Found 5 policy files in claude harness directory\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523799Z\u001b[0m \u001b[32m INFO\u001b[0m Scanning system directory: \"./.cupcake/system\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523802Z\u001b[0m \u001b[32m INFO\u001b[0m Scanning for .rego files in: \"./.cupcake/system\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523832Z\u001b[0m \u001b[32m INFO\u001b[0m Scan complete: found 1 .rego files\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.523835Z\u001b[0m \u001b[32m INFO\u001b[0m Found 1 system policy files\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.524066Z\u001b[0m \u001b[32m INFO\u001b[0m Successfully parsed policy: cupcake.policies.builtins.git_pre_check from \"./.cupcake/policies/claude/builtins/git_pre_check.rego\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.524220Z\u001b[0m \u001b[32m INFO\u001b[0m Successfully parsed policy: cupcake.policies.builtins.protected_paths from \"./.cupcake/policies/claude/builtins/protected_paths.rego\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.524321Z\u001b[0m \u001b[32m INFO\u001b[0m Successfully parsed policy: cupcake.policies.builtins.git_block_no_verify from \"./.cupcake/policies/claude/builtins/git_block_no_verify.rego\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.524425Z\u001b[0m \u001b[32m INFO\u001b[0m Successfully parsed policy: cupcake.policies.builtins.rulebook_security_guardrails from \"./.cupcake/policies/claude/builtins/rulebook_security_guardrails.rego\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.524508Z\u001b[0m \u001b[32m INFO\u001b[0m Successfully parsed policy: cupcake.policies.example from \"./.cupcake/policies/claude/example.rego\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.524603Z\u001b[0m \u001b[32m INFO\u001b[0m Successfully parsed system policy: cupcake.system from \"./.cupcake/system/evaluate.rego\"\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.524649Z\u001b[0m \u001b[32m INFO\u001b[0m Built routing map with 10 entries\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.524651Z\u001b[0m \u001b[32m INFO\u001b[0m Compiling 6 policies into unified WASM module\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.525931Z\u001b[0m \u001b[32m INFO\u001b[0m Executing OPA build command...\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.573713Z\u001b[0m \u001b[32m INFO\u001b[0m OPA compilation successful\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.581921Z\u001b[0m \u001b[32m INFO\u001b[0m Extracted WASM module: 379828 bytes\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.582772Z\u001b[0m \u001b[32m INFO\u001b[0m Successfully compiled unified WASM module (379828 bytes)\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.610979Z\u001b[0m \u001b[32m INFO\u001b[0m WASM runtime initialized\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.611000Z\u001b[0m \u001b[32m INFO\u001b[0m Trust mode not initialized (optional) - run 'cupcake trust init' to enable\r\n"] [0.15, "o", "\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\r\n"] [0.15, "o", "\u2502 Cupcake is running in STANDARD mode \u2502\r\n"] [0.15, "o", "\u2502 \u2502\r\n"] @@ -83,6 +90,6 @@ [0.15, "o", "\u2502 \u2502\r\n"] [0.15, "o", "\u2502 Learn more: cupcake trust --help \u2502\r\n"] [0.15, "o", "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\r\n"] -[0.15, "o", "\u001b[2m2025-12-03T14:30:39.069669Z\u001b[0m \u001b[32m INFO\u001b[0m Engine initialization complete\r\n"] +[0.15, "o", "\u001b[2m2025-12-08T18:28:44.611006Z\u001b[0m \u001b[32m INFO\u001b[0m Engine initialization complete\r\n"] [0.075, "o", "\r\n"] [5.0, "o", "\r\n$ "] From f4532d903ab80a3868f836cf1866a6d2a12a9e34 Mon Sep 17 00:00:00 2001 From: captjt Date: Mon, 8 Dec 2025 13:39:49 -0500 Subject: [PATCH 5/7] chore: PR comments --- cupcake-cli/tests/init_command_test.rs | 2 +- cupcake-core/src/engine/compiler.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cupcake-cli/tests/init_command_test.rs b/cupcake-cli/tests/init_command_test.rs index 4f4192b5..a44ff861 100644 --- a/cupcake-cli/tests/init_command_test.rs +++ b/cupcake-cli/tests/init_command_test.rs @@ -558,7 +558,7 @@ fn test_correct_number_of_files_created() -> Result<()> { "Should have exactly 11 files (1 rulebook + 1 example + 1 helper + 1 evaluate + 7 builtins)" ); - // We should have exactly 6 directories (new structure): + // We should have exactly 7 directories (new structure): // system, helpers, policies, policies/claude, policies/claude/builtins, signals, actions assert_eq!(dir_count, 7, "Should have exactly 7 directories"); diff --git a/cupcake-core/src/engine/compiler.rs b/cupcake-core/src/engine/compiler.rs index 0d29fea6..5ccb5d39 100644 --- a/cupcake-core/src/engine/compiler.rs +++ b/cupcake-core/src/engine/compiler.rs @@ -174,15 +174,15 @@ pub async fn compile_policies_with_namespace( let root_helpers = cupcake.join("helpers"); if root_helpers.exists() && root_helpers.is_dir() { debug!("Using helpers from cupcake root: {:?}", root_helpers); - Some(root_helpers) + root_helpers } else { debug!("No helpers at cupcake root, checking policies root"); - None + let policies_helpers = policies_root.join("helpers"); + policies_helpers } } else { - None - } - .unwrap_or_else(|| policies_root.join("helpers")); + policies_root.join("helpers") + }; if helpers_src.exists() && helpers_src.is_dir() { debug!("Copying helpers directory: {:?}", helpers_src); From 5455ca5c2f8d66cda730dd346b4474254421144e Mon Sep 17 00:00:00 2001 From: captjt Date: Mon, 8 Dec 2025 14:01:16 -0500 Subject: [PATCH 6/7] chore: fix clippy errors --- cupcake-core/src/engine/compiler.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cupcake-core/src/engine/compiler.rs b/cupcake-core/src/engine/compiler.rs index 5ccb5d39..f7584747 100644 --- a/cupcake-core/src/engine/compiler.rs +++ b/cupcake-core/src/engine/compiler.rs @@ -177,8 +177,7 @@ pub async fn compile_policies_with_namespace( root_helpers } else { debug!("No helpers at cupcake root, checking policies root"); - let policies_helpers = policies_root.join("helpers"); - policies_helpers + policies_root.join("helpers") } } else { policies_root.join("helpers") From 4eaa9627c2511627ccb5e53c79209be3d75e10d2 Mon Sep 17 00:00:00 2001 From: captjt Date: Mon, 8 Dec 2025 14:11:51 -0500 Subject: [PATCH 7/7] ci: split out opencode plugin to its own release workflow --- .github/workflows/ci-opencode-plugin.yml | 46 ++ .github/workflows/release-opencode-plugin.yml | 82 +++ .github/workflows/release.yml | 50 +- cupcake-cli/src/harness_config.rs | 11 +- cupcake-plugins/opencode/package-lock.json | 569 ------------------ cupcake-plugins/opencode/package.json | 2 +- docs/docs/getting-started/usage/opencode.md | 2 +- docs/docs/reference/harnesses/opencode.md | 2 +- 8 files changed, 139 insertions(+), 625 deletions(-) create mode 100644 .github/workflows/ci-opencode-plugin.yml create mode 100644 .github/workflows/release-opencode-plugin.yml delete mode 100644 cupcake-plugins/opencode/package-lock.json diff --git a/.github/workflows/ci-opencode-plugin.yml b/.github/workflows/ci-opencode-plugin.yml new file mode 100644 index 00000000..928d386f --- /dev/null +++ b/.github/workflows/ci-opencode-plugin.yml @@ -0,0 +1,46 @@ +name: CI - OpenCode Plugin + +on: + pull_request: + paths: + - 'cupcake-plugins/opencode/**' + - '.github/workflows/ci-opencode-plugin.yml' + push: + branches: [main] + paths: + - 'cupcake-plugins/opencode/**' + - '.github/workflows/ci-opencode-plugin.yml' + +jobs: + build: + name: Build & Type Check + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + working-directory: cupcake-plugins/opencode + run: bun install + + - name: Type check + working-directory: cupcake-plugins/opencode + run: bun run typecheck + + - name: Build + working-directory: cupcake-plugins/opencode + run: bun run build + + - name: Verify output exists + working-directory: cupcake-plugins/opencode + run: | + if [ ! -f dist/cupcake.js ]; then + echo "Error: dist/cupcake.js not found" + exit 1 + fi + echo "Build output size: $(wc -c < dist/cupcake.js) bytes" diff --git a/.github/workflows/release-opencode-plugin.yml b/.github/workflows/release-opencode-plugin.yml new file mode 100644 index 00000000..8301195e --- /dev/null +++ b/.github/workflows/release-opencode-plugin.yml @@ -0,0 +1,82 @@ +name: Release OpenCode Plugin + +on: + push: + branches: [main] + paths: + - 'cupcake-plugins/opencode/**' + workflow_dispatch: + +jobs: + build-and-release: + name: Build and Release Plugin + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + working-directory: cupcake-plugins/opencode + run: bun install + + - name: Type check + working-directory: cupcake-plugins/opencode + run: bun run typecheck + + - name: Build plugin + working-directory: cupcake-plugins/opencode + run: bun run build + + - name: Generate checksum + working-directory: cupcake-plugins/opencode/dist + run: | + sha256sum cupcake.js > cupcake.js.sha256 + echo "Generated checksum:" + cat cupcake.js.sha256 + + - name: Get commit info + id: commit + run: | + echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + echo "date=$(date -u +%Y-%m-%d)" >> $GITHUB_OUTPUT + + - name: Update plugin release + uses: softprops/action-gh-release@v2 + with: + tag_name: opencode-plugin-latest + name: OpenCode Plugin (Latest) + body: | + Latest build of the Cupcake OpenCode plugin. + + This release is automatically updated when changes are merged to main. + + **Build:** ${{ steps.commit.outputs.sha_short }} (${{ steps.commit.outputs.date }}) + + ## Files + - `opencode-plugin.js` - The plugin file + - `opencode-plugin.js.sha256` - SHA256 checksum + + ## Installation + + The plugin is automatically downloaded when you run: + ```bash + cupcake init --harness opencode + ``` + + Or manually download: + ```bash + curl -fsSL https://github.com/eqtylab/cupcake/releases/download/opencode-plugin-latest/opencode-plugin.js \ + -o .opencode/plugin/cupcake.js + ``` + files: | + cupcake-plugins/opencode/dist/cupcake.js#opencode-plugin.js + cupcake-plugins/opencode/dist/cupcake.js.sha256#opencode-plugin.js.sha256 + make_latest: false + prerelease: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ec3e08df..160b7652 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -163,54 +163,8 @@ jobs: draft: true prerelease: ${{ contains(steps.get_version.outputs.version, '-') }} - # Build OpenCode plugin - build-opencode-plugin: - name: Build OpenCode Plugin - needs: create-release - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install dependencies - working-directory: cupcake-plugins/opencode - run: npm install - - - name: Build plugin - working-directory: cupcake-plugins/opencode - run: npm run build - - - name: Generate checksum - working-directory: cupcake-plugins/opencode/dist - run: | - sha256sum cupcake.js > cupcake.js.sha256 - echo "Generated checksum:" - cat cupcake.js.sha256 - - - name: Upload plugin to release - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.create-release.outputs.upload_url }} - asset_path: cupcake-plugins/opencode/dist/cupcake.js - asset_name: opencode-plugin.js - asset_content_type: application/javascript - - - name: Upload plugin checksum - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.create-release.outputs.upload_url }} - asset_path: cupcake-plugins/opencode/dist/cupcake.js.sha256 - asset_name: opencode-plugin.js.sha256 - asset_content_type: text/plain + # NOTE: OpenCode plugin is released separately via release-opencode-plugin.yml + # It uses a dedicated release tag (opencode-plugin-latest) that is updated on merges to main # Upload install scripts to release upload-install-scripts: diff --git a/cupcake-cli/src/harness_config.rs b/cupcake-cli/src/harness_config.rs index 86f6f87e..18c4eda4 100644 --- a/cupcake-cli/src/harness_config.rs +++ b/cupcake-cli/src/harness_config.rs @@ -272,12 +272,13 @@ impl OpenCodeHarness { let plugin_path = plugin_dir.join("cupcake.js"); - // Use latest release to avoid version sync issues between CLI and plugin + // Use dedicated plugin release to allow independent plugin updates // The plugin is forward-compatible, so latest is always safe - let plugin_url = - format!("https://github.com/{GITHUB_REPO}/releases/latest/download/opencode-plugin.js"); + let plugin_url = format!( + "https://github.com/{GITHUB_REPO}/releases/download/opencode-plugin-latest/opencode-plugin.js" + ); let checksum_url = format!( - "https://github.com/{GITHUB_REPO}/releases/latest/download/opencode-plugin.js.sha256" + "https://github.com/{GITHUB_REPO}/releases/download/opencode-plugin-latest/opencode-plugin.js.sha256" ); println!(" Downloading OpenCode plugin (latest release)..."); @@ -345,7 +346,7 @@ impl OpenCodeHarness { eprintln!(); eprintln!(" Or download from GitHub releases:"); eprintln!( - " curl -fsSL https://github.com/{GITHUB_REPO}/releases/latest/download/opencode-plugin.js \\" + " curl -fsSL https://github.com/{GITHUB_REPO}/releases/download/opencode-plugin-latest/opencode-plugin.js \\" ); eprintln!(" -o .opencode/plugin/cupcake.js"); eprintln!(); diff --git a/cupcake-plugins/opencode/package-lock.json b/cupcake-plugins/opencode/package-lock.json deleted file mode 100644 index 5df560a6..00000000 --- a/cupcake-plugins/opencode/package-lock.json +++ /dev/null @@ -1,569 +0,0 @@ -{ - "name": "@cupcake/opencode-plugin", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@cupcake/opencode-plugin", - "version": "0.1.0", - "license": "MIT", - "devDependencies": { - "@opencode-ai/plugin": "^1.0.81", - "@types/bun": "^1.1.0", - "@types/node": "^20.0.0", - "esbuild": "^0.24.0", - "typescript": "^5.3.0" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "@opencode-ai/plugin": ">=1.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", - "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", - "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", - "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", - "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", - "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", - "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", - "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", - "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", - "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", - "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", - "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", - "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", - "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", - "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", - "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", - "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", - "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", - "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", - "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", - "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", - "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", - "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", - "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", - "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@opencode-ai/plugin": { - "version": "1.0.81", - "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.0.81.tgz", - "integrity": "sha512-pTXpc8WYGxdP7YRhZSm2CTq51L5pWJTfjItpM4epdVvGfnr9e9LSB4w0wLExb46FyieAFfv57eqbhNS+dVA2lg==", - "dev": true, - "dependencies": { - "@opencode-ai/sdk": "1.0.81", - "zod": "4.1.8" - } - }, - "node_modules/@opencode-ai/sdk": { - "version": "1.0.81", - "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.0.81.tgz", - "integrity": "sha512-sSlgr9yF3DhiaYLIssZv99UIoIYMP9awnMk+gsQs0af4F/CP84H83vy0duALzAoZgQaWYXbBg7S8ODPsGBhijA==", - "dev": true - }, - "node_modules/@types/bun": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.3.tgz", - "integrity": "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "bun-types": "1.3.3" - } - }, - "node_modules/@types/node": { - "version": "20.19.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", - "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/bun-types": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.3.tgz", - "integrity": "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/esbuild": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", - "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.2", - "@esbuild/android-arm": "0.24.2", - "@esbuild/android-arm64": "0.24.2", - "@esbuild/android-x64": "0.24.2", - "@esbuild/darwin-arm64": "0.24.2", - "@esbuild/darwin-x64": "0.24.2", - "@esbuild/freebsd-arm64": "0.24.2", - "@esbuild/freebsd-x64": "0.24.2", - "@esbuild/linux-arm": "0.24.2", - "@esbuild/linux-arm64": "0.24.2", - "@esbuild/linux-ia32": "0.24.2", - "@esbuild/linux-loong64": "0.24.2", - "@esbuild/linux-mips64el": "0.24.2", - "@esbuild/linux-ppc64": "0.24.2", - "@esbuild/linux-riscv64": "0.24.2", - "@esbuild/linux-s390x": "0.24.2", - "@esbuild/linux-x64": "0.24.2", - "@esbuild/netbsd-arm64": "0.24.2", - "@esbuild/netbsd-x64": "0.24.2", - "@esbuild/openbsd-arm64": "0.24.2", - "@esbuild/openbsd-x64": "0.24.2", - "@esbuild/sunos-x64": "0.24.2", - "@esbuild/win32-arm64": "0.24.2", - "@esbuild/win32-ia32": "0.24.2", - "@esbuild/win32-x64": "0.24.2" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/zod": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", - "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/cupcake-plugins/opencode/package.json b/cupcake-plugins/opencode/package.json index ca227a52..264a9791 100644 --- a/cupcake-plugins/opencode/package.json +++ b/cupcake-plugins/opencode/package.json @@ -1,6 +1,6 @@ { "name": "@cupcake/opencode-plugin", - "version": "0.1.0", + "version": "0.1.1", "description": "Cupcake policy engine plugin for OpenCode - enforce security policies on AI coding agent actions", "type": "module", "main": "dist/cupcake.js", diff --git a/docs/docs/getting-started/usage/opencode.md b/docs/docs/getting-started/usage/opencode.md index 8d7f3da4..87512684 100644 --- a/docs/docs/getting-started/usage/opencode.md +++ b/docs/docs/getting-started/usage/opencode.md @@ -51,7 +51,7 @@ If automatic download fails (e.g., network issues): ```bash # Download from GitHub releases mkdir -p .opencode/plugin -curl -fsSL https://github.com/eqtylab/cupcake/releases/latest/download/opencode-plugin.js \ +curl -fsSL https://github.com/eqtylab/cupcake/releases/download/opencode-plugin-latest/opencode-plugin.js \ -o .opencode/plugin/cupcake.js # Or build from source diff --git a/docs/docs/reference/harnesses/opencode.md b/docs/docs/reference/harnesses/opencode.md index 78b7872b..eca128fc 100644 --- a/docs/docs/reference/harnesses/opencode.md +++ b/docs/docs/reference/harnesses/opencode.md @@ -192,7 +192,7 @@ The plugin is automatically installed by `cupcake init --harness opencode`: ```bash # Download from releases mkdir -p .opencode/plugin -curl -fsSL https://github.com/eqtylab/cupcake/releases/latest/download/opencode-plugin.js \ +curl -fsSL https://github.com/eqtylab/cupcake/releases/download/opencode-plugin-latest/opencode-plugin.js \ -o .opencode/plugin/cupcake.js # Or build from source