rulewright 0.1.0__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.
- rulewright-0.1.0/.gitattributes +1 -0
- rulewright-0.1.0/.github/workflows/ci.yml +33 -0
- rulewright-0.1.0/.gitignore +9 -0
- rulewright-0.1.0/CHANGELOG.md +23 -0
- rulewright-0.1.0/LICENSE +21 -0
- rulewright-0.1.0/PKG-INFO +144 -0
- rulewright-0.1.0/README.md +92 -0
- rulewright-0.1.0/SECURITY.md +12 -0
- rulewright-0.1.0/docs/ARCHITECTURE.md +103 -0
- rulewright-0.1.0/docs/DECISIONS.md +297 -0
- rulewright-0.1.0/docs/RELEASING.md +65 -0
- rulewright-0.1.0/docs/demo.tape +27 -0
- rulewright-0.1.0/pyproject.toml +72 -0
- rulewright-0.1.0/rulewright/__init__.py +1 -0
- rulewright-0.1.0/rulewright/cli.py +490 -0
- rulewright-0.1.0/rulewright/cluster/__init__.py +0 -0
- rulewright-0.1.0/rulewright/cluster/embed.py +66 -0
- rulewright-0.1.0/rulewright/cluster/group.py +93 -0
- rulewright-0.1.0/rulewright/config.py +19 -0
- rulewright-0.1.0/rulewright/guard/__init__.py +0 -0
- rulewright-0.1.0/rulewright/guard/emit.py +75 -0
- rulewright-0.1.0/rulewright/guard/profile.py +172 -0
- rulewright-0.1.0/rulewright/ingest/__init__.py +0 -0
- rulewright-0.1.0/rulewright/ingest/git_history.py +185 -0
- rulewright-0.1.0/rulewright/ingest/reviews.py +1 -0
- rulewright-0.1.0/rulewright/ingest/sessions/__init__.py +0 -0
- rulewright-0.1.0/rulewright/ingest/sessions/base.py +47 -0
- rulewright-0.1.0/rulewright/ingest/sessions/claude_code.py +138 -0
- rulewright-0.1.0/rulewright/ingest/sessions/codex.py +112 -0
- rulewright-0.1.0/rulewright/mine/__init__.py +0 -0
- rulewright-0.1.0/rulewright/mine/detectors.py +256 -0
- rulewright-0.1.0/rulewright/mine/score.py +28 -0
- rulewright-0.1.0/rulewright/mine/signals.py +20 -0
- rulewright-0.1.0/rulewright/pipeline.py +146 -0
- rulewright-0.1.0/rulewright/pr.py +123 -0
- rulewright-0.1.0/rulewright/propose/__init__.py +0 -0
- rulewright-0.1.0/rulewright/propose/guards.py +131 -0
- rulewright-0.1.0/rulewright/propose/llm.py +141 -0
- rulewright-0.1.0/rulewright/propose/redact.py +44 -0
- rulewright-0.1.0/rulewright/propose/writer.py +240 -0
- rulewright-0.1.0/rulewright/prove/__init__.py +0 -0
- rulewright-0.1.0/rulewright/prove/agent.py +131 -0
- rulewright-0.1.0/rulewright/prove/judge.py +62 -0
- rulewright-0.1.0/rulewright/prove/runner.py +198 -0
- rulewright-0.1.0/rulewright/prove/sandbox.py +138 -0
- rulewright-0.1.0/rulewright/prove/tasks.py +85 -0
- rulewright-0.1.0/rulewright/report.py +63 -0
- rulewright-0.1.0/rulewright/rules/__init__.py +0 -0
- rulewright-0.1.0/rulewright/rules/emit/__init__.py +55 -0
- rulewright-0.1.0/rulewright/rules/emit/agents_md.py +9 -0
- rulewright-0.1.0/rulewright/rules/emit/base.py +74 -0
- rulewright-0.1.0/rulewright/rules/emit/claude_md.py +9 -0
- rulewright-0.1.0/rulewright/rules/emit/copilot.py +9 -0
- rulewright-0.1.0/rulewright/rules/emit/cursor.py +12 -0
- rulewright-0.1.0/rulewright/rules/model.py +24 -0
- rulewright-0.1.0/rulewright/rules/source.py +127 -0
- rulewright-0.1.0/rulewright/rules/stale.py +142 -0
- rulewright-0.1.0/rulewright/store.py +56 -0
- rulewright-0.1.0/tests/__init__.py +0 -0
- rulewright-0.1.0/tests/fixtures/__init__.py +0 -0
- rulewright-0.1.0/tests/fixtures/build_repos.py +183 -0
- rulewright-0.1.0/tests/golden/AGENTS.md +7 -0
- rulewright-0.1.0/tests/golden/CLAUDE.md +7 -0
- rulewright-0.1.0/tests/golden/copilot-instructions.md +7 -0
- rulewright-0.1.0/tests/golden/cursorrules +5 -0
- rulewright-0.1.0/tests/golden/rulewright.yaml +18 -0
- rulewright-0.1.0/tests/helpers.py +120 -0
- rulewright-0.1.0/tests/test_attribution.py +32 -0
- rulewright-0.1.0/tests/test_cli_rules.py +99 -0
- rulewright-0.1.0/tests/test_cluster.py +137 -0
- rulewright-0.1.0/tests/test_emit.py +93 -0
- rulewright-0.1.0/tests/test_guard.py +350 -0
- rulewright-0.1.0/tests/test_pipeline.py +130 -0
- rulewright-0.1.0/tests/test_pr.py +118 -0
- rulewright-0.1.0/tests/test_propose.py +469 -0
- rulewright-0.1.0/tests/test_prove_agent.py +89 -0
- rulewright-0.1.0/tests/test_prove_cli.py +127 -0
- rulewright-0.1.0/tests/test_prove_docker.py +110 -0
- rulewright-0.1.0/tests/test_prove_runner.py +143 -0
- rulewright-0.1.0/tests/test_prove_tasks.py +115 -0
- rulewright-0.1.0/tests/test_refix_detector.py +41 -0
- rulewright-0.1.0/tests/test_revert_detector.py +29 -0
- rulewright-0.1.0/tests/test_rules_source.py +72 -0
- rulewright-0.1.0/tests/test_scan_cli.py +106 -0
- rulewright-0.1.0/tests/test_score.py +23 -0
- rulewright-0.1.0/tests/test_sessions.py +56 -0
- rulewright-0.1.0/tests/test_stale.py +96 -0
- rulewright-0.1.0/uv.lock +3548 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* text=auto eol=lf
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
pull_request:
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
python: ["3.11", "3.12", "3.13"]
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: astral-sh/setup-uv@v5
|
|
16
|
+
- run: uv python install ${{ matrix.python }}
|
|
17
|
+
- run: uv sync --python ${{ matrix.python }}
|
|
18
|
+
- run: uv run ruff check .
|
|
19
|
+
- run: uv run mypy rulewright
|
|
20
|
+
- run: uv run pytest
|
|
21
|
+
|
|
22
|
+
publish:
|
|
23
|
+
needs: test
|
|
24
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
environment: pypi
|
|
27
|
+
permissions:
|
|
28
|
+
id-token: write
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
- uses: astral-sh/setup-uv@v5
|
|
32
|
+
- run: uv build
|
|
33
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
First release.
|
|
6
|
+
|
|
7
|
+
- `scan`: reads git history (and Claude Code session logs), finds reverts, quick human fixes of
|
|
8
|
+
agent commits and user corrections, clusters them, and proposes rules that each cite at least
|
|
9
|
+
3 distinct commits or messages. Clusters are ranked by how much their changes have in common
|
|
10
|
+
before any LLM call. Changes no file.
|
|
11
|
+
- `apply`, `emit`, `init`, `stale`: accept proposed rules into `rulewright.yaml` and render
|
|
12
|
+
`AGENTS.md`, `CLAUDE.md`, `.cursorrules` and Copilot instructions between markers; find rules
|
|
13
|
+
that mention paths or commands that no longer exist. `scan --pr` opens a pull request.
|
|
14
|
+
- `prove`: replay past tasks in a Docker sandbox (no network, no secrets) with and without a
|
|
15
|
+
rule and report how often the mistake recurs. Evidence, not statistical proof.
|
|
16
|
+
- `guard`: propose Claude Code `permissions.allow` rules from the commands agents ran, limited
|
|
17
|
+
to a built-in list of read-only, test, lint and build commands. Reads Claude Code and Codex
|
|
18
|
+
logs; `--diff` and `--apply`.
|
|
19
|
+
- LLM: a local Ollama model, or your own Groq, Anthropic or OpenAI key (any LiteLLM model name).
|
|
20
|
+
The cost is shown and a paid call needs a yes.
|
|
21
|
+
|
|
22
|
+
Known limits: git history alone gives few or no rules on many repositories; Codex-authored
|
|
23
|
+
commits without a trailer are not recognised; PowerShell commands are ignored by `guard`.
|
rulewright-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rulewright contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: rulewright
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Mine repeated coding-agent mistakes from git history and propose evidence-backed rules.
|
|
5
|
+
Project-URL: Homepage, https://github.com/AbdulRehman19-bit/rulewright
|
|
6
|
+
Project-URL: Issues, https://github.com/AbdulRehman19-bit/rulewright/issues
|
|
7
|
+
Author: rulewright contributors
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 rulewright contributors
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: agents-md,ai-agents,claude-code,developer-tools,llm
|
|
31
|
+
Classifier: Development Status :: 3 - Alpha
|
|
32
|
+
Classifier: Environment :: Console
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
39
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
40
|
+
Requires-Python: >=3.11
|
|
41
|
+
Requires-Dist: docker>=7
|
|
42
|
+
Requires-Dist: litellm>=1.50
|
|
43
|
+
Requires-Dist: numpy>=1.26
|
|
44
|
+
Requires-Dist: pydantic>=2.6
|
|
45
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
46
|
+
Requires-Dist: rich>=13
|
|
47
|
+
Requires-Dist: scikit-learn>=1.3
|
|
48
|
+
Requires-Dist: sentence-transformers>=3
|
|
49
|
+
Requires-Dist: tenacity>=8
|
|
50
|
+
Requires-Dist: typer>=0.12
|
|
51
|
+
Description-Content-Type: text/markdown
|
|
52
|
+
|
|
53
|
+
# rulewright
|
|
54
|
+
|
|
55
|
+
**Your coding agent learns from its mistakes, provably, and safely.**
|
|
56
|
+
|
|
57
|
+
rulewright reads your repo's git history (and Claude Code session logs), finds the mistakes
|
|
58
|
+
coding agents keep repeating, and proposes evidence-backed rules for `AGENTS.md`,
|
|
59
|
+
`CLAUDE.md`, `.cursorrules` and Copilot instructions. Every rule cites at least 3 commits or
|
|
60
|
+
messages. Nothing is written until you approve it.
|
|
61
|
+
|
|
62
|
+
> **Status: v0.1, early.** It has been run on a handful of public repositories. It works best
|
|
63
|
+
> on repos where a lot of agent-written commits get fixed by a person soon after (on litellm,
|
|
64
|
+
> about 3 of 5 proposed rules were good, the rest weak). On repos with little of that it says
|
|
65
|
+
> so and proposes nothing. Treat every proposal as a shortlist to review, not a list to accept.
|
|
66
|
+
> `prove` and `guard` are newer still: `prove` needs Docker and shows evidence, not proof.
|
|
67
|
+
|
|
68
|
+
<!-- TODO before launch: GIF of `rulewright scan` on a known public repo -->
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
pipx install git+https://github.com/AbdulRehman19-bit/rulewright.git
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Python 3.11+. Or, once published: `pipx install rulewright`.
|
|
77
|
+
|
|
78
|
+
## Use
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
rulewright scan # read the last 500 commits, print proposed rules, change nothing
|
|
82
|
+
rulewright apply r-3e6f373f r-5f86d161 # accept chosen rules, write rulewright.yaml and agent files
|
|
83
|
+
rulewright emit --dry-run # preview re-rendering every agent file from rulewright.yaml
|
|
84
|
+
rulewright stale # rules that mention paths or commands that no longer exist
|
|
85
|
+
rulewright prove r-3e6f373f # replay past tasks with and without the rule (needs Docker)
|
|
86
|
+
rulewright guard # propose a permission allowlist from the commands agents ran
|
|
87
|
+
rulewright guard --diff # show the change to .claude/settings.json, write nothing
|
|
88
|
+
rulewright guard --apply # write it after you confirm
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Mining and clustering run locally with no API key. Only the last step, writing the rule text,
|
|
92
|
+
calls an LLM: a local Ollama model if one is running, otherwise your own `GROQ_API_KEY`,
|
|
93
|
+
`ANTHROPIC_API_KEY` or `OPENAI_API_KEY` (or pick one with `--model`, any LiteLLM name such as
|
|
94
|
+
`groq/openai/gpt-oss-120b`). The estimated cost is shown, and you are asked, before any paid call.
|
|
95
|
+
|
|
96
|
+
## What a result looks like
|
|
97
|
+
|
|
98
|
+
This is real output on a small demo repo with planted mistakes (not a public repo), using a local
|
|
99
|
+
`qwen2.5-coder:7b`:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
Read 45 commits (15 agent-authored); 15 signal(s) [refix] in 3 cluster(s).
|
|
103
|
+
id proposed rule scope evidence
|
|
104
|
+
r-5f86d161 Do not skip failing pytest tests; fix them instead. tests/*.py e0d279a, b4b4120, 4365f63 +2
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`rulewright-report.md` lists the evidence behind each rule as commit links.
|
|
108
|
+
|
|
109
|
+
**Rule quality depends on the model.** On busy real repositories a small local model (7B) wrote
|
|
110
|
+
generic rules from good clusters. A second check asks the model whether each rule is specific or
|
|
111
|
+
generic advice and drops the generic ones (`--no-judge` skips it), but judge every rule by its
|
|
112
|
+
evidence, and for real repos use a stronger model, for example
|
|
113
|
+
`rulewright scan --model groq/openai/gpt-oss-120b` with your own key (the cost is shown first).
|
|
114
|
+
|
|
115
|
+
## How it stays safe
|
|
116
|
+
|
|
117
|
+
rulewright writes text that agents will obey, so everything it mines is treated as hostile.
|
|
118
|
+
|
|
119
|
+
- Mined text reaches the LLM only inside a delimited data block (random tag), and the reply must
|
|
120
|
+
match a fixed schema.
|
|
121
|
+
- Proposed rules are scanned for URLs, curl/wget, credentials, env files, new shell commands and
|
|
122
|
+
disabling tests or permissions. Flagged rules are shown in red and never pre-selected.
|
|
123
|
+
- A rule needs 3+ pieces of evidence, and each cited commit must exist in your repo.
|
|
124
|
+
- Likely secrets are redacted before any API call.
|
|
125
|
+
- Nothing is written without `apply` (or a pull request you review). Only the content between
|
|
126
|
+
`<!-- rulewright:start -->` and `<!-- rulewright:end -->` is ever edited.
|
|
127
|
+
|
|
128
|
+
See [SECURITY.md](SECURITY.md).
|
|
129
|
+
|
|
130
|
+
## Roadmap
|
|
131
|
+
|
|
132
|
+
- **v0.1** `scan`, `apply`, `emit`, `stale`: this release.
|
|
133
|
+
- **v0.2** `prove` (implemented, pre-release): replay past tasks in a Docker sandbox (no network,
|
|
134
|
+
no secrets) with and without a rule and report how often the mistake recurs. Results are
|
|
135
|
+
evidence, not statistical proof: a handful of tasks, nondeterministic agents, and prompts rebuilt
|
|
136
|
+
from commit messages. The built-in agent is simple; its runs are only as good as the model.
|
|
137
|
+
- **v0.3** `guard` (implemented, pre-release): reads the shell commands your agents ran (Claude
|
|
138
|
+
Code and Codex logs) and proposes `permissions.allow` rules for Claude Code, but only for a
|
|
139
|
+
short built-in list of read-only, test, lint and build commands. Everything else it sees is
|
|
140
|
+
shown for you to judge, and risky commands (`rm`, `curl`, `git push`, interpreters, `docker`) are
|
|
141
|
+
never offered. It runs no LLM and executes nothing.
|
|
142
|
+
|
|
143
|
+
How it works and why each choice was made: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md),
|
|
144
|
+
[docs/DECISIONS.md](docs/DECISIONS.md).
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# rulewright
|
|
2
|
+
|
|
3
|
+
**Your coding agent learns from its mistakes, provably, and safely.**
|
|
4
|
+
|
|
5
|
+
rulewright reads your repo's git history (and Claude Code session logs), finds the mistakes
|
|
6
|
+
coding agents keep repeating, and proposes evidence-backed rules for `AGENTS.md`,
|
|
7
|
+
`CLAUDE.md`, `.cursorrules` and Copilot instructions. Every rule cites at least 3 commits or
|
|
8
|
+
messages. Nothing is written until you approve it.
|
|
9
|
+
|
|
10
|
+
> **Status: v0.1, early.** It has been run on a handful of public repositories. It works best
|
|
11
|
+
> on repos where a lot of agent-written commits get fixed by a person soon after (on litellm,
|
|
12
|
+
> about 3 of 5 proposed rules were good, the rest weak). On repos with little of that it says
|
|
13
|
+
> so and proposes nothing. Treat every proposal as a shortlist to review, not a list to accept.
|
|
14
|
+
> `prove` and `guard` are newer still: `prove` needs Docker and shows evidence, not proof.
|
|
15
|
+
|
|
16
|
+
<!-- TODO before launch: GIF of `rulewright scan` on a known public repo -->
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
pipx install git+https://github.com/AbdulRehman19-bit/rulewright.git
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Python 3.11+. Or, once published: `pipx install rulewright`.
|
|
25
|
+
|
|
26
|
+
## Use
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
rulewright scan # read the last 500 commits, print proposed rules, change nothing
|
|
30
|
+
rulewright apply r-3e6f373f r-5f86d161 # accept chosen rules, write rulewright.yaml and agent files
|
|
31
|
+
rulewright emit --dry-run # preview re-rendering every agent file from rulewright.yaml
|
|
32
|
+
rulewright stale # rules that mention paths or commands that no longer exist
|
|
33
|
+
rulewright prove r-3e6f373f # replay past tasks with and without the rule (needs Docker)
|
|
34
|
+
rulewright guard # propose a permission allowlist from the commands agents ran
|
|
35
|
+
rulewright guard --diff # show the change to .claude/settings.json, write nothing
|
|
36
|
+
rulewright guard --apply # write it after you confirm
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Mining and clustering run locally with no API key. Only the last step, writing the rule text,
|
|
40
|
+
calls an LLM: a local Ollama model if one is running, otherwise your own `GROQ_API_KEY`,
|
|
41
|
+
`ANTHROPIC_API_KEY` or `OPENAI_API_KEY` (or pick one with `--model`, any LiteLLM name such as
|
|
42
|
+
`groq/openai/gpt-oss-120b`). The estimated cost is shown, and you are asked, before any paid call.
|
|
43
|
+
|
|
44
|
+
## What a result looks like
|
|
45
|
+
|
|
46
|
+
This is real output on a small demo repo with planted mistakes (not a public repo), using a local
|
|
47
|
+
`qwen2.5-coder:7b`:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
Read 45 commits (15 agent-authored); 15 signal(s) [refix] in 3 cluster(s).
|
|
51
|
+
id proposed rule scope evidence
|
|
52
|
+
r-5f86d161 Do not skip failing pytest tests; fix them instead. tests/*.py e0d279a, b4b4120, 4365f63 +2
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`rulewright-report.md` lists the evidence behind each rule as commit links.
|
|
56
|
+
|
|
57
|
+
**Rule quality depends on the model.** On busy real repositories a small local model (7B) wrote
|
|
58
|
+
generic rules from good clusters. A second check asks the model whether each rule is specific or
|
|
59
|
+
generic advice and drops the generic ones (`--no-judge` skips it), but judge every rule by its
|
|
60
|
+
evidence, and for real repos use a stronger model, for example
|
|
61
|
+
`rulewright scan --model groq/openai/gpt-oss-120b` with your own key (the cost is shown first).
|
|
62
|
+
|
|
63
|
+
## How it stays safe
|
|
64
|
+
|
|
65
|
+
rulewright writes text that agents will obey, so everything it mines is treated as hostile.
|
|
66
|
+
|
|
67
|
+
- Mined text reaches the LLM only inside a delimited data block (random tag), and the reply must
|
|
68
|
+
match a fixed schema.
|
|
69
|
+
- Proposed rules are scanned for URLs, curl/wget, credentials, env files, new shell commands and
|
|
70
|
+
disabling tests or permissions. Flagged rules are shown in red and never pre-selected.
|
|
71
|
+
- A rule needs 3+ pieces of evidence, and each cited commit must exist in your repo.
|
|
72
|
+
- Likely secrets are redacted before any API call.
|
|
73
|
+
- Nothing is written without `apply` (or a pull request you review). Only the content between
|
|
74
|
+
`<!-- rulewright:start -->` and `<!-- rulewright:end -->` is ever edited.
|
|
75
|
+
|
|
76
|
+
See [SECURITY.md](SECURITY.md).
|
|
77
|
+
|
|
78
|
+
## Roadmap
|
|
79
|
+
|
|
80
|
+
- **v0.1** `scan`, `apply`, `emit`, `stale`: this release.
|
|
81
|
+
- **v0.2** `prove` (implemented, pre-release): replay past tasks in a Docker sandbox (no network,
|
|
82
|
+
no secrets) with and without a rule and report how often the mistake recurs. Results are
|
|
83
|
+
evidence, not statistical proof: a handful of tasks, nondeterministic agents, and prompts rebuilt
|
|
84
|
+
from commit messages. The built-in agent is simple; its runs are only as good as the model.
|
|
85
|
+
- **v0.3** `guard` (implemented, pre-release): reads the shell commands your agents ran (Claude
|
|
86
|
+
Code and Codex logs) and proposes `permissions.allow` rules for Claude Code, but only for a
|
|
87
|
+
short built-in list of read-only, test, lint and build commands. Everything else it sees is
|
|
88
|
+
shown for you to judge, and risky commands (`rm`, `curl`, `git push`, interpreters, `docker`) are
|
|
89
|
+
never offered. It runs no LLM and executes nothing.
|
|
90
|
+
|
|
91
|
+
How it works and why each choice was made: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md),
|
|
92
|
+
[docs/DECISIONS.md](docs/DECISIONS.md).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
Report vulnerabilities privately through GitHub: open
|
|
4
|
+
[a security advisory](https://github.com/AbdulRehman19-bit/rulewright/security/advisories/new)
|
|
5
|
+
on this repository. Please do not file a public issue for a vulnerability.
|
|
6
|
+
|
|
7
|
+
## Threat model
|
|
8
|
+
|
|
9
|
+
rulewright writes text that coding agents will obey, so all mined text (commit messages, diffs,
|
|
10
|
+
review comments, session logs) is untrusted. Mined text is only ever passed to an LLM inside a
|
|
11
|
+
delimited data block, proposed rules pass a risk scanner, each rule must cite 3+ verified
|
|
12
|
+
commits, secrets are redacted before any API call, and no file is changed without `apply`.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Architecture: flow, how it works, what is used
|
|
2
|
+
|
|
3
|
+
Keep this file current: update it in the same change that alters the flow, a module or a dependency.
|
|
4
|
+
Why things were chosen lives in [DECISIONS.md](DECISIONS.md).
|
|
5
|
+
|
|
6
|
+
## Flow
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
git history / session logs / PR comments
|
|
10
|
+
-> ingest raw history to uniform events (no judgment)
|
|
11
|
+
-> mine detectors find agent mistakes, score them -> Signal
|
|
12
|
+
-> cluster embed + HDBSCAN, keep clusters of 3+ signals
|
|
13
|
+
-> propose LLM drafts one rule per cluster, guards check it
|
|
14
|
+
-> approve user runs `apply` (or approves a PR)
|
|
15
|
+
-> rules rulewright.yaml (single source of truth) -> emit to AGENTS.md, CLAUDE.md, .cursorrules, copilot file
|
|
16
|
+
```
|
|
17
|
+
Ingest, mine and cluster run locally with no API key. Only propose calls an LLM.
|
|
18
|
+
Nothing is written to an agent file without approval, and only between the rulewright markers.
|
|
19
|
+
|
|
20
|
+
## Modules (package `rulewright/`)
|
|
21
|
+
|
|
22
|
+
| Module | Job | State |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `cli.py` | Typer app: `init`, `scan`, `apply`, `emit`, `stale`, `prove` | done |
|
|
25
|
+
| `pipeline.py` | `run_scan`: ingest, detect, dedupe, score, embed, cluster, propose, stale check; writes nothing | done |
|
|
26
|
+
| `report.py` | `rulewright-report.md` | done |
|
|
27
|
+
| `pr.py` | `scan --pr`: branch, commit, push, `gh pr create`; GitHub commit URLs | done |
|
|
28
|
+
| `config.py` | `.rulewright/config.toml` with defaults | done |
|
|
29
|
+
| `ingest/git_history.py` | `git log` to `Commit` with line ranges and patch, agent attribution | done |
|
|
30
|
+
| `ingest/sessions/base.py`, `claude_code.py` | `SessionSource` interface, Claude Code transcript reader | done |
|
|
31
|
+
| `ingest/sessions/codex.py` | Codex rollout reader (commands only), checked against a real rollout | done |
|
|
32
|
+
| `ingest/reviews.py` | PR review comments (needs token) | placeholder |
|
|
33
|
+
| `mine/signals.py` | `Signal` model | done |
|
|
34
|
+
| `mine/detectors.py` | revert, re-fix, correction detectors | done; review detector todo |
|
|
35
|
+
| `mine/score.py` | recency weighting and threshold | done |
|
|
36
|
+
| `cluster/embed.py` | `Embedder` interface, sentence-transformers embedder, SQLite embedding cache | done |
|
|
37
|
+
| `cluster/group.py` | HDBSCAN grouping, `Cluster` model, `coherence` score, cold-start message | done |
|
|
38
|
+
| `propose/redact.py` | secret redaction before any API call | done |
|
|
39
|
+
| `propose/guards.py` | risk scanner, scope cleaning, evidence verification | done |
|
|
40
|
+
| `propose/llm.py` | model choice (Ollama, else user key), LiteLLM client, cost estimate | done |
|
|
41
|
+
| `propose/writer.py` | prompt with delimited data block, schema parsing, `propose_rules` | done |
|
|
42
|
+
| `rules/model.py` | `Rule`, `Evidence` | done |
|
|
43
|
+
| `rules/source.py` | rulewright.yaml load/save, pending-proposals store | done |
|
|
44
|
+
| `rules/emit/` | marker-based block replacement, one writer per target (AGENTS.md, CLAUDE.md, .cursorrules, copilot-instructions.md) | done |
|
|
45
|
+
| `rules/stale.py` | dead paths, dead commands, dead scopes, silent rules | done |
|
|
46
|
+
| `prove/tasks.py` | rebuild past tasks from a rule's evidence (parent tree, prompt, known-bad lines) | done |
|
|
47
|
+
| `prove/sandbox.py` | `Sandbox` interface, `DockerSandbox` (no network, no env, caps dropped, limits) | done |
|
|
48
|
+
| `prove/agent.py` | `AgentRunner` interface, `BuiltinAgent` JSON-action loop | done |
|
|
49
|
+
| `prove/judge.py` | workspace snapshots, deterministic recurrence check | done |
|
|
50
|
+
| `prove/runner.py` | `prove_rule`: both arms, trials, cost ceiling, LLM-judge fallback, report | done |
|
|
51
|
+
| `guard/profile.py` | split commands, classify against a curated safe list, count uses, build the profile | done |
|
|
52
|
+
| `guard/emit.py` | add `Bash(<prefix>:*)` rules to `.claude/settings.json` (merge, diff, atomic write) | done |
|
|
53
|
+
| `store.py` | SQLite cache in `.rulewright/` (embeddings table) | embeddings done; signals and rule history later |
|
|
54
|
+
|
|
55
|
+
## How the finished pieces work
|
|
56
|
+
|
|
57
|
+
- **`guard`** (no LLM, nothing executed): reads the shell commands agents ran for this repo (Claude Code `Bash` tool calls with their results; Codex `shell` calls whose session `cwd` is the repo). Each command is split on `&&`, `||`, `;` and `|`; commands with `$(...)`, backticks, redirects to real files, here-documents or `FOO=bar cmd` are skipped whole (`2>&1` and `>/dev/null` are ignored). Each part is matched against our own curated list of safe prefixes (`git status`, `uv run pytest`, `cargo test`, ...); the longest match becomes the pattern, so the rule is always one of our prefixes and never text from the log. Tiers: **safe** (on the list), **review** (run often, not on the list; written only with `--include-review`), **never** (interpreters, wrappers like `timeout`, `rm`, `curl`, `git push`, `docker`, paths; reported, never offered). A command the person refused is left out. A pattern needs 3 uses (`--min-uses`). The output is `Bash(<prefix>:*)` in `permissions.allow` of `.claude/settings.json` (`--local` for settings.local.json), merged into the existing file, other keys untouched, written atomically, and only after `--apply` and a yes (or `--yes`). `--diff` prints the change and writes nothing. A settings file that is not valid JSON is refused. PowerShell commands (Claude's `PowerShell` tool, Codex `shell_command`) are counted and ignored because their rule syntax differs.
|
|
58
|
+
|
|
59
|
+
- **Commit reading**: one `git log -p -U0 --numstat --no-renames` call (lockfiles, minified bundles, snapshots, SVGs and source maps are excluded at the git level); records and header fields split by control characters. Each commit keeps per-file counts, changed line ranges, the text of added and removed lines (capped at 1000 per file) and a truncated patch. A commit is agent-authored if its body has a Co-Authored-By trailer naming a coding agent (Claude, Codex, Copilot, Cursor, Devin, Aider, OpenHands, Jules, Gemini, Sweep), or its author is a `[bot]` whose name contains one of those, or `noreply@anthropic.com`. Release and dependency bots (dependabot, github-actions, huppy-bot) are not agents.
|
|
60
|
+
- **Revert detector**: an agent commit followed within 7 days by a commit that names it in "This reverts commit <sha>" (confidence 1.0), or that removes at least 50% of the significant lines the agent added (matched by line content, each added line matching at most once) in the same files (0.6). The signal's excerpt is the human's change (`- removed` / `+ added`).
|
|
61
|
+
- **Re-fix detector**: a human commit within 48 hours that removes lines the agent commit added, where the removed line numbers also overlap the agent's added range in the same file (0.8). Explicit reverts are skipped; one event is one signal (revert and re-fix signals for the same commit pair are deduplicated, highest confidence kept).
|
|
62
|
+
- **Session reader**: reads `~/.claude/projects/<slug>/*.jsonl`, keeps only text blocks of user/assistant records, skips sidechains, meta records and malformed lines.
|
|
63
|
+
- **Correction detector** (0.5): a user message that follows an assistant turn, is at most 400 characters, is not harness-injected text, and opens with a rejection phrase ("no", "don't", "stop", "instead" ...). Its signal has no commit SHA; the evidence is the session message.
|
|
64
|
+
- **Scoring**: detectors emit confidence in `score`; `score_signals` multiplies by a recency weight (half-life 180 days) and drops anything below 0.2. Apply once per run.
|
|
65
|
+
- **Embedding**: each signal becomes a short summary (`kind: text (files: ...)`), embedded with `all-MiniLM-L6-v2` (offline after the first model download). Vectors are cached in SQLite keyed by model and SHA-256 of the text, so a rescan only embeds new signals.
|
|
66
|
+
- **Clustering**: scikit-learn HDBSCAN, min cluster size 3, first in its strict mode; only if that finds nothing is it retried allowing a single cluster (the lone repeated mistake case). Noise and groups under 3 are dropped. The embedded summary starts with the human's change excerpt, then the commit subjects. A cluster has a deterministic id (hash of its signal ids), a centroid and the text of the signal nearest the centroid as its summary.
|
|
67
|
+
- **Cold start**: fewer than 3 clusters gives a plain message suggesting `--commits 2000` or session logs, instead of inventing weak rules.
|
|
68
|
+
- **Propose**: per cluster, `build_prompt` sends the fixed instructions as the system message and the top 12 signals as JSON lines inside `<mined-data-RANDOM>` tags (the text can neither guess nor close the tag). Everything is redacted first (key patterns, secret-named assignments, high-entropy tokens; git SHAs are kept). The prompt demands a rule specific to the repository and lets the model decline with an empty rule (dropped as "no specific repeated mistake"). The reply must parse into `{rule, scope, cited_signals}`; anything else drops the cluster.
|
|
69
|
+
- **Guards**: a rule keeps only cited signals that belong to its cluster and whose evidence is real (the commit exists in the repo, or it is a session message). Evidence is counted per distinct commit (several fixes of one commit are one item); fewer than 3 means the rule is dropped. Rule text is scanned for URLs, network commands, credentials, env files, new shell commands, disabling tests or permissions, injection phrases and length over 30 words; prohibitions such as "never skip tests" pass. Flagged rules are kept with `risk_flags` set so the UI can show them red and unselected. Scope must be repo-relative globs.
|
|
70
|
+
- **Model and cost**: a local Ollama model if one is running, else `ANTHROPIC_API_KEY`, else `OPENAI_API_KEY`, else none. `estimate_cost` approximates tokens (chars / 4) and prices them through LiteLLM; local models are free, unknown models give no price. The scan command (phase 6) must show it and ask before a paid call.
|
|
71
|
+
- **Source of truth**: `rulewright.yaml` (version, targets, rules with id, text, scope, status, evidence refs). Evidence is stored as commit SHAs, or signal ids for session messages. `scan` (phase 6) will leave proposals in `.rulewright/pending.json`; nothing reaches the yaml until `apply`.
|
|
72
|
+
- **Emit**: only accepted rules are rendered. Content goes between `<!-- rulewright:start -->` and `<!-- rulewright:end -->`: a missing file is created, a file without markers gets the block appended after a blank line, a file with markers has only that span replaced (line endings kept). Broken markers (missing, duplicated, reversed) abort with an error before any file is written. Rule text is flattened to one line and marker strings are stripped from it.
|
|
73
|
+
- **Commands**: `init` writes an empty rulewright.yaml. `apply [ids] | --all-safe` moves pending rules into the yaml as accepted and emits the targets; risk-flagged rules need `--allow-risky`. `emit [--dry-run]` re-renders every target (the dry run prints a diff and writes nothing). `stale [--mark]` lists findings and can set those rules to `stale`, which the next `emit` leaves out.
|
|
74
|
+
- **Stale check**: for accepted rules, a path-like token that is not a tracked file or directory, a backticked `make X` / `npm run X` whose target or script does not exist, a scope glob matching no tracked file, and (given signals) no signal in the last 90 days that touches the rule's scope or shares two content words with it.
|
|
75
|
+
- **Scan** (`rulewright scan`): reads N commits (default 500) and Claude Code session logs, runs the detectors, keeps one signal per (agent commit, later commit) pair, scores, embeds, clusters. Fewer than 3 clusters means the cold-start message and no LLM call. Otherwise up to 10 clusters not already covered by a rule in rulewright.yaml go to the LLM, chosen by `select_clusters`: clusters whose changes share almost no identifiers (coherence under 0.10) are skipped with a note, and the rest are ranked by coherence, then distinct commits; the cost estimate is shown first and a paid call needs a yes (`--yes` skips the question; local models are free and never ask). It prints a Rich table (flagged rules red), writes `rulewright-report.md`, and stores the proposals in `.rulewright/pending.json` (replacing the previous list). It changes no agent file and not rulewright.yaml.
|
|
76
|
+
- **`--pr`**: puts the guard-approved rules (no risk flags) on a branch `rulewright/rules-<timestamp>` with rulewright.yaml and the target files, pushes it and opens a pull request with `gh`. The PR is the approval step. It refuses if the files it would edit have uncommitted changes or broken markers, and returns to the original branch. A missing remote or `gh` is reported as a note.
|
|
77
|
+
- **Report**: counts, the cold-start message, each rule with scope, risk flags and evidence as commit links (GitHub repos), dropped clusters and stale rules. It contains SHAs and signal kinds, never mined text.
|
|
78
|
+
- **Prove** (`rulewright prove <rule-id>`): re-mines the history to find the rule's evidence commits and, for each, the lines the human fix removed (the mistake). A task is the agent commit's parent tree (`git archive`, no history, so the fix is not visible), a prompt taken from the agent commit's message (attribution lines removed) and those bad lines. Up to 10 tasks. For each task and trial it runs two arms on fresh copies: without the rule and with the rule in the agent's system prompt. After a cost estimate (free for local models; a paid run asks first; `--max-cost` stops the run at the ceiling) each run goes: temp copy, Docker container (no network, no environment variables, all capabilities dropped, no-new-privileges, 1 GB memory, 1 CPU, 256 processes, commands killed after 60 s, image `python:3.12-slim`), built-in agent loop, then the judge. The agent loop runs on the host so the API key never enters the container; the container only executes the agent's file and shell actions. File reads and writes go through the container, never host-side, and host snapshots skip symlinks. The judge is deterministic when the evidence shows the mistake (the agent wrote one of the removed lines), otherwise an LLM judge decides. The report shows, per arm, recurrences out of judged runs, runs that changed files (so "did nothing" is not mistaken for "avoided the mistake"), runs that failed (excluded) and cost, and is saved to `.rulewright/prove/<rule-id>.json`.
|
|
79
|
+
- **Tests**: `tests/fixtures/build_repos.py` builds throwaway repos with planted patterns and negative cases; transcripts are generated in tmp dirs; tests assert detectors find exactly the planted signals.
|
|
80
|
+
|
|
81
|
+
## What is used
|
|
82
|
+
|
|
83
|
+
Python 3.11+, Typer + Rich (CLI), Pydantic v2 (models), PyYAML (rulewright.yaml), git via subprocess, hatchling (build), uv (dev), pytest, ruff, mypy (strict), GitHub Actions on Python 3.11-3.13.
|
|
84
|
+
CI also publishes to PyPI on `v*` tags (trusted publishing). Also now: docker (docker-py), sentence-transformers (torch), scikit-learn HDBSCAN, numpy, SQLite. LiteLLM (lazy import). Planned: SQLite, PyYAML; v0.2 Docker (docker-py).
|
|
85
|
+
|
|
86
|
+
## Phases
|
|
87
|
+
|
|
88
|
+
Spec timeline: v0.1 ends week 3 (phases 0-6), v0.2 ends week 7 (phase 7), v0.3 after that (phase 8).
|
|
89
|
+
Week 1 = phases 0-1, week 2 = phase 2 plus embeddings/clustering (phase 3), week 3 = phases 4-6.
|
|
90
|
+
|
|
91
|
+
| Phase | Scope | Status |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| 0 Scaffold | repo layout, pyproject, CI, SECURITY.md, stub CLI | done |
|
|
94
|
+
| 1 Ingest + revert | git ingest, Signal, revert detector, fixture script | done |
|
|
95
|
+
| 2 Detect + score | re-fix detector, Claude Code session reader + correction detector, scoring and threshold done; optional review detector (GitHub token) todo | mostly done |
|
|
96
|
+
| 3 Cluster | local embeddings cached in SQLite, HDBSCAN, 3+ signal rule, cold-start message | done |
|
|
97
|
+
| 4 Propose + guard | LiteLLM writer with delimited data block, schema-parsed output, risk scanner, secret redaction, SHA verification, cost estimate | done (real LLM path not yet run) |
|
|
98
|
+
| 5 Rules + emit | rulewright.yaml, marker-based emitters with golden tests, stale check, `init`, `apply`, `emit`, `stale` | done |
|
|
99
|
+
| 6 Ship v0.1 | `scan`, Rich table, rulewright-report.md, `--pr`, injection tests, README; real-repo check on litellm, tldraw (done, found and fixed 4 bugs) | code done; launch gate partly done |
|
|
100
|
+
|
|
101
|
+
Launch gate for v0.1 (partly done: real scans on litellm with Groq reviewed by hand, four more repos surveyed; see D75, D77): run `scan` on 3 public repos that have agent co-authored commits and read the output by hand; record the GIF; renamed from `rulesmith`, which is taken on PyPI (D79, D80); rename the GitHub repository, then configure the PyPI trusted publisher. Done: LICENSE, package metadata, clean-install check, security contact via GitHub advisories (enable it in repo settings).
|
|
102
|
+
| 7 v0.2 prove | rebuild tasks, cost estimate/`--max-cost`, Docker sandbox, AgentRunner, judge, before/after report | code done and tested against real containers; real-model replay in progress (see D70) |
|
|
103
|
+
| 8 v0.3 guard | permission profiler, allowlist emitter, `guard --diff`/`--apply`, Codex reader | code done and tested; run on this machine's real logs (see D78) |
|