ferroguard 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. ferroguard-0.3.1/.agents/skills/git-workflow/SKILL.md +35 -0
  2. ferroguard-0.3.1/.agents/skills/stack-guardrails/SKILL.md +25 -0
  3. ferroguard-0.3.1/.claude/agents/api-dev.md +9 -0
  4. ferroguard-0.3.1/.claude/agents/backend-dev.md +9 -0
  5. ferroguard-0.3.1/.claude/agents/code-reviewer.md +14 -0
  6. ferroguard-0.3.1/.claude/commands/commit.md +9 -0
  7. ferroguard-0.3.1/.claude/commands/create-pr.md +7 -0
  8. ferroguard-0.3.1/.claude/commands/implement.md +11 -0
  9. ferroguard-0.3.1/.claude/commands/verify.md +17 -0
  10. ferroguard-0.3.1/.claude/hooks/scripts/commit-style.py +59 -0
  11. ferroguard-0.3.1/.claude/hooks/scripts/commit-style.sh +3 -0
  12. ferroguard-0.3.1/.claude/hooks/scripts/git-guardrails.sh +3 -0
  13. ferroguard-0.3.1/.claude/hooks/scripts/git_guardrails.py +60 -0
  14. ferroguard-0.3.1/.claude/hooks/scripts/stack-guardrails.sh +3 -0
  15. ferroguard-0.3.1/.claude/hooks/scripts/stack_guardrails.py +80 -0
  16. ferroguard-0.3.1/.claude/rules/coding-standards.md +15 -0
  17. ferroguard-0.3.1/.claude/rules/security.md +13 -0
  18. ferroguard-0.3.1/.claude/settings.json +48 -0
  19. ferroguard-0.3.1/.claude/skills/git-workflow/SKILL.md +35 -0
  20. ferroguard-0.3.1/.claude/skills/stack-guardrails/SKILL.md +25 -0
  21. ferroguard-0.3.1/.claude/stack-guardrails.enabled +0 -0
  22. ferroguard-0.3.1/.commitlintrc.json +42 -0
  23. ferroguard-0.3.1/.cursor/rules/coding-standards.mdc +9 -0
  24. ferroguard-0.3.1/.cursor/rules/security.mdc +9 -0
  25. ferroguard-0.3.1/.cursor/skills/git-workflow/SKILL.md +35 -0
  26. ferroguard-0.3.1/.cursor/skills/stack-guardrails/SKILL.md +25 -0
  27. ferroguard-0.3.1/.env-template +22 -0
  28. ferroguard-0.3.1/.github/CODEOWNERS +2 -0
  29. ferroguard-0.3.1/.github/ISSUE_TEMPLATE/bug_report.yml +55 -0
  30. ferroguard-0.3.1/.github/ISSUE_TEMPLATE/config.yml +5 -0
  31. ferroguard-0.3.1/.github/ISSUE_TEMPLATE/feature_request.yml +32 -0
  32. ferroguard-0.3.1/.github/PULL_REQUEST_TEMPLATE.md +17 -0
  33. ferroguard-0.3.1/.github/copilot-instructions.md +5 -0
  34. ferroguard-0.3.1/.github/dependabot.yml +56 -0
  35. ferroguard-0.3.1/.github/instructions/coding-standards.instructions.md +7 -0
  36. ferroguard-0.3.1/.github/instructions/security.instructions.md +7 -0
  37. ferroguard-0.3.1/.github/workflows/ci.yml +164 -0
  38. ferroguard-0.3.1/.github/workflows/codeql.yml +40 -0
  39. ferroguard-0.3.1/.github/workflows/release.yml +276 -0
  40. ferroguard-0.3.1/.github/workflows/scorecard.yml +42 -0
  41. ferroguard-0.3.1/.github/workflows/security.yml +59 -0
  42. ferroguard-0.3.1/.gitignore +51 -0
  43. ferroguard-0.3.1/.pre-commit-config.yaml +40 -0
  44. ferroguard-0.3.1/.python-version +1 -0
  45. ferroguard-0.3.1/.version +1 -0
  46. ferroguard-0.3.1/AGENTS.md +139 -0
  47. ferroguard-0.3.1/CHANGELOG.md +83 -0
  48. ferroguard-0.3.1/CLAUDE.md +12 -0
  49. ferroguard-0.3.1/CODE_OF_CONDUCT.md +133 -0
  50. ferroguard-0.3.1/CONTRIBUTING.md +110 -0
  51. ferroguard-0.3.1/Cargo.lock +260 -0
  52. ferroguard-0.3.1/Cargo.toml +20 -0
  53. ferroguard-0.3.1/Dockerfile +48 -0
  54. ferroguard-0.3.1/LICENSE +21 -0
  55. ferroguard-0.3.1/PKG-INFO +308 -0
  56. ferroguard-0.3.1/README.md +280 -0
  57. ferroguard-0.3.1/RELEASING.md +117 -0
  58. ferroguard-0.3.1/SECURITY.md +27 -0
  59. ferroguard-0.3.1/api/__init__.py +0 -0
  60. ferroguard-0.3.1/api/main.py +244 -0
  61. ferroguard-0.3.1/api/pii_store.py +58 -0
  62. ferroguard-0.3.1/deny.toml +28 -0
  63. ferroguard-0.3.1/justfile +36 -0
  64. ferroguard-0.3.1/llm_guardrails/__init__.py +19 -0
  65. ferroguard-0.3.1/llm_guardrails/_core.pyi +25 -0
  66. ferroguard-0.3.1/llm_guardrails/bias_scorer.py +39 -0
  67. ferroguard-0.3.1/llm_guardrails/injection_detector.py +71 -0
  68. ferroguard-0.3.1/llm_guardrails/output_validator.py +76 -0
  69. ferroguard-0.3.1/llm_guardrails/pii_redactor.py +87 -0
  70. ferroguard-0.3.1/llm_guardrails/pipeline.py +189 -0
  71. ferroguard-0.3.1/llm_guardrails/py.typed +0 -0
  72. ferroguard-0.3.1/pyproject.toml +78 -0
  73. ferroguard-0.3.1/rust-toolchain.toml +5 -0
  74. ferroguard-0.3.1/scripts/commit-types.json +18 -0
  75. ferroguard-0.3.1/scripts/install-harness-from.sh +24 -0
  76. ferroguard-0.3.1/scripts/release_publish.py +428 -0
  77. ferroguard-0.3.1/scripts/release_subject.py +1068 -0
  78. ferroguard-0.3.1/scripts/test_guardrails.py +159 -0
  79. ferroguard-0.3.1/src/bias_scorer.rs +164 -0
  80. ferroguard-0.3.1/src/injection_detector.rs +382 -0
  81. ferroguard-0.3.1/src/lib.rs +21 -0
  82. ferroguard-0.3.1/src/output_validator.rs +236 -0
  83. ferroguard-0.3.1/src/pii_redactor.rs +463 -0
  84. ferroguard-0.3.1/tests/__init__.py +0 -0
  85. ferroguard-0.3.1/tests/test_api.py +284 -0
  86. ferroguard-0.3.1/tests/test_bias_scorer.py +44 -0
  87. ferroguard-0.3.1/tests/test_injection_detector.py +125 -0
  88. ferroguard-0.3.1/tests/test_packaging.py +79 -0
  89. ferroguard-0.3.1/tests/test_pii_redactor.py +229 -0
  90. ferroguard-0.3.1/tests/test_pipeline.py +94 -0
  91. ferroguard-0.3.1/tests/test_types.py +76 -0
  92. ferroguard-0.3.1/uv.lock +1138 -0
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: git-workflow
3
+ description: Commit subjects, pull request shape, and direct releases from main. Use when committing, opening a PR, or changing release automation.
4
+ ---
5
+
6
+ # Git workflow
7
+
8
+ ## Commit subject
9
+
10
+ `<emoji> <type>(<scope>): <lower-case imperative>`. Under 72 characters. No trailing period. The body says why. Footers are `Closes #N`, `Fixes #N`, `Refs #N`, or `BREAKING CHANGE:`. No `Co-authored-by` and no "Generated with" trailer.
11
+
12
+ Types and emoji are the table in `AGENTS.md` and `scripts/commit-types.json`. Extra emoji that keep the same type: 🔥 🚚 🚨 💚 🎉 🔖.
13
+
14
+ ## Pull request
15
+
16
+ Title is the subject. One label from `feature`, `bug`, `refactor`, `upgrade`, `docs`, `internal`, `breaking`, `security`.
17
+
18
+ Body:
19
+
20
+ - **What and why**, with `Closes #N`
21
+ - **Evidence**: Before, After, Not verified
22
+ - **Merge danger**: Door (one-way or two-way), Blast radius, Rollback
23
+ - **Breaking changes, migrations, config**
24
+
25
+ Add a Mermaid diagram when the architecture or the data flow changes.
26
+
27
+ Branch from `main` as `feat/issue-N-short-name` or `fix/issue-N-short-name`. Do not force-push a shared branch.
28
+
29
+ ## Release
30
+
31
+ A releasable push to `main` publishes in one workflow run. `scripts/release_subject.py` takes the highest bump since the last tag and consumes `## Unreleased`. The job commits `🔖 chore(release): vX.Y.Z`, pushes `main` without force, and tags that commit. The release commit does not publish again. The same run builds the abi3 wheels and sdist from that tag and publishes them to PyPI.
32
+
33
+ ## Merge
34
+
35
+ Only when the human asks: `gh pr merge <number> --squash --delete-branch`.
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: stack-guardrails
3
+ description: Opt-in blocks for publish and deploy, plus the uv, ruff, and cargo checks this library expects. Use before publishing, deploying, or changing the toolchain.
4
+ ---
5
+
6
+ # Stack guardrails
7
+
8
+ Opt in by committing an empty `.claude/stack-guardrails.enabled` at the git root, or export `SDLC_STACK_GUARDRAILS=1`. This repository commits the marker, so the blocks are on.
9
+
10
+ The hook exits 2, in every profile, for:
11
+
12
+ - `npm publish`, `pnpm publish`, `yarn publish`, `uv publish`, `changeset publish` (a `--dry-run` is allowed)
13
+ - `railway up`, `deploy`, `redeploy`, `down`, `variables --set`, `config apply`, `config migrate --apply`
14
+
15
+ It warns on a hand edit of `uv.lock` or `pnpm-lock.yaml`. `Cargo.lock` is committed on purpose. Do not gitignore it. Regenerate `uv.lock` with `uv lock`, do not hand-edit it.
16
+
17
+ ## Python and Rust
18
+
19
+ Python is 3.11 in CI and 3.14 in `.python-version`. Do not select a newer interpreter. Do not raise `requires-python`, the setup-uv `python-version`, or Ruff `target-version` past 3.14. Ruff's `target-version` is `py311`. Tooling is `uv`.
20
+
21
+ Lint and format with `uv run ruff check .` and `uv run ruff format --check .`. Test with `uv run pytest`.
22
+
23
+ Rust's bar is `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, and `cargo test`. The stable channel is `rust-toolchain.toml`. CI passes that channel into `dtolnay/rust-toolchain` because the action is pinned by commit SHA.
24
+
25
+ There is no TypeScript app and no Node toolchain.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: api-dev
3
+ description: Implement a change in the Python wrappers or the FastAPI demo.
4
+ model: sonnet
5
+ ---
6
+
7
+ Work in `llm_guardrails/` or `api/`. Wrappers stay thin. The demo calls `GuardrailsPipeline` and does not load environment variables. Run `uv run ruff check .`, `uv run ruff format --check .`, and `uv run pytest` before you claim the change is done.
8
+
9
+ Follow `AGENTS.md`. There is no frontend.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: backend-dev
3
+ description: Implement a Rust change in the guardrail core.
4
+ model: sonnet
5
+ ---
6
+
7
+ Work in `src/`. Export new behaviour through PyO3 in `src/lib.rs`. Run `cargo fmt`, `cargo clippy --all-targets -- -D warnings`, and `cargo test` before you claim the change is done.
8
+
9
+ Follow `AGENTS.md`. Do not expand into the next issue.
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: code-reviewer
3
+ description: Review a change on two axes, standards and spec fidelity.
4
+ model: opus
5
+ ---
6
+
7
+ Review the diff against `AGENTS.md`.
8
+
9
+ Report two axes separately:
10
+
11
+ 1. Standards. fmt, clippy, ruff, tests, secrets, commit and PR shape. The import name stays `llm_guardrails`.
12
+ 2. Spec fidelity. Does the change do what was asked, and nothing it forbids?
13
+
14
+ Lead with findings, by severity. Do not rewrite the change unless asked.
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Commit the current change with a hybrid subject.
3
+ ---
4
+
5
+ Follow `.claude/skills/git-workflow/SKILL.md`.
6
+
7
+ Stage one logical change. The subject is `<emoji> <type>(<scope>): <lower-case imperative>`, under 72 characters, no trailing period. The body says why. No attribution trailer.
8
+
9
+ Do not use `--no-verify` unless the human asked.
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Open a pull request. Do not merge it.
3
+ ---
4
+
5
+ Follow `.claude/skills/git-workflow/SKILL.md`.
6
+
7
+ Title is the hybrid subject. Body has what and why, `Closes #N`, evidence, merge danger, and breaking changes. One label. Push the feature branch. Do not push `main`. Do not merge.
@@ -0,0 +1,11 @@
1
+ ---
2
+ description: Implement one issue with tests, then stop.
3
+ ---
4
+
5
+ Read the issue. Read `AGENTS.md`.
6
+
7
+ Agree the seams, then make one failing test pass at a time. Rust tests live in `src/`. Python tests live in `tests/`. Mock only at process boundaries.
8
+
9
+ Verify with the commands in `AGENTS.md` that the diff can affect. Read the output.
10
+
11
+ Commit a meaningful unit when it is green. Open or update the pull request. Do not merge. Do not start the next issue.
@@ -0,0 +1,17 @@
1
+ ---
2
+ description: Run the library verification loop and report each phase.
3
+ ---
4
+
5
+ Run, in order, and record pass or fail from the command output:
6
+
7
+ 1. `cargo fmt --check`
8
+ 2. `cargo clippy --all-targets -- -D warnings`
9
+ 3. `cargo test`
10
+ 4. `uv run ruff check .`
11
+ 5. `uv run ruff format --check .`
12
+ 6. `uv run pytest`
13
+ 7. `python3 scripts/release_subject.py --self-test`
14
+ 8. `python3 scripts/release_publish.py --self-test`
15
+ 9. `python3 scripts/test_guardrails.py`
16
+
17
+ Say what you did not run. Do not claim a phase passed without its output.
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env python3
2
+ """Warn or deny a git commit whose subject misses the hybrid convention."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import os
8
+ import re
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ ROOT = Path(__file__).resolve().parents[3]
13
+ TYPES_PATH = ROOT / "scripts" / "commit-types.json"
14
+ MESSAGE = re.compile(r"""(?:-m|--message)(?:=|\s+)(?:"([^"]+)"|'([^']+)'|(\S+))""")
15
+
16
+
17
+ def subject_pattern() -> re.Pattern[str]:
18
+ """Subject regex built from scripts/commit-types.json."""
19
+ names = [item["name"] for item in json.loads(TYPES_PATH.read_text(encoding="utf-8"))["types"]]
20
+ joined = "|".join(names)
21
+ return re.compile(rf"^\S+\s+(?:{joined})(\([a-z0-9][a-z0-9._-]*\))?(!)?: [a-z].*")
22
+
23
+
24
+ def commit_subject(command: str) -> str | None:
25
+ """Return the -m subject when this command is a git commit."""
26
+ if not re.search(r"\bgit\s+commit\b", command):
27
+ return None
28
+ match = MESSAGE.search(command)
29
+ if match is None:
30
+ return None
31
+ return next(group for group in match.groups() if group)
32
+
33
+
34
+ def decision(command: str, profile: str | None = None) -> tuple[int, str | None]:
35
+ """Return (exit code, warning). Exit 2 denies. Standard only warns."""
36
+ subject = commit_subject(command)
37
+ if subject is None or subject_pattern().match(subject):
38
+ return 0, None
39
+ message = f"commit subject does not match <emoji> <type>(<scope>): <imperative>: {subject}"
40
+ mode = profile if profile is not None else os.environ.get("SDLC_HOOK_PROFILE", "standard")
41
+ if mode == "strict":
42
+ return 2, message
43
+ if mode == "minimal":
44
+ return 0, None
45
+ return 0, message
46
+
47
+
48
+ def main() -> int:
49
+ """Read the hook payload and apply the profile."""
50
+ payload = json.load(sys.stdin)
51
+ command = str(payload.get("tool_input", {}).get("command", ""))
52
+ code, warning = decision(command)
53
+ if warning:
54
+ print(warning, file=sys.stderr)
55
+ return code
56
+
57
+
58
+ if __name__ == "__main__":
59
+ raise SystemExit(main())
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # PreToolUse wrapper. stdin is the Claude Code hook payload.
3
+ exec python3 "$(dirname "$0")/commit-style.py"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # PreToolUse wrapper. stdin is the Claude Code hook payload.
3
+ exec python3 "$(dirname "$0")/git_guardrails.py"
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env python3
2
+ """Ask before destructive git commands. A plain git push is allowed."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import re
8
+ import sys
9
+
10
+ FORCE_PUSH = re.compile(r"\bgit\s+push\b[^\n]*--force\b|\bgit\s+push\b[^\n]*--force-with-lease\b")
11
+ FORCE_FLAG = re.compile(r"\bgit\s+push\b[^\n]*(?:^|\s)-f(?:\s|$)")
12
+ PLUS_REF = re.compile(r"\bgit\s+push\b[^\n]*\s\+\S+")
13
+ RESET_HARD = re.compile(r"\bgit\s+reset\b[^\n]*--hard\b")
14
+ CLEAN_FORCE = re.compile(r"\bgit\s+clean\b[^\n]*\s-[a-zA-Z]*f")
15
+ BRANCH_DELETE = re.compile(r"\bgit\s+branch\b[^\n]*(?:^|\s)-D(?:\s|$)")
16
+
17
+
18
+ def needs_ask(command: str) -> str | None:
19
+ """Return a reason to ask, or None when the command may run."""
20
+ if "git" not in command:
21
+ return None
22
+ if "--no-verify" in command:
23
+ return "git command skips hooks (--no-verify)"
24
+ if FORCE_PUSH.search(command) or FORCE_FLAG.search(command) or PLUS_REF.search(command):
25
+ return "force-push"
26
+ if RESET_HARD.search(command):
27
+ return "git reset --hard"
28
+ if CLEAN_FORCE.search(command):
29
+ return "git clean -f"
30
+ if BRANCH_DELETE.search(command):
31
+ return "git branch -D"
32
+ return None
33
+
34
+
35
+ def hook_response(command: str) -> dict | None:
36
+ """Claude Code PreToolUse payload, or None to allow."""
37
+ reason = needs_ask(command)
38
+ if reason is None:
39
+ return None
40
+ return {
41
+ "hookSpecificOutput": {
42
+ "hookEventName": "PreToolUse",
43
+ "permissionDecision": "ask",
44
+ "permissionDecisionReason": f"git guardrail: {reason}",
45
+ }
46
+ }
47
+
48
+
49
+ def main() -> int:
50
+ """Read the hook payload on stdin and print a decision when needed."""
51
+ payload = json.load(sys.stdin)
52
+ command = str(payload.get("tool_input", {}).get("command", ""))
53
+ response = hook_response(command)
54
+ if response is not None:
55
+ print(json.dumps(response))
56
+ return 0
57
+
58
+
59
+ if __name__ == "__main__":
60
+ raise SystemExit(main())
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # PreToolUse and PostToolUse wrapper. stdin is the Claude Code hook payload.
3
+ exec python3 "$(dirname "$0")/stack_guardrails.py"
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+ """Block publish and deploy commands when stack guardrails are opted in.
3
+
4
+ Opt in with `.claude/stack-guardrails.enabled` at the git root, or
5
+ `SDLC_STACK_GUARDRAILS=1`. The block exits 2 in every profile, including
6
+ standard. Dry-run publishes are allowed.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import re
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ PUBLISH = re.compile(r"\b(?:npm|pnpm|yarn|uv)\s+publish\b|\bchangeset\s+publish\b")
18
+ RAILWAY = re.compile(
19
+ r"\brailway\s+(?:up|deploy|redeploy|down)\b"
20
+ r"|\brailway\s+variables\s+--set\b"
21
+ r"|\brailway\s+config\s+apply\b"
22
+ r"|\brailway\s+config\s+migrate\b[^\n]*--apply\b"
23
+ )
24
+ LOCKFILE = re.compile(r"(?:^|/)(?:uv\.lock|pnpm-lock\.yaml)$")
25
+
26
+
27
+ def enabled(start: Path | None = None) -> bool:
28
+ """True when the repo opted in or the environment variable is set."""
29
+ if os.environ.get("SDLC_STACK_GUARDRAILS") == "1":
30
+ return True
31
+ here = start or Path.cwd()
32
+ for parent in [here, *here.parents]:
33
+ marker = parent / ".claude" / "stack-guardrails.enabled"
34
+ if marker.is_file():
35
+ return True
36
+ if (parent / ".git").exists():
37
+ return marker.is_file()
38
+ return False
39
+
40
+
41
+ def blocked_reason(command: str) -> str | None:
42
+ """Return why a shell command is blocked, ignoring publish dry-runs."""
43
+ if not enabled():
44
+ return None
45
+ if PUBLISH.search(command) and "--dry-run" not in command:
46
+ return "package publish is blocked by stack guardrails"
47
+ if RAILWAY.search(command):
48
+ return "railway deploy is blocked by stack guardrails"
49
+ return None
50
+
51
+
52
+ def lockfile_warning(path: str) -> str | None:
53
+ """Warn on a hand edit of a lockfile this harness does not regenerate."""
54
+ if not enabled():
55
+ return None
56
+ if LOCKFILE.search(path):
57
+ return f"hand edit of {path}; prefer the package manager that owns it"
58
+ return None
59
+
60
+
61
+ def main() -> int:
62
+ """Apply the pre-bash block or the post-edit lockfile warning."""
63
+ payload = json.load(sys.stdin)
64
+ tool = str(payload.get("tool_name", ""))
65
+ tool_input = payload.get("tool_input", {})
66
+ if tool == "Bash":
67
+ reason = blocked_reason(str(tool_input.get("command", "")))
68
+ if reason:
69
+ print(reason, file=sys.stderr)
70
+ return 2
71
+ return 0
72
+ path = str(tool_input.get("file_path", ""))
73
+ warning = lockfile_warning(path)
74
+ if warning:
75
+ print(warning, file=sys.stderr)
76
+ return 0
77
+
78
+
79
+ if __name__ == "__main__":
80
+ raise SystemExit(main())
@@ -0,0 +1,15 @@
1
+ ---
2
+ description: Size, typing, and test shape for Python and Rust
3
+ paths: "**/*.py, **/*.rs"
4
+ ---
5
+
6
+ # Coding standards
7
+
8
+ - Functions stay within about 30 lines. Files stay within about 300 lines.
9
+ - No magic numbers. Name them.
10
+ - `TODO` comments name an issue: `TODO(#123)`.
11
+ - Python tooling is `uv` only. Ruff lints and formats. Scripts under `scripts/` that ship in CI are stdlib, so CI does not need a venv for those scripts.
12
+ - Rust: `cargo fmt` and `cargo clippy --all-targets -- -D warnings`. Guard logic stays in `src/`.
13
+ - Tests are arrange, act, assert. One assertion. No logic in the test.
14
+
15
+ There is no TypeScript in this repository.
@@ -0,0 +1,13 @@
1
+ ---
2
+ description: Secrets, injection, and browser sinks
3
+ paths: "**/*.py, **/*.rs, **/*.yaml, **/*.yml, **/*.json"
4
+ ---
5
+
6
+ # Security
7
+
8
+ - No hard-coded secrets. No secrets in logs.
9
+ - No `eval`. No unsanitized `innerHTML` or `dangerouslySetInnerHTML`.
10
+ - No `*` CORS on a deployment that is not loopback-only.
11
+ - The guards are regex and heuristic checks. Do not describe them as a complete injection, bias, or compliance control.
12
+ - PII mappings stay in memory and are returned to the caller. Do not log the mapping or the original text.
13
+ - The output validator checks top-level JSON `type` and `required` only. It is not full JSON Schema.
@@ -0,0 +1,48 @@
1
+ {
2
+ "attribution": {
3
+ "commit": "",
4
+ "pr": ""
5
+ },
6
+ "includeCoAuthoredBy": false,
7
+ "hooks": {
8
+ "PreToolUse": [
9
+ {
10
+ "matcher": "Bash",
11
+ "hooks": [
12
+ {
13
+ "type": "command",
14
+ "command": ".claude/hooks/scripts/git-guardrails.sh"
15
+ },
16
+ {
17
+ "type": "command",
18
+ "command": ".claude/hooks/scripts/commit-style.sh"
19
+ },
20
+ {
21
+ "type": "command",
22
+ "command": ".claude/hooks/scripts/stack-guardrails.sh"
23
+ }
24
+ ]
25
+ },
26
+ {
27
+ "matcher": "Edit|Write",
28
+ "hooks": [
29
+ {
30
+ "type": "command",
31
+ "command": ".claude/hooks/scripts/stack-guardrails.sh"
32
+ }
33
+ ]
34
+ }
35
+ ],
36
+ "PostToolUse": [
37
+ {
38
+ "matcher": "Edit|Write",
39
+ "hooks": [
40
+ {
41
+ "type": "command",
42
+ "command": ".claude/hooks/scripts/stack-guardrails.sh"
43
+ }
44
+ ]
45
+ }
46
+ ]
47
+ }
48
+ }
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: git-workflow
3
+ description: Commit subjects, pull request shape, and direct releases from main. Use when committing, opening a PR, or changing release automation.
4
+ ---
5
+
6
+ # Git workflow
7
+
8
+ ## Commit subject
9
+
10
+ `<emoji> <type>(<scope>): <lower-case imperative>`. Under 72 characters. No trailing period. The body says why. Footers are `Closes #N`, `Fixes #N`, `Refs #N`, or `BREAKING CHANGE:`. No `Co-authored-by` and no "Generated with" trailer.
11
+
12
+ Types and emoji are the table in `AGENTS.md` and `scripts/commit-types.json`. Extra emoji that keep the same type: 🔥 🚚 🚨 💚 🎉 🔖.
13
+
14
+ ## Pull request
15
+
16
+ Title is the subject. One label from `feature`, `bug`, `refactor`, `upgrade`, `docs`, `internal`, `breaking`, `security`.
17
+
18
+ Body:
19
+
20
+ - **What and why**, with `Closes #N`
21
+ - **Evidence**: Before, After, Not verified
22
+ - **Merge danger**: Door (one-way or two-way), Blast radius, Rollback
23
+ - **Breaking changes, migrations, config**
24
+
25
+ Add a Mermaid diagram when the architecture or the data flow changes.
26
+
27
+ Branch from `main` as `feat/issue-N-short-name` or `fix/issue-N-short-name`. Do not force-push a shared branch.
28
+
29
+ ## Release
30
+
31
+ A releasable push to `main` publishes in one workflow run. `scripts/release_subject.py` takes the highest bump since the last tag and consumes `## Unreleased`. The job commits `🔖 chore(release): vX.Y.Z`, pushes `main` without force, and tags that commit. The release commit does not publish again. The same run builds the abi3 wheels and sdist from that tag and publishes them to PyPI.
32
+
33
+ ## Merge
34
+
35
+ Only when the human asks: `gh pr merge <number> --squash --delete-branch`.
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: stack-guardrails
3
+ description: Opt-in blocks for publish and deploy, plus the uv, ruff, and cargo checks this library expects. Use before publishing, deploying, or changing the toolchain.
4
+ ---
5
+
6
+ # Stack guardrails
7
+
8
+ Opt in by committing an empty `.claude/stack-guardrails.enabled` at the git root, or export `SDLC_STACK_GUARDRAILS=1`. This repository commits the marker, so the blocks are on.
9
+
10
+ The hook exits 2, in every profile, for:
11
+
12
+ - `npm publish`, `pnpm publish`, `yarn publish`, `uv publish`, `changeset publish` (a `--dry-run` is allowed)
13
+ - `railway up`, `deploy`, `redeploy`, `down`, `variables --set`, `config apply`, `config migrate --apply`
14
+
15
+ It warns on a hand edit of `uv.lock` or `pnpm-lock.yaml`. `Cargo.lock` is committed on purpose. Do not gitignore it. Regenerate `uv.lock` with `uv lock`, do not hand-edit it.
16
+
17
+ ## Python and Rust
18
+
19
+ Python is 3.11 in CI and 3.14 in `.python-version`. Do not select a newer interpreter. Do not raise `requires-python`, the setup-uv `python-version`, or Ruff `target-version` past 3.14. Ruff's `target-version` is `py311`. Tooling is `uv`.
20
+
21
+ Lint and format with `uv run ruff check .` and `uv run ruff format --check .`. Test with `uv run pytest`.
22
+
23
+ Rust's bar is `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, and `cargo test`. The stable channel is `rust-toolchain.toml`. CI passes that channel into `dtolnay/rust-toolchain` because the action is pinned by commit SHA.
24
+
25
+ There is no TypeScript app and no Node toolchain.
File without changes
@@ -0,0 +1,42 @@
1
+ {
2
+ "rules": {
3
+ "type-enum": [
4
+ 2,
5
+ "always",
6
+ [
7
+ "feat",
8
+ "fix",
9
+ "docs",
10
+ "style",
11
+ "refactor",
12
+ "perf",
13
+ "test",
14
+ "build",
15
+ "ci",
16
+ "chore",
17
+ "deps",
18
+ "security",
19
+ "revert"
20
+ ]
21
+ ],
22
+ "scope-enum": [
23
+ 1,
24
+ "always",
25
+ [
26
+ "core",
27
+ "api",
28
+ "docs",
29
+ "deps",
30
+ "ci",
31
+ "release",
32
+ "repo",
33
+ "agents",
34
+ "commands",
35
+ "hooks",
36
+ "rules",
37
+ "security"
38
+ ]
39
+ ],
40
+ "header-max-length": [2, "always", 72]
41
+ }
42
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Size, typing, and test shape for Python and Rust
3
+ globs: "**/*.{py,rs}"
4
+ alwaysApply: false
5
+ ---
6
+
7
+ Follow `.claude/rules/coding-standards.md` and `AGENTS.md`.
8
+
9
+ Python tooling is uv and ruff. Rust guard logic stays in `src/`. Do not rename `llm_guardrails`.
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Secrets, injection, and browser sinks
3
+ globs: "**/*.{py,rs,yml,yaml,json}"
4
+ alwaysApply: false
5
+ ---
6
+
7
+ Follow `.claude/rules/security.md`.
8
+
9
+ No hard-coded secrets. No `eval`. Do not widen a safety claim past what the regex guards do.
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: git-workflow
3
+ description: Commit subjects, pull request shape, and direct releases from main. Use when committing, opening a PR, or changing release automation.
4
+ ---
5
+
6
+ # Git workflow
7
+
8
+ ## Commit subject
9
+
10
+ `<emoji> <type>(<scope>): <lower-case imperative>`. Under 72 characters. No trailing period. The body says why. Footers are `Closes #N`, `Fixes #N`, `Refs #N`, or `BREAKING CHANGE:`. No `Co-authored-by` and no "Generated with" trailer.
11
+
12
+ Types and emoji are the table in `AGENTS.md` and `scripts/commit-types.json`. Extra emoji that keep the same type: 🔥 🚚 🚨 💚 🎉 🔖.
13
+
14
+ ## Pull request
15
+
16
+ Title is the subject. One label from `feature`, `bug`, `refactor`, `upgrade`, `docs`, `internal`, `breaking`, `security`.
17
+
18
+ Body:
19
+
20
+ - **What and why**, with `Closes #N`
21
+ - **Evidence**: Before, After, Not verified
22
+ - **Merge danger**: Door (one-way or two-way), Blast radius, Rollback
23
+ - **Breaking changes, migrations, config**
24
+
25
+ Add a Mermaid diagram when the architecture or the data flow changes.
26
+
27
+ Branch from `main` as `feat/issue-N-short-name` or `fix/issue-N-short-name`. Do not force-push a shared branch.
28
+
29
+ ## Release
30
+
31
+ A releasable push to `main` publishes in one workflow run. `scripts/release_subject.py` takes the highest bump since the last tag and consumes `## Unreleased`. The job commits `🔖 chore(release): vX.Y.Z`, pushes `main` without force, and tags that commit. The release commit does not publish again. The same run builds the abi3 wheels and sdist from that tag and publishes them to PyPI.
32
+
33
+ ## Merge
34
+
35
+ Only when the human asks: `gh pr merge <number> --squash --delete-branch`.
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: stack-guardrails
3
+ description: Opt-in blocks for publish and deploy, plus the uv, ruff, and cargo checks this library expects. Use before publishing, deploying, or changing the toolchain.
4
+ ---
5
+
6
+ # Stack guardrails
7
+
8
+ Opt in by committing an empty `.claude/stack-guardrails.enabled` at the git root, or export `SDLC_STACK_GUARDRAILS=1`. This repository commits the marker, so the blocks are on.
9
+
10
+ The hook exits 2, in every profile, for:
11
+
12
+ - `npm publish`, `pnpm publish`, `yarn publish`, `uv publish`, `changeset publish` (a `--dry-run` is allowed)
13
+ - `railway up`, `deploy`, `redeploy`, `down`, `variables --set`, `config apply`, `config migrate --apply`
14
+
15
+ It warns on a hand edit of `uv.lock` or `pnpm-lock.yaml`. `Cargo.lock` is committed on purpose. Do not gitignore it. Regenerate `uv.lock` with `uv lock`, do not hand-edit it.
16
+
17
+ ## Python and Rust
18
+
19
+ Python is 3.11 in CI and 3.14 in `.python-version`. Do not select a newer interpreter. Do not raise `requires-python`, the setup-uv `python-version`, or Ruff `target-version` past 3.14. Ruff's `target-version` is `py311`. Tooling is `uv`.
20
+
21
+ Lint and format with `uv run ruff check .` and `uv run ruff format --check .`. Test with `uv run pytest`.
22
+
23
+ Rust's bar is `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, and `cargo test`. The stable channel is `rust-toolchain.toml`. CI passes that channel into `dtolnay/rust-toolchain` because the action is pinned by commit SHA.
24
+
25
+ There is no TypeScript app and no Node toolchain.
@@ -0,0 +1,22 @@
1
+ # ──────────────────────────────────────────────
2
+ # LLM Guardrails -- Environment Configuration
3
+ # ──────────────────────────────────────────────
4
+ # Copy this file to .env and fill in the values.
5
+
6
+ # Guardrails behaviour --------------------------------------------------------
7
+ # Injection-detection threshold (0.0 = permissive, 1.0 = strict)
8
+ GUARDRAILS_INJECTION_THRESHOLD=0.5
9
+
10
+ # Enable / disable individual guards (true | false)
11
+ GUARDRAILS_PII_ENABLED=true
12
+ GUARDRAILS_INJECTION_ENABLED=true
13
+ GUARDRAILS_BIAS_ENABLED=true
14
+ GUARDRAILS_OUTPUT_VALIDATION_ENABLED=true
15
+
16
+ # Maximum allowed output length (characters)
17
+ GUARDRAILS_MAX_OUTPUT_LENGTH=4096
18
+
19
+ # API server -------------------------------------------------------------------
20
+ API_HOST=0.0.0.0
21
+ API_PORT=8000
22
+ API_LOG_LEVEL=info