feat: generate policy YAML from CLAUDE.md - #802
Conversation
|
CHANGELOG reminder — This PR has no changelog fragment. Add a file to |
Review status: nothing to review yetThis PR currently contains only the empty objective-setting commit ( Re-request review once the first implementation commit is pushed. When you do, here are the things I'd particularly want to look at for this objective (generating a policy YAML from
Happy to do a real review as soon as there's code on the branch. |
Adds luthien_proxy.policy_generation.claude_md: extracts enforceable behavioral rules from a CLAUDE.md/AGENTS.md with a deterministic heuristic (normative-language filter, code fences and tables skipped), emits a SimpleLLMPolicy YAML with every rule tagged [<file>:<line>], and round-trip validates the output through load_policy_from_yaml. Run: uv run python -m luthien_proxy.policy_generation.claude_md CLAUDE.md Trello: https://trello.com/c/j9DbbAaC
Review — feat: generate policy YAML from CLAUDE.mdNice, tight, deterministic feature. The line-tagged rule provenance and round-trip validation are the right instincts. A few things worth addressing before merge, ordered by impact. Correctness1. The check runs on the final joined text and inspects
Since a normative marker is already required by the outer filter (line 141), you could safely restrict this check to paragraphs that lack a normative marker, or move it to run BEFORE the normative check but only on the raw first line rather than the joined+stripped text. Simplest fix: only apply the command heuristic when the text has no normative marker (i.e., use it as a secondary drop, not primary). 2. Silent length-based drops —
3. Tests4. Tests reach into private state —
5. Missing CLI-level coverage for the flag paths that are most likely to break in the field:
Each is a one-line CLI test. Nits & minor cleanup6. Absolute path leaked into the header —
7. 8. Double file read — Security note (worth surfacing in docs, not blocking)The extracted rules are dumped verbatim into the judge's system prompt. A CLAUDE.md checked into the repo is trusted content by definition, but this creates a prompt-injection surface: an attacker who lands a PR touching CLAUDE.md can inject text like What's good
Overall: no blockers, but (1) and (2) are worth fixing before merge — they'll bite real users on real CLAUDE.md files. |
…blic-API tests - Remove _looks_like_command filter (false positives on rules like 'git rebase should never be run interactively'); the normative-language gate already excludes plain commands - Extract rules from blockquote callouts (strip leading '>') - Return ExtractionResult with skipped_too_long count; CLI reports skips on stderr and takes --max-rule-chars to override the cap - Header comments use the file name, not the absolute path - generate_policy_yaml accepts source_text to avoid a second file read - Tests assert via parsed YAML instead of SimpleLLMPolicy._config; add CLI coverage for --model/--on-error/--no-validate/validation-failure - Docs: --max-rule-chars flag + trust note on regeneration
Review — feat: generate policy YAML from CLAUDE.mdNicely scoped, well-tested PR. Extraction heuristic is thoughtful (fences, headings, tables, blockquotes, dedup, continuation lines, long-rule counter), CLI is clean, and the round-trip through Below is a list of observations, ordered roughly by how much they'd matter. None are blocking; most are nits or v2 fodder. Correctness / edge cases
Small clarity / hygiene nits
Design observations (not asks)
Verified against the codebase
Nice touches
Overall: LGTM. The blockquote-absorption and HR-line edge cases (1 and 2) are the only items I'd consider fixing before merge; everything else is optional polish. |
|
Post-review note (adversarial verification pass, Jul 7): the PR body's test count is inconsistent ('29 unit tests' vs '19 new unit tests pass' in the agent report); the committed test file contains 29 test functions. 29 is correct. |
|
Claude-generated merge-queue triage of all open Luthien PRs, requested by Scott (Jul 7, 2026). Advisory only; Scott has not yet acted on these recommendations. Recommendation: merge, lowest priority in the queue. Self-contained new module plus CLI plus docs; nothing in the runtime request path changes, the generator is deterministic (no LLM call), and output round-trips through the real policy loader. As an onboarding hook ("point Luthien at your CLAUDE.md, get a starting policy") it is worth having. The honest counterargument: this is the one net-new feature in the Jul 7 batch, roughly 700 lines of new surface, and under a strict fixes-and-security-only bar it would be deferred instead. Either call is defensible; merge is recommended because the code is isolated and only runs when invoked. |
Summary
Auto-generate a Luthien policy YAML from an existing CLAUDE.md file, so users who already maintain a CLAUDE.md can get a working policy without writing YAML by hand.
Trello: https://trello.com/c/j9DbbAaC ("Generate policy from claude.md")
uv run python -m luthien_proxy.policy_generation.claude_md CLAUDE.md -o config/claude_md_policy.yaml export POLICY_CONFIG=config/claude_md_policy.yamlWhat is included
luthien_proxy/policy_generation/claude_md.py— deterministic heuristic extraction (no LLM call, no credentials at generation time): walks the markdown, skips fenced code blocks / headings / tables / shell-command lines, and keeps bullets and short paragraphs carrying normative language (never / always / must / avoid / prefer / required / ...). Emits aSimpleLLMPolicyconfig.[<file>:<line>], and the header records the source path + sha256 of the source content.load_policy_from_yamland fails (exit 2) if the proxy wouldn't accept it.--no-validateto skip.tests/luthien_proxy/unit_tests/policy_generation/test_claude_md.py: extraction heuristics (fences, tables, blockquotes, dedup, line numbers, markdown stripping, continuation joining), YAML structure, CLI flags and exit codes (including the validation-failure path), and a realistic round-trip against this repo's ownAGENTS.md(loose bounds so the test survives AGENTS.md edits).docs/policies.md, pointer inREADME.md, changelog fragment.Example: run on this repo's own AGENTS.md
uv run python -m luthien_proxy.policy_generation.claude_md AGENTS.mdextracts 22 rules, e.g.:The output loads cleanly through
POLICY_CONFIG(verified:load_policy_from_yaml()returns aSimpleLLMPolicywith all 22 tagged rules).Prior art
PR #424 (
ParallelRulesPolicy) and its stacked follow-up #429 (ClaudeMdRulesPolicy) were closed unmerged (2026-05-29, abandoned with red CI). #429 took a runtime approach: scan system prompts for CLAUDE.md content on turn 1, extract rules via LLM, persist to a newsession_rulesDB table. This PR deliberately takes the offline approach the Trello card describes: a one-shot generation step, no new policy class, no DB schema, output is plainSimpleLLMPolicyYAML the user can read and edit. Nothing from those branches was resurrected.Review response (second commit)
Addressed the automated review: removed the command-prefix filter (false positives on rules like "git rebase should never be run interactively"), blockquote rules now extract, overlong rules are counted and reported on stderr with a
--max-rule-charsoverride, headers use the file name instead of the absolute path,generate_policy_yamltakessource_textto avoid a double read, tests assert via parsed YAML instead of private policy state, and docs gained a trust note about CLAUDE.md being inside the policy trust boundary.Left for v2 (intentionally out of scope)
--llmflag): better recall on rules phrased without normative keywords, and rewriting multi-sentence rules into crisp judge instructions. The output format wouldn't change.luthien policy from-claude-mdin luthien-cli: the standalone CLI talks to the gateway over HTTP and doesn't depend onluthien_proxy, so surfacing this there needs either a gateway admin endpoint or a code move; worth its own PR.Test plan
./scripts/dev_checks.shpasses (format, lint, pyright, full unit suite, coverage 92%)POLICY_CONFIG, policy instantiates with 22 traceable rules🤖 Generated with Claude Code