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.
Files changed (88) hide show
  1. rulewright-0.1.0/.gitattributes +1 -0
  2. rulewright-0.1.0/.github/workflows/ci.yml +33 -0
  3. rulewright-0.1.0/.gitignore +9 -0
  4. rulewright-0.1.0/CHANGELOG.md +23 -0
  5. rulewright-0.1.0/LICENSE +21 -0
  6. rulewright-0.1.0/PKG-INFO +144 -0
  7. rulewright-0.1.0/README.md +92 -0
  8. rulewright-0.1.0/SECURITY.md +12 -0
  9. rulewright-0.1.0/docs/ARCHITECTURE.md +103 -0
  10. rulewright-0.1.0/docs/DECISIONS.md +297 -0
  11. rulewright-0.1.0/docs/RELEASING.md +65 -0
  12. rulewright-0.1.0/docs/demo.tape +27 -0
  13. rulewright-0.1.0/pyproject.toml +72 -0
  14. rulewright-0.1.0/rulewright/__init__.py +1 -0
  15. rulewright-0.1.0/rulewright/cli.py +490 -0
  16. rulewright-0.1.0/rulewright/cluster/__init__.py +0 -0
  17. rulewright-0.1.0/rulewright/cluster/embed.py +66 -0
  18. rulewright-0.1.0/rulewright/cluster/group.py +93 -0
  19. rulewright-0.1.0/rulewright/config.py +19 -0
  20. rulewright-0.1.0/rulewright/guard/__init__.py +0 -0
  21. rulewright-0.1.0/rulewright/guard/emit.py +75 -0
  22. rulewright-0.1.0/rulewright/guard/profile.py +172 -0
  23. rulewright-0.1.0/rulewright/ingest/__init__.py +0 -0
  24. rulewright-0.1.0/rulewright/ingest/git_history.py +185 -0
  25. rulewright-0.1.0/rulewright/ingest/reviews.py +1 -0
  26. rulewright-0.1.0/rulewright/ingest/sessions/__init__.py +0 -0
  27. rulewright-0.1.0/rulewright/ingest/sessions/base.py +47 -0
  28. rulewright-0.1.0/rulewright/ingest/sessions/claude_code.py +138 -0
  29. rulewright-0.1.0/rulewright/ingest/sessions/codex.py +112 -0
  30. rulewright-0.1.0/rulewright/mine/__init__.py +0 -0
  31. rulewright-0.1.0/rulewright/mine/detectors.py +256 -0
  32. rulewright-0.1.0/rulewright/mine/score.py +28 -0
  33. rulewright-0.1.0/rulewright/mine/signals.py +20 -0
  34. rulewright-0.1.0/rulewright/pipeline.py +146 -0
  35. rulewright-0.1.0/rulewright/pr.py +123 -0
  36. rulewright-0.1.0/rulewright/propose/__init__.py +0 -0
  37. rulewright-0.1.0/rulewright/propose/guards.py +131 -0
  38. rulewright-0.1.0/rulewright/propose/llm.py +141 -0
  39. rulewright-0.1.0/rulewright/propose/redact.py +44 -0
  40. rulewright-0.1.0/rulewright/propose/writer.py +240 -0
  41. rulewright-0.1.0/rulewright/prove/__init__.py +0 -0
  42. rulewright-0.1.0/rulewright/prove/agent.py +131 -0
  43. rulewright-0.1.0/rulewright/prove/judge.py +62 -0
  44. rulewright-0.1.0/rulewright/prove/runner.py +198 -0
  45. rulewright-0.1.0/rulewright/prove/sandbox.py +138 -0
  46. rulewright-0.1.0/rulewright/prove/tasks.py +85 -0
  47. rulewright-0.1.0/rulewright/report.py +63 -0
  48. rulewright-0.1.0/rulewright/rules/__init__.py +0 -0
  49. rulewright-0.1.0/rulewright/rules/emit/__init__.py +55 -0
  50. rulewright-0.1.0/rulewright/rules/emit/agents_md.py +9 -0
  51. rulewright-0.1.0/rulewright/rules/emit/base.py +74 -0
  52. rulewright-0.1.0/rulewright/rules/emit/claude_md.py +9 -0
  53. rulewright-0.1.0/rulewright/rules/emit/copilot.py +9 -0
  54. rulewright-0.1.0/rulewright/rules/emit/cursor.py +12 -0
  55. rulewright-0.1.0/rulewright/rules/model.py +24 -0
  56. rulewright-0.1.0/rulewright/rules/source.py +127 -0
  57. rulewright-0.1.0/rulewright/rules/stale.py +142 -0
  58. rulewright-0.1.0/rulewright/store.py +56 -0
  59. rulewright-0.1.0/tests/__init__.py +0 -0
  60. rulewright-0.1.0/tests/fixtures/__init__.py +0 -0
  61. rulewright-0.1.0/tests/fixtures/build_repos.py +183 -0
  62. rulewright-0.1.0/tests/golden/AGENTS.md +7 -0
  63. rulewright-0.1.0/tests/golden/CLAUDE.md +7 -0
  64. rulewright-0.1.0/tests/golden/copilot-instructions.md +7 -0
  65. rulewright-0.1.0/tests/golden/cursorrules +5 -0
  66. rulewright-0.1.0/tests/golden/rulewright.yaml +18 -0
  67. rulewright-0.1.0/tests/helpers.py +120 -0
  68. rulewright-0.1.0/tests/test_attribution.py +32 -0
  69. rulewright-0.1.0/tests/test_cli_rules.py +99 -0
  70. rulewright-0.1.0/tests/test_cluster.py +137 -0
  71. rulewright-0.1.0/tests/test_emit.py +93 -0
  72. rulewright-0.1.0/tests/test_guard.py +350 -0
  73. rulewright-0.1.0/tests/test_pipeline.py +130 -0
  74. rulewright-0.1.0/tests/test_pr.py +118 -0
  75. rulewright-0.1.0/tests/test_propose.py +469 -0
  76. rulewright-0.1.0/tests/test_prove_agent.py +89 -0
  77. rulewright-0.1.0/tests/test_prove_cli.py +127 -0
  78. rulewright-0.1.0/tests/test_prove_docker.py +110 -0
  79. rulewright-0.1.0/tests/test_prove_runner.py +143 -0
  80. rulewright-0.1.0/tests/test_prove_tasks.py +115 -0
  81. rulewright-0.1.0/tests/test_refix_detector.py +41 -0
  82. rulewright-0.1.0/tests/test_revert_detector.py +29 -0
  83. rulewright-0.1.0/tests/test_rules_source.py +72 -0
  84. rulewright-0.1.0/tests/test_scan_cli.py +106 -0
  85. rulewright-0.1.0/tests/test_score.py +23 -0
  86. rulewright-0.1.0/tests/test_sessions.py +56 -0
  87. rulewright-0.1.0/tests/test_stale.py +96 -0
  88. 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,9 @@
1
+ __pycache__/
2
+ .venv/
3
+ .rulewright/
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ dist/
8
+ *.egg-info/
9
+ rulewright-report.md
@@ -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`.
@@ -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) |