Skip to content

Commit e370801

Browse files
committed
Docs(refactor[agents]): Route instead of restate
why: AGENTS.md is read on every task, so a policy that matters to one class of change costs context on all the others. Splitting how we work from how we write lets each be loaded when it applies, and keeps both discoverable to humans rather than only to agents. what: - Replace the AGENTS.md body with a router: the project map, universal change discipline, then one pointer per class of change - Add .github/WRITING.md for prose policy, including the CLI's real exit-status/stdout/stderr contract, the logging key schema, and the Django-shape CHANGES conventions - Add .github/CONTRIBUTING.md for workflow policy, with real gate commands read from pyproject.toml, the Makefile, and CI - Rename .github/contributing.md to .github/CONTRIBUTING.md, keeping its Decorum section - Delete docs/AGENTS.md and docs/CLAUDE.md, now absorbed into .github/WRITING.md - Turn docs/project/contributing.md and docs/project/code-style.md into pointer pages at the canonical files, since a Sphinx {include} of a file outside docs/ rewrites its links into dead anchors - Correct README's Python version claim and the developmental-release uv commands, which were missing --prerelease and could not work
1 parent 881a69f commit e370801

9 files changed

Lines changed: 986 additions & 1029 deletions

File tree

.github/CONTRIBUTING.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Contributing
2+
3+
Thanks for looking. A bug report with a reproduction — the directory layout
4+
and the exact `g` invocation — is the most useful thing you can send; a
5+
correction to somewhere the documentation misled you is a close second.
6+
7+
How this project writes prose — README, `CHANGES`, commit messages,
8+
docstrings, source comments, and CLI/error text — is set out separately in
9+
[WRITING.md](WRITING.md). Read that before changing any of it. The
10+
constraints every change is held to, and the map of what is where, are in
11+
[AGENTS.md](../AGENTS.md).
12+
13+
## Getting set up
14+
15+
Install [uv], then sync the dev and docs extras:
16+
17+
```console
18+
$ uv sync --all-extras --dev
19+
```
20+
21+
[uv]: https://github.com/astral-sh/uv
22+
23+
## The gates
24+
25+
CI is the order of record; every gate it runs has to pass before a change is
26+
done.
27+
28+
Format:
29+
30+
```console
31+
$ uv run ruff format .
32+
```
33+
34+
Lint:
35+
36+
```console
37+
$ uv run ruff check . --fix --show-fixes
38+
```
39+
40+
Type-check (`[tool.mypy] strict = true`):
41+
42+
```console
43+
$ uv run mypy .
44+
```
45+
46+
Test:
47+
48+
```console
49+
$ uv run py.test
50+
```
51+
52+
Documentation is a gate, not a courtesy. Examples in docstrings under
53+
`src/g` and pages under `docs/` are executed by `pytest`/`py.test` — the
54+
doctest flags live in `pyproject.toml`, so there is no separate doctest
55+
step and a green test run is the proof. `README.md` is not included, so its
56+
examples are never executed. Which blocks qualify, and the one mistake that
57+
silently removes a test, are in
58+
[WRITING.md](WRITING.md#documented-examples-that-run).
59+
60+
Ruff's isort config requires `from __future__ import annotations` in every
61+
module (`required-imports`, backed by the `FA100` rule) — the linter, not
62+
this file, catches a missing one. Import stdlib modules by namespace
63+
(`import typing as t`, `import logging`) rather than `from typing import
64+
`; third-party packages may use `from X import Y`. Nothing enforces this
65+
one — it is a convention, not a lint rule.
66+
67+
Before claiming a test or a gate works, show it failing. A gate that has
68+
never been red is an assumption.
69+
70+
## Tests
71+
72+
Tests live in `tests/test_cli.py`, parametrized through a
73+
`CommandLineTestFixture` `NamedTuple`. The autouse `setup` fixture in
74+
`conftest.py` sets `G_IS_TEST=1` for every test, which makes `run()` return
75+
the `subprocess.Popen` object instead of `None` so assertions can inspect
76+
it — outside tests, `run()` always returns `None`.
77+
78+
CLI tests invoke real VCS binaries (at minimum `git`) rather than mocking
79+
the subprocess call; use `tmp_path` and `monkeypatch` to simulate a
80+
non-repo directory.
81+
82+
`find_repo_type()` requires a `.git`, `.svn`, or `.hg` **directory**. A git
83+
worktree checkout's top-level `.git` is a file, not a directory, so
84+
`test_command_line[g-cmd-inside-git-dir]` and its `--help` sibling fail
85+
there even though nothing is broken. Work from a normal clone (not `git
86+
worktree add`) to run the full suite green.
87+
88+
When subprocess output seems swallowed, set `G_IS_TEST=1` and call
89+
`run(wait=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)` to capture
90+
it for inspection.
91+
92+
Assert on `caplog.records` attributes, not string matching on `caplog.text`
93+
`caplog.record_tuples` cannot see `extra` fields. Scope capture with
94+
`caplog.at_level(logging.DEBUG, logger="g")``g`'s modules log through
95+
`logging.getLogger(__name__)`, so the logger name is the module's own
96+
dotted path, `g` at the package root. Filter records rather than index by
97+
position: `[r for r in caplog.records if hasattr(r, "vcs_cmd")]`.
98+
99+
## Documentation
100+
101+
Build once:
102+
103+
```console
104+
$ make build_docs
105+
```
106+
107+
Live-reloading preview at `http://localhost:8034`:
108+
109+
```console
110+
$ make start_docs
111+
```
112+
113+
From inside `docs/`, the same tasks are `just html` and `just start`; `just
114+
--list` in `docs/` shows the rest (`watch`, `serve`, `linkcheck`,
115+
`doctest`). `make build_docs` and `just html` both wrap
116+
`sphinx-build`; nothing under `docs/_build` is hand-edited.
117+
118+
`AGENTS.md` and `CLAUDE.md` are excluded from the build
119+
(`exclude_patterns` in `docs/conf.py`) — they are agent guidance, not site
120+
pages. `make build_docs` catches a broken cross-reference; the test suite
121+
does not, so build the docs before committing a documentation change.
122+
123+
## Releasing
124+
125+
Never create tags. Never push tags. The owner handles tagging and tag
126+
pushes, because a tag triggers the publish workflow. See
127+
[Release commits](WRITING.md#release-commits).
128+
129+
1. Update `CHANGES` with the release notes.
130+
2. Bump the version in `src/g/__about__.py` and `pyproject.toml`.
131+
3. Commit the release files with the subject `Tag v<version>`.
132+
4. Tag (`git tag v<version>`) and push the branch, then the tag
133+
(`git push --tags`).
134+
5. CI builds and publishes to PyPI automatically over OIDC trusted
135+
publishing once the tag lands.
136+
137+
Full detail: [docs/project/releasing.md](../docs/project/releasing.md).
138+
139+
## Pull requests
140+
141+
One subject per pull request. Unrelated cleanup found along the way belongs
142+
in its own commit, and usually in its own pull request.
143+
144+
Discuss a substantial change via an issue before making it.
145+
146+
Commit format is in [WRITING.md](WRITING.md#commits).
147+
148+
Merge once you have the sign-off of one other developer. If you do not have
149+
permission to merge, ask a maintainer to merge it for you.
150+
151+
## Decorum
152+
153+
- Participants will be tolerant of opposing views.
154+
- Participants must ensure that their language and actions are free of
155+
personal attacks and disparaging personal remarks.
156+
- When interpreting the words and actions of others, participants should
157+
always assume good intentions.
158+
- Behaviour which can be reasonably considered harassment will not be
159+
tolerated.
160+
161+
Based on [Ruby's Community Conduct Guideline](https://www.ruby-lang.org/en/conduct/).
162+
163+
## Security
164+
165+
This repository has no `SECURITY.md`. Please do not open a public issue for
166+
a vulnerability — report it privately via [GitHub's security
167+
advisories](https://github.com/vcs-python/g/security/advisories/new).

0 commit comments

Comments
 (0)