Files
noteflow/repomix-output.md
2026-01-22 04:40:05 +00:00

116 KiB

This file is a merged representation of a subset of the codebase, containing specifically included files and files not matching ignore patterns, combined into a single document by Repomix. The content has been processed where line numbers have been added, content has been formatted for parsing in markdown style.

File Summary

Purpose

This file contains a packed representation of a subset of the repository's contents that is considered the most important context. It is designed to be easily consumable by AI systems for analysis, code review, or other automated processes.

File Format

The content is organized as follows:

  1. This summary section
  2. Repository information
  3. Directory structure
  4. Repository files (if enabled)
  5. Multiple file entries, each consisting of: a. A header with the file path (## File: path/to/file) b. The full contents of the file in a code block

Usage Guidelines

  • This file should be treated as read-only. Any changes should be made to the original repository files, not this packed version.
  • When processing this file, use the file path to distinguish between different files in the repository.
  • Be aware that this file may contain sensitive information. Handle it with the same level of security as you would the original repository.

Notes

  • Some files may have been excluded based on .gitignore rules and Repomix's configuration
  • Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files
  • Only files matching these patterns are included: .cupcake/policies/opencode
  • Files matching these patterns are excluded: **/_pb2.py, **/_pb2_grpc.py, **/.pb2.py, **/.pb2_grpc.py, **/.pyi, **/.wav, **/.nfaudio, **/.m4a, **/.mp3, **/.mp4, **/.mov, **/.avi, **/.mkv, **/.flv, **/.wmv, **/.webm, **/.m3u8, **/noteflow.rs, **/noteflow_pb2.py, src/noteflow_pb2.py, client/src-tauri/src/grpc/noteflow.rs, src/noteflow/grpc/proto/noteflow_pb2.py, src/noteflow/grpc/proto/noteflow_pb2_grpc.py, src/noteflow/grpc/proto/noteflow_pb2.pyi, /persistence/migrations/, /node_modules/, /target/, /gen/, /pycache/, **/.pyc, /.pytest_cache/, /.mypy_cache/, /.ruff_cache/, /dist/, /build/, /.vite/, /coverage/, /htmlcov/, /playwright-report/, /test-results/, uv.lock, **/Cargo.lock, **/package-lock.json, **/bun.lockb, **/yarn.lock, **/.lock, **/.lockb, **/.png, **/.jpg, **/.jpeg, **/.gif, **/.ico, **/.svg, **/.icns, **/.webp, /.xml, /icons/, /public/, client/app-icon.png, **/.md, .benchmarks/, noteflow-api-spec.json, scratch.md, repomix-output.md, /logs/, **/status_line.json
  • Files matching patterns in .gitignore are excluded
  • Files matching default ignore patterns are excluded
  • Line numbers have been added to the beginning of each line
  • Content has been formatted for parsing in markdown style
  • Long base64 data strings (e.g., data:image/png;base64,...) have been truncated to reduce token count
  • Files are sorted by Git change count (files with more changes are at the bottom)

Directory Structure

.cupcake/
  policies/
    opencode/
      ban_stdlib_logger.rego
      block_assertion_roulette.rego
      block_biome_ignore_bash.rego
      block_biome_ignore.rego
      block_broad_exception_handler.rego
      block_code_quality_test_bash.rego
      block_code_quality_test_edits.rego
      block_code_quality_test_serena_plugin.rego
      block_code_quality_test_serena.rego
      block_datetime_now_fallback.rego
      block_default_value_swallow.rego
      block_duplicate_fixtures.rego
      block_linter_config_frontend_bash.rego
      block_linter_config_frontend.rego
      block_linter_config_python_bash.rego
      block_linter_config_python.rego
      block_magic_numbers.rego
      block_makefile_bash.rego
      block_makefile_edit.rego
      block_no_verify.rego
      block_silent_none_return.rego
      block_test_loops_conditionals.rego
      block_tests_quality_bash.rego
      block_tests_quality.rego
      prevent_any_type.rego
      prevent_type_suppression.rego
      warn_baselines_edit_bash.rego
      warn_baselines_edit.rego
      warn_large_file.rego

Files

File: .cupcake/policies/opencode/block_biome_ignore_bash.rego

 1: # METADATA
 2: # scope: package
 3: # title: Block Biome Ignore (Bash)
 4: # description: Blocks Bash commands that add ignore directives to JS/TS files
 5: # custom:
 6: #   routing:
 7: #     required_events: ["PreToolUse"]
 8: #     required_tools: ["Bash"]
 9: package cupcake.policies.opencode.block_biome_ignore_bash
10: import rego.v1
11: 
12: ignore_pattern := `(biome-ignore|@ts-ignore|@ts-expect-error|@ts-nocheck|eslint-disable).*\.(js|jsx|ts|tsx|mjs|cjs)`
13: 
14: deny contains decision if {
15:     input.hook_event_name == "PreToolUse"
16:     input.tool_name == "Bash"
17: 
18:     command := input.tool_input.command
19:     regex.match(ignore_pattern, command)
20: 
21:     decision := {
22:         "rule_id": "TS-LINT-001",
23:         "reason": "Ignore directives for Biome/TypeScript/ESLint are prohibited.",
24:         "severity": "HIGH"
25:     }
26: }

File: .cupcake/policies/opencode/block_code_quality_test_bash.rego

 1: # METADATA
 2: # scope: package
 3: # title: Block Code Quality Test (Bash)
 4: # description: Blocks Bash edits to src/test/code-quality.test.ts
 5: # custom:
 6: #   routing:
 7: #     required_events: ["PreToolUse"]
 8: #     required_tools: ["Bash"]
 9: package cupcake.policies.opencode.block_code_quality_test_bash
10: import rego.v1
11: 
12: pattern := `(sed|awk|cat\s*>|echo\s*>|tee|cp\s+.*code-quality\.test\.ts|mv\s+.*code-quality\.test\.ts|rm\s+.*code-quality\.test\.ts|>|>>).*code-quality\.test\.ts|code-quality\.test\.ts.*(>|>>|\|.*tee)`
13: 
14: deny contains decision if {
15:     input.hook_event_name == "PreToolUse"
16:     input.tool_name == "Bash"
17: 
18:     command := input.tool_input.command
19:     regex.match(pattern, command)
20: 
21:     decision := {
22:         "rule_id": "TS-QUALITY-001",
23:         "reason": "Direct edits to src/test/code-quality.test.ts are prohibited.",
24:         "severity": "HIGH"
25:     }
26: }

File: .cupcake/policies/opencode/block_code_quality_test_serena_plugin.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block Code Quality Test (Serena Plugin)
  4: # description: Blocks Serena plugin edits to src/test/code-quality.test.ts
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: []
  9: package cupcake.policies.opencode.block_code_quality_test_serena_plugin
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: file_path_pattern := `(^|/)src/test/code-quality\.test\.ts$`
102: 
103: get_relative_path := path if {
104:     path := tool_input.relative_path
105: } else := path if {
106:     path := tool_input.path
107: } else := ""
108: 
109: # Block Serena plugin operations targeting code-quality test file
110: 
111: deny contains decision if {
112:     input.hook_event_name == "PreToolUse"
113: 
114:     tool_names := {"mcp__plugin_serena_serena__replace_content", "mcp__plugin_serena_serena__replace_symbol_body", "mcp__plugin_serena_serena__create_text_file", "mcp__plugin_serena_serena__insert_before_symbol", "mcp__plugin_serena_serena__insert_after_symbol", "mcp__plugin_serena_serena__rename_symbol"}
115:     tool_name in tool_names
116: 
117:     file_path := get_relative_path
118:     regex.match(file_path_pattern, file_path)
119: 
120:     decision := {
121:         "rule_id": "TS-QUALITY-004",
122:         "reason": "Direct edits to src/test/code-quality.test.ts are prohibited.",
123:         "severity": "HIGH"
124:     }
125: }

File: .cupcake/policies/opencode/block_code_quality_test_serena.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block Code Quality Test (Serena)
  4: # description: Blocks Serena edits to src/test/code-quality.test.ts
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: []
  9: package cupcake.policies.opencode.block_code_quality_test_serena
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: file_path_pattern := `(^|/)src/test/code-quality\.test\.ts$`
102: 
103: get_relative_path := path if {
104:     path := tool_input.relative_path
105: } else := path if {
106:     path := tool_input.path
107: } else := ""
108: 
109: # Block Serena operations targeting code-quality test file
110: 
111: deny contains decision if {
112:     input.hook_event_name == "PreToolUse"
113: 
114:     tool_names := {"mcp__serena__replace_content", "mcp__serena__replace_symbol_body", "mcp__serena__create_text_file", "mcp__serena__insert_before_symbol", "mcp__serena__insert_after_symbol", "mcp__serena__rename_symbol"}
115:     tool_name in tool_names
116: 
117:     file_path := get_relative_path
118:     regex.match(file_path_pattern, file_path)
119: 
120:     decision := {
121:         "rule_id": "TS-QUALITY-003",
122:         "reason": "Direct edits to src/test/code-quality.test.ts are prohibited.",
123:         "severity": "HIGH"
124:     }
125: }

File: .cupcake/policies/opencode/block_linter_config_frontend_bash.rego

 1: # METADATA
 2: # scope: package
 3: # title: Block Frontend Linter Config (Bash)
 4: # description: Blocks Bash edits to frontend linter config files
 5: # custom:
 6: #   routing:
 7: #     required_events: ["PreToolUse"]
 8: #     required_tools: ["Bash"]
 9: package cupcake.policies.opencode.block_linter_config_frontend_bash
10: import rego.v1
11: 
12: pattern := `(rm|mv|cp|sed|awk|chmod|chown|touch|truncate|tee|>|>>)\s.*client/.*(?:biome\.json|tsconfig\.json|\.?eslint(?:rc|\.config)|\.?prettier(?:rc|\.config)|\.?rustfmt\.toml|\.?clippy\.toml)`
13: 
14: deny contains decision if {
15:     input.hook_event_name == "PreToolUse"
16:     input.tool_name == "Bash"
17: 
18:     command := input.tool_input.command
19:     regex.match(pattern, command)
20: 
21:     decision := {
22:         "rule_id": "TS-CONFIG-001",
23:         "reason": "Frontend linter/config file edits are prohibited.",
24:         "severity": "HIGH"
25:     }
26: }

File: .cupcake/policies/opencode/block_linter_config_python_bash.rego

 1: # METADATA
 2: # scope: package
 3: # title: Block Python Linter Config (Bash)
 4: # description: Blocks Bash edits to Python linter config files
 5: # custom:
 6: #   routing:
 7: #     required_events: ["PreToolUse"]
 8: #     required_tools: ["Bash"]
 9: package cupcake.policies.opencode.block_linter_config_python_bash
10: import rego.v1
11: 
12: pattern := `(rm|mv|cp|sed|awk|chmod|chown|touch|truncate|tee|>|>>)\s.*(?:pyproject\.toml|\.?ruff\.toml|\.?pyrightconfig\.json|\.?mypy\.ini|setup\.cfg|\.flake8|tox\.ini|\.?pylintrc)`
13: 
14: deny contains decision if {
15:     input.hook_event_name == "PreToolUse"
16:     input.tool_name == "Bash"
17: 
18:     command := input.tool_input.command
19:     regex.match(pattern, command)
20: 
21:     decision := {
22:         "rule_id": "PY-CONFIG-001",
23:         "reason": "Python linter/config file edits are prohibited.",
24:         "severity": "HIGH"
25:     }
26: }

File: .cupcake/policies/opencode/block_makefile_bash.rego

 1: # METADATA
 2: # scope: package
 3: # title: Block Makefile Edit (Bash)
 4: # description: Blocks Bash edits to Makefile
 5: # custom:
 6: #   routing:
 7: #     required_events: ["PreToolUse"]
 8: #     required_tools: ["Bash"]
 9: package cupcake.policies.opencode.block_makefile_bash
10: import rego.v1
11: 
12: pattern := `(>>?\s*Makefile|sed\s+.*-i.*Makefile|sed\s+-i.*Makefile|perl\s+-[pi].*Makefile|tee\s+.*Makefile|(mv|cp)\s+\S+\s+Makefile\b|>\s*Makefile)`
13: 
14: deny contains decision if {
15:     input.hook_event_name == "PreToolUse"
16:     input.tool_name == "Bash"
17: 
18:     command := input.tool_input.command
19:     regex.match(pattern, command)
20: 
21:     decision := {
22:         "rule_id": "BUILD-001",
23:         "reason": "Makefile edits are prohibited.",
24:         "severity": "HIGH"
25:     }
26: }

File: .cupcake/policies/opencode/block_no_verify.rego

 1: # METADATA
 2: # scope: package
 3: # title: Block Git --no-verify
 4: # description: Blocks git commit --no-verify
 5: # custom:
 6: #   routing:
 7: #     required_events: ["PreToolUse"]
 8: #     required_tools: ["Bash"]
 9: package cupcake.policies.opencode.block_no_verify
10: import rego.v1
11: 
12: pattern := `git\s+commit\s+.*--no-verify|git\s+commit\s+--no-verify`
13: 
14: deny contains decision if {
15:     input.hook_event_name == "PreToolUse"
16:     input.tool_name == "Bash"
17: 
18:     command := input.tool_input.command
19:     regex.match(pattern, command)
20: 
21:     decision := {
22:         "rule_id": "GIT-001",
23:         "reason": "Git commit --no-verify is prohibited.",
24:         "severity": "HIGH"
25:     }
26: }

File: .cupcake/policies/opencode/block_tests_quality_bash.rego

 1: # METADATA
 2: # scope: package
 3: # title: Block Tests Quality (Bash)
 4: # description: Blocks Bash edits to tests/quality (except baselines.json)
 5: # custom:
 6: #   routing:
 7: #     required_events: ["PreToolUse"]
 8: #     required_tools: ["Bash"]
 9: package cupcake.policies.opencode.block_tests_quality_bash
10: import rego.v1
11: 
12: pattern := `(rm|mv|cp|sed|awk|chmod|chown|touch|mkdir|rmdir|truncate|tee|>|>>)\s.*tests/quality/`
13: 
14: deny contains decision if {
15:     input.hook_event_name == "PreToolUse"
16:     input.tool_name == "Bash"
17: 
18:     command := input.tool_input.command
19:     regex.match(pattern, command)
20:     not contains(lower(command), "tests/quality/baselines.json")
21: 
22:     decision := {
23:         "rule_id": "TEST-QUALITY-001",
24:         "reason": "Direct edits to tests/quality are prohibited (except baselines.json).",
25:         "severity": "HIGH"
26:     }
27: }

File: .cupcake/policies/opencode/warn_baselines_edit_bash.rego

 1: # METADATA
 2: # scope: package
 3: # title: Warn on Baselines Edit (Bash)
 4: # description: Warns on Bash edits to tests/quality/baselines.json
 5: # custom:
 6: #   routing:
 7: #     required_events: ["PreToolUse"]
 8: #     required_tools: ["Bash"]
 9: package cupcake.policies.opencode.warn_baselines_edit_bash
10: import rego.v1
11: 
12: pattern := `(sed|awk|echo|cat|tee|>|>>|cp|mv).*tests/quality/baselines\.json`
13: 
14: deny contains decision if {
15:     input.hook_event_name == "PreToolUse"
16:     input.tool_name == "Bash"
17: 
18:     command := input.tool_input.command
19:     regex.match(pattern, command)
20: 
21:     decision := {
22:         "rule_id": "TEST-QUALITY-003",
23:         "reason": "Warning: editing tests/quality/baselines.json should be avoided unless explicitly required.",
24:         "severity": "LOW"
25:     }
26: }

File: .cupcake/policies/opencode/ban_stdlib_logger.rego

  1: # METADATA
  2: # scope: package
  3: # title: Ban Stdlib Logger
  4: # description: Blocks use of stdlib logging in Python code
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.ban_stdlib_logger
 10: 
 11: import rego.v1
 12: 
 13: tool_name := input.tool_name if {
 14:     input.tool_name != null
 15: } else := input.tool
 16: 
 17: tool_input := input.tool_input if {
 18:     input.tool_input != null
 19: } else := input.args
 20: 
 21: resolved_file_path := input.resolved_file_path if {
 22:     input.resolved_file_path != null
 23: } else := tool_input.file_path if {
 24:     tool_input.file_path != null
 25: } else := tool_input.filePath if {
 26:     tool_input.filePath != null
 27: } else := tool_input.path if {
 28:     tool_input.path != null
 29: } else := tool_input.notebook_path if {
 30:     tool_input.notebook_path != null
 31: } else := tool_input.notebookPath if {
 32:     tool_input.notebookPath != null
 33: } else := ""
 34: 
 35: new_content := tool_input.new_string if {
 36:     tool_input.new_string != null
 37: } else := tool_input.newText if {
 38:     tool_input.newText != null
 39: } else := tool_input.new_text if {
 40:     tool_input.new_text != null
 41: } else := tool_input.content if {
 42:     tool_input.content != null
 43: } else := ""
 44: 
 45: old_content := tool_input.old_string if {
 46:     tool_input.old_string != null
 47: } else := tool_input.oldText if {
 48:     tool_input.oldText != null
 49: } else := tool_input.old_text if {
 50:     tool_input.old_text != null
 51: } else := tool_input.previousContent if {
 52:     tool_input.previousContent != null
 53: } else := ""
 54: 
 55: patch_content := tool_input.patch if {
 56:     tool_input.patch != null
 57: } else := tool_input.patchText if {
 58:     tool_input.patchText != null
 59: } else := tool_input.patch_text if {
 60:     tool_input.patch_text != null
 61: } else := ""
 62: 
 63: edit_path(edit) := path if {
 64:     edit.resolved_file_path != null
 65:     path := edit.resolved_file_path
 66: } else := path if {
 67:     edit.file_path != null
 68:     path := edit.file_path
 69: } else := path if {
 70:     edit.filePath != null
 71:     path := edit.filePath
 72: } else := path if {
 73:     edit.path != null
 74:     path := edit.path
 75: } else := ""
 76: 
 77: edit_new_content(edit) := content if {
 78:     edit.new_string != null
 79:     content := edit.new_string
 80: } else := content if {
 81:     edit.newText != null
 82:     content := edit.newText
 83: } else := content if {
 84:     edit.new_text != null
 85:     content := edit.new_text
 86: } else := content if {
 87:     edit.content != null
 88:     content := edit.content
 89: } else := ""
 90: 
 91: edit_old_content(edit) := content if {
 92:     edit.old_string != null
 93:     content := edit.old_string
 94: } else := content if {
 95:     edit.oldText != null
 96:     content := edit.oldText
 97: } else := content if {
 98:     edit.old_text != null
 99:     content := edit.old_text
100: } else := ""
101: 
102: is_python_patch(patch_text) if {
103:     contains(patch_text, ".py")
104: }
105: 
106: is_python_patch(patch_text) if {
107:     contains(patch_text, ".pyi")
108: }
109: 
110: stdlib_logger_pattern := `import logging|from logging import|logging\.getLogger`
111: file_path_pattern := `\.py$`
112: 
113: # Block Write/Edit operations that introduce stdlib logging
114: 
115: deny contains decision if {
116:     input.hook_event_name == "PreToolUse"
117:     tool_name in {"Write", "Edit", "NotebookEdit"}
118: 
119:     file_path := resolved_file_path
120:     regex.match(file_path_pattern, file_path)
121: 
122:     content := new_content
123:     content != null
124:     regex.match(stdlib_logger_pattern, content)
125: 
126:     decision := {
127:         "rule_id": "PY-LOG-001",
128:         "reason": "Stdlib logging usage is prohibited. Use the project logging utilities instead.",
129:         "severity": "HIGH"
130:     }
131: }
132: 
133: deny contains decision if {
134:     input.hook_event_name == "PreToolUse"
135:     tool_name == "MultiEdit"
136: 
137:     some edit in tool_input.edits
138:     file_path := edit_path(edit)
139:     regex.match(file_path_pattern, file_path)
140: 
141:     content := edit_new_content(edit)
142:     content != null
143:     regex.match(stdlib_logger_pattern, content)
144: 
145:     decision := {
146:         "rule_id": "PY-LOG-001",
147:         "reason": "Stdlib logging usage is prohibited. Use the project logging utilities instead.",
148:         "severity": "HIGH"
149:     }
150: }
151: 
152: deny contains decision if {
153:     input.hook_event_name == "PreToolUse"
154:     tool_name in {"Patch", "Apply_Patch"}
155: 
156:     patch := patch_content
157:     patch != null
158: 
159:     lower_patch := lower(patch)
160:     is_python_patch(lower_patch)
161:     regex.match(stdlib_logger_pattern, patch)
162: 
163:     decision := {
164:         "rule_id": "PY-LOG-001",
165:         "reason": "Stdlib logging usage is prohibited. Use the project logging utilities instead.",
166:         "severity": "HIGH"
167:     }
168: }

File: .cupcake/policies/opencode/block_assertion_roulette.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block Assertion Roulette
  4: # description: Blocks multiple bare asserts in a single test without messages
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.block_assertion_roulette
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: 
102: patch_targets_path(pattern) if {
103:     patch := patch_content
104:     patch != null
105:     lines := split(patch, "\n")
106:     some line in lines
107:     startswith(line, "+++ b/")
108:     path := replace(line, "+++ b/", "")
109:     regex.match(pattern, path)
110: }
111: 
112: patch_targets_path(pattern) if {
113:     patch := patch_content
114:     patch != null
115:     lines := split(patch, "\n")
116:     some line in lines
117:     startswith(line, "--- a/")
118:     path := replace(line, "--- a/", "")
119:     regex.match(pattern, path)
120: }
121: 
122: file_path_pattern := `tests?/.*\.py$`
123: assertion_pattern := `^\s*assert\s+[^,\n]+\n\s*assert\s+[^,\n]+$`
124: 
125: # Block Write/Edit operations that introduce assertion roulette
126: 
127: deny contains decision if {
128:     input.hook_event_name == "PreToolUse"
129:     tool_name in {"Write", "Edit", "NotebookEdit"}
130: 
131:     file_path := resolved_file_path
132:     regex.match(file_path_pattern, file_path)
133: 
134:     content := new_content
135:     content != null
136:     regex.match(assertion_pattern, content)
137: 
138:     decision := {
139:         "rule_id": "TEST-ASSERT-001",
140:         "reason": "Multiple bare asserts detected. Use one assert per test or add assertion messages.",
141:         "severity": "HIGH"
142:     }
143: }
144: 
145: deny contains decision if {
146:     input.hook_event_name == "PreToolUse"
147:     tool_name == "MultiEdit"
148: 
149:     some edit in tool_input.edits
150:     file_path := edit_path(edit)
151:     regex.match(file_path_pattern, file_path)
152: 
153:     content := edit_new_content(edit)
154:     content != null
155:     regex.match(assertion_pattern, content)
156: 
157:     decision := {
158:         "rule_id": "TEST-ASSERT-001",
159:         "reason": "Multiple bare asserts detected. Use one assert per test or add assertion messages.",
160:         "severity": "HIGH"
161:     }
162: }
163: 
164: deny contains decision if {
165:     input.hook_event_name == "PreToolUse"
166:     tool_name in {"Patch", "Apply_Patch"}
167: 
168:     patch := patch_content
169:     patch != null
170: 
171:     patch_targets_path(file_path_pattern)
172: 
173:     regex.match(assertion_pattern, patch)
174: 
175:     decision := {
176:             "rule_id": "TEST-ASSERT-001",
177:             "reason": "Multiple bare asserts detected. Use one assert per test or add assertion messages.",
178:             "severity": "HIGH"
179:         }
180: }

File: .cupcake/policies/opencode/block_biome_ignore.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block Biome Ignore
  4: # description: Blocks ignore directives in JS/TS files
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.block_biome_ignore
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: 
102: patch_targets_path(pattern) if {
103:     patch := patch_content
104:     patch != null
105:     lines := split(patch, "\n")
106:     some line in lines
107:     startswith(line, "+++ b/")
108:     path := replace(line, "+++ b/", "")
109:     regex.match(pattern, path)
110: }
111: 
112: patch_targets_path(pattern) if {
113:     patch := patch_content
114:     patch != null
115:     lines := split(patch, "\n")
116:     some line in lines
117:     startswith(line, "--- a/")
118:     path := replace(line, "--- a/", "")
119:     regex.match(pattern, path)
120: }
121: 
122: file_path_pattern := `\.(js|jsx|ts|tsx|mjs|cjs)$`
123: ignore_pattern := `//\s*biome-ignore|//\s*@ts-ignore|//\s*@ts-expect-error|//\s*@ts-nocheck|//\s*eslint-disable|/\*\s*eslint-disable`
124: 
125: # Block Write/Edit operations that introduce ignore directives
126: 
127: deny contains decision if {
128:     input.hook_event_name == "PreToolUse"
129:     tool_name in {"Write", "Edit", "NotebookEdit"}
130: 
131:     file_path := resolved_file_path
132:     regex.match(file_path_pattern, file_path)
133: 
134:     content := new_content
135:     content != null
136:     regex.match(ignore_pattern, content)
137: 
138:     decision := {
139:         "rule_id": "TS-LINT-002",
140:         "reason": "Ignore directives for Biome/TypeScript/ESLint are prohibited.",
141:         "severity": "HIGH"
142:     }
143: }
144: 
145: deny contains decision if {
146:     input.hook_event_name == "PreToolUse"
147:     tool_name == "MultiEdit"
148: 
149:     some edit in tool_input.edits
150:     file_path := edit_path(edit)
151:     regex.match(file_path_pattern, file_path)
152: 
153:     content := edit_new_content(edit)
154:     content != null
155:     regex.match(ignore_pattern, content)
156: 
157:     decision := {
158:         "rule_id": "TS-LINT-002",
159:         "reason": "Ignore directives for Biome/TypeScript/ESLint are prohibited.",
160:         "severity": "HIGH"
161:     }
162: }
163: 
164: deny contains decision if {
165:     input.hook_event_name == "PreToolUse"
166:     tool_name in {"Patch", "Apply_Patch"}
167: 
168:     patch := patch_content
169:     patch != null
170: 
171:     patch_targets_path(file_path_pattern)
172: 
173:     regex.match(ignore_pattern, patch)
174: 
175:     decision := {
176:             "rule_id": "TS-LINT-002",
177:             "reason": "Ignore directives for Biome/TypeScript/ESLint are prohibited.",
178:             "severity": "HIGH"
179:         }
180: }

File: .cupcake/policies/opencode/block_broad_exception_handler.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block Broad Exception Handler
  4: # description: Blocks bare Exception handlers that only log
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.block_broad_exception_handler
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: handler_pattern := `except\s+Exception\s*(?:as\s+\w+)?:\s*\n\s+(?:logger\.|logging\.)`
102: 
103: # Block Write/Edit operations that introduce broad exception handlers
104: 
105: deny contains decision if {
106:     input.hook_event_name == "PreToolUse"
107:     tool_name in {"Write", "Edit", "NotebookEdit"}
108: 
109:     content := new_content
110:     content != null
111:     regex.match(handler_pattern, content)
112: 
113:     decision := {
114:         "rule_id": "PY-EXC-001",
115:         "reason": "Broad Exception handlers that only log are prohibited.",
116:         "severity": "HIGH"
117:     }
118: }
119: 
120: deny contains decision if {
121:     input.hook_event_name == "PreToolUse"
122:     tool_name == "MultiEdit"
123: 
124:     some edit in tool_input.edits
125:     content := edit_new_content(edit)
126:     content != null
127:     regex.match(handler_pattern, content)
128: 
129:     decision := {
130:         "rule_id": "PY-EXC-001",
131:         "reason": "Broad Exception handlers that only log are prohibited.",
132:         "severity": "HIGH"
133:     }
134: }
135: 
136: deny contains decision if {
137:     input.hook_event_name == "PreToolUse"
138:     tool_name in {"Patch", "Apply_Patch"}
139: 
140:     patch := patch_content
141:     patch != null
142: 
143: 
144:     regex.match(handler_pattern, patch)
145: 
146:     decision := {
147:             "rule_id": "PY-EXC-001",
148:             "reason": "Broad Exception handlers that only log are prohibited.",
149:             "severity": "HIGH"
150:         }
151: }

File: .cupcake/policies/opencode/block_code_quality_test_edits.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block Code Quality Test (Edits)
  4: # description: Blocks file edits to src/test/code-quality.test.ts
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.block_code_quality_test_edits
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: file_path_pattern := `src/test/code-quality\.test\.ts$`
102: 
103: # Block Write/Edit operations targeting code-quality test file
104: 
105: deny contains decision if {
106:     input.hook_event_name == "PreToolUse"
107:     tool_name in {"Write", "Edit", "NotebookEdit"}
108: 
109:     file_path := resolved_file_path
110:     regex.match(file_path_pattern, file_path)
111: 
112:     decision := {
113:         "rule_id": "TS-QUALITY-002",
114:         "reason": "Direct edits to src/test/code-quality.test.ts are prohibited.",
115:         "severity": "HIGH"
116:     }
117: }
118: 
119: deny contains decision if {
120:     input.hook_event_name == "PreToolUse"
121:     tool_name == "MultiEdit"
122: 
123:     some edit in tool_input.edits
124:     file_path := edit_path(edit)
125:     regex.match(file_path_pattern, file_path)
126: 
127:     decision := {
128:         "rule_id": "TS-QUALITY-002",
129:         "reason": "Direct edits to src/test/code-quality.test.ts are prohibited.",
130:         "severity": "HIGH"
131:     }
132: }

File: .cupcake/policies/opencode/block_datetime_now_fallback.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block datetime.now Fallback
  4: # description: Blocks returning datetime.now() as a fallback
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.block_datetime_now_fallback
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: pattern := `return\s+datetime\.now\s*\(`
102: 
103: # Block Write/Edit operations that introduce datetime.now fallback
104: 
105: deny contains decision if {
106:     input.hook_event_name == "PreToolUse"
107:     tool_name in {"Write", "Edit", "NotebookEdit"}
108: 
109:     content := new_content
110:     content != null
111:     regex.match(pattern, content)
112: 
113:     decision := {
114:         "rule_id": "PY-DT-001",
115:         "reason": "Returning datetime.now() as a fallback is prohibited. Use a caller-provided timestamp.",
116:         "severity": "HIGH"
117:     }
118: }
119: 
120: deny contains decision if {
121:     input.hook_event_name == "PreToolUse"
122:     tool_name == "MultiEdit"
123: 
124:     some edit in tool_input.edits
125:     content := edit_new_content(edit)
126:     content != null
127:     regex.match(pattern, content)
128: 
129:     decision := {
130:         "rule_id": "PY-DT-001",
131:         "reason": "Returning datetime.now() as a fallback is prohibited. Use a caller-provided timestamp.",
132:         "severity": "HIGH"
133:     }
134: }
135: 
136: deny contains decision if {
137:     input.hook_event_name == "PreToolUse"
138:     tool_name in {"Patch", "Apply_Patch"}
139: 
140:     patch := patch_content
141:     patch != null
142: 
143: 
144:     regex.match(pattern, patch)
145: 
146:     decision := {
147:             "rule_id": "PY-DT-001",
148:             "reason": "Returning datetime.now() as a fallback is prohibited. Use a caller-provided timestamp.",
149:             "severity": "HIGH"
150:         }
151: }

File: .cupcake/policies/opencode/block_default_value_swallow.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block Default Value Swallow
  4: # description: Blocks exception handlers that warn and return defaults
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.block_default_value_swallow
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: pattern := `except\s+\w*(?:Error|Exception).*?:\s*\n\s+.*?(?:logger\.|logging\.).*?(?:warning|warn).*?\n\s+return\s+(?:\w+Settings|Defaults?\(|default_|\{[^}]*\}|[A-Z_]+_DEFAULT)`
102: 
103: # Block Write/Edit operations that swallow exceptions with defaults
104: 
105: deny contains decision if {
106:     input.hook_event_name == "PreToolUse"
107:     tool_name in {"Write", "Edit", "NotebookEdit"}
108: 
109:     content := new_content
110:     content != null
111:     regex.match(pattern, content)
112: 
113:     decision := {
114:         "rule_id": "PY-EXC-002",
115:         "reason": "Swallowing exceptions and returning defaults is prohibited.",
116:         "severity": "HIGH"
117:     }
118: }
119: 
120: deny contains decision if {
121:     input.hook_event_name == "PreToolUse"
122:     tool_name == "MultiEdit"
123: 
124:     some edit in tool_input.edits
125:     content := edit_new_content(edit)
126:     content != null
127:     regex.match(pattern, content)
128: 
129:     decision := {
130:         "rule_id": "PY-EXC-002",
131:         "reason": "Swallowing exceptions and returning defaults is prohibited.",
132:         "severity": "HIGH"
133:     }
134: }
135: 
136: deny contains decision if {
137:     input.hook_event_name == "PreToolUse"
138:     tool_name in {"Patch", "Apply_Patch"}
139: 
140:     patch := patch_content
141:     patch != null
142: 
143: 
144:     regex.match(pattern, patch)
145: 
146:     decision := {
147:             "rule_id": "PY-EXC-002",
148:             "reason": "Swallowing exceptions and returning defaults is prohibited.",
149:             "severity": "HIGH"
150:         }
151: }

File: .cupcake/policies/opencode/block_duplicate_fixtures.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block Duplicate Fixtures
  4: # description: Blocks redefining global pytest fixtures outside conftest.py
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.block_duplicate_fixtures
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: 
102: patch_targets_path(pattern) if {
103:     patch := patch_content
104:     patch != null
105:     lines := split(patch, "\n")
106:     some line in lines
107:     startswith(line, "+++ b/")
108:     path := replace(line, "+++ b/", "")
109:     regex.match(pattern, path)
110: }
111: 
112: patch_targets_path(pattern) if {
113:     patch := patch_content
114:     patch != null
115:     lines := split(patch, "\n")
116:     some line in lines
117:     startswith(line, "--- a/")
118:     path := replace(line, "--- a/", "")
119:     regex.match(pattern, path)
120: }
121: 
122: file_path_pattern := `tests?/.*\.py$`
123: conftest_pattern := `tests?/conftest\.py$`
124: fixture_pattern := `@pytest\.fixture[^@]*\ndef\s+(mock_uow|crypto|meetings_dir|webhook_config|webhook_config_all_events|sample_datetime|calendar_settings|meeting_id|sample_meeting|recording_meeting|mock_grpc_context|mock_asr_engine|mock_optional_extras)\s*\(`
125: 
126: # Block Write/Edit operations that introduce duplicate fixtures
127: 
128: deny contains decision if {
129:     input.hook_event_name == "PreToolUse"
130:     tool_name in {"Write", "Edit", "NotebookEdit"}
131: 
132:     file_path := resolved_file_path
133:     regex.match(file_path_pattern, file_path)
134:     not regex.match(conftest_pattern, file_path)
135: 
136:     content := new_content
137:     content != null
138:     regex.match(fixture_pattern, content)
139: 
140:     decision := {
141:         "rule_id": "TEST-FIX-001",
142:         "reason": "Duplicate global fixtures are prohibited. Use tests/conftest.py fixtures instead.",
143:         "severity": "HIGH"
144:     }
145: }
146: 
147: deny contains decision if {
148:     input.hook_event_name == "PreToolUse"
149:     tool_name == "MultiEdit"
150: 
151:     some edit in tool_input.edits
152:     file_path := edit_path(edit)
153:     regex.match(file_path_pattern, file_path)
154:     not regex.match(conftest_pattern, file_path)
155: 
156:     content := edit_new_content(edit)
157:     content != null
158:     regex.match(fixture_pattern, content)
159: 
160:     decision := {
161:         "rule_id": "TEST-FIX-001",
162:         "reason": "Duplicate global fixtures are prohibited. Use tests/conftest.py fixtures instead.",
163:         "severity": "HIGH"
164:     }
165: }
166: 
167: deny contains decision if {
168:     input.hook_event_name == "PreToolUse"
169:     tool_name in {"Patch", "Apply_Patch"}
170: 
171:     patch := patch_content
172:     patch != null
173: 
174:     patch_targets_path(file_path_pattern)
175:     not regex.match(conftest_pattern, patch)
176: 
177:     regex.match(fixture_pattern, patch)
178: 
179:     decision := {
180:             "rule_id": "TEST-FIX-001",
181:             "reason": "Duplicate global fixtures are prohibited. Use tests/conftest.py fixtures instead.",
182:             "severity": "HIGH"
183:         }
184: }

File: .cupcake/policies/opencode/block_linter_config_frontend.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block Frontend Linter Config
  4: # description: Blocks edits to frontend linter config files
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.block_linter_config_frontend
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: file_path_pattern := `(^|/)client/.*(?:\.?eslint(?:rc|\.config).*|\.?prettier(?:rc|\.config).*|biome\.json|tsconfig\.json|\.?rustfmt\.toml|\.?clippy\.toml)$`
102: 
103: # Block Write/Edit operations targeting frontend linter configs
104: 
105: deny contains decision if {
106:     input.hook_event_name == "PreToolUse"
107:     tool_name in {"Write", "Edit", "NotebookEdit"}
108: 
109:     file_path := resolved_file_path
110:     regex.match(file_path_pattern, file_path)
111:     not contains(lower(file_path), "node_modules/")
112: 
113:     decision := {
114:         "rule_id": "TS-CONFIG-002",
115:         "reason": "Frontend linter/config file edits are prohibited.",
116:         "severity": "HIGH"
117:     }
118: }
119: 
120: deny contains decision if {
121:     input.hook_event_name == "PreToolUse"
122:     tool_name == "MultiEdit"
123: 
124:     some edit in tool_input.edits
125:     file_path := edit_path(edit)
126:     regex.match(file_path_pattern, file_path)
127:     not contains(lower(file_path), "node_modules/")
128: 
129:     decision := {
130:         "rule_id": "TS-CONFIG-002",
131:         "reason": "Frontend linter/config file edits are prohibited.",
132:         "severity": "HIGH"
133:     }
134: }

File: .cupcake/policies/opencode/block_linter_config_python.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block Python Linter Config
  4: # description: Blocks edits to Python linter config files
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.block_linter_config_python
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: file_path_pattern := `(?:pyproject\.toml|\.?ruff\.toml|\.?pyrightconfig\.json|\.?mypy\.ini|setup\.cfg|\.flake8|tox\.ini|\.?pylintrc)$`
102: 
103: # Block Write/Edit operations targeting Python linter configs
104: 
105: deny contains decision if {
106:     input.hook_event_name == "PreToolUse"
107:     tool_name in {"Write", "Edit", "NotebookEdit"}
108: 
109:     file_path := resolved_file_path
110:     regex.match(file_path_pattern, file_path)
111:     not contains(lower(file_path), "/.venv/")
112: 
113:     decision := {
114:         "rule_id": "PY-CONFIG-002",
115:         "reason": "Python linter/config file edits are prohibited.",
116:         "severity": "HIGH"
117:     }
118: }
119: 
120: deny contains decision if {
121:     input.hook_event_name == "PreToolUse"
122:     tool_name == "MultiEdit"
123: 
124:     some edit in tool_input.edits
125:     file_path := edit_path(edit)
126:     regex.match(file_path_pattern, file_path)
127:     not contains(lower(file_path), "/.venv/")
128: 
129:     decision := {
130:         "rule_id": "PY-CONFIG-002",
131:         "reason": "Python linter/config file edits are prohibited.",
132:         "severity": "HIGH"
133:     }
134: }

File: .cupcake/policies/opencode/block_magic_numbers.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block Magic Numbers
  4: # description: Blocks introduction of magic numbers outside constants modules
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.block_magic_numbers
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: 
102: patch_targets_path(pattern) if {
103:     patch := patch_content
104:     patch != null
105:     lines := split(patch, "\n")
106:     some line in lines
107:     startswith(line, "+++ b/")
108:     path := replace(line, "+++ b/", "")
109:     regex.match(pattern, path)
110: }
111: 
112: patch_targets_path(pattern) if {
113:     patch := patch_content
114:     patch != null
115:     lines := split(patch, "\n")
116:     some line in lines
117:     startswith(line, "--- a/")
118:     path := replace(line, "--- a/", "")
119:     regex.match(pattern, path)
120: }
121: 
122: file_path_pattern := `\.(py|ts|tsx|js|jsx)$`
123: number_pattern := `(?:timeout|delay|interval|duration|limit|max|min|size|count|threshold|retry|retries|attempts|port|width|height|margin|padding|offset|index|length|capacity|buffer|batch|chunk|page|rate|fps|dpi|quality|level|priority|weight|score|factor|multiplier|divisor|percentage|ratio|scale)\s*[=:]\s*([2-9]|[1-9]\d+)|(?:if|while|for|elif|range|slice|sleep|wait|setTimeout|setInterval)\s*\([^)]*([2-9]|[1-9]\d+)`
124: 
125: # Block Write/Edit operations that introduce magic numbers
126: 
127: deny contains decision if {
128:     input.hook_event_name == "PreToolUse"
129:     tool_name in {"Write", "Edit", "NotebookEdit"}
130: 
131:     file_path := resolved_file_path
132:     regex.match(file_path_pattern, file_path)
133:     not contains(lower(file_path), "constants")
134: 
135:     content := new_content
136:     content != null
137:     regex.match(number_pattern, content)
138: 
139:     decision := {
140:         "rule_id": "STYLE-001",
141:         "reason": "Magic numbers are prohibited. Use named constants.",
142:         "severity": "HIGH"
143:     }
144: }
145: 
146: deny contains decision if {
147:     input.hook_event_name == "PreToolUse"
148:     tool_name == "MultiEdit"
149: 
150:     some edit in tool_input.edits
151:     file_path := edit_path(edit)
152:     regex.match(file_path_pattern, file_path)
153:     not contains(lower(file_path), "constants")
154: 
155:     content := edit_new_content(edit)
156:     content != null
157:     regex.match(number_pattern, content)
158: 
159:     decision := {
160:         "rule_id": "STYLE-001",
161:         "reason": "Magic numbers are prohibited. Use named constants.",
162:         "severity": "HIGH"
163:     }
164: }
165: 
166: deny contains decision if {
167:     input.hook_event_name == "PreToolUse"
168:     tool_name in {"Patch", "Apply_Patch"}
169: 
170:     patch := patch_content
171:     patch != null
172: 
173:     patch_targets_path(file_path_pattern)
174:     not contains(lower(patch), "constants")
175: 
176:     regex.match(number_pattern, patch)
177: 
178:     decision := {
179:             "rule_id": "STYLE-001",
180:             "reason": "Magic numbers are prohibited. Use named constants.",
181:             "severity": "HIGH"
182:         }
183: }

File: .cupcake/policies/opencode/block_makefile_edit.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block Makefile Edit
  4: # description: Blocks file edits to Makefile
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.block_makefile_edit
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: file_path_pattern := `(?:^|/)Makefile$`
102: 
103: # Block Write/Edit operations targeting Makefile
104: 
105: deny contains decision if {
106:     input.hook_event_name == "PreToolUse"
107:     tool_name in {"Write", "Edit", "NotebookEdit"}
108: 
109:     file_path := resolved_file_path
110:     regex.match(file_path_pattern, file_path)
111: 
112:     decision := {
113:         "rule_id": "BUILD-002",
114:         "reason": "Makefile edits are prohibited.",
115:         "severity": "HIGH"
116:     }
117: }
118: 
119: deny contains decision if {
120:     input.hook_event_name == "PreToolUse"
121:     tool_name == "MultiEdit"
122: 
123:     some edit in tool_input.edits
124:     file_path := edit_path(edit)
125:     regex.match(file_path_pattern, file_path)
126: 
127:     decision := {
128:         "rule_id": "BUILD-002",
129:         "reason": "Makefile edits are prohibited.",
130:         "severity": "HIGH"
131:     }
132: }

File: .cupcake/policies/opencode/block_silent_none_return.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block Silent None Return
  4: # description: Blocks exception handlers that log and return empty values
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.block_silent_none_return
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: pattern := `except\s+\w*Error.*?:\s*\n\s+.*?(?:logger\.|logging\.).*?\n\s+return\s+(?:None|\[\]|False|\{\}|0)`
102: 
103: # Block Write/Edit operations that swallow exceptions with empty returns
104: 
105: deny contains decision if {
106:     input.hook_event_name == "PreToolUse"
107:     tool_name in {"Write", "Edit", "NotebookEdit"}
108: 
109:     content := new_content
110:     content != null
111:     regex.match(pattern, content)
112: 
113:     decision := {
114:         "rule_id": "PY-EXC-003",
115:         "reason": "Silent exception handlers returning empty values are prohibited.",
116:         "severity": "HIGH"
117:     }
118: }
119: 
120: deny contains decision if {
121:     input.hook_event_name == "PreToolUse"
122:     tool_name == "MultiEdit"
123: 
124:     some edit in tool_input.edits
125:     content := edit_new_content(edit)
126:     content != null
127:     regex.match(pattern, content)
128: 
129:     decision := {
130:         "rule_id": "PY-EXC-003",
131:         "reason": "Silent exception handlers returning empty values are prohibited.",
132:         "severity": "HIGH"
133:     }
134: }
135: 
136: deny contains decision if {
137:     input.hook_event_name == "PreToolUse"
138:     tool_name in {"Patch", "Apply_Patch"}
139: 
140:     patch := patch_content
141:     patch != null
142: 
143: 
144:     regex.match(pattern, patch)
145: 
146:     decision := {
147:             "rule_id": "PY-EXC-003",
148:             "reason": "Silent exception handlers returning empty values are prohibited.",
149:             "severity": "HIGH"
150:         }
151: }

File: .cupcake/policies/opencode/block_test_loops_conditionals.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block Test Loops/Conditionals
  4: # description: Blocks loops or conditionals inside tests with asserts
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.block_test_loops_conditionals
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: 
102: patch_targets_path(pattern) if {
103:     patch := patch_content
104:     patch != null
105:     lines := split(patch, "\n")
106:     some line in lines
107:     startswith(line, "+++ b/")
108:     path := replace(line, "+++ b/", "")
109:     regex.match(pattern, path)
110: }
111: 
112: patch_targets_path(pattern) if {
113:     patch := patch_content
114:     patch != null
115:     lines := split(patch, "\n")
116:     some line in lines
117:     startswith(line, "--- a/")
118:     path := replace(line, "--- a/", "")
119:     regex.match(pattern, path)
120: }
121: 
122: file_path_pattern := `tests?/.*\.py$`
123: pattern := `def test_[^(]+\([^)]*\)[^:]*:[\s\S]*?\b(for|while|if)\s+[^:]+:[\s\S]*?assert`
124: 
125: # Block Write/Edit operations that introduce loops/conditionals in tests
126: 
127: deny contains decision if {
128:     input.hook_event_name == "PreToolUse"
129:     tool_name in {"Write", "Edit", "NotebookEdit"}
130: 
131:     file_path := resolved_file_path
132:     regex.match(file_path_pattern, file_path)
133: 
134:     content := new_content
135:     content != null
136:     regex.match(pattern, content)
137: 
138:     decision := {
139:         "rule_id": "TEST-STRUCT-001",
140:         "reason": "Loops or conditionals inside tests are prohibited. Use parametrization.",
141:         "severity": "HIGH"
142:     }
143: }
144: 
145: deny contains decision if {
146:     input.hook_event_name == "PreToolUse"
147:     tool_name == "MultiEdit"
148: 
149:     some edit in tool_input.edits
150:     file_path := edit_path(edit)
151:     regex.match(file_path_pattern, file_path)
152: 
153:     content := edit_new_content(edit)
154:     content != null
155:     regex.match(pattern, content)
156: 
157:     decision := {
158:         "rule_id": "TEST-STRUCT-001",
159:         "reason": "Loops or conditionals inside tests are prohibited. Use parametrization.",
160:         "severity": "HIGH"
161:     }
162: }
163: 
164: deny contains decision if {
165:     input.hook_event_name == "PreToolUse"
166:     tool_name in {"Patch", "Apply_Patch"}
167: 
168:     patch := patch_content
169:     patch != null
170: 
171:     patch_targets_path(file_path_pattern)
172: 
173:     regex.match(pattern, patch)
174: 
175:     decision := {
176:             "rule_id": "TEST-STRUCT-001",
177:             "reason": "Loops or conditionals inside tests are prohibited. Use parametrization.",
178:             "severity": "HIGH"
179:         }
180: }

File: .cupcake/policies/opencode/block_tests_quality.rego

  1: # METADATA
  2: # scope: package
  3: # title: Block Tests Quality
  4: # description: Blocks edits to tests/quality (except baselines.json)
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.block_tests_quality
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: file_path_pattern := `tests/quality/`
102: exclude_pattern := `baselines\.json$`
103: 
104: # Block Write/Edit operations targeting tests/quality
105: 
106: deny contains decision if {
107:     input.hook_event_name == "PreToolUse"
108:     tool_name in {"Write", "Edit", "NotebookEdit"}
109: 
110:     file_path := resolved_file_path
111:     regex.match(file_path_pattern, file_path)
112:     not regex.match(exclude_pattern, file_path)
113: 
114:     decision := {
115:         "rule_id": "TEST-QUALITY-002",
116:         "reason": "Direct edits to tests/quality are prohibited (except baselines.json).",
117:         "severity": "HIGH"
118:     }
119: }
120: 
121: deny contains decision if {
122:     input.hook_event_name == "PreToolUse"
123:     tool_name == "MultiEdit"
124: 
125:     some edit in tool_input.edits
126:     file_path := edit_path(edit)
127:     regex.match(file_path_pattern, file_path)
128:     not regex.match(exclude_pattern, file_path)
129: 
130:     decision := {
131:         "rule_id": "TEST-QUALITY-002",
132:         "reason": "Direct edits to tests/quality are prohibited (except baselines.json).",
133:         "severity": "HIGH"
134:     }
135: }

File: .cupcake/policies/opencode/prevent_any_type.rego

  1: # METADATA
  2: # scope: package
  3: # title: Ban Python Any Type
  4: # description: Blocks introduction of typing.Any in Python code
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.prevent_any_type
 10: 
 11: import rego.v1
 12: 
 13: tool_name := input.tool_name if {
 14:     input.tool_name != null
 15: } else := input.tool
 16: 
 17: tool_input := input.tool_input if {
 18:     input.tool_input != null
 19: } else := input.args
 20: 
 21: resolved_file_path := input.resolved_file_path if {
 22:     input.resolved_file_path != null
 23: } else := tool_input.file_path if {
 24:     tool_input.file_path != null
 25: } else := tool_input.filePath if {
 26:     tool_input.filePath != null
 27: } else := tool_input.path if {
 28:     tool_input.path != null
 29: } else := tool_input.notebook_path if {
 30:     tool_input.notebook_path != null
 31: } else := tool_input.notebookPath if {
 32:     tool_input.notebookPath != null
 33: } else := ""
 34: 
 35: new_content := tool_input.new_string if {
 36:     tool_input.new_string != null
 37: } else := tool_input.newText if {
 38:     tool_input.newText != null
 39: } else := tool_input.new_text if {
 40:     tool_input.new_text != null
 41: } else := tool_input.content if {
 42:     tool_input.content != null
 43: } else := ""
 44: 
 45: old_content := tool_input.old_string if {
 46:     tool_input.old_string != null
 47: } else := tool_input.oldText if {
 48:     tool_input.oldText != null
 49: } else := tool_input.old_text if {
 50:     tool_input.old_text != null
 51: } else := tool_input.previousContent if {
 52:     tool_input.previousContent != null
 53: } else := ""
 54: 
 55: patch_content := tool_input.patch if {
 56:     tool_input.patch != null
 57: } else := tool_input.patchText if {
 58:     tool_input.patchText != null
 59: } else := tool_input.patch_text if {
 60:     tool_input.patch_text != null
 61: } else := ""
 62: 
 63: edit_path(edit) := path if {
 64:     edit.resolved_file_path != null
 65:     path := edit.resolved_file_path
 66: } else := path if {
 67:     edit.file_path != null
 68:     path := edit.file_path
 69: } else := path if {
 70:     edit.filePath != null
 71:     path := edit.filePath
 72: } else := path if {
 73:     edit.path != null
 74:     path := edit.path
 75: } else := ""
 76: 
 77: edit_new_content(edit) := content if {
 78:     edit.new_string != null
 79:     content := edit.new_string
 80: } else := content if {
 81:     edit.newText != null
 82:     content := edit.newText
 83: } else := content if {
 84:     edit.new_text != null
 85:     content := edit.new_text
 86: } else := content if {
 87:     edit.content != null
 88:     content := edit.content
 89: } else := ""
 90: 
 91: edit_old_content(edit) := content if {
 92:     edit.old_string != null
 93:     content := edit.old_string
 94: } else := content if {
 95:     edit.oldText != null
 96:     content := edit.oldText
 97: } else := content if {
 98:     edit.old_text != null
 99:     content := edit.old_text
100: } else := ""
101: 
102: is_python_file(path) if {
103:     endswith(path, ".py")
104: }
105: 
106: is_python_file(path) if {
107:     endswith(path, ".pyi")
108: }
109: 
110: # Regex patterns indicating use of Any in type annotations/imports
111: any_type_patterns := [
112:     `(?m)^\s*from\s+typing\s+import\s+[^#\n]*\bAny\b`,
113:     `\btyping\.Any\b`,
114:     `:\s*Any\b`,
115:     `:\s*"Any"`,
116:     `:\s*'Any'`,
117:     `->\s*Any\b`,
118:     `->\s*"Any"`,
119:     `->\s*'Any'`,
120:     `\[\s*Any\s*\]`,
121:     `\[\s*Any\s*,`,
122:     `,\s*Any\s*\]`,
123:     `,\s*Any\s*,`,
124:     `Union\[[^\]]*\bAny\b[^\]]*\]`,
125:     `Optional\[Any\]`,
126: ]
127: 
128: # Block Write/Edit operations that introduce Any in Python files
129: deny contains decision if {
130:     input.hook_event_name == "PreToolUse"
131:     tool_name in {"Write", "Edit", "NotebookEdit"}
132: 
133:     # Only enforce for Python files
134:     file_path := lower(resolved_file_path)
135:     is_python_file(file_path)
136: 
137:     content := new_content
138:     content != null
139: 
140:     some pattern in any_type_patterns
141:     regex.match(pattern, content)
142: 
143:     decision := {
144:         "rule_id": "PY-TYPE-001",
145:         "reason": "Use of Any is prohibited in Python type annotations/imports. Replace with Protocol, TypeVar, TypedDict, or a concrete type.",
146:         "severity": "HIGH"
147:     }
148: }
149: 
150: deny contains decision if {
151:     input.hook_event_name == "PreToolUse"
152:     tool_name in {"Patch", "Apply_Patch"}
153: 
154:     content := patch_content
155:     content != null
156: 
157:     some pattern in any_type_patterns
158:     regex.match(pattern, content)
159: 
160:     decision := {
161:         "rule_id": "PY-TYPE-001",
162:         "reason": "Use of Any is prohibited in Python type annotations/imports. Replace with Protocol, TypeVar, TypedDict, or a concrete type.",
163:         "severity": "HIGH"
164:     }
165: }
166: 
167: deny contains decision if {
168:     input.hook_event_name == "PreToolUse"
169:     tool_name == "MultiEdit"
170: 
171:     some edit in tool_input.edits
172:     file_path := lower(edit_path(edit))
173:     is_python_file(file_path)
174: 
175:     content := edit_new_content(edit)
176:     content != null
177: 
178:     some pattern in any_type_patterns
179:     regex.match(pattern, content)
180: 
181:     decision := {
182:         "rule_id": "PY-TYPE-001",
183:         "reason": "Use of Any is prohibited in Python type annotations/imports. Replace with Protocol, TypeVar, TypedDict, or a concrete type.",
184:         "severity": "HIGH"
185:     }
186: }

File: .cupcake/policies/opencode/prevent_type_suppression.rego

  1: # METADATA
  2: # scope: package
  3: # title: Ban Python Type Suppression
  4: # description: Blocks type suppression directives in Python code
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.prevent_type_suppression
 10: 
 11: import rego.v1
 12: 
 13: tool_name := input.tool_name if {
 14:     input.tool_name != null
 15: } else := input.tool
 16: 
 17: tool_input := input.tool_input if {
 18:     input.tool_input != null
 19: } else := input.args
 20: 
 21: resolved_file_path := input.resolved_file_path if {
 22:     input.resolved_file_path != null
 23: } else := tool_input.file_path if {
 24:     tool_input.file_path != null
 25: } else := tool_input.filePath if {
 26:     tool_input.filePath != null
 27: } else := tool_input.path if {
 28:     tool_input.path != null
 29: } else := tool_input.notebook_path if {
 30:     tool_input.notebook_path != null
 31: } else := tool_input.notebookPath if {
 32:     tool_input.notebookPath != null
 33: } else := ""
 34: 
 35: new_content := tool_input.new_string if {
 36:     tool_input.new_string != null
 37: } else := tool_input.newText if {
 38:     tool_input.newText != null
 39: } else := tool_input.new_text if {
 40:     tool_input.new_text != null
 41: } else := tool_input.content if {
 42:     tool_input.content != null
 43: } else := ""
 44: 
 45: old_content := tool_input.old_string if {
 46:     tool_input.old_string != null
 47: } else := tool_input.oldText if {
 48:     tool_input.oldText != null
 49: } else := tool_input.old_text if {
 50:     tool_input.old_text != null
 51: } else := tool_input.previousContent if {
 52:     tool_input.previousContent != null
 53: } else := ""
 54: 
 55: patch_content := tool_input.patch if {
 56:     tool_input.patch != null
 57: } else := tool_input.patchText if {
 58:     tool_input.patchText != null
 59: } else := tool_input.patch_text if {
 60:     tool_input.patch_text != null
 61: } else := ""
 62: 
 63: edit_path(edit) := path if {
 64:     edit.resolved_file_path != null
 65:     path := edit.resolved_file_path
 66: } else := path if {
 67:     edit.file_path != null
 68:     path := edit.file_path
 69: } else := path if {
 70:     edit.filePath != null
 71:     path := edit.filePath
 72: } else := path if {
 73:     edit.path != null
 74:     path := edit.path
 75: } else := ""
 76: 
 77: edit_new_content(edit) := content if {
 78:     edit.new_string != null
 79:     content := edit.new_string
 80: } else := content if {
 81:     edit.newText != null
 82:     content := edit.newText
 83: } else := content if {
 84:     edit.new_text != null
 85:     content := edit.new_text
 86: } else := content if {
 87:     edit.content != null
 88:     content := edit.content
 89: } else := ""
 90: 
 91: edit_old_content(edit) := content if {
 92:     edit.old_string != null
 93:     content := edit.old_string
 94: } else := content if {
 95:     edit.oldText != null
 96:     content := edit.oldText
 97: } else := content if {
 98:     edit.old_text != null
 99:     content := edit.old_text
100: } else := ""
101: 
102: is_python_file(path) if {
103:     endswith(path, ".py")
104: }
105: 
106: is_python_file(path) if {
107:     endswith(path, ".pyi")
108: }
109: 
110: # Regex patterns indicating type suppression directives
111: type_suppression_patterns := [
112:     `#\s*type:\s*ignore(\[[^\]]+\])?\b`,
113:     `#\s*pyright:\s*ignore(\[[^\]]+\])?\b`,
114:     `#\s*mypy:\s*ignore(\[[^\]]+\])?\b`,
115:     `#\s*pyre-ignore\b`,
116:     `#\s*pyre-fixme\b`,
117:     `#\s*pyrefly:\s*ignore(\[[^\]]+\])?\b`,
118:     `#\s*basedpyright:\s*ignore(\[[^\]]+\])?\b`,
119:     `#\s*noqa\b`,
120:     `#\s*noqa:\s*\w+`,
121: ]
122: 
123: # Block Write/Edit operations that introduce type suppression in Python files
124: deny contains decision if {
125:     input.hook_event_name == "PreToolUse"
126:     tool_name in {"Write", "Edit", "NotebookEdit"}
127: 
128:     # Only enforce for Python files
129:     file_path := lower(resolved_file_path)
130:     is_python_file(file_path)
131: 
132:     content := new_content
133:     content != null
134: 
135:     some pattern in type_suppression_patterns
136:     regex.match(pattern, content)
137: 
138:     decision := {
139:         "rule_id": "PY-TYPE-002",
140:         "reason": "Type suppression directives are prohibited in Python code. Fix the underlying type/lint issues instead.",
141:         "severity": "HIGH"
142:     }
143: }
144: 
145: deny contains decision if {
146:     input.hook_event_name == "PreToolUse"
147:     tool_name in {"Patch", "Apply_Patch"}
148: 
149:     content := patch_content
150:     content != null
151: 
152:     some pattern in type_suppression_patterns
153:     regex.match(pattern, content)
154: 
155:     decision := {
156:         "rule_id": "PY-TYPE-002",
157:         "reason": "Type suppression directives are prohibited in Python code. Fix the underlying type/lint issues instead.",
158:         "severity": "HIGH"
159:     }
160: }
161: 
162: deny contains decision if {
163:     input.hook_event_name == "PreToolUse"
164:     tool_name == "MultiEdit"
165: 
166:     some edit in tool_input.edits
167:     file_path := lower(edit_path(edit))
168:     is_python_file(file_path)
169: 
170:     content := edit_new_content(edit)
171:     content != null
172: 
173:     some pattern in type_suppression_patterns
174:     regex.match(pattern, content)
175: 
176:     decision := {
177:         "rule_id": "PY-TYPE-002",
178:         "reason": "Type suppression directives are prohibited in Python code. Fix the underlying type/lint issues instead.",
179:         "severity": "HIGH"
180:     }
181: }

File: .cupcake/policies/opencode/warn_baselines_edit.rego

  1: # METADATA
  2: # scope: package
  3: # title: Warn on Baselines Edit
  4: # description: Warns on edits to tests/quality/baselines.json
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.warn_baselines_edit
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: file_path_pattern := `tests/quality/baselines\.json$`
102: 
103: # Warn on Write/Edit operations targeting baselines.json
104: 
105: deny contains decision if {
106:     input.hook_event_name == "PreToolUse"
107:     tool_name in {"Write", "Edit", "NotebookEdit"}
108: 
109:     file_path := resolved_file_path
110:     regex.match(file_path_pattern, file_path)
111: 
112:     decision := {
113:         "rule_id": "TEST-QUALITY-004",
114:         "reason": "Warning: editing tests/quality/baselines.json should be avoided unless explicitly required.",
115:         "severity": "LOW"
116:     }
117: }
118: 
119: deny contains decision if {
120:     input.hook_event_name == "PreToolUse"
121:     tool_name == "MultiEdit"
122: 
123:     some edit in tool_input.edits
124:     file_path := edit_path(edit)
125:     regex.match(file_path_pattern, file_path)
126: 
127:     decision := {
128:         "rule_id": "TEST-QUALITY-004",
129:         "reason": "Warning: editing tests/quality/baselines.json should be avoided unless explicitly required.",
130:         "severity": "LOW"
131:     }
132: }

File: .cupcake/policies/opencode/warn_large_file.rego

  1: # METADATA
  2: # scope: package
  3: # title: Warn on Large File
  4: # description: Warns when writing large files (>= 500 lines)
  5: # custom:
  6: #   routing:
  7: #     required_events: ["PreToolUse"]
  8: #     required_tools: ["Apply_Patch", "Edit", "MultiEdit", "NotebookEdit", "Patch", "Write"]
  9: package cupcake.policies.opencode.warn_large_file
 10: import rego.v1
 11: 
 12: tool_name := input.tool_name if {
 13:     input.tool_name != null
 14: } else := input.tool
 15: 
 16: tool_input := input.tool_input if {
 17:     input.tool_input != null
 18: } else := input.args
 19: 
 20: resolved_file_path := input.resolved_file_path if {
 21:     input.resolved_file_path != null
 22: } else := tool_input.file_path if {
 23:     tool_input.file_path != null
 24: } else := tool_input.filePath if {
 25:     tool_input.filePath != null
 26: } else := tool_input.path if {
 27:     tool_input.path != null
 28: } else := tool_input.notebook_path if {
 29:     tool_input.notebook_path != null
 30: } else := tool_input.notebookPath if {
 31:     tool_input.notebookPath != null
 32: } else := ""
 33: 
 34: new_content := tool_input.new_string if {
 35:     tool_input.new_string != null
 36: } else := tool_input.newText if {
 37:     tool_input.newText != null
 38: } else := tool_input.new_text if {
 39:     tool_input.new_text != null
 40: } else := tool_input.content if {
 41:     tool_input.content != null
 42: } else := ""
 43: 
 44: old_content := tool_input.old_string if {
 45:     tool_input.old_string != null
 46: } else := tool_input.oldText if {
 47:     tool_input.oldText != null
 48: } else := tool_input.old_text if {
 49:     tool_input.old_text != null
 50: } else := tool_input.previousContent if {
 51:     tool_input.previousContent != null
 52: } else := ""
 53: 
 54: patch_content := tool_input.patch if {
 55:     tool_input.patch != null
 56: } else := tool_input.patchText if {
 57:     tool_input.patchText != null
 58: } else := tool_input.patch_text if {
 59:     tool_input.patch_text != null
 60: } else := ""
 61: 
 62: edit_path(edit) := path if {
 63:     edit.resolved_file_path != null
 64:     path := edit.resolved_file_path
 65: } else := path if {
 66:     edit.file_path != null
 67:     path := edit.file_path
 68: } else := path if {
 69:     edit.filePath != null
 70:     path := edit.filePath
 71: } else := path if {
 72:     edit.path != null
 73:     path := edit.path
 74: } else := ""
 75: 
 76: edit_new_content(edit) := content if {
 77:     edit.new_string != null
 78:     content := edit.new_string
 79: } else := content if {
 80:     edit.newText != null
 81:     content := edit.newText
 82: } else := content if {
 83:     edit.new_text != null
 84:     content := edit.new_text
 85: } else := content if {
 86:     edit.content != null
 87:     content := edit.content
 88: } else := ""
 89: 
 90: edit_old_content(edit) := content if {
 91:     edit.old_string != null
 92:     content := edit.old_string
 93: } else := content if {
 94:     edit.oldText != null
 95:     content := edit.oldText
 96: } else := content if {
 97:     edit.old_text != null
 98:     content := edit.old_text
 99: } else := ""
100: 
101: 
102: patch_targets_path(pattern) if {
103:     patch := patch_content
104:     patch != null
105:     lines := split(patch, "\n")
106:     some line in lines
107:     startswith(line, "+++ b/")
108:     path := replace(line, "+++ b/", "")
109:     regex.match(pattern, path)
110: }
111: 
112: patch_targets_path(pattern) if {
113:     patch := patch_content
114:     patch != null
115:     lines := split(patch, "\n")
116:     some line in lines
117:     startswith(line, "--- a/")
118:     path := replace(line, "--- a/", "")
119:     regex.match(pattern, path)
120: }
121: 
122: file_path_pattern := `\.(py|ts|tsx|js|jsx)$`
123: pattern := `(?:.*\n){500,}`
124: 
125: # Warn on Write/Edit operations that introduce large file content
126: 
127: deny contains decision if {
128:     input.hook_event_name == "PreToolUse"
129:     tool_name in {"Write", "Edit", "NotebookEdit"}
130: 
131:     file_path := resolved_file_path
132:     regex.match(file_path_pattern, file_path)
133: 
134:     content := new_content
135:     content != null
136:     regex.match(pattern, content)
137: 
138:     decision := {
139:         "rule_id": "STYLE-002",
140:         "reason": "Warning: file content exceeds 500 lines. Consider refactoring.",
141:         "severity": "LOW"
142:     }
143: }
144: 
145: deny contains decision if {
146:     input.hook_event_name == "PreToolUse"
147:     tool_name == "MultiEdit"
148: 
149:     some edit in tool_input.edits
150:     file_path := edit_path(edit)
151:     regex.match(file_path_pattern, file_path)
152: 
153:     content := edit_new_content(edit)
154:     content != null
155:     regex.match(pattern, content)
156: 
157:     decision := {
158:         "rule_id": "STYLE-002",
159:         "reason": "Warning: file content exceeds 500 lines. Consider refactoring.",
160:         "severity": "LOW"
161:     }
162: }
163: 
164: deny contains decision if {
165:     input.hook_event_name == "PreToolUse"
166:     tool_name in {"Patch", "Apply_Patch"}
167: 
168:     patch := patch_content
169:     patch != null
170: 
171:     patch_targets_path(file_path_pattern)
172: 
173:     regex.match(pattern, patch)
174: 
175:     decision := {
176:             "rule_id": "STYLE-002",
177:             "reason": "Warning: file content exceeds 500 lines. Consider refactoring.",
178:             "severity": "LOW"
179:         }
180: }