From 5823ec0f6c571ce4dc2fae116a514ff4db0e368f Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Mon, 8 Jun 2026 18:37:16 -0400 Subject: [PATCH 01/13] feat: add --source and --schema flags to mcp serve Add repeatable --source and --schema flags to the mcp serve command, allowing direct configuration without a YAML file. When --source flags are present, a ComplyPackConfig is built from flag values; otherwise the existing --config file path is used. - parseSourceFlags: handles oci:// (TLS) and oci+http:// (plain HTTP) - parseSchemaFlags: handles bare platform names and platform=source syntax - Refactor NewServer to accept ServerOptions.Config directly Assisted-by: Claude (Anthropic, Claude Opus 4.6) Signed-off-by: Jennifer Power --- cmd/complypack/cli/mcp.go | 107 ++++++++++++++++++++++- cmd/complypack/cli/mcp_test.go | 155 ++++++++++++++++++++++++++++++++- internal/mcp/server.go | 19 +++- 3 files changed, 271 insertions(+), 10 deletions(-) diff --git a/cmd/complypack/cli/mcp.go b/cmd/complypack/cli/mcp.go index e0656e1..42c75a1 100644 --- a/cmd/complypack/cli/mcp.go +++ b/cmd/complypack/cli/mcp.go @@ -7,7 +7,9 @@ import ( "log" "os" "path/filepath" + "strings" + "github.com/complytime/complypack/internal/config" "github.com/complytime/complypack/internal/mcp" mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/spf13/cobra" @@ -31,6 +33,8 @@ func mcpServeCmd() *cobra.Command { var ( configPath string cacheDir string + sources []string + schemas []string ) cmd := &cobra.Command{ @@ -45,6 +49,12 @@ specified in complypack.yaml. Example: complypack mcp serve --config complypack.yaml + # Or use flags directly (no config file needed): + complypack mcp serve \ + --source oci://ghcr.io/org/catalog:v1 \ + --schema kubernetes \ + --schema ci=cue://cue.dev/x/githubactions@v0#Workflow + The server runs until interrupted (Ctrl+C) or the client disconnects.`, RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() @@ -59,10 +69,20 @@ The server runs until interrupted (Ctrl+C) or the client disconnects.`, resolvedCacheDir = filepath.Join(homeDir, ".complypack", "cache") } - // Create MCP server + // Create MCP server options opts := &mcp.ServerOptions{ - ConfigPath: configPath, - CacheDir: resolvedCacheDir, + CacheDir: resolvedCacheDir, + } + + // If --source flags are present, build config from flags + if len(sources) > 0 { + cfg, err := buildConfigFromFlags(sources, schemas) + if err != nil { + return fmt.Errorf("failed to build config from flags: %w", err) + } + opts.Config = cfg + } else { + opts.ConfigPath = configPath } server, err := mcp.NewServer(ctx, opts) @@ -82,6 +102,87 @@ The server runs until interrupted (Ctrl+C) or the client disconnects.`, cmd.Flags().StringVarP(&configPath, "config", "c", "complypack.yaml", "Path to complypack.yaml config file") cmd.Flags().StringVar(&cacheDir, "cache-dir", "", "Cache directory (default: $HOME/.complypack/cache)") + cmd.Flags().StringArrayVar(&sources, "source", nil, "Gemara OCI source (repeatable, e.g. oci://ghcr.io/org/catalog:v1)") + cmd.Flags().StringArrayVar(&schemas, "schema", nil, "Platform schema (repeatable, e.g. kubernetes or ci=cue://...)") return cmd } + +// buildConfigFromFlags creates a ComplyPackConfig from --source and --schema flag values. +func buildConfigFromFlags(sources, schemas []string) (*config.ComplyPackConfig, error) { + entries, err := parseSourceFlags(sources) + if err != nil { + return nil, err + } + + schemaRefs, err := parseSchemaFlags(schemas) + if err != nil { + return nil, err + } + + return &config.ComplyPackConfig{ + Version: "1.0", + Gemara: config.GemaraConfig{Sources: entries}, + Schemas: schemaRefs, + }, nil +} + +// parseSourceFlags converts --source flag values into GemaraSourceEntry values. +// +// - oci://... -> GemaraSourceEntry{Source: "oci://...", PlainHTTP: false} +// - oci+http://... -> GemaraSourceEntry{Source: "oci://...", PlainHTTP: true} +func parseSourceFlags(sources []string) ([]config.GemaraSourceEntry, error) { + if len(sources) == 0 { + return nil, nil + } + + entries := make([]config.GemaraSourceEntry, 0, len(sources)) + for _, s := range sources { + if s == "" { + return nil, fmt.Errorf("empty source flag value") + } + + entry := config.GemaraSourceEntry{} + if strings.HasPrefix(s, "oci+http://") { + entry.Source = "oci://" + strings.TrimPrefix(s, "oci+http://") + entry.PlainHTTP = true + } else { + entry.Source = s + } + entries = append(entries, entry) + } + return entries, nil +} + +// parseSchemaFlags converts --schema flag values into SchemaRef values. +// +// - "kubernetes" -> SchemaRef{Platform: "kubernetes"} (embedded) +// - "ci=cue://cue.dev/x/actions@v0" -> SchemaRef{Platform: "ci", Source: "cue://..."} +func parseSchemaFlags(schemas []string) ([]config.SchemaRef, error) { + if len(schemas) == 0 { + return nil, nil + } + + refs := make([]config.SchemaRef, 0, len(schemas)) + for _, s := range schemas { + if s == "" { + return nil, fmt.Errorf("empty schema flag value") + } + + ref := config.SchemaRef{} + if idx := strings.IndexByte(s, '='); idx >= 0 { + ref.Platform = s[:idx] + ref.Source = s[idx+1:] + if ref.Platform == "" { + return nil, fmt.Errorf("empty platform name in schema flag %q", s) + } + if ref.Source == "" { + return nil, fmt.Errorf("empty source for platform %q in schema flag %q", ref.Platform, s) + } + } else { + ref.Platform = s + } + refs = append(refs, ref) + } + return refs, nil +} diff --git a/cmd/complypack/cli/mcp_test.go b/cmd/complypack/cli/mcp_test.go index 761765e..067f901 100644 --- a/cmd/complypack/cli/mcp_test.go +++ b/cmd/complypack/cli/mcp_test.go @@ -1,17 +1,17 @@ // SPDX-License-Identifier: Apache-2.0 -package cli_test +package cli import ( "testing" - "github.com/complytime/complypack/cmd/complypack/cli" + "github.com/complytime/complypack/internal/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestMcpCommand(t *testing.T) { - root := cli.New() + root := New() // Find the mcp command mcpCmd, _, err := root.Find([]string{"mcp"}) @@ -29,4 +29,153 @@ func TestMcpCommand(t *testing.T) { flags := serveCmd.Flags() assert.NotNil(t, flags.Lookup("config"), "should have --config flag") assert.NotNil(t, flags.Lookup("cache-dir"), "should have --cache-dir flag") + assert.NotNil(t, flags.Lookup("source"), "should have --source flag") + assert.NotNil(t, flags.Lookup("schema"), "should have --schema flag") +} + +func TestParseSourceFlags(t *testing.T) { + tests := []struct { + name string + sources []string + want []config.GemaraSourceEntry + wantErr string + }{ + { + name: "single OCI source with TLS", + sources: []string{"oci://ghcr.io/org/catalog:v1"}, + want: []config.GemaraSourceEntry{ + {Source: "oci://ghcr.io/org/catalog:v1", PlainHTTP: false}, + }, + }, + { + name: "single OCI source with plain HTTP", + sources: []string{"oci+http://localhost:5000/catalog:v1"}, + want: []config.GemaraSourceEntry{ + {Source: "oci://localhost:5000/catalog:v1", PlainHTTP: true}, + }, + }, + { + name: "multiple mixed sources", + sources: []string{ + "oci://ghcr.io/org/catalog:v1", + "oci+http://localhost:5000/guidance:latest", + "oci://ghcr.io/org/policy:v2", + }, + want: []config.GemaraSourceEntry{ + {Source: "oci://ghcr.io/org/catalog:v1", PlainHTTP: false}, + {Source: "oci://localhost:5000/guidance:latest", PlainHTTP: true}, + {Source: "oci://ghcr.io/org/policy:v2", PlainHTTP: false}, + }, + }, + { + name: "empty source", + sources: []string{""}, + wantErr: "empty source flag value", + }, + { + name: "nil sources returns nil", + sources: nil, + want: nil, + }, + { + name: "empty slice returns nil", + sources: []string{}, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseSourceFlags(tt.sources) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestParseSchemaFlags(t *testing.T) { + tests := []struct { + name string + schemas []string + want []config.SchemaRef + wantErr string + }{ + { + name: "bare platform name uses embedded schema", + schemas: []string{"kubernetes"}, + want: []config.SchemaRef{ + {Platform: "kubernetes"}, + }, + }, + { + name: "platform with external CUE source", + schemas: []string{"ci=cue://cue.dev/x/githubactions@v0#Workflow"}, + want: []config.SchemaRef{ + {Platform: "ci", Source: "cue://cue.dev/x/githubactions@v0#Workflow"}, + }, + }, + { + name: "platform with HTTPS source", + schemas: []string{"terraform=https://example.com/schema.json"}, + want: []config.SchemaRef{ + {Platform: "terraform", Source: "https://example.com/schema.json"}, + }, + }, + { + name: "mixed embedded and external schemas", + schemas: []string{ + "kubernetes", + "ci=cue://cue.dev/x/githubactions@v0#Workflow", + "docker", + }, + want: []config.SchemaRef{ + {Platform: "kubernetes"}, + {Platform: "ci", Source: "cue://cue.dev/x/githubactions@v0#Workflow"}, + {Platform: "docker"}, + }, + }, + { + name: "empty schema", + schemas: []string{""}, + wantErr: "empty schema flag value", + }, + { + name: "empty platform in key=value", + schemas: []string{"=cue://something"}, + wantErr: "empty platform name", + }, + { + name: "empty source in key=value", + schemas: []string{"ci="}, + wantErr: "empty source for platform", + }, + { + name: "nil schemas returns nil", + schemas: nil, + want: nil, + }, + { + name: "empty slice returns nil", + schemas: []string{}, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseSchemaFlags(tt.schemas) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } } diff --git a/internal/mcp/server.go b/internal/mcp/server.go index f930b9e..9a762db 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -31,8 +31,13 @@ type Server struct { // ServerOptions configures ComplyPack MCP server initialization. type ServerOptions struct { // ConfigPath is the path to complypack.yaml. + // Ignored when Config is set. ConfigPath string + // Config provides configuration directly, bypassing file loading. + // When set, ConfigPath is ignored. + Config *config.ComplyPackConfig + // OCIStore is the directory for OCI artifact caching. OCIStore string @@ -58,10 +63,16 @@ func NewServer(ctx context.Context, opts *ServerOptions) (*Server, error) { return nil, fmt.Errorf("ServerOptions cannot be nil") } - // Load config - cfg, err := config.LoadConfig(opts.ConfigPath) - if err != nil { - return nil, fmt.Errorf("failed to load config: %w", err) + // Load config: use provided config or load from file + var cfg *config.ComplyPackConfig + if opts.Config != nil { + cfg = opts.Config + } else { + var err error + cfg, err = config.LoadConfig(opts.ConfigPath) + if err != nil { + return nil, fmt.Errorf("failed to load config: %w", err) + } } if err := cfg.ValidateForMCP(); err != nil { return nil, fmt.Errorf("failed to load config: %w", err) From 0b646476c80463f2fab6fad09d6e64a9e2a3ec0a Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Mon, 8 Jun 2026 18:52:19 -0400 Subject: [PATCH 02/13] fix: remove hardcoded version and add test for buildConfigFromFlags Remove hardcoded version "1.0" from buildConfigFromFlags in mcp.go since the MCP server does not use the version field (it's only needed for pack/scan commands). Add comprehensive test for buildConfigFromFlags to verify complete flag-to-config transformation including source parsing, schema parsing, and proper struct field population. Assisted-by: Claude (Anthropic, Claude Opus 4.6) Signed-off-by: Jennifer Power --- cmd/complypack/cli/mcp.go | 1 - cmd/complypack/cli/mcp_test.go | 85 ++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/cmd/complypack/cli/mcp.go b/cmd/complypack/cli/mcp.go index 42c75a1..0770815 100644 --- a/cmd/complypack/cli/mcp.go +++ b/cmd/complypack/cli/mcp.go @@ -121,7 +121,6 @@ func buildConfigFromFlags(sources, schemas []string) (*config.ComplyPackConfig, } return &config.ComplyPackConfig{ - Version: "1.0", Gemara: config.GemaraConfig{Sources: entries}, Schemas: schemaRefs, }, nil diff --git a/cmd/complypack/cli/mcp_test.go b/cmd/complypack/cli/mcp_test.go index 067f901..f9dfb7d 100644 --- a/cmd/complypack/cli/mcp_test.go +++ b/cmd/complypack/cli/mcp_test.go @@ -98,6 +98,91 @@ func TestParseSourceFlags(t *testing.T) { } } +func TestBuildConfigFromFlags(t *testing.T) { + tests := []struct { + name string + sources []string + schemas []string + want *config.ComplyPackConfig + wantErr string + }{ + { + name: "single source and schema", + sources: []string{"oci://ghcr.io/org/catalog:v1"}, + schemas: []string{"kubernetes"}, + want: &config.ComplyPackConfig{ + Gemara: config.GemaraConfig{ + Sources: []config.GemaraSourceEntry{ + {Source: "oci://ghcr.io/org/catalog:v1", PlainHTTP: false}, + }, + }, + Schemas: []config.SchemaRef{ + {Platform: "kubernetes"}, + }, + }, + }, + { + name: "multiple sources and schemas", + sources: []string{ + "oci://ghcr.io/org/catalog:v1", + "oci+http://localhost:5000/guidance:latest", + }, + schemas: []string{ + "kubernetes", + "ci=cue://cue.dev/x/githubactions@v0#Workflow", + }, + want: &config.ComplyPackConfig{ + Gemara: config.GemaraConfig{ + Sources: []config.GemaraSourceEntry{ + {Source: "oci://ghcr.io/org/catalog:v1", PlainHTTP: false}, + {Source: "oci://localhost:5000/guidance:latest", PlainHTTP: true}, + }, + }, + Schemas: []config.SchemaRef{ + {Platform: "kubernetes"}, + {Platform: "ci", Source: "cue://cue.dev/x/githubactions@v0#Workflow"}, + }, + }, + }, + { + name: "empty sources and schemas", + sources: nil, + schemas: nil, + want: &config.ComplyPackConfig{ + Gemara: config.GemaraConfig{ + Sources: nil, + }, + Schemas: nil, + }, + }, + { + name: "invalid source", + sources: []string{""}, + schemas: []string{"kubernetes"}, + wantErr: "empty source flag value", + }, + { + name: "invalid schema", + sources: []string{"oci://ghcr.io/org/catalog:v1"}, + schemas: []string{""}, + wantErr: "empty schema flag value", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := buildConfigFromFlags(tt.sources, tt.schemas) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + func TestParseSchemaFlags(t *testing.T) { tests := []struct { name string From ae6c62bfe9275fd681d6b786ec409801c404630e Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Mon, 8 Jun 2026 18:58:04 -0400 Subject: [PATCH 03/13] build: add Containerfile for container distribution Multi-stage build with UBI 9 micro base image. Produces a minimal container for MCP server distribution via GHCR. Refs: #24 ADR: docs/adr/012-container-mcp-distribution.md Assisted-by: Claude (Anthropic, Claude Opus 4.6) Signed-off-by: Jennifer Power --- .dockerignore | 12 ++++++++++++ Containerfile | 13 +++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 .dockerignore create mode 100644 Containerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ffa7566 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +.git +.github +.complytime +.cursor +.opencode +docs +kb +skills +tests +acceptance +*.md +!LICENSE diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..45289c5 --- /dev/null +++ b/Containerfile @@ -0,0 +1,13 @@ +FROM golang:1.26-alpine AS builder + +WORKDIR /build +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o complypack ./cmd/complypack + +FROM registry.access.redhat.com/ubi9-micro:latest + +COPY --from=builder /build/complypack /usr/local/bin/complypack + +ENTRYPOINT ["complypack"] From 62927c17d62c91f029cdfb0fb2c4f535efa110d0 Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Mon, 8 Jun 2026 19:13:41 -0400 Subject: [PATCH 04/13] ci: add container image build and push workflow Uses org-infra reusable workflows for GHCR publish with SLSA provenance, SBOM attestations, and Sigstore signing. Multi-arch (amd64/arm64) build on version tags and main pushes. Refs: #24 Assisted-by: Claude (Anthropic, Claude Opus 4.6) Signed-off-by: Jennifer Power --- .github/workflows/container.yml | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/container.yml diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml new file mode 100644 index 0000000..862fe80 --- /dev/null +++ b/.github/workflows/container.yml @@ -0,0 +1,41 @@ +name: Container Image + +on: + push: + tags: + - 'v*' + branches: + - main + +permissions: + contents: read + +jobs: + push: + uses: complytime/org-infra/.github/workflows/reusable_publish_ghcr.yml@main + permissions: + contents: read + packages: write + actions: read + id-token: write + attestations: write + with: + component_name: complypack + containerfile_path: Containerfile + context_path: . + image_name: complytime/complypack + image_description: "ComplyPack MCP server for compliance policy generation" + platforms: linux/amd64,linux/arm64 + + sign: + needs: push + uses: complytime/org-infra/.github/workflows/reusable_sign_and_verify.yml@main + permissions: + contents: read + packages: write + id-token: write + with: + image_name: ${{ needs.push.outputs.image }} + digest: ${{ needs.push.outputs.digest }} + allowed_identity_regex: "https://github.com/complytime/.*" + verify_vuln: false From 87b163446ca86228dc2642ffa1971bcb99020af4 Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Mon, 8 Jun 2026 19:21:13 -0400 Subject: [PATCH 05/13] feat: add multi-platform plugin manifests Claude Code, Cursor, and Gemini CLI manifests following the superpowers multi-manifest pattern. Updates .mcp.json to reference the container image. Restructures skill directory layout. Removes openpackage.yml and legacy install docs. Refs: #24 Assisted-by: Claude (Anthropic, Claude Opus 4.6) Signed-off-by: Jennifer Power --- .claude-plugin/plugin.json | 21 +++ .cursor-plugin/plugin.json | 20 +++ .mcp.json | 8 +- gemini-extension.json | 5 + skills/complypack/INSTALL.md | 162 ------------------------ skills/complypack/{skills => }/SKILL.md | 0 skills/complypack/openpackage.yml | 27 ---- 7 files changed, 52 insertions(+), 191 deletions(-) create mode 100644 .claude-plugin/plugin.json create mode 100644 .cursor-plugin/plugin.json create mode 100644 gemini-extension.json delete mode 100644 skills/complypack/INSTALL.md rename skills/complypack/{skills => }/SKILL.md (100%) delete mode 100644 skills/complypack/openpackage.yml diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..e491e87 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,21 @@ +{ + "name": "complypack", + "displayName": "ComplyPack", + "version": "2.0.0", + "description": "Generate Rego policies from Gemara catalogs and extract assessment requirements via MCP server", + "author": { + "name": "Jennifer Power", + "url": "https://github.com/complytime" + }, + "homepage": "https://github.com/complytime/complypack", + "repository": "https://github.com/complytime/complypack", + "license": "Apache-2.0", + "keywords": [ + "compliance", + "rego", + "opa", + "gemara", + "policy", + "mcp" + ] +} diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json new file mode 100644 index 0000000..4ebc985 --- /dev/null +++ b/.cursor-plugin/plugin.json @@ -0,0 +1,20 @@ +{ + "name": "complypack", + "displayName": "ComplyPack", + "version": "2.0.0", + "description": "Generate Rego policies from Gemara catalogs and extract assessment requirements via MCP server", + "author": { + "name": "Jennifer Power", + "url": "https://github.com/complytime" + }, + "repository": "https://github.com/complytime/complypack", + "license": "Apache-2.0", + "keywords": [ + "compliance", + "rego", + "opa", + "gemara", + "policy", + "mcp" + ] +} diff --git a/.mcp.json b/.mcp.json index 4c1fac4..0f4cc16 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,8 +1,12 @@ { "mcpServers": { "complypack": { - "command": "/tmp/complypack", - "args": ["mcp", "serve", "--config", "/tmp/mcp-test/complypack.yaml"] + "command": "docker", + "args": ["run", "--rm", "-i", + "ghcr.io/complytime/complypack:latest", + "mcp", "serve", + "--source", "oci://YOUR_REGISTRY/gemara/YOUR_CATALOG:TAG", + "--schema", "YOUR_PLATFORM"] } } } diff --git a/gemini-extension.json b/gemini-extension.json new file mode 100644 index 0000000..f70eb41 --- /dev/null +++ b/gemini-extension.json @@ -0,0 +1,5 @@ +{ + "name": "complypack", + "description": "Generate Rego policies from Gemara catalogs and extract assessment requirements via MCP server", + "version": "2.0.0" +} diff --git a/skills/complypack/INSTALL.md b/skills/complypack/INSTALL.md deleted file mode 100644 index 30ed835..0000000 --- a/skills/complypack/INSTALL.md +++ /dev/null @@ -1,162 +0,0 @@ -# Installing the Gemara Policy Generation Skill - -This skill works with any AI agent that can: -- Read markdown documentation -- Access MCP servers or file-based catalogs -- Generate Rego code -- Write files to disk - -## Quick Install (Recommended) - -### Using OpenPackage (OPKG) - -**Prerequisites:** Package must be published to local registry first: - -```bash -cd /path/to/complypack/skills/generating-gemara-policies -opkg publish --local -``` - -**Install for Claude Code:** -```bash -cd /path/to/your/project -opkg install generating-gemara-policies --platforms claude -``` - -**Install for Cursor:** -```bash -opkg install generating-gemara-policies --platforms cursor -``` - -**Install for Windsurf:** -```bash -opkg install generating-gemara-policies --platforms windsurf -``` - -**Learn more:** [OpenPackage Documentation](https://github.com/enulus/OpenPackage) - -## Manual Installation by Platform - -### Claude Code (Anthropic) - -**Location:** `~/.claude/skills/generating-gemara-policies/` - -```bash -mkdir -p ~/.claude/skills/generating-gemara-policies -cp skills/generating-gemara-policies/SKILL.md ~/.claude/skills/generating-gemara-policies/ -``` - -Claude Code automatically discovers skills in `~/.claude/skills/`. - -**Verify:** -```bash -ls ~/.claude/skills/generating-gemara-policies/SKILL.md -``` - -### Cursor - -**Location:** `.cursor/skills/generating-gemara-policies/` (project-level) - -```bash -mkdir -p .cursor/skills/generating-gemara-policies -cp skills/generating-gemara-policies/SKILL.md .cursor/skills/generating-gemara-policies/ -``` - -May require explicit `@` mention: `@generating-gemara-policies` - -### Windsurf - -**Location:** `.windsurf/skills/generating-gemara-policies/` (project-level) - -```bash -mkdir -p .windsurf/skills/generating-gemara-policies -cp skills/generating-gemara-policies/SKILL.md .windsurf/skills/generating-gemara-policies/ -``` - -### Other AI Agents - -**For agents that support skill directories:** -Check your agent's documentation for the skills directory location, then copy `SKILL.md` there. - -**For web-based AI without skill auto-loading:** - -1. Copy skill content to your prompt -2. Prepend to request: - -``` -I have a skill for generating Rego policies from Gemara controls. - -[Paste SKILL.md content here] - -Now, using this skill: Generate a policy for AC-1 targeting Kubernetes using Conftest. -``` - -## Project-Level Installation - -To include this skill in your project for team sharing: - -```bash -cd /path/to/your/project - -# For Claude Code projects -mkdir -p .claude/skills -cp /path/to/complypack/skills/generating-gemara-policies/SKILL.md \ - .claude/skills/generating-gemara-policies/ - -# For Cursor projects -mkdir -p .cursor/skills -cp /path/to/complypack/skills/generating-gemara-policies/SKILL.md \ - .cursor/skills/generating-gemara-policies/ - -git add .claude/skills/ .cursor/skills/ -git commit -m "Add Gemara policy generation skill" -``` - -## Verification - -After installation, test the skill is accessible: - -**For agents with CLI:** -```bash - --help # Should show skills if supported -``` - -**For all agents:** -Ask the AI: -``` -Do you have access to a skill called "generating-gemara-policies"? -``` - -If yes, it should describe the skill's purpose. - -## Troubleshooting - -**Skill not found:** -- Check skill is in correct directory for your platform -- Verify SKILL.md has proper frontmatter (name, description) -- Restart the AI agent/IDE - -**Skill doesn't execute correctly:** -- Ensure MCP server is configured (for ComplyPack integration) -- Check agent has file write permissions -- Verify platform schemas are accessible - -## Updating the Skill - -When the skill is updated in the ComplyPack repo: - -```bash -cd /path/to/complypack -git pull - -# Re-copy to your platform's skills directory -cp skills/generating-gemara-policies/SKILL.md ~/.claude/skills/generating-gemara-policies/ -# or -cp skills/generating-gemara-policies/SKILL.md .cursor/skills/generating-gemara-policies/ -# etc. -``` - -Or use OpenPackage to update: -```bash -opkg update generating-gemara-policies -``` diff --git a/skills/complypack/skills/SKILL.md b/skills/complypack/SKILL.md similarity index 100% rename from skills/complypack/skills/SKILL.md rename to skills/complypack/SKILL.md diff --git a/skills/complypack/openpackage.yml b/skills/complypack/openpackage.yml deleted file mode 100644 index 911af55..0000000 --- a/skills/complypack/openpackage.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: complypack -version: 2.0.0 -type: skill -description: Generate policies from Gemara catalogs and extract assessment requirements with ComplyPack MCP server -author: Jennifer Power -license: Apache-2.0 - -repository: - type: git - url: https://github.com/complytime/complypack - -keywords: - - rego - - opa - - conftest - - gemara - - compliance - - policy - - kubernetes - - terraform - - docker - - complypack - -files: - - "skills/SKILL.md" - - "INSTALL.md" - - "mcp.jsonc" From 1ee43bd0e8289e61607c78be37c307b8f87079f8 Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Mon, 8 Jun 2026 19:23:38 -0400 Subject: [PATCH 06/13] docs: add multi-platform install instructions Covers Claude Code, OpenCode, flag syntax, config file fallback, and image verification. Refs: #24 Assisted-by: Claude (Anthropic, Claude Opus 4.6) Signed-off-by: Jennifer Power --- INSTALL.md | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 INSTALL.md diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..b2326e9 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,107 @@ +# Installing ComplyPack + +ComplyPack is a plugin that provides a compliance policy generation skill and +an MCP server for working with Gemara catalogs. + +## Prerequisites + +- Docker or Podman (Fedora users: `sudo dnf install podman-docker`) + +## Claude Code + +Install from the marketplace: + +``` +/plugin install complypack@claude-plugins-official +``` + +The skill is auto-discovered. To configure the MCP server, create a +`.mcp.json` in your project: + +```json +{ + "mcpServers": { + "complypack": { + "command": "docker", + "args": ["run", "--rm", "-i", + "ghcr.io/complytime/complypack:latest", + "mcp", "serve", + "--source", "oci://your-registry/gemara/your-catalog:v1", + "--schema", "ci"] + } + } +} +``` + +Replace the `--source` and `--schema` values with your Gemara catalog +references and target platforms. + +### Multiple sources and schemas + +```json +"args": ["run", "--rm", "-i", + "ghcr.io/complytime/complypack:latest", + "mcp", "serve", + "--source", "oci://registry.example.com/gemara/controls:v1", + "--source", "oci://registry.example.com/gemara/guidance:v1", + "--schema", "ci=cue://cue.dev/x/githubactions@v0#Workflow", + "--schema", "kubernetes"] +``` + +### Plain HTTP registries (development) + +Use `oci+http://` for registries without TLS: + +```json +"--source", "oci+http://localhost:5001/gemara/controls:v1" +``` + +## OpenCode + +Add to your `opencode.json`: + +```json +{ + "mcpServers": { + "complypack": { + "command": "docker", + "args": ["run", "--rm", "-i", + "ghcr.io/complytime/complypack:latest", + "mcp", "serve", + "--source", "oci://your-registry/gemara/your-catalog:v1", + "--schema", "ci"] + } + } +} +``` + +## Using a config file (advanced) + +If you prefer YAML configuration, mount a `complypack.yaml`: + +```json +"args": ["run", "--rm", "-i", + "-v", "./complypack.yaml:/config/complypack.yaml:ro", + "ghcr.io/complytime/complypack:latest", + "mcp", "serve", + "--config", "/config/complypack.yaml"] +``` + +## Verifying the image + +Images include SLSA provenance and SBOM attestations. To verify: + +``` +gh attestation verify oci://ghcr.io/complytime/complypack:latest \ + --owner complytime +``` + +## Embedded schemas + +These platforms have built-in schemas (no `--schema source` needed): + +- `kubernetes` +- `terraform` +- `docker` +- `ansible` +- `ci` From 2378fea150a0277563077ca0b6260c2d3d6541e7 Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Mon, 8 Jun 2026 19:43:24 -0400 Subject: [PATCH 07/13] fix: add CA certificates to container image Copy CA certificates into the UBI micro container so the CUE registry (registry.cue.works) is reachable over TLS. Assisted-by: Claude (Anthropic, Claude Opus 4.6) Signed-off-by: Jennifer Power --- Containerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Containerfile b/Containerfile index 45289c5..e7a3eb3 100644 --- a/Containerfile +++ b/Containerfile @@ -8,6 +8,7 @@ RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o complypack ./cmd/comply FROM registry.access.redhat.com/ubi9-micro:latest +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt COPY --from=builder /build/complypack /usr/local/bin/complypack ENTRYPOINT ["complypack"] From 7a822ce10fada5f02d1b3192d8e80600f95b11cc Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Tue, 9 Jun 2026 19:47:07 -0400 Subject: [PATCH 08/13] fix: pin workflow refs and container base image Pin reusable workflow references to SHA and pin ubi9-micro to a versioned digest to satisfy zizmor and hadolint. Assisted-by: Claude (Anthropic, Claude Opus 4.6) Signed-off-by: Jennifer Power --- .github/workflows/container.yml | 4 ++-- Containerfile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index 862fe80..5bee7e6 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -12,7 +12,7 @@ permissions: jobs: push: - uses: complytime/org-infra/.github/workflows/reusable_publish_ghcr.yml@main + uses: complytime/org-infra/.github/workflows/reusable_publish_ghcr.yml@e266be092e71ac9343fcd6d5cafc50402161981e # main permissions: contents: read packages: write @@ -29,7 +29,7 @@ jobs: sign: needs: push - uses: complytime/org-infra/.github/workflows/reusable_sign_and_verify.yml@main + uses: complytime/org-infra/.github/workflows/reusable_sign_and_verify.yml@e266be092e71ac9343fcd6d5cafc50402161981e # main permissions: contents: read packages: write diff --git a/Containerfile b/Containerfile index e7a3eb3..5078596 100644 --- a/Containerfile +++ b/Containerfile @@ -6,7 +6,7 @@ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o complypack ./cmd/complypack -FROM registry.access.redhat.com/ubi9-micro:latest +FROM registry.access.redhat.com/ubi9-micro:9.6-4@sha256:b498b3ea26111ab4b81d65139f2ebd2ef9a2abb7a4588b7fdcc54889f95e9caa COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt COPY --from=builder /build/complypack /usr/local/bin/complypack From 888408612adbf411c7a3a8b411771f50050d4c74 Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Wed, 10 Jun 2026 13:53:39 -0400 Subject: [PATCH 09/13] chore: apply suggestions from code review Signed-off-by: Jennifer Power Co-authored-by: Jennifer Power --- .claude-plugin/plugin.json | 4 ++-- .cursor-plugin/plugin.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index e491e87..23e20fc 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,10 +1,10 @@ { "name": "complypack", "displayName": "ComplyPack", - "version": "2.0.0", + "version": "0.1.0", "description": "Generate Rego policies from Gemara catalogs and extract assessment requirements via MCP server", "author": { - "name": "Jennifer Power", + "name": "ComplyTime Authors", "url": "https://github.com/complytime" }, "homepage": "https://github.com/complytime/complypack", diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 4ebc985..bbc1c40 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -1,10 +1,10 @@ { "name": "complypack", "displayName": "ComplyPack", - "version": "2.0.0", + "version": "0.1.0", "description": "Generate Rego policies from Gemara catalogs and extract assessment requirements via MCP server", "author": { - "name": "Jennifer Power", + "name": "ComplyTime Authors", "url": "https://github.com/complytime" }, "repository": "https://github.com/complytime/complypack", From 38622a4f28be48f5d9c99d88740978d5ab1b6b6e Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Wed, 10 Jun 2026 13:54:03 -0400 Subject: [PATCH 10/13] chore: update gemini-extension.json Signed-off-by: Jennifer Power --- gemini-extension.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gemini-extension.json b/gemini-extension.json index f70eb41..554c82c 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,5 +1,5 @@ { "name": "complypack", "description": "Generate Rego policies from Gemara catalogs and extract assessment requirements via MCP server", - "version": "2.0.0" + "version": "0.1.0" } From 2627c971bd9e7010df3a03c6da12ae6e30c1fc35 Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Fri, 12 Jun 2026 13:08:19 -0400 Subject: [PATCH 11/13] fix: allow --schema flag without --source in mcp serve Previously, passing only --schema without --source fell through to config file loading and failed. Now either flag triggers the flags-based config path. Assisted-by: Claude (Anthropic, Claude Opus 4.6) Signed-off-by: Jennifer Power --- cmd/complypack/cli/mcp.go | 4 ++-- cmd/complypack/cli/mcp_test.go | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/cmd/complypack/cli/mcp.go b/cmd/complypack/cli/mcp.go index 0770815..98f38a3 100644 --- a/cmd/complypack/cli/mcp.go +++ b/cmd/complypack/cli/mcp.go @@ -74,8 +74,8 @@ The server runs until interrupted (Ctrl+C) or the client disconnects.`, CacheDir: resolvedCacheDir, } - // If --source flags are present, build config from flags - if len(sources) > 0 { + // If any CLI flags are present, build config from flags + if len(sources) > 0 || len(schemas) > 0 { cfg, err := buildConfigFromFlags(sources, schemas) if err != nil { return fmt.Errorf("failed to build config from flags: %w", err) diff --git a/cmd/complypack/cli/mcp_test.go b/cmd/complypack/cli/mcp_test.go index f9dfb7d..07ff047 100644 --- a/cmd/complypack/cli/mcp_test.go +++ b/cmd/complypack/cli/mcp_test.go @@ -155,6 +155,19 @@ func TestBuildConfigFromFlags(t *testing.T) { Schemas: nil, }, }, + { + name: "schema only without sources", + sources: nil, + schemas: []string{"kubernetes"}, + want: &config.ComplyPackConfig{ + Gemara: config.GemaraConfig{ + Sources: nil, + }, + Schemas: []config.SchemaRef{ + {Platform: "kubernetes"}, + }, + }, + }, { name: "invalid source", sources: []string{""}, From 3a493693554400343c372ffa427b2174e9ae40c6 Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Fri, 12 Jun 2026 13:39:46 -0400 Subject: [PATCH 12/13] fix: rename .mcp.json to .mcp.json.example Prevents auto-loading a broken config with placeholder values and :latest tag. Users copy and fill in their own registry, source, and pinned version. Assisted-by: Claude (Anthropic, Claude Opus 4.6) Signed-off-by: Jennifer Power --- .mcp.json => .mcp.json.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .mcp.json => .mcp.json.example (82%) diff --git a/.mcp.json b/.mcp.json.example similarity index 82% rename from .mcp.json rename to .mcp.json.example index 0f4cc16..e8694a4 100644 --- a/.mcp.json +++ b/.mcp.json.example @@ -3,7 +3,7 @@ "complypack": { "command": "docker", "args": ["run", "--rm", "-i", - "ghcr.io/complytime/complypack:latest", + "ghcr.io/complytime/complypack:VERSION", "mcp", "serve", "--source", "oci://YOUR_REGISTRY/gemara/YOUR_CATALOG:TAG", "--schema", "YOUR_PLATFORM"] From d4818091c6b33be5d7227f573334174e89852dc3 Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Fri, 12 Jun 2026 18:38:37 -0400 Subject: [PATCH 13/13] fix: address container workflow review feedback - Fix allowed_identity_regex to match org-infra reusable workflow origin - Add Trivy image scan stage between build and sign - Gate signing on scan success via verify_vuln - Run container as non-root user (ARG USER_UID=10001) Assisted-by: Claude (Anthropic, Claude Opus 4.6) Signed-off-by: Jennifer Power --- .github/workflows/container.yml | 26 +++++++++++++++++++++++--- Containerfile | 3 +++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index 5bee7e6..e16cb9b 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -27,8 +27,28 @@ jobs: image_description: "ComplyPack MCP server for compliance policy generation" platforms: linux/amd64,linux/arm64 - sign: + scan: needs: push + if: >- + always() + && needs.push.result == 'success' + && needs.push.outputs.image != '' + uses: complytime/org-infra/.github/workflows/reusable_trivy_image_scan.yml@e266be092e71ac9343fcd6d5cafc50402161981e # main + permissions: + contents: read + packages: write + security-events: write + id-token: write + with: + image_ref: ${{ needs.push.outputs.image }}:${{ needs.push.outputs.tag }} + image_digest: ${{ needs.push.outputs.digest }} + trivy_severity: HIGH,CRITICAL + + sign: + needs: [push, scan] + if: >- + always() + && needs.push.result == 'success' uses: complytime/org-infra/.github/workflows/reusable_sign_and_verify.yml@e266be092e71ac9343fcd6d5cafc50402161981e # main permissions: contents: read @@ -37,5 +57,5 @@ jobs: with: image_name: ${{ needs.push.outputs.image }} digest: ${{ needs.push.outputs.digest }} - allowed_identity_regex: "https://github.com/complytime/.*" - verify_vuln: false + allowed_identity_regex: "https://github.com/complytime/org-infra(/.*)?$" + verify_vuln: ${{ needs.scan.result == 'success' }} diff --git a/Containerfile b/Containerfile index 5078596..3f2f055 100644 --- a/Containerfile +++ b/Containerfile @@ -11,4 +11,7 @@ FROM registry.access.redhat.com/ubi9-micro:9.6-4@sha256:b498b3ea26111ab4b81d6513 COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt COPY --from=builder /build/complypack /usr/local/bin/complypack +ARG USER_UID=10001 +USER ${USER_UID} + ENTRYPOINT ["complypack"]