contribos 0.2.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.
- contribos-0.2.0/PKG-INFO +93 -0
- contribos-0.2.0/README.md +78 -0
- contribos-0.2.0/contribos/__init__.py +3 -0
- contribos-0.2.0/contribos/__main__.py +5 -0
- contribos-0.2.0/contribos/agent_rules.py +148 -0
- contribos-0.2.0/contribos/bench.py +80 -0
- contribos-0.2.0/contribos/brief.py +299 -0
- contribos-0.2.0/contribos/check.py +395 -0
- contribos-0.2.0/contribos/claim.py +92 -0
- contribos-0.2.0/contribos/cli.py +371 -0
- contribos-0.2.0/contribos/find.py +295 -0
- contribos-0.2.0/contribos/github.py +96 -0
- contribos-0.2.0/contribos/llm.py +93 -0
- contribos-0.2.0/contribos/mcp_server.py +192 -0
- contribos-0.2.0/contribos/policy.py +341 -0
- contribos-0.2.0/contribos/precedent.py +115 -0
- contribos-0.2.0/contribos/proof.py +105 -0
- contribos-0.2.0/contribos/record.py +135 -0
- contribos-0.2.0/contribos/repo.py +105 -0
- contribos-0.2.0/contribos/review.py +162 -0
- contribos-0.2.0/contribos/setup_doctor.py +368 -0
- contribos-0.2.0/contribos/tone.py +66 -0
- contribos-0.2.0/contribos.egg-info/PKG-INFO +93 -0
- contribos-0.2.0/contribos.egg-info/SOURCES.txt +31 -0
- contribos-0.2.0/contribos.egg-info/dependency_links.txt +1 -0
- contribos-0.2.0/contribos.egg-info/entry_points.txt +2 -0
- contribos-0.2.0/contribos.egg-info/top_level.txt +1 -0
- contribos-0.2.0/pyproject.toml +29 -0
- contribos-0.2.0/setup.cfg +4 -0
- contribos-0.2.0/tests/test_check.py +42 -0
- contribos-0.2.0/tests/test_core.py +53 -0
- contribos-0.2.0/tests/test_journey.py +222 -0
- contribos-0.2.0/tests/test_v5.py +194 -0
contribos-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: contribos
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Get trusted, get merged, come back: a guide for new open-source contributors in the AI era.
|
|
5
|
+
Project-URL: Homepage, https://github.com/adityatiwari101104/contribos
|
|
6
|
+
Project-URL: Issues, https://github.com/adityatiwari101104/contribos/issues
|
|
7
|
+
Keywords: open-source,contributors,github,mcp,ai-policy,good-first-issue,onboarding
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Software Development
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# ContribOS
|
|
17
|
+
|
|
18
|
+
<!-- mcp-name: io.github.adityatiwari101104/contribos -->
|
|
19
|
+
|
|
20
|
+
**Get trusted. Get merged. Come back.** ContribOS helps new open-source contributors earn trust. It finds where you're welcome, shows how this repo wants the change done, checks that you truly understand your change, coaches you through review, and turns merged PRs into a record the next maintainer can verify.
|
|
21
|
+
|
|
22
|
+
In 2026, writing code is not the hard part of contributing. AI made PRs cheap, maintainers answered with PR caps, vouch lists and AI policies, and newcomer merge rates fell. ContribOS makes your PR worth a maintainer's time. See `../plan-v4.md` and `../plan-v5-research.md` for the full reasoning.
|
|
23
|
+
|
|
24
|
+
ContribOS **never** opens PRs, posts comments or claims issues for you, and it never writes your explanation. It points, and you decide.
|
|
25
|
+
|
|
26
|
+
## The journey
|
|
27
|
+
|
|
28
|
+
| Step | Command | What you get |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| Find | `contribos find --lang python --topic web` | Open issues sorted into Good bet / Possible / Skip, each with evidence: claimed? open PR? maintainer active? clear? Plus repo welcome: archived, vouch list, AI policy, outside-PR merge rate, how long outside PRs wait for a first human reply, how many stall, a stale bot, and your own open PRs there |
|
|
31
|
+
| Rules | `contribos policy owner/repo` | The repo's AI policy, disclosure format (`Assisted-by:` and similar trailers), vouch gate, "no AI on good first issues", issue-first and claim-first norms, tests, changelog, DCO/CLA, style tools and activity, each quoted with `file:line`. `--json` gives the same rules in a form agents can read |
|
|
32
|
+
| Propose | `contribos claim <issue-url>` | A short proposal to post *before* coding: likely files, a similar-size past PR, where the test goes, and one real question. Warns about taken issues, no maintainer yet, good-first-issue AI rules and issue-first rules. If the repo has a vouch list, it drafts the introduction first |
|
|
33
|
+
| Tone | `contribos tone comment.md --kind comment` | Flags machine-written tells (delve, buzzwords, assistant openers, filler), length, a missing question and leftover placeholders in anything you're about to post |
|
|
34
|
+
| Agent rules | `contribos agent-rules . --ai "Claude Code"` | Teaches your coding agent this repo's rules: a SKILL.md for Claude Code, Codex, Copilot and Cursor, a hook that stops the agent opening PRs or posting comments, and a commit-msg hook for sign-off and the AI-disclosure trailer. All local-only, excluded from git |
|
|
35
|
+
| Setup | `contribos setup owner/repo` | Exact steps taken from the repo's CI and contributing guide: runtime version, install, services, env vars, test and lint commands |
|
|
36
|
+
| Diagnose | `contribos setup --diagnose output.txt` | What a failure means: missing dependency, wrong version, service down, env var, native build tools, flaky test, or a real test failure (and how to check it isn't yours) |
|
|
37
|
+
| Understand | `contribos brief <issue-url>` | Files to read with the reasons each was picked, similar past PRs and what reviewers said, related tests, look-alike files to leave alone, house rules, and likely review questions. Add `--llm` for a plain-words summary that is grounded in the evidence |
|
|
38
|
+
| Prove | `contribos check --explain explain.md --verify-test "pytest tests/test_x.py" --issue 123 --ai "Claude Code: drafted the test" --pr-draft pr.md` | Pre-submit review: scope versus similar past changes, tests, changelog, sign-off, debug leftovers, AI policy and disclosure trailers, competing PRs for the same issue. `--verify-test` runs your test with the fix and again with your source files swapped back to the base version, so you can show it fails without your change. It checks that *your* explanation covers every changed file and states what you're unsure of, then drafts the PR: what and why, how you verified it, what you're unsure about, related past changes, AI assistance |
|
|
39
|
+
| Respond | `contribos review <pr-url> --path .` | Each reviewer comment classified (blocking, change, question, nit…), the code it points at, the house rule it echoes, a reply draft, what's still unanswered and for how long, and failing CI |
|
|
40
|
+
| Grow | `contribos record <github-user> --html me.html` | A public, linkable record: merged PRs, change requests, whether you answered every review comment, whether you explained your change and disclosed AI use |
|
|
41
|
+
| Agents | `contribos mcp` | Nine MCP tools for Claude Code, Cursor, Codex and others (policy, brief, setup, diagnose, check, review, find, claim, tone), taking text as input where an agent has text: `claude mcp add contribos -- python -m contribos mcp` |
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
pip install -e . # Python 3.10+, git. No other dependencies.
|
|
47
|
+
export GITHUB_TOKEN=... # needed for find, claim/brief from an issue URL, review, record
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Everything that can run offline does: `policy`, `brief --title`, `claim --title`, `setup`, `check`, `bench` and `review --data` need only `git`. Add `--offline` to skip the API, and `--update` to fetch new commits. Clones and indexes are cached in `~/.cache/contribos` (`CONTRIBOS_CACHE`).
|
|
51
|
+
|
|
52
|
+
Optional AI summaries: `CONTRIBOS_LLM=anthropic` (with `ANTHROPIC_API_KEY`) or `CONTRIBOS_LLM=openai` (with `OPENAI_API_KEY`), plus optional `CONTRIBOS_MODEL`. The model only explains evidence ContribOS already gathered. Any sentence citing a file outside that evidence is dropped.
|
|
53
|
+
|
|
54
|
+
## How it works
|
|
55
|
+
|
|
56
|
+
- `repo.py` handles cached clones. It reads any revision with `git show` and `git grep`, so no checkout is needed.
|
|
57
|
+
- `precedent.py` builds a SQLite index of main-line changes (squash and merge PRs), the files they touched, and the issues they fixed.
|
|
58
|
+
- `policy.py` is the policy radar, built from contribution docs, AI policies, templates, vouch lists, CI config and history.
|
|
59
|
+
- `brief.py` ranks files by rare-term matches in code, path matches, and files touched by similar past PRs. It also finds related tests via co-change history.
|
|
60
|
+
- `check.py` is the pre-submit check, the proof-of-understanding check and the PR draft.
|
|
61
|
+
- `setup_doctor.py` produces setup steps from CI and docs, and diagnoses failures.
|
|
62
|
+
- `find.py` checks issue takeability, repo welcome and responsiveness.
|
|
63
|
+
- `claim.py` drafts the proposal and vouch introduction.
|
|
64
|
+
- `proof.py` runs a test with and without your change (files restored in `finally`).
|
|
65
|
+
- `tone.py` checks text you're about to post.
|
|
66
|
+
- `agent_rules.py` writes the local agent skill and hooks.
|
|
67
|
+
- `review.py` classifies review comments, drafts replies and tracks follow-through.
|
|
68
|
+
- `record.py` builds the contribution record (markdown plus a self-contained HTML page).
|
|
69
|
+
- `llm.py` is the optional provider layer (Anthropic or OpenAI) with citation grounding.
|
|
70
|
+
- `mcp_server.py` is a dependency-free MCP stdio server.
|
|
71
|
+
- `github.py` is the optional API client. Every caller handles its absence.
|
|
72
|
+
- `bench.py` runs the history benchmark.
|
|
73
|
+
|
|
74
|
+
## Benchmark (2026-09-28, offline, PR titles standing in for issues)
|
|
75
|
+
|
|
76
|
+
| Repo | Brief hit@5 | Grep-only hit@5 |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| pallets/flask (30 PRs) | 0.77 | 0.73 |
|
|
79
|
+
| pytest-dev/pytest (30 PRs) | 0.87 | 0.87 |
|
|
80
|
+
|
|
81
|
+
Matching against past PRs barely improves file finding, which supports the plan's bet that finding files is a commodity. The value is in how past PRs did it and what reviewers asked. The next benchmark needs real issue text and review comments, which requires `GITHUB_TOKEN`.
|
|
82
|
+
|
|
83
|
+
## Status
|
|
84
|
+
|
|
85
|
+
- Tested on real repos (Flask, pytest, Ghostty): `policy`, `brief`, `claim --title`, `setup`, `check`, `bench`, `mcp`.
|
|
86
|
+
- Tested with realistic fake GitHub data, but not yet against the live API (it was blocked in the build environment): `find`, `claim <url>`, `review <url>`, `record`, and the `brief` review quotes. Run them with a token before relying on them.
|
|
87
|
+
- Tested on temporary git repos: `agent-rules` (git status stays clean, the guard blocks `gh pr create`, the commit-msg hook enforces sign-off and the trailer), `check --verify-test`, `tone`, policy radar v2.
|
|
88
|
+
- Tests: `python3 -m unittest discover -s tests` (37 tests).
|
|
89
|
+
|
|
90
|
+
## Publishing (when ready)
|
|
91
|
+
|
|
92
|
+
- PyPI: `python -m build && twine upload dist/*` (version 0.2.0 in `pyproject.toml`).
|
|
93
|
+
- MCP registry: `server.json` describes the package as `io.github.adityatiwari101104/contribos`; the `mcp-name` comment at the top of this README lets the registry verify the PyPI package. Check `server.json` against the current registry schema before publishing.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# ContribOS
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.adityatiwari101104/contribos -->
|
|
4
|
+
|
|
5
|
+
**Get trusted. Get merged. Come back.** ContribOS helps new open-source contributors earn trust. It finds where you're welcome, shows how this repo wants the change done, checks that you truly understand your change, coaches you through review, and turns merged PRs into a record the next maintainer can verify.
|
|
6
|
+
|
|
7
|
+
In 2026, writing code is not the hard part of contributing. AI made PRs cheap, maintainers answered with PR caps, vouch lists and AI policies, and newcomer merge rates fell. ContribOS makes your PR worth a maintainer's time. See `../plan-v4.md` and `../plan-v5-research.md` for the full reasoning.
|
|
8
|
+
|
|
9
|
+
ContribOS **never** opens PRs, posts comments or claims issues for you, and it never writes your explanation. It points, and you decide.
|
|
10
|
+
|
|
11
|
+
## The journey
|
|
12
|
+
|
|
13
|
+
| Step | Command | What you get |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Find | `contribos find --lang python --topic web` | Open issues sorted into Good bet / Possible / Skip, each with evidence: claimed? open PR? maintainer active? clear? Plus repo welcome: archived, vouch list, AI policy, outside-PR merge rate, how long outside PRs wait for a first human reply, how many stall, a stale bot, and your own open PRs there |
|
|
16
|
+
| Rules | `contribos policy owner/repo` | The repo's AI policy, disclosure format (`Assisted-by:` and similar trailers), vouch gate, "no AI on good first issues", issue-first and claim-first norms, tests, changelog, DCO/CLA, style tools and activity, each quoted with `file:line`. `--json` gives the same rules in a form agents can read |
|
|
17
|
+
| Propose | `contribos claim <issue-url>` | A short proposal to post *before* coding: likely files, a similar-size past PR, where the test goes, and one real question. Warns about taken issues, no maintainer yet, good-first-issue AI rules and issue-first rules. If the repo has a vouch list, it drafts the introduction first |
|
|
18
|
+
| Tone | `contribos tone comment.md --kind comment` | Flags machine-written tells (delve, buzzwords, assistant openers, filler), length, a missing question and leftover placeholders in anything you're about to post |
|
|
19
|
+
| Agent rules | `contribos agent-rules . --ai "Claude Code"` | Teaches your coding agent this repo's rules: a SKILL.md for Claude Code, Codex, Copilot and Cursor, a hook that stops the agent opening PRs or posting comments, and a commit-msg hook for sign-off and the AI-disclosure trailer. All local-only, excluded from git |
|
|
20
|
+
| Setup | `contribos setup owner/repo` | Exact steps taken from the repo's CI and contributing guide: runtime version, install, services, env vars, test and lint commands |
|
|
21
|
+
| Diagnose | `contribos setup --diagnose output.txt` | What a failure means: missing dependency, wrong version, service down, env var, native build tools, flaky test, or a real test failure (and how to check it isn't yours) |
|
|
22
|
+
| Understand | `contribos brief <issue-url>` | Files to read with the reasons each was picked, similar past PRs and what reviewers said, related tests, look-alike files to leave alone, house rules, and likely review questions. Add `--llm` for a plain-words summary that is grounded in the evidence |
|
|
23
|
+
| Prove | `contribos check --explain explain.md --verify-test "pytest tests/test_x.py" --issue 123 --ai "Claude Code: drafted the test" --pr-draft pr.md` | Pre-submit review: scope versus similar past changes, tests, changelog, sign-off, debug leftovers, AI policy and disclosure trailers, competing PRs for the same issue. `--verify-test` runs your test with the fix and again with your source files swapped back to the base version, so you can show it fails without your change. It checks that *your* explanation covers every changed file and states what you're unsure of, then drafts the PR: what and why, how you verified it, what you're unsure about, related past changes, AI assistance |
|
|
24
|
+
| Respond | `contribos review <pr-url> --path .` | Each reviewer comment classified (blocking, change, question, nit…), the code it points at, the house rule it echoes, a reply draft, what's still unanswered and for how long, and failing CI |
|
|
25
|
+
| Grow | `contribos record <github-user> --html me.html` | A public, linkable record: merged PRs, change requests, whether you answered every review comment, whether you explained your change and disclosed AI use |
|
|
26
|
+
| Agents | `contribos mcp` | Nine MCP tools for Claude Code, Cursor, Codex and others (policy, brief, setup, diagnose, check, review, find, claim, tone), taking text as input where an agent has text: `claude mcp add contribos -- python -m contribos mcp` |
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pip install -e . # Python 3.10+, git. No other dependencies.
|
|
32
|
+
export GITHUB_TOKEN=... # needed for find, claim/brief from an issue URL, review, record
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Everything that can run offline does: `policy`, `brief --title`, `claim --title`, `setup`, `check`, `bench` and `review --data` need only `git`. Add `--offline` to skip the API, and `--update` to fetch new commits. Clones and indexes are cached in `~/.cache/contribos` (`CONTRIBOS_CACHE`).
|
|
36
|
+
|
|
37
|
+
Optional AI summaries: `CONTRIBOS_LLM=anthropic` (with `ANTHROPIC_API_KEY`) or `CONTRIBOS_LLM=openai` (with `OPENAI_API_KEY`), plus optional `CONTRIBOS_MODEL`. The model only explains evidence ContribOS already gathered. Any sentence citing a file outside that evidence is dropped.
|
|
38
|
+
|
|
39
|
+
## How it works
|
|
40
|
+
|
|
41
|
+
- `repo.py` handles cached clones. It reads any revision with `git show` and `git grep`, so no checkout is needed.
|
|
42
|
+
- `precedent.py` builds a SQLite index of main-line changes (squash and merge PRs), the files they touched, and the issues they fixed.
|
|
43
|
+
- `policy.py` is the policy radar, built from contribution docs, AI policies, templates, vouch lists, CI config and history.
|
|
44
|
+
- `brief.py` ranks files by rare-term matches in code, path matches, and files touched by similar past PRs. It also finds related tests via co-change history.
|
|
45
|
+
- `check.py` is the pre-submit check, the proof-of-understanding check and the PR draft.
|
|
46
|
+
- `setup_doctor.py` produces setup steps from CI and docs, and diagnoses failures.
|
|
47
|
+
- `find.py` checks issue takeability, repo welcome and responsiveness.
|
|
48
|
+
- `claim.py` drafts the proposal and vouch introduction.
|
|
49
|
+
- `proof.py` runs a test with and without your change (files restored in `finally`).
|
|
50
|
+
- `tone.py` checks text you're about to post.
|
|
51
|
+
- `agent_rules.py` writes the local agent skill and hooks.
|
|
52
|
+
- `review.py` classifies review comments, drafts replies and tracks follow-through.
|
|
53
|
+
- `record.py` builds the contribution record (markdown plus a self-contained HTML page).
|
|
54
|
+
- `llm.py` is the optional provider layer (Anthropic or OpenAI) with citation grounding.
|
|
55
|
+
- `mcp_server.py` is a dependency-free MCP stdio server.
|
|
56
|
+
- `github.py` is the optional API client. Every caller handles its absence.
|
|
57
|
+
- `bench.py` runs the history benchmark.
|
|
58
|
+
|
|
59
|
+
## Benchmark (2026-09-28, offline, PR titles standing in for issues)
|
|
60
|
+
|
|
61
|
+
| Repo | Brief hit@5 | Grep-only hit@5 |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| pallets/flask (30 PRs) | 0.77 | 0.73 |
|
|
64
|
+
| pytest-dev/pytest (30 PRs) | 0.87 | 0.87 |
|
|
65
|
+
|
|
66
|
+
Matching against past PRs barely improves file finding, which supports the plan's bet that finding files is a commodity. The value is in how past PRs did it and what reviewers asked. The next benchmark needs real issue text and review comments, which requires `GITHUB_TOKEN`.
|
|
67
|
+
|
|
68
|
+
## Status
|
|
69
|
+
|
|
70
|
+
- Tested on real repos (Flask, pytest, Ghostty): `policy`, `brief`, `claim --title`, `setup`, `check`, `bench`, `mcp`.
|
|
71
|
+
- Tested with realistic fake GitHub data, but not yet against the live API (it was blocked in the build environment): `find`, `claim <url>`, `review <url>`, `record`, and the `brief` review quotes. Run them with a token before relying on them.
|
|
72
|
+
- Tested on temporary git repos: `agent-rules` (git status stays clean, the guard blocks `gh pr create`, the commit-msg hook enforces sign-off and the trailer), `check --verify-test`, `tone`, policy radar v2.
|
|
73
|
+
- Tests: `python3 -m unittest discover -s tests` (37 tests).
|
|
74
|
+
|
|
75
|
+
## Publishing (when ready)
|
|
76
|
+
|
|
77
|
+
- PyPI: `python -m build && twine upload dist/*` (version 0.2.0 in `pyproject.toml`).
|
|
78
|
+
- MCP registry: `server.json` describes the package as `io.github.adityatiwari101104/contribos`; the `mcp-name` comment at the top of this README lets the registry verify the PyPI package. Check `server.json` against the current registry schema before publishing.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Agent guardrails: make your coding agent follow this repo's rules, where slop starts.
|
|
2
|
+
|
|
3
|
+
Writes, into your local checkout:
|
|
4
|
+
- a skill (`.claude/skills/contribos/SKILL.md`, mirrored to `.agents/skills/`)
|
|
5
|
+
that Claude Code, Codex, Copilot and Cursor load, with this repo's rules;
|
|
6
|
+
- a Claude Code hook that blocks the agent from opening PRs or posting
|
|
7
|
+
comments on its own;
|
|
8
|
+
- a git `commit-msg` hook that enforces sign-off and the AI-disclosure line
|
|
9
|
+
this repo asks for.
|
|
10
|
+
|
|
11
|
+
None of these files are part of your contribution: they're added to
|
|
12
|
+
`.git/info/exclude` so they never end up in your PR.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import stat
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from .policy import Policy, structured
|
|
22
|
+
from .setup_doctor import SetupPlan
|
|
23
|
+
|
|
24
|
+
GUARD = '''#!/usr/bin/env python3
|
|
25
|
+
"""ContribOS guard: stop the coding agent from acting publicly on its own."""
|
|
26
|
+
import json, re, sys
|
|
27
|
+
|
|
28
|
+
data = json.load(sys.stdin)
|
|
29
|
+
cmd = (data.get("tool_input") or {}).get("command", "")
|
|
30
|
+
if re.search(r"\\bgh\\s+(pr\\s+(create|comment|review|merge|edit|ready|close|reopen)|"
|
|
31
|
+
r"issue\\s+(create|comment|edit|close|reopen)|"
|
|
32
|
+
r"api\\b.*(-X|--method)\\s*(POST|PATCH|PUT|DELETE))", cmd, re.I):
|
|
33
|
+
print("ContribOS: opening PRs, posting comments or claiming issues must be done by the human "
|
|
34
|
+
"contributor, not the agent. Prepare the text and let the human post it.", file=sys.stderr)
|
|
35
|
+
sys.exit(2)
|
|
36
|
+
sys.exit(0)
|
|
37
|
+
'''
|
|
38
|
+
|
|
39
|
+
COMMIT_MSG = '''#!/bin/sh
|
|
40
|
+
# ContribOS commit-msg hook: enforce this repo's sign-off and AI-disclosure rules.
|
|
41
|
+
msg="$1"
|
|
42
|
+
{dco}
|
|
43
|
+
if [ -f "$(git rev-parse --git-dir)/contribos-ai-used" ] && [ -n "{trailer}" ]; then
|
|
44
|
+
if ! grep -qi "^{trailer}:" "$msg"; then
|
|
45
|
+
echo "ContribOS: this repo asks for a '{trailer}' line when AI helped. Add, for example:" >&2
|
|
46
|
+
echo " {trailer}: $(cat "$(git rev-parse --git-dir)/contribos-ai-used")" >&2
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
fi
|
|
50
|
+
exit 0
|
|
51
|
+
'''
|
|
52
|
+
|
|
53
|
+
DCO_CHECK = '''if ! grep -q "^Signed-off-by:" "$msg"; then
|
|
54
|
+
echo "ContribOS: this repo requires sign-off. Commit with: git commit -s" >&2
|
|
55
|
+
exit 1
|
|
56
|
+
fi'''
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def skill_text(policy: Policy, setup: SetupPlan | None) -> str:
|
|
60
|
+
s = structured(policy)
|
|
61
|
+
ai = s["ai"]
|
|
62
|
+
rules = ["Never open pull requests, post comments, or claim issues yourself. Draft text; the human posts it.",
|
|
63
|
+
"Keep the change minimal and focused on the issue. Do not refactor unrelated code.",
|
|
64
|
+
"The human must understand every line. Explain your changes plainly and point out anything uncertain.",
|
|
65
|
+
"Never write the human's PR explanation, issue comments or review replies as final text; offer a "
|
|
66
|
+
"short draft they will rewrite."]
|
|
67
|
+
if ai["stance"] == "restricted":
|
|
68
|
+
rules.append("This repo restricts AI use. Read the AI policy evidence below and stop if the task is not allowed.")
|
|
69
|
+
if s["trust_gate"]:
|
|
70
|
+
rules.append("This repo only accepts PRs from vouched contributors. If the human isn't vouched yet, "
|
|
71
|
+
"help them write a short introduction instead of code.")
|
|
72
|
+
if ai["no_ai_on_good_first_issues"]:
|
|
73
|
+
rules.append("Do not work on issues labelled good-first-issue: this repo reserves them for humans learning.")
|
|
74
|
+
if ai["disclosure_trailer"]:
|
|
75
|
+
rules.append(f"Every commit you help with must include a `{ai['disclosure_trailer']}` line naming the tool.")
|
|
76
|
+
elif ai["disclose_in_pr"]:
|
|
77
|
+
rules.append("Remind the human to disclose in the PR description which tool was used and for what.")
|
|
78
|
+
if s["dco"]:
|
|
79
|
+
rules.append("Commits must be signed off by the human (`git commit -s`). Never add Signed-off-by yourself.")
|
|
80
|
+
if s["tests_expected"]:
|
|
81
|
+
rules.append("Add or update a test that fails without the fix.")
|
|
82
|
+
if s["changelog"]:
|
|
83
|
+
rules.append("Add a changelog entry in the format the repo uses.")
|
|
84
|
+
if s["issue_first"] or s["claim_first"]:
|
|
85
|
+
rules.append("Only work on an issue a maintainer has agreed to. If unsure, stop and ask the human.")
|
|
86
|
+
steps = []
|
|
87
|
+
if setup:
|
|
88
|
+
for st in setup.steps:
|
|
89
|
+
if st.commands and ("test" in st.title.lower() or "lint" in st.title.lower() or "pre-commit" in st.title.lower()):
|
|
90
|
+
steps += [f"`{c}`" for c in st.commands[:2]]
|
|
91
|
+
L = ["---", "name: contribos", "description: Rules for contributing to this open-source repository. Use before "
|
|
92
|
+
"writing or committing any change here.", "---", "",
|
|
93
|
+
f"# Contributing to {policy.repo}", "",
|
|
94
|
+
"You are helping a human contributor. Maintainers here review human work and reject unexplained AI output.",
|
|
95
|
+
"", "## Rules", ""] + [f"- {r}" for r in rules]
|
|
96
|
+
if steps:
|
|
97
|
+
L += ["", "## Run before saying you're done", ""] + [f"- {c}" for c in steps]
|
|
98
|
+
L += ["", "## Tools", "",
|
|
99
|
+
"If the ContribOS MCP server is available, use `contribos_brief` to find relevant files and past PRs, "
|
|
100
|
+
"and `contribos_check` before the human submits.", "", "## Evidence for these rules", ""]
|
|
101
|
+
for f in policy.findings[:8]:
|
|
102
|
+
src = f" ({f.evidence[0].source})" if f.evidence else ""
|
|
103
|
+
L.append(f"- {f.topic}: {f.verdict}{src}")
|
|
104
|
+
return "\n".join(L) + "\n"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def install(root: Path, policy: Policy, setup: SetupPlan | None, ai_note: str | None = None) -> list[str]:
|
|
108
|
+
written = []
|
|
109
|
+
text = skill_text(policy, setup)
|
|
110
|
+
for d in (root / ".claude/skills/contribos", root / ".agents/skills/contribos"):
|
|
111
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
112
|
+
(d / "SKILL.md").write_text(text)
|
|
113
|
+
written.append((d / "SKILL.md").relative_to(root).as_posix())
|
|
114
|
+
|
|
115
|
+
guard = root / ".contribos/guard.py"
|
|
116
|
+
guard.parent.mkdir(exist_ok=True)
|
|
117
|
+
guard.write_text(GUARD)
|
|
118
|
+
guard.chmod(guard.stat().st_mode | stat.S_IEXEC)
|
|
119
|
+
written.append(".contribos/guard.py")
|
|
120
|
+
settings_path = root / ".claude/settings.local.json"
|
|
121
|
+
settings = json.loads(settings_path.read_text()) if settings_path.exists() else {}
|
|
122
|
+
hooks = settings.setdefault("hooks", {}).setdefault("PreToolUse", [])
|
|
123
|
+
entry = {"matcher": "Bash", "hooks": [{"type": "command", "command": "python3 .contribos/guard.py"}]}
|
|
124
|
+
if entry not in hooks:
|
|
125
|
+
hooks.append(entry)
|
|
126
|
+
settings_path.write_text(json.dumps(settings, indent=2) + "\n")
|
|
127
|
+
written.append(".claude/settings.local.json")
|
|
128
|
+
|
|
129
|
+
s = structured(policy)
|
|
130
|
+
git_dir = root / ".git"
|
|
131
|
+
if git_dir.is_dir():
|
|
132
|
+
hook = git_dir / "hooks" / "commit-msg"
|
|
133
|
+
target = hook if not hook.exists() or "ContribOS" in hook.read_text() else hook.with_name("commit-msg.contribos")
|
|
134
|
+
target.write_text(COMMIT_MSG.format(dco=DCO_CHECK if s["dco"] else "", trailer=s["ai"]["disclosure_trailer"] or ""))
|
|
135
|
+
target.chmod(target.stat().st_mode | stat.S_IEXEC)
|
|
136
|
+
written.append(target.relative_to(root).as_posix())
|
|
137
|
+
marker = git_dir / "contribos-ai-used"
|
|
138
|
+
if ai_note:
|
|
139
|
+
marker.write_text(ai_note + "\n")
|
|
140
|
+
exclude = git_dir / "info" / "exclude"
|
|
141
|
+
exclude.parent.mkdir(exist_ok=True)
|
|
142
|
+
current = exclude.read_text() if exclude.exists() else ""
|
|
143
|
+
add = [p for p in (".claude/skills/contribos/", ".agents/skills/contribos/", ".contribos/",
|
|
144
|
+
".claude/settings.local.json") if p not in current]
|
|
145
|
+
if add:
|
|
146
|
+
exclude.write_text(current.rstrip("\n") + ("\n" if current else "") + "# ContribOS (local only)\n"
|
|
147
|
+
+ "\n".join(add) + "\n")
|
|
148
|
+
return written
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""History benchmark: replay past merged changes and check whether the brief finds their files.
|
|
2
|
+
|
|
3
|
+
For each sampled change we rebuild the brief at its parent commit, using only
|
|
4
|
+
history older than the change, and the PR title as a stand-in for the issue.
|
|
5
|
+
Titles are shorter and more precise than real issues, so treat scores as an
|
|
6
|
+
upper-bound smoke test; the real benchmark uses linked issue text (needs the
|
|
7
|
+
GitHub API).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import random
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
|
|
15
|
+
from . import brief as brief_mod
|
|
16
|
+
from .policy import is_code
|
|
17
|
+
from .precedent import Change
|
|
18
|
+
from .repo import Repo
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Case:
|
|
23
|
+
change: Change
|
|
24
|
+
code_files: list[str]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class Result:
|
|
29
|
+
case: Case
|
|
30
|
+
full_hit: bool
|
|
31
|
+
full_recall: float
|
|
32
|
+
grep_hit: bool
|
|
33
|
+
grep_recall: float
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def sample(history: list[Change], n: int, seed: int = 7) -> list[Case]:
|
|
37
|
+
pool = []
|
|
38
|
+
for i, c in enumerate(history[:-50]): # keep older history as precedent
|
|
39
|
+
code = [f for f in c.files if is_code(f)]
|
|
40
|
+
if c.pr and 1 <= len(code) <= 4 and len(c.title.split()) >= 4 \
|
|
41
|
+
and not c.title.lower().startswith(("bump", "release", "merge", "revert", "update dependencies")):
|
|
42
|
+
pool.append(Case(c, code))
|
|
43
|
+
random.Random(seed).shuffle(pool)
|
|
44
|
+
return pool[:n]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _score(top: list[str], truth: list[str]) -> tuple[bool, float]:
|
|
48
|
+
found = len(set(top) & set(truth))
|
|
49
|
+
return found > 0, found / len(truth)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def run(repo: Repo, history: list[Change], n: int = 20, k: int = 5, log=print) -> list[Result]:
|
|
53
|
+
results = []
|
|
54
|
+
order = {c.sha: i for i, c in enumerate(history)}
|
|
55
|
+
for case in sample(history, n):
|
|
56
|
+
c = case.change
|
|
57
|
+
parent = f"{c.sha}^1"
|
|
58
|
+
older = history[order[c.sha] + 1:]
|
|
59
|
+
full = brief_mod.build(repo, c.title, "", older, None, rev=parent, top=k)
|
|
60
|
+
grep = brief_mod.build(repo, c.title, "", [], None, rev=parent, top=k)
|
|
61
|
+
fh, fr = _score([h.path for h in full.files], case.code_files)
|
|
62
|
+
gh, gr = _score([h.path for h in grep.files], case.code_files)
|
|
63
|
+
results.append(Result(case, fh, fr, gh, gr))
|
|
64
|
+
log(f" #{c.pr} {'✓' if fh else '·'} {'✓' if gh else '·'} {_short(c.title, 70)}")
|
|
65
|
+
return results
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def summary(results: list[Result]) -> dict:
|
|
69
|
+
n = len(results) or 1
|
|
70
|
+
return {
|
|
71
|
+
"cases": len(results),
|
|
72
|
+
"brief_hit@5": sum(r.full_hit for r in results) / n,
|
|
73
|
+
"brief_recall@5": sum(r.full_recall for r in results) / n,
|
|
74
|
+
"grep_only_hit@5": sum(r.grep_hit for r in results) / n,
|
|
75
|
+
"grep_only_recall@5": sum(r.grep_recall for r in results) / n,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _short(text: str, n: int) -> str:
|
|
80
|
+
return text if len(text) <= n else text[:n - 1].rstrip() + "…"
|