import from local vendored copy (2026-05-06)
Some checks failed
CI / validate (push) Failing after 0s

This commit is contained in:
Hongming Wang 2026-05-06 13:53:29 -07:00
commit 7f46f585d6
10 changed files with 165 additions and 0 deletions

5
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,5 @@
name: CI
on: [push, pull_request]
jobs:
validate:
uses: Molecule-AI/molecule-ci/.github/workflows/validate-plugin.yml@main

21
.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
# Credentials — never commit. Use .env.example as the template.
.env
.env.local
.env.*.local
.env.*
!.env.example
!.env.sample
# Private keys + certs
*.pem
*.key
*.crt
*.p12
*.pfx
# Secret directories
.secrets/
# Workspace auth tokens
.auth-token
.auth_token

19
README.md Normal file
View File

@ -0,0 +1,19 @@
# molecule-prompt-watchdog
Molecule AI plugin. Install via the Molecule AI platform plugin system.
## Usage
### In org template (org.yaml)
```yaml
plugins:
- molecule-prompt-watchdog
```
### From URL (community install)
```
github://Molecule-AI/molecule-ai-plugin-molecule-prompt-watchdog
```
## License
Business Source License 1.1 — © Molecule AI.

0
adapters/__init__.py Normal file
View File

2
adapters/claude_code.py Normal file
View File

@ -0,0 +1,2 @@
"""Claude Code adaptor — uses the generic rule+skill+hooks installer."""
from plugins_registry.builtins import AgentskillsAdaptor as Adaptor # noqa: F401

46
hooks/_lib.py Executable file
View File

@ -0,0 +1,46 @@
"""Common helpers for Claude Code hooks. Imported by the .py hook scripts.
Hooks receive JSON on stdin per the Claude Code hook spec, and may emit
JSON on stdout or exit with code 2 to block. This module wraps both.
"""
import json
import sys
def read_input() -> dict:
"""Parse stdin JSON. Empty input → empty dict."""
raw = sys.stdin.read().strip()
if not raw:
return {}
try:
return json.loads(raw)
except json.JSONDecodeError:
return {}
def emit(payload: dict) -> None:
"""Print JSON payload to stdout for the harness to interpret."""
print(json.dumps(payload))
def deny_pretooluse(reason: str) -> None:
"""Emit a PreToolUse denial with reason and exit 0."""
emit({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": reason,
}
})
sys.exit(0)
def add_context(text: str) -> None:
"""Emit additionalContext for SessionStart / UserPromptSubmit hooks."""
if text and text.strip():
emit({"additionalContext": text})
def warn_to_stderr(msg: str) -> None:
"""Non-blocking warning visible to the next agent turn via stderr."""
print(msg, file=sys.stderr)

58
hooks/user-prompt-tag.py Executable file
View File

@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""UserPromptSubmit — inject context warnings for destructive-keyword prompts."""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _lib import read_input, add_context, warn_to_stderr # noqa
PATTERNS = [
(
["force push", "force-push", "git push -f", "--force"],
"Mention of force-push detected. Confirm scope (which branch? to main? careful-mode REFUSES force to main).",
),
(
["delete all", "drop all", "wipe all", "remove all", "clear all"],
"'all'-scoped destructive operation detected. Re-confirm exact target set (which workspaces / which rows / which files) before tooling.",
),
(
["drop table", "truncate", "delete from", "drop database"],
"Direct SQL DDL/DML detected. Use a migration via goose or a parameterized query through platform handlers — not raw psql against prod.",
),
(
["merge directly", "push to main", "commit to main", "directly to main"],
"Mention of working on main detected. Standing rule: never push to main. Use a branch + PR.",
),
]
CLOSE_BULK = ["close all", "close every"]
CLOSE_OBJ = ["pr", "issue", "workspace"]
def main() -> None:
data = read_input()
prompt = data.get("prompt", "").lower()
if not prompt:
return
warnings = []
for needles, msg in PATTERNS:
if any(n in prompt for n in needles):
warnings.append(f"{msg}")
if any(b in prompt for b in CLOSE_BULK) and any(o in prompt for o in CLOSE_OBJ):
warnings.append("• Bulk close requested. List the targets first; do NOT loop a close command.")
if warnings:
add_context(
"## ⚠ Prompt-watchdog warnings\n\n"
+ "\n".join(warnings)
+ "\n\ncareful-mode applies — re-confirm scope before any destructive tool call."
)
if __name__ == "__main__":
try:
main()
except Exception as e:
warn_to_stderr(f"[prompt-tag hook error] {e}")
sys.exit(0)

2
hooks/user-prompt-tag.sh Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
exec python3 "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/user-prompt-tag.py"

11
plugin.yaml Normal file
View File

@ -0,0 +1,11 @@
name: molecule-prompt-watchdog
version: 1.0.0
description: Inject context warnings when the user prompt mentions destructive keywords (force push, drop table, delete all). UserPromptSubmit hook.
author: Molecule AI
tags: [molecule, guardrails]
runtimes:
- claude_code
hooks:
- user-prompt-tag

1
settings-fragment.json Normal file
View File

@ -0,0 +1 @@
{"hooks":{"UserPromptSubmit":[{"hooks":[{"type":"command","command":"bash ${CLAUDE_DIR}/hooks/user-prompt-tag.sh"}]}]}}