From e370801ffeb5afeea8760e0458748e096384ba28 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 22 Aug 2026 09:59:59 -0500 Subject: [PATCH] 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 --- .github/CONTRIBUTING.md | 167 ++++++++ .github/WRITING.md | 739 +++++++++++++++++++++++++++++++++++ .github/contributing.md | 27 -- AGENTS.md | 625 ++--------------------------- README.md | 40 +- docs/AGENTS.md | 131 ------- docs/CLAUDE.md | 1 - docs/project/code-style.md | 27 +- docs/project/contributing.md | 258 +----------- 9 files changed, 986 insertions(+), 1029 deletions(-) create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/WRITING.md delete mode 100644 .github/contributing.md delete mode 100644 docs/AGENTS.md delete mode 120000 docs/CLAUDE.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..8bf0cf8 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,167 @@ +# Contributing + +Thanks for looking. A bug report with a reproduction — the directory layout +and the exact `g` invocation — is the most useful thing you can send; a +correction to somewhere the documentation misled you is a close second. + +How this project writes prose — README, `CHANGES`, commit messages, +docstrings, source comments, and CLI/error text — is set out separately in +[WRITING.md](WRITING.md). Read that before changing any of it. The +constraints every change is held to, and the map of what is where, are in +[AGENTS.md](../AGENTS.md). + +## Getting set up + +Install [uv], then sync the dev and docs extras: + +```console +$ uv sync --all-extras --dev +``` + +[uv]: https://github.com/astral-sh/uv + +## The gates + +CI is the order of record; every gate it runs has to pass before a change is +done. + +Format: + +```console +$ uv run ruff format . +``` + +Lint: + +```console +$ uv run ruff check . --fix --show-fixes +``` + +Type-check (`[tool.mypy] strict = true`): + +```console +$ uv run mypy . +``` + +Test: + +```console +$ uv run py.test +``` + +Documentation is a gate, not a courtesy. Examples in docstrings under +`src/g` and pages under `docs/` are executed by `pytest`/`py.test` — the +doctest flags live in `pyproject.toml`, so there is no separate doctest +step and a green test run is the proof. `README.md` is not included, so its +examples are never executed. Which blocks qualify, and the one mistake that +silently removes a test, are in +[WRITING.md](WRITING.md#documented-examples-that-run). + +Ruff's isort config requires `from __future__ import annotations` in every +module (`required-imports`, backed by the `FA100` rule) — the linter, not +this file, catches a missing one. Import stdlib modules by namespace +(`import typing as t`, `import logging`) rather than `from typing import +…`; third-party packages may use `from X import Y`. Nothing enforces this +one — it is a convention, not a lint rule. + +Before claiming a test or a gate works, show it failing. A gate that has +never been red is an assumption. + +## Tests + +Tests live in `tests/test_cli.py`, parametrized through a +`CommandLineTestFixture` `NamedTuple`. The autouse `setup` fixture in +`conftest.py` sets `G_IS_TEST=1` for every test, which makes `run()` return +the `subprocess.Popen` object instead of `None` so assertions can inspect +it — outside tests, `run()` always returns `None`. + +CLI tests invoke real VCS binaries (at minimum `git`) rather than mocking +the subprocess call; use `tmp_path` and `monkeypatch` to simulate a +non-repo directory. + +`find_repo_type()` requires a `.git`, `.svn`, or `.hg` **directory**. A git +worktree checkout's top-level `.git` is a file, not a directory, so +`test_command_line[g-cmd-inside-git-dir]` and its `--help` sibling fail +there even though nothing is broken. Work from a normal clone (not `git +worktree add`) to run the full suite green. + +When subprocess output seems swallowed, set `G_IS_TEST=1` and call +`run(wait=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)` to capture +it for inspection. + +Assert on `caplog.records` attributes, not string matching on `caplog.text` +— `caplog.record_tuples` cannot see `extra` fields. Scope capture with +`caplog.at_level(logging.DEBUG, logger="g")` — `g`'s modules log through +`logging.getLogger(__name__)`, so the logger name is the module's own +dotted path, `g` at the package root. Filter records rather than index by +position: `[r for r in caplog.records if hasattr(r, "vcs_cmd")]`. + +## Documentation + +Build once: + +```console +$ make build_docs +``` + +Live-reloading preview at `http://localhost:8034`: + +```console +$ make start_docs +``` + +From inside `docs/`, the same tasks are `just html` and `just start`; `just +--list` in `docs/` shows the rest (`watch`, `serve`, `linkcheck`, +`doctest`). `make build_docs` and `just html` both wrap +`sphinx-build`; nothing under `docs/_build` is hand-edited. + +`AGENTS.md` and `CLAUDE.md` are excluded from the build +(`exclude_patterns` in `docs/conf.py`) — they are agent guidance, not site +pages. `make build_docs` catches a broken cross-reference; the test suite +does not, so build the docs before committing a documentation change. + +## Releasing + +Never create tags. Never push tags. The owner handles tagging and tag +pushes, because a tag triggers the publish workflow. See +[Release commits](WRITING.md#release-commits). + +1. Update `CHANGES` with the release notes. +2. Bump the version in `src/g/__about__.py` and `pyproject.toml`. +3. Commit the release files with the subject `Tag v`. +4. Tag (`git tag v`) and push the branch, then the tag + (`git push --tags`). +5. CI builds and publishes to PyPI automatically over OIDC trusted + publishing once the tag lands. + +Full detail: [docs/project/releasing.md](../docs/project/releasing.md). + +## Pull requests + +One subject per pull request. Unrelated cleanup found along the way belongs +in its own commit, and usually in its own pull request. + +Discuss a substantial change via an issue before making it. + +Commit format is in [WRITING.md](WRITING.md#commits). + +Merge once you have the sign-off of one other developer. If you do not have +permission to merge, ask a maintainer to merge it for you. + +## Decorum + +- Participants will be tolerant of opposing views. +- Participants must ensure that their language and actions are free of + personal attacks and disparaging personal remarks. +- When interpreting the words and actions of others, participants should + always assume good intentions. +- Behaviour which can be reasonably considered harassment will not be + tolerated. + +Based on [Ruby's Community Conduct Guideline](https://www.ruby-lang.org/en/conduct/). + +## Security + +This repository has no `SECURITY.md`. Please do not open a public issue for +a vulnerability — report it privately via [GitHub's security +advisories](https://github.com/vcs-python/g/security/advisories/new). diff --git a/.github/WRITING.md b/.github/WRITING.md new file mode 100644 index 0000000..730f88d --- /dev/null +++ b/.github/WRITING.md @@ -0,0 +1,739 @@ +# Writing + +How this project writes prose, for humans and agents alike. It governs +`README.md`, `CHANGES`, commit messages, CLI and error-message text, +docstrings, source comments, logging, and the Sphinx docs under `docs/` +— every surface a reader reaches. + +For environment setup, the gates, and pull request workflow, see +[CONTRIBUTING.md](CONTRIBUTING.md). + +## Voice + +Three surfaces, one voice. A docstring says what a caller may rely on; a +`CHANGES` entry says what changed; prose says what happens. All three are +present tense, lead with the thing being described, and stop. Why it was built +that way belongs in the commit message, which is timestamped and attached to +the diff. + +The most useful editing operation is deleting the introductory sentence. + +Lead with verbs and name concrete things. Put identifiers in backticks. Prefer +short declarative sentences, one operational fact each. Do not explain Python +to Python developers; do explain this project's semantics. + +Type annotations describe shape. Documentation describes meaning. A sentence +that restates a signature has said nothing. + +Use MUST, SHOULD, and MAY only where the normative sense is meant. Say what +actually happens rather than that something is "supported". + +| Instead of | Prefer | +| --------------------------------- | --------------------------------- | +| "We added…" | "`g` now proxies…" | +| "New and improved" | "`g` now…" | +| "powerful", "seamless" | state the capability | +| "easily", "simply", "just" | omit | +| "simple", "obvious", "intuitive" | omit | +| "robust" | name the failure that is handled | +| "comprehensive" | name what is covered | +| "production-ready" | state the guarantee | +| "optimized", "blazingly fast" | give the magnitude | +| "various fixes" | name the components | +| "under the hood" | omit unless observable | +| "please note that", "note that" | state the fact | +| "leverage", "utilize" | "use" | +| "delve into" | "read", or omit | +| "best practices" | name the practice | +| "in order to" | "to" | + +## Who you are writing for + +The default reader works at a shell prompt inside a checkout and runs `g` +where they would otherwise type `git`, `svn`, or `hg`. They are fluent in +their own VCS — status, commit, log, diff — but you cannot assume they read +Python or know g's internals: `find_repo_type()`, the `vcspath_registry` +marker mapping, or the `G_IS_TEST` escape hatch the tests use. + +A second, smaller reader writes Python: they call `run()` directly, build on +`create_parser()`, or contribute to g itself. Serve them too, but mark their +material opt-in — "for the rarer cases", "internal" — so the default reader +knows they can stop. Never make the common case pay a comprehension tax for +the advanced one. + +Rules that follow: + +- **Second person, present tense, active.** "You run `g status`", not "the + command is proxied". Address the reader who is doing the thing. +- **Concept before mechanics.** Open by saying what g *does* for the reader — + one command that becomes the right VCS command. The generated option + listing, the marker table, and the exit behaviour are the last details they + need, not the first. A page that opens with argparse output has buried the + idea under its plumbing. +- **Say when they can stop.** g has no configuration; most pages owe the + reader one reassurance up front — run `g` where you'd run your VCS, and + you're done. Let a skimmer leave after one sentence. +- **Grant permission, do not demand attention.** "Reach for this when…", "for + the rarer cases" — tell readers they are in the right place without + implying they must read on. +- **Progressive disclosure.** Order by how many readers need it: the everyday + command, then the one flag g intercepts (`--version`/`-V`), then how + detection works, then the Python entry points. Each step is for a smaller + audience than the last. +- **Lean on the pipeline.** The reader's mental model is a straight line: + current directory → walk up the parents → a `.git`, `.svn`, or `.hg` + marker → the matching VCS command, with arguments forwarded. Reinforce + that chain whenever explaining detection or dispatch; it is the whole + tool. +- **Name the trade-off.** If a call costs something, say so and say what it + buys — `--version`/`-V` never reaches the underlying VCS, and g's own exit + status does not mirror the wrapped command's (see + [CLI and error messages](#cli-and-error-messages)). State it; do not sell + it. +- **Frame by concept, not by mechanism.** Do not headline a feature by its + directory marker or argparse detail; name the concept — detection, + forwarding. The mechanics vocabulary — the marker-to-command table, the + generated option reference — belongs in the CLI reference, and only there. + +## README + +A README is the shortest path from "what is this?" to competent use, not the +project's autobiography. + +The first sentence is a contract. It says what abstraction the reader has +been handed, concretely enough to tell this package apart from the +neighbouring one. + +Get to a runnable command or snippet before anything the reader can skip. A +logo, a mission statement, a comparison matrix and three paragraphs of +history in front of the install line all cost the same thing. + +State the minimum Python version in prose, not only in badges. +`requires-python` in `pyproject.toml` is the authority; the README must +agree with it. + +Document the semantic model, not the flag list. `--help` already enumerates +flags (or, here, forwards straight to the VCS's own `--help` — see +[CLI and error messages](#cli-and-error-messages)); what it cannot say is +what goes to stdout versus stderr and what a non-zero exit means. + +State defaults explicitly — defaults are API. State negative guarantees +where they exist: g has no configuration file, makes no network calls of its +own, and never invokes anything but the VCS binary the marker maps to. They +establish boundaries faster than any amount of description. + +Headings stay conventional and stable, because people deep-link them. Badges +are few and load-bearing. + +## CLI and error messages + +g's whole surface is one command, so its error-message and exit-status +contract carries more weight than its flag list. This section is precise on +purpose; verify a claim against the source before repeating it elsewhere. + +**Only `--version`/`-V` is intercepted.** g checks whether the first argument +is `--version` or `-V` before doing anything else; if so, it prints +`g ` to stdout and exits `0`. Every other argument — including +`-h`/`--help` — is forwarded to the detected VCS untouched. `g --help` runs +`git --help` (or `svn`/`hg`), not g's own help text. + +**No detection, no error.** Outside any `.git`, `.svn`, or `.hg` directory, g +writes `No VCS found in current directory.` to stderr through the `logging` +module and returns — this is not treated as a failure. Keep that message +string exact; docs and tests refer to it literally. + +**Output is inherited, not reshaped.** g does not capture, filter, or +reformat the VCS's own stdout or stderr. The subprocess inherits the +parent's file descriptors, so a proxied command prints exactly what the VCS +itself would print, byte for byte. + +**g's own exit status does not mirror the wrapped command's.** After a +proxied VCS command completes, `run()` returns `None` and the process exits +`0` regardless of whether the VCS itself failed — a failing `git` command +still leaves `g` reporting success to the shell. If the mapped binary is +missing entirely (a `.hg` marker with no `hg` on `PATH`), `subprocess.Popen` +raises `FileNotFoundError`, an unhandled traceback prints to stderr, and the +process exits `1`. State this behaviour plainly wherever it comes up; do not +imply it is a bug being fixed or a feature being sold. + +## Logging + +g logs through the standard `logging` module rather than `print()`; log +output is a surface downstream tooling can parse, so treat its shape like +any other documented output. + +### Structured context via `extra` + +Pass structured data on a log call whenever it helps filtering, searching, or +test assertions. + +**Core keys** (stable, scalar, safe at any log level): + +| Key | Type | Context | +|-----|------|---------| +| `vcs_cmd` | `str` | VCS command line | +| `vcs_type` | `str` | VCS type (git, svn, hg) | +| `vcs_url` | `str` | repository URL | +| `vcs_exit_code` | `int` | VCS process exit code | +| `vcs_repo_path` | `str` | local repository path | + +**Heavy/optional keys** (DEBUG only, potentially large): + +| Key | Type | Context | +|-----|------|---------| +| `vcs_stdout` | `list[str]` | VCS stdout lines (truncate or cap) | +| `vcs_stderr` | `list[str]` | VCS stderr lines (same caveats) | + +Treat these keys as compatibility-sensitive — downstream users may build +dashboards and alerts on them. Change them deliberately: `snake_case`, not +dotted, `vcs_` prefix, scalars over ad-hoc objects. + +### Lazy formatting + +`logger.debug("msg %s", val)`, not an f-string. Deferred interpolation is +skipped entirely when the level is filtered, and a `"Running %s"` template +groups as one signature in an aggregator instead of one unique line per call. +Guard an expensive `val` with `if logger.isEnabledFor(logging.DEBUG)`. + +### stacklevel and persistent context + +Increment `stacklevel` for each wrapper layer so `%(filename)s:%(lineno)d` +points at the real caller; verify whenever call depth changes. For an object +with stable identity, prefer a `LoggerAdapter` (override `process()` to +merge `extra`) over repeating the same `extra` on every call. + +### Log levels + +| Level | Use for | Examples | +|-------|---------|----------| +| `DEBUG` | Internal mechanics, VCS I/O | VCS command + stdout, URL parsing steps | +| `INFO` | Repository lifecycle, user-visible operations | Repository cloned, sync completed | +| `WARNING` | Recoverable issues, deprecation, user-actionable config | Deprecated VCS option, unrecognized remote | +| `ERROR` | Failures that stop an operation | VCS command failed, invalid URL | + +Config discovery noise belongs in `DEBUG`; only a surprising or +user-actionable config issue goes to `WARNING`. + +### Message style + +Lowercase, past tense for events — `"repository cloned"`, `"vcs command +failed"` — no trailing punctuation. Keep the message short; put detail in +`extra`, not the string. + +### Exception logging + +Use `logger.exception()` only inside an `except` block you are not +re-raising from. Use `logger.error(..., exc_info=True)` when you need the +traceback outside an `except` block. Avoid `logger.exception()` followed by +`raise` — it duplicates the traceback; add context via `extra` instead, or +let the exception propagate. + +### Avoid + +f-strings or `.format()` in log calls; unguarded logging in hot loops; +catch-log-reraise with no new context; `print()` for diagnostics; logging a +secret env var's value (log the key name only); non-scalar ad-hoc objects in +`extra`; a custom `extra` field referenced in a format string with no safe +default (a missing key raises `KeyError`). + +## Documented examples that run + +Examples in this project are tests. This section is the contract for +writing one the test suite can actually see, and it states this repo's real +mechanism — read `[tool.pytest.ini_options]` in `pyproject.toml` before +assuming otherwise. + +**A fence tag is cosmetic. Only a `>>> ` prompt executes.** A block written +as + + ```python + parser = create_parser() + ``` + +is prose that looks like a test. Nothing collects it, nothing runs it, and +it can be wrong for years. The same block written with prompts is a test: + + ```python + >>> parser = create_parser() + ``` + +This is the single most expensive mistake available when editing +documentation, because removing the prompts leaves a green test suite and a +silently deleted test. When editing a file that contains examples, count the +prompts before and after. + +**The fence tag is `python`.** Not `pycon`, not bare. + +**Where examples run, precisely.** `pyproject.toml` sets +`addopts = "--doctest-modules …"`, `doctest_optionflags = "ELLIPSIS +NORMALIZE_WHITESPACE"`, and `testpaths = ["src/g", "tests", "docs"]`. +Docstring examples under `src/g` run as part of every `pytest`/`py.test` +invocation. Markdown under `docs/` is also collected and executed — this was +verified directly: a `>>> ` prompt added to a page under `docs/` is +collected as its own test item and fails on a wrong expected value, exactly +like a docstring doctest. **`README.md` is not in `testpaths`.** A `>>> ` +prompt there is never executed — there are none in the current README, and +adding one would silently do nothing rather than add a test, so do not +promise a reader that a README example is checked. + +**No shared fixtures outside `src/g`.** This repository defines no +`doctest_namespace` fixture. A module doctest under `src/g` runs with that +module's own globals, so a name already imported or defined at module level +(`pathlib`, `create_parser`, `find_repo_type`, …) is available without +importing it again inside the block — see `create_parser`'s own docstring +for a working example. A prompted block anywhere else (`docs/`, if one is +ever added) starts bare: import or define every name the block uses. + +**`# doctest: +SKIP` is not permitted.** It is a workaround that tests +nothing. + +**Do not downgrade a doctest to a non-executed block to make it pass.** A +`.. code-block::` or an unprompted fence does not run. If an example cannot +pass, fix the example or fix the code. + +**Option flags.** `ELLIPSIS` and `NORMALIZE_WHITESPACE` are enabled +globally, so `...` elides variable output and whitespace differences do not +fail a comparison. Reach for an inline `# doctest: +FLAG` only for the block +that needs it. + +**Docstring examples** use the NumPy `Examples` section: + + Examples + -------- + >>> parser = create_parser() + >>> parser.prog + 'g' + +**Console blocks are not examples that run.** A ```` ```console ```` block +at a `$` prompt is not collected by anything — `--doctest-modules` finds +doctests in Python modules, and the `docs/` collector runs `>>> ` blocks, +neither of which touches a shell transcript. Run a `console` block by hand +before committing it, and re-run it whenever you reshape the page around it; +nothing catches drift automatically. + +## The changelog + +`CHANGES` is the changelog, rendered as the Sphinx history page +(`docs/history.md`, published at `/history.html`). Its shape follows +Django's release-notes model — +deliverables get titles and prose, not bullets — because CHANGES here does +double duty as both the permanent ledger and the editorial release note; +there is no separate release page. + +**Release entry boilerplate.** Every release header is +`## g X.Y.Z (YYYY-MM-DD)`. The file opens with a `## g X.Y.Z (unreleased)` +placeholder fenced by `` and +`` HTML comments — new entries land immediately +below the END marker, never above it. + +**Open a release entry with a multi-sentence lead paragraph.** Plain prose, +no italics. Open with the version as the sentence subject ("g X.Y.Z +ships…") so the lead is self-contained when excerpted. Two to four +sentences on what shipped and who cares — user-visible takeaways, not +internal mechanism. Cross-reference detail docs with `{ref}` to keep the +lead compact. + +**The unreleased entry carries no lead paragraph and no version summary** — +sections only (`### Breaking changes`, `### What's new` deliverables, +`### Fixes`, …). Speaking for a release — what the version "is", "ships", or +"focuses on" — is presumptuous before its scope is final. Only the person +cutting the release writes that, and only when the user explicitly asks to +release. Never write or edit a lead paragraph from a feature branch, and +never ask or imply that a release should happen. + +**Each deliverable is a section, not a bullet.** Inside `### What's new`, +every distinct deliverable gets a `#### Deliverable title (#NN)` heading +naming it in user vocabulary, followed by one to three prose paragraphs. Do +not wrap a paragraph in `- ` — bullets are for enumerable lists, not +paragraph containers. + +**The deliverable test.** Before writing an entry, ask: "What's the +deliverable, in user vocabulary?" If you cannot answer in one sentence, the +entry is not ready. Mechanism — helper internals, byte counters, +schema-validation locations — belongs in the pull request description and +code comments, not the changelog. + +**Fixed subheadings**, in this order when present: `### Breaking changes`, +`### Dependencies`, `### What's new`, `### Fixes`, `### Documentation`, +`### Development`. Dev tooling (helper scripts, internal automation) lives +under `### Development`. For a breaking change, show the migration path +with a concrete `# Before` / `# After` code block. Dependency floor bumps +use ``Minimum `pkg>=X.Y.Z` (was `>=X.Y.W`)``. + +**PR refs `(#NN)`** sit in each deliverable's `####` heading. + +**When bullets are appropriate.** Catch-all sections (`### Fixes`, +occasionally `### Documentation`) with three or more genuinely small items +use bullets — one line each, never paragraphs. A bullet that swells past two +lines gets promoted to a `#### Title (#NN)` heading with a prose body. + +**Always link autodoc'd APIs** — any class, method, function, exception, or +attribute with its own rendered page — via the roles in +[Documentation cross-references](#documentation-cross-references), never +with plain backticks. Plain backticks stay correct for code syntax, env +vars, parameter names, and file paths that have no autodoc destination. + +**Semantic-versioning meaning applies to the whole documented public +API** — command names, options, exit statuses, not only imported Python +symbols. A change to what `-V` prints or what a non-zero exit means is a +compatibility break here just as much as a signature change. + +**Anti-patterns.** Fragile metrics that go stale silently — token +ceilings, third-party version pins, percent benchmarks, exact byte counts. +Describe the capability, not the math. Private symbols and internal jargon. +Walls of text dressed up as bullets. A breaking change buried mid-entry +instead of given its own subheading at the top. + +**Summarization style.** Asked "what changed in the latest version?", lead +with the entry's lead paragraph (paraphrased if needed), then each `####` +deliverable heading under `### What's new` with a one-sentence summary. Cite +`(#NN)` only if asked for source links. Do not invent versions, dates, or +numbers absent from `CHANGES`; do not quote line numbers, which shift as the +file evolves. + +## Docstrings + +The prime directive: never restate the type. The annotation is the source of +truth; the docstring carries what the annotation cannot. + +This is documentation debt wearing a docstring: + + def get_id(pane: Pane) -> str: + """Get the pane's identifier. + + Parameters + ---------- + pane : Pane + The pane. + + Returns + ------- + str + The identifier. + """ + +Document instead the dimensions the type system cannot encode: mutation, +ownership, ordering, timing, failure, idempotence, concurrency, units and +ranges, boundary behaviour, platform differences, and any security boundary +— what is executed versus what is only read. + +The first sentence stands alone; tooling truncates there. PEP 257 applies: +triple double quotes, an imperative one-line summary ending in a period, a +blank line before any extended description. Do not repeat an introspectable +signature. + +Ruff's `pydocstyle` rule (`D`, `convention = "numpy"`) is the enforced +dialect — do not relitigate NumPy versus Google style in review. + +**Classes with fields** — `NamedTuple`, dataclasses — document every field +in an `Attributes` section: + + class CommandLineTestFixture(t.NamedTuple): + """Test fixture for CLI params, environment, and expected result. + + Attributes + ---------- + test_id : str + pytest parametrization id for the case. + env : EnvFlag + Directory state to simulate before invoking the CLI. + argv_args : list[str] + Arguments passed through to ``g`` on the command line. + expect_cmd : str | None + VCS command line expected, or ``None`` when none should run. + """ + +Autodoc renders every field whether or not you describe it, so an +undocumented `NamedTuple` field ships to the API docs as "Alias for field +number 0", and a dataclass field ships bare. Document all of them — a class +with three fields and two documented still ships a stub for the third. + +## Source comments + +A comment ships only if it passes all three gates. Fail any: delete or +rewrite. Borderline: delete — borderline means the information is +reconstructible, which is what makes deletion cheap. + +**Loss.** Three years from now, would losing this cost a maintainer real +time rediscovering intent, an invariant, a constraint, or a failure mode the +code and tests do not already make obvious? + +**Elite.** Would SQLite, Redis, the Go standard library, or CPython write +this comment, at this length? Those projects state the constraint and stop. +They do not argue with an imagined objector. + +**Upkeep.** Will it stay true without maintenance? A comment that hand-syncs +a value the code owns — a count, an offset, a line reference, a duplicated +constant — is false the first time that value moves. + +### Ceiling + +One or two lines. A comment reaching four is either carrying several facts, +in which case split it, or arguing, in which case cut it to the fact. + +Rationale, alternatives weighed, and the story of how the code got here +belong in the commit message: timestamped, attached to the exact diff, and +free to maintain. + +### Keep + +- Why over how: upstream quirks, protocol and compatibility constraints, + performance tradeoffs still part of the contract. +- Invariants, preconditions, ordering, lifetime, and concurrency + requirements that types and tests cannot express. +- Code that looks wrong but is not, so a later cleanup does not reintroduce + the bug. +- A high-level sketch of an algorithm whose local operations do not reveal + the whole. + +### Delete + +- Narration of the next lines; code translated into English. +- Restated names, types, defaults, or control flow. +- Values duplicated from the code and hand-synced. +- Justification, hedging, or apology for a choice. +- Speculation about future requirements. +- History version control already holds, including commented-out code. +- Ticket and issue numbers — they say nothing to a reader without tracker + access, and they rot when the tracker moves. +- Transient observations — "currently", "for now", "the latest release" — + that go stale with no nearby edit. + +### The upkeep gate in practice + +It reaches values that track our own code. It does not reach frozen +external facts. + +Bad (Delete): + + # There are 321 tests to complete for servers. + +Good (Keep): + + # CPython < 3.11 has no ExceptionGroup, so this branch stays. + +### Documentation exception + +Doctests, minimal usage examples, and `Parameters`/`Returns`/`Raises` +entries on public API are exempt from the loss gate — they serve the +caller, not the maintainer. They are exempt from nothing else. Ceiling: a +good man page entry. + +## Terminology and capitalization + +Pick the domain noun and keep it. Call the marker directories `.git`, +`.svn`, `.hg` and the thing they select the "VCS type" or "VCS command" +consistently, rather than alternating with "backend", "handler", or +"engine". + +Python and PyPI keep their own capitalisation. Distribution names are +written as they are published. + +Do not write counts into prose — how many tests exist, how many functions +are exported. They go stale silently and no reader needs them. + +## Documentation cross-references + +`docs/` is built with Sphinx and MyST. Class references use `{class}`, +methods `{meth}`, functions `{func}`, exceptions `{exc}`, attributes +`{attr}`, internal anchors `{ref}`, and doc-path links `{doc}`. A `{ref}` +must match its target's anchor exactly — anchors here are lowercase and +hyphenated (`cli-main`, `cli-supported-vcs`, `developmental-releases`). + +Link the first prose mention of any symbol with a useful destination on +that page — a Python object, a CLI reference anchor, a project page, or an +external tool. Use the most specific role available for an API object; +`{ref}` or `{doc}` for a documentation page or section anchor; a plain +Markdown link for an external project. Do not rely on a later reference +section to satisfy the first-mention rule — if the first occurrence would +be a heading or a grid-card teaser, link that occurrence or retitle the +heading so the first prose mention can carry the link. Leave command +examples, code blocks, and literal values as code; link the surrounding +prose instead. + +What stays exact, never paraphrased: the marker-to-command table, the +message strings named in [CLI and error messages](#cli-and-error-messages), +version strings, and function cross-references. Warm the framing sentences +around a precise block; never the block itself. + +`make build_docs` catches a broken cross-reference; the test suite does +not — build the docs before committing a documentation change. +`docs/cli/index.md` is the worked example of all of the above: a +concept-first opening line, a three-step "How it works" before any +generated reference, and the precise marker table left exact below +everything the everyday reader needs. + +## Markdown + +Prose wraps at 80 columns. Table rows, badge lines, and long links are +exempt, because breaking them harms rendering. A pull request or issue body +does not wrap at all: GitHub renders a single newline as a space in a file +and as a line break in a comment, so a wrapped comment body arrives as +ragged stubs. + +GitHub alert blocks — `> [!NOTE]`, `> [!WARNING]` — render as literal text +outside GitHub, so reserve them for at most one load-bearing warning per +document. Write the sentence so it carries the fact on its own, and a +renderer that drops the marker loses nothing. + +Do not use a local absolute path or an email address in anything published. + +## Code blocks + +Code blocks are paste-and-run units: pasting one block runs exactly one +intended action. Executed examples are exempt — the test suite runs them, +nobody pastes them. + +- **One command per block.** Multiple steps may share a block only when + explicitly chained with `&&`, `;`, or `\` continuations — the chain is + then one logical command. +- **Explanations go in prose above the block**, never as `#` comments + inside it. +- **Command menus are per-command blocks with prose lead-ins**, not tables. +- **Shell commands use the `console` tag with a `$ ` prefix.** This + separates interactive commands from scripts and enables prompt-aware + copy. +- **Split long commands with `\`** — one flag or flag+value pair per + indented continuation line, positional arguments last. + +Good — show the last ten commits as a graph: + +```console +$ git log \ + --max-count=10 \ + --graph \ + --oneline +``` + +Bad: + +```console +# Show the last ten commits as a graph +$ git log --max-count=10 --graph --oneline +``` + +## Commits + +``` +Scope(type[detail]): concise description + +why: Explanation of necessity or impact. + +what: +- Specific technical changes made +- Focused on a single topic +``` + +Keep the subject to 50 characters or fewer, excluding any trailing `(#NN)` +pull request reference, and wrap body lines at 72. Separate the `why:` and +`what:` blocks with a blank line. + +Routine maintenance commits drop the colon and take a capitalised +description, which is what distinguishes them at a glance in `git log +--oneline`: + +``` +py(deps[dev]) Bump dev packages +ai(rules[AGENTS]) Judge comments by three gates +``` + +Everything that changes behaviour keeps the colon. + +Common types: + +- **feat**: New features or enhancements +- **fix**: Bug fixes +- **refactor**: Code restructuring without functional change +- **docs**: Documentation updates +- **chore**: Maintenance (dependencies, tooling, config) +- **test**: Test-related updates +- **style**: Code style and formatting +- **ci**: Workflow and pipeline changes +- **py(deps)**: Dependencies +- **py(deps[dev])**: Dev dependencies +- **ai(rules[AGENTS])**: AI rule updates +- **ai(claude[rules])**: Claude Code rules (`CLAUDE.md`) +- **ai(claude[command])**: Claude Code command changes + +Example: + +``` +cli(feat[version]): Print version for -V + +why: -V is the common short flag for version and was unhandled. + +what: +- Treat -V as an alias for --version in run() +- Add a CLI test for the short flag +``` + +For a multi-line message, use a heredoc so the formatting survives: + +```console +$ git commit -m "$(cat <<'EOF' +Scope(feat[detail]): Concise description + +why: Explanation of the change. + +what: +- First change +- Second change +EOF +)" +``` + +### Release commits + +Never create tags. Never push tags. The owner handles tagging and tag +pushes, because a tag triggers the publish workflow. + +A release commit subject is plain and short: `Tag v`. The detailed +why and what go in the body. Do not use the `Scope(type[detail]):` format +for a release — it buries the lede. + +## Slop prevention + +Treat AI slop as review-hostile noise, not as proof that text or code is +wrong. The goal is to maximise information density. + +- **AI signatures.** No "Generated by", no conversational filler, no + unexplained emoji, no tool metadata. +- **Brittle references.** No hard-coded line numbers, fragile file counts, + dated "as of" claims, bare SHAs, or local absolute paths — unless they + are strict evidentiary artefacts such as a benchmark log. +- **Diff narration.** Do not restate what moved, was renamed, or was + removed in anything the reader holds alongside the diff: code, + docstrings, README, or a pull request description. The diff and the + commit message already carry it. +- **Branch-internal narrative.** Do not mention intermediate states, + abandoned approaches, or "no longer" behaviour unless users of a + published release actually experienced the old state. +- **Low-value scaffolding.** No ownerless TODOs, unused future-proofing, + debug artefacts, or defensive wrappers around failure modes nothing can + reach. +- **Prose inflation.** The diction table under [Voice](#voice) governs; + replace an inflated word with a concrete description of behaviour, + constraints, or trade-offs. +- **Coded labels.** Write rules and findings as plain imperatives. No + `[R1]`, `Option B`, or any index a reader has to decode. + +Preserve the "why". Never delete a comment documenting an invariant, a +protocol constraint, a platform quirk, or an upstream workaround — those +are the facts [Source comments](#source-comments) keeps, and every other +comment is judged by it. + +### Durable source links + +Link to a pinned revision, never to trunk, for anything cited as evidence. +`blob/master/…` links rot silently — the file moves, lines shift, and the +anchor lands on unrelated code while still resolving. + +- Prefer a release tag (`blob/v1.4.0/…`). Most durable, and it tells the + reader which released version the claim held for. +- Otherwise use a 7-character commit SHA (`blob/9a29b1a/…`) reachable from + trunk, for a claim about unreleased code. Never a pull-request-head SHA + — it can be rebased or garbage-collected. +- Reserve `blob/master/…` for a living document meant to always show the + latest state — `.github/CONTRIBUTING.md` and this file are exactly that + case. +- Line anchors (`#L120-L145`) are only safe on a pinned ref. diff --git a/.github/contributing.md b/.github/contributing.md deleted file mode 100644 index c0eddac..0000000 --- a/.github/contributing.md +++ /dev/null @@ -1,27 +0,0 @@ -# Contributing - -When contributing to this repository, please first discuss the change you wish to make via issue, -email, or any other method with the maintainers of this repository before making a change. - -See [developing](../docs/developing.md) for environment setup and [AGENTS.md](../AGENTS.md) for -detailed coding standards. - -## Pull Request Process - -1. **Format and lint**: `uv run ruff format .` then `uv run ruff check . --fix --show-fixes` -2. **Type check**: `uv run mypy` -3. **Test**: `uv run pytest` — all tests must pass before submitting -4. **Document**: Update docs if your change affects the public interface -5. You may merge the Pull Request once you have the sign-off of one other developer. If you - do not have permission to do that, you may request a reviewer to merge it for you. - -## Decorum - -- Participants will be tolerant of opposing views. -- Participants must ensure that their language and actions are free of personal - attacks and disparaging personal remarks. -- When interpreting the words and actions of others, participants should always - assume good intentions. -- Behaviour which can be reasonably considered harassment will not be tolerated. - -Based on [Ruby's Community Conduct Guideline](https://www.ruby-lang.org/en/conduct/) diff --git a/AGENTS.md b/AGENTS.md index df9371d..21dca84 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,348 +1,53 @@ # AGENTS.md -This file provides guidance to AI agents (including Claude Code, Cursor, and other LLM-powered tools) when working with code in this repository. The tooling and docs rely on the gp-libs ecosystem; treat gp-libs as the shared dev toolkit that underpins this project. +g is a lightweight CLI wrapper: it detects the current directory's VCS +(git, svn, or hg) and proxies your arguments straight to it. -## CRITICAL REQUIREMENTS +Follow the conventions already in the tree, and keep a change scoped to what +was asked for. -### Test Success -- ALL tests MUST pass for code to be considered complete and working -- Never describe code as "working as expected" if there are ANY failing tests -- Even if specific feature tests pass, failing tests elsewhere indicate broken functionality -- Changes that break existing tests must be fixed before considering implementation complete -- A successful implementation must pass linting, type checking, AND all existing tests +## What is here -## Project Overview +| Path | What it is | +| ---- | ---------- | +| `src/g/__init__.py` | Everything: `find_repo_type()`, `create_parser()`, `run()` (console-script entry point) | +| `src/g/__about__.py` | Package metadata (`__version__`, URLs); exec'd by `docs/conf.py` | +| `tests/test_cli.py` | Parametrized CLI tests | +| `conftest.py` | Autouse fixture setting `G_IS_TEST=1` | +| `docs/` | Sphinx (MyST) site; `docs/cli/index.md` is the CLI reference | +| `CHANGES` | Changelog, rendered as the docs history page | +| `Makefile`, `justfile`, `docs/justfile` | Task runners wrapping `uv`/`just` | -g is a lightweight CLI wrapper that proxies to the current directory's VCS command (git, svn, or hg). It auto-detects the repo type, forwards user arguments, and exits after invoking the native tool. The project lives in the gp-libs family of git-pull utilities and uses gp-libs packages for docs and development helpers. +## Which policy applies -Key features: -- Detects VCS by walking parent directories and mapping `.git`, `.svn`, or `.hg` -- Proxies CLI arguments to the detected VCS binary (--version/-V is handled by g) -- Minimal surface area: primary logic lives in `src/g/__init__.py` -- Test fixtures cover CLI behavior for both repo and non-repo directories +- Documentation, user-facing text, `CHANGES`, commit messages, docstrings, + source comments, logging, and CLI/error text: + [.github/WRITING.md](.github/WRITING.md) +- Environment, the gates, tests, documentation builds, releases, and pull + requests: [.github/CONTRIBUTING.md](.github/CONTRIBUTING.md) -## Development Environment +Each of those is the single home for its subject. Where a rule seems to be +stated twice, the file listed above is the one that governs. -This project uses: -- Python 3.10+ -- [uv](https://github.com/astral-sh/uv) for dependency management and execution -- [ruff](https://github.com/astral-sh/ruff) for linting and formatting -- [mypy](https://github.com/python/mypy) for type checking -- [pytest](https://docs.pytest.org/) (invoked as `py.test`) for testing -- [gp-libs](https://gp-libs.git-pull.com/) for shared Sphinx/test helpers (included in dev/docs extras) +## Change discipline -## Common Commands - -### Setting Up Environment - -```bash -# Install all dev and doc dependencies -uv sync --all-extras --dev -``` - -### Running Tests - -```bash -# Run full suite -make test -# or directly -uv run py.test - -# Watch tests (pytest-watcher) -make start # runs tests once then ptw . - -# Watch tests via entr (requires entr(1)) -make watch_test -``` - -### Linting and Type Checking - -```bash -# Lint and format with ruff -uv run ruff check . -uv run ruff format . - -# make targets -make ruff -make ruff_format -make watch_ruff - -# Type checking -uv run mypy . -make mypy -make watch_mypy -``` - -### Documentation - -```bash -# Build docs -make build_docs - -# Live docs server with autoreload -make start_docs - -# Docs design assets -make design_docs -``` - -## Code Architecture - -``` -src/g/__init__.py - ├─ find_repo_type(): detect VCS by walking parent directories - ├─ run(): CLI entrypoint; proxies args to detected VCS, honors G_IS_TEST - └─ DEFAULT + vcspath_registry helpers - -tests/test_cli.py - └─ Parametrized CLI tests for git/non-repo scenarios -``` - -## Testing Strategy - -- Tests live in `tests/test_cli.py` and use `pytest` with parametrized fixtures. -- `G_IS_TEST` env flag forces `run()` to return the subprocess so output can be asserted; set when modifying run logic. -- CLI tests rely on actual VCS binaries (e.g., `git`) being available on PATH. If adding tests for svn/hg, ensure binaries are installed or skip appropriately. -- Use `tmp_path` and `monkeypatch` to simulate non-repo directories instead of mocks where possible. -- Prefer pytest-watcher (`make start`) for TDD loops; for file-watch without ptw, use `make watch_test` (requires entr). - -## Coding Standards - -- Include `from __future__ import annotations` at the top of Python modules. -- Use namespace imports for stdlib: `import typing as t`, `import logging`, etc.; third-party packages may use `from X import Y`. -- Follow NumPy-style docstrings (see existing docstrings in `run` and pytest config requiring `pydocstyle` via ruff). -- Ruff is the source of truth for lint rules; see `pyproject.toml` for enabled checks (E, F, I, UP, A, B, C4, COM, EM, Q, PTH, SIM, TRY, PERF, RUF, D, FA100). -- Type checking is strict (`mypy --strict`); favor precise types and avoid `Any` unless necessary. - -**Classes with fields** — `NamedTuple`, dataclasses — document every field in -an `Attributes` section: - -```python -class CommandLineTestFixture(t.NamedTuple): - """Test fixture for CLI params, environment, and expected result. - - Attributes - ---------- - test_id : str - pytest parametrization id for the case. - env : EnvFlag - Directory state to simulate before invoking the CLI. - argv_args : list[str] - Arguments passed through to ``g`` on the command line. - expect_cmd : str | None - VCS command line expected, or ``None`` when none should run. - """ -``` - -Autodoc renders every field whether or not you describe it, so an -undocumented `NamedTuple` field ships to the API docs as "Alias for field -number 0" and a dataclass field ships bare. Document all of them — a class -with three fields and two documented still ships a stub for the third. - -## Logging Standards - -These rules guide future logging changes; existing code may not yet conform. - -### Logger setup - -- Use `logging.getLogger(__name__)` in every module -- Add `NullHandler` in library `__init__.py` files -- Never configure handlers, levels, or formatters in library code — that's the application's job - -### Structured context via `extra` - -Pass structured data on every log call where useful for filtering, searching, or test assertions. - -**Core keys** (stable, scalar, safe at any log level): - -| Key | Type | Context | -|-----|------|---------| -| `vcs_cmd` | `str` | VCS command line | -| `vcs_type` | `str` | VCS type (git, svn, hg) | -| `vcs_url` | `str` | repository URL | -| `vcs_exit_code` | `int` | VCS process exit code | -| `vcs_repo_path` | `str` | local repository path | - -**Heavy/optional keys** (DEBUG only, potentially large): - -| Key | Type | Context | -|-----|------|---------| -| `vcs_stdout` | `list[str]` | VCS stdout lines (truncate or cap; `%(vcs_stdout)s` produces repr) | -| `vcs_stderr` | `list[str]` | VCS stderr lines (same caveats) | - -Treat established keys as compatibility-sensitive — downstream users may build dashboards and alerts on them. Change deliberately. - -### Key naming rules - -- `snake_case`, not dotted; `vcs_` prefix -- Prefer stable scalars; avoid ad-hoc objects -- Heavy keys (`vcs_stdout`, `vcs_stderr`) are DEBUG-only; consider companion `vcs_stdout_len` fields or hard truncation (e.g. `stdout[:100]`) - -### Lazy formatting - -`logger.debug("msg %s", val)` not f-strings. Two rationales: -- Deferred string interpolation: skipped entirely when level is filtered -- Aggregator message template grouping: `"Running %s"` is one signature grouped ×10,000; f-strings make each line unique - -When computing `val` itself is expensive, guard with `if logger.isEnabledFor(logging.DEBUG)`. - -### stacklevel for wrappers - -Increment for each wrapper layer so `%(filename)s:%(lineno)d` and OTel `code.filepath` point to the real caller. Verify whenever call depth changes. - -### LoggerAdapter for persistent context - -For objects with stable identity (Repository, Remote, Sync), use `LoggerAdapter` to avoid repeating the same `extra` on every call. Lead with the portable pattern (override `process()` to merge); `merge_extra=True` simplifies this on Python 3.13+. - -### Log levels - -| Level | Use for | Examples | -|-------|---------|----------| -| `DEBUG` | Internal mechanics, VCS I/O | VCS command + stdout, URL parsing steps | -| `INFO` | Repository lifecycle, user-visible operations | Repository cloned, sync completed | -| `WARNING` | Recoverable issues, deprecation, user-actionable config | Deprecated VCS option, unrecognized remote | -| `ERROR` | Failures that stop an operation | VCS command failed, invalid URL | - -Config discovery noise belongs in `DEBUG`; only surprising/user-actionable config issues → `WARNING`. - -### Message style - -- Lowercase, past tense for events: `"repository cloned"`, `"vcs command failed"` -- No trailing punctuation -- Keep messages short; put details in `extra`, not the message string - -### Exception logging - -- Use `logger.exception()` only inside `except` blocks when you are **not** re-raising -- Use `logger.error(..., exc_info=True)` when you need the traceback outside an `except` block -- Avoid `logger.exception()` followed by `raise` — this duplicates the traceback. Either add context via `extra` that would otherwise be lost, or let the exception propagate - -### Testing logs - -Assert on `caplog.records` attributes, not string matching on `caplog.text`: -- Scope capture: `caplog.at_level(logging.DEBUG, logger="g.cli")` -- Filter records rather than index by position: `[r for r in caplog.records if hasattr(r, "vcs_cmd")]` -- Assert on schema: `record.vcs_exit_code == 0` not `"exit code 0" in caplog.text` -- `caplog.record_tuples` cannot access extra fields — always use `caplog.records` - -### Avoid - -- f-strings/`.format()` in log calls -- Unguarded logging in hot loops (guard with `isEnabledFor()`) -- Catch-log-reraise without adding new context -- `print()` for diagnostics -- Logging secret env var values (log key names only) -- Non-scalar ad-hoc objects in `extra` -- Requiring custom `extra` fields in format strings without safe defaults (missing keys raise `KeyError`) - -### Git Commit Standards - -Format commit messages as: -``` -Scope(type[detail]): concise description - -why: Explanation of necessity or impact. - -what: -- Specific technical changes made -- Focused on a single topic -``` - -Keep the subject ≤50 chars (excluding any trailing `(#NN)` PR ref); wrap -body lines at ≤72 chars. Separate the `why:` and `what:` blocks with a -blank line. - -Common commit types: -- **feat**: New features or enhancements -- **fix**: Bug fixes -- **refactor**: Code restructuring without functional change -- **docs**: Documentation updates -- **chore**: Maintenance (dependencies, tooling, config) -- **test**: Test-related updates -- **style**: Code style and formatting -- **py(deps)**: Dependencies -- **py(deps[dev])**: Dev Dependencies -- **ai(rules[AGENTS])**: AI rule updates -- **ai(claude[rules])**: Claude Code rules (CLAUDE.md) -- **ai(claude[command])**: Claude Code command changes - -#### Release commits - -Never create tags. Never push tags. The user handles tagging and tag -pushes (tags trigger the CI publish workflow). - -Release commit subjects are plain and short: `Tag v`. Put -the detailed why/what in the commit body. Don't use the -`Scope(type[detail]):` format for releases — don't bury the lede. - -## Doctests - -**All functions and methods MUST have working doctests.** Doctests serve as both documentation and tests. - -**CRITICAL RULES:** -- Doctests MUST actually execute - never comment out function calls or similar -- Doctests MUST NOT be converted to `.. code-block::` as a workaround (code-blocks don't run) -- If you cannot create a working doctest, **STOP and ask for help** - -**Available tools for doctests:** -- `doctest_namespace` fixtures: `tmp_path` -- Ellipsis for variable output: `# doctest: +ELLIPSIS` -- Update `conftest.py` to add new fixtures to `doctest_namespace` - -**`# doctest: +SKIP` is NOT permitted** - it's just another workaround that doesn't test anything. If a VCS binary might not be installed, use proper skip markers in pytest. - -**Using fixtures in doctests:** -```python ->>> from g import find_repo_type ->>> find_repo_type('/some/git/repo') # doctest: +ELLIPSIS -'git' -``` - -**When output varies, use ellipsis:** -```python ->>> import pathlib ->>> pathlib.Path.cwd() # doctest: +ELLIPSIS -PosixPath('...') -``` - -## Changelog Conventions - -These rules apply when authoring entries in `CHANGES`, which is rendered as the Sphinx changelog page. Modeled on Django's release-notes shape — deliverables get titles and prose, not bullets. Older entries used a flat `### Section` + bullet shape; new entries follow the Django shape below. - -**Release entry boilerplate.** Every release header is `## g X.Y.Z (YYYY-MM-DD)`. The file opens with a `## g X.Y.Z (unreleased)` placeholder block fenced by `` and `` HTML comments — new release entries land immediately below the END marker, never above it. - -**Open with a multi-sentence lead paragraph.** Plain prose, no italic. Open with the version as sentence subject (*"g X.Y.Z ships …"*) so the lead is self-contained when excerpted. Two to four sentences telling the reader what shipped and who cares — user-visible takeaways, not internal mechanism. Cross-reference detail docs with `{ref}` to keep the lead compact. - -**Lead paragraphs are release-time material — off-limits to branches and PRs.** The unreleased entry carries no lead paragraph and no version summary: sections only (`### Breaking changes`, `### What's new` deliverables, `### Fixes`, …). Speaking for the release — what the version "is", "ships", or "focuses on" — is presumptuous before its scope is final; only the person cutting the release writes that, and only when the user explicitly asks to release. Never write or edit a lead from a feature branch, and never ask or imply that a release should happen. - -**Each deliverable is a section, not a bullet.** Inside `### What's new`, every distinct deliverable gets a `#### Deliverable title (#NN)` heading naming it in user vocabulary, followed by 1-3 prose paragraphs explaining what shipped. Don't wrap a paragraph in `- ` — bullets are for enumerable lists, not paragraph containers. Cross-link detail docs (`See {ref}\`foo\` for details.`) so prose stays focused. - -**The deliverable test.** Before writing an entry, ask: "What's the deliverable, in user vocabulary?" If you can't answer in one sentence, the entry isn't ready. Mechanism (helper internals, byte counters, schema-validation locations) belongs in PR descriptions and code comments, not the changelog. - -**Fixed subheadings**, in this order when present: `### Breaking changes`, `### Dependencies`, `### What's new`, `### Fixes`, `### Documentation`, `### Development`. Dev tooling (helper scripts, internal automation) lives under `### Development`. For breaking changes, show the migration path with concrete inline code (e.g. a `# Before` / `# After` fenced code block). Dependency floor bumps use the form ``Minimum `pkg>=X.Y.Z` (was `>=X.Y.W`)``. - -**PR refs `(#NN)`** sit in each deliverable's `####` heading. - -**When bullets are appropriate.** Catch-all sections (`### Fixes`, occasionally `### Documentation`) with 3+ genuinely small items use bullets — one line each, never paragraphs. If a bullet swells past two lines, promote it to a `#### Title (#NN)` heading with prose body. - -**Anti-patterns.** - -- Fragile metrics: token ceilings, third-party version pins, percent benchmarks, exact byte counts. Describe the *capability*, not the math. -- Internal jargon: private symbols (leading-underscore identifiers), algorithm names exposed for the first time, backend scaffolding. -- Walls of text dressed up as bullets. -- Buried breaking changes — they get their own subheading at the top of the entry. - -**Always link autodoc'd APIs.** Any class, method, function, exception, or attribute that has its own rendered page must be cited via the appropriate role (`{class}`, `{meth}`, `{func}`, `{exc}`, `{attr}`) — never with plain backticks. Doc pages without explicit ref labels use `{doc}`. Plain backticks are correct for code syntax, env vars, parameter names, and file paths that aren't doc pages — anything without an autodoc destination. - -**MyST roles.** Class references use `{class}`, methods use `{meth}`, functions use `{func}`, exceptions use `{exc}`, attributes use `{attr}`, internal anchors use `{ref}`, doc-path links use `{doc}`. - -**Summarization style.** When a user asks "what changed in the latest version?" or similar, lead with the entry's lead paragraph (paraphrased if needed), followed by each `####` deliverable heading under `### What's new` with a one-sentence summary. Cite `(#NN)` only if the user asks for source links. Don't invent versions, dates, or numbers not present in `CHANGES`. Don't quote line numbers or file offsets — those shift as the file evolves. - -## Debugging Tips - -- Add logging with `logging` configured in `run`; keep output minimal because the CLI forwards to underlying VCS. -- When diagnosing repo detection, log the path iteration in `find_repo_type` or unit-test with synthetic directory trees. -- If subprocess output is swallowed, run with `G_IS_TEST=1` and `wait=True` to capture stdout/stderr in tests. +- Make the smallest coherent change that solves the verified problem; keep + unrelated cleanup out of it. +- Reuse an existing file, helper, API, or test before adding a new one. Keep + a new API private until a caller outside the module needs it. +- Add a file only for a durable boundary — a distinct responsibility, + independent reuse, or splitting an oversized module — not for a + single-use helper or a one-line re-export. +- Add a test for every user-visible behaviour change, and a `CHANGES` entry + for every change to the public API, CLI, configuration, or output. +- A passing gate is evidence only once it has been shown capable of + failing. Pair a new test with a deliberate break that proves it bites. +- `find_repo_type()` requires a `.git`/`.svn`/`.hg` **directory**; it will + not detect a repo from a git worktree checkout, whose top-level `.git` is + a file. This is a real limitation, not a bug in a test. +- Keep this file lean: delete a line whose removal would not cause a + mistake; push a multi-step procedure into a skill and a path-specific + rule into a nested `AGENTS.md`. ## References @@ -351,253 +56,3 @@ These rules apply when authoring entries in `CHANGES`, which is rendered as the - Changelog: https://g.git-pull.com/history.html - Repository: https://github.com/vcs-python/g - Shared tooling (gp-libs): https://gp-libs.git-pull.com/ - -## Documentation Standards - -### Code Blocks - -Code blocks are paste-and-run units: pasting one block runs exactly one -intended action. Doctests and other executed examples are exempt — the test -suite runs them, nobody pastes them. - -- **One command per block.** Multiple steps may share a block only when - explicitly chained with `&&`, `;`, or `\` continuations — the chain is - then one logical command. -- **Explanations go in prose above the block**, never as `#` comments inside it. -- **Command menus are per-command blocks with prose lead-ins**, not tables. -- **Shell commands use the `console` tag with a `$ ` prefix.** This separates - interactive commands from scripts and enables prompt-aware copy. -- **Split long commands with `\`** — one flag or flag+value pair per indented - continuation line, positional arguments last. - -Good: - -Show the last ten commits as a graph: - -```console -$ git log \ - --max-count=10 \ - --graph \ - --oneline -``` - -Bad: - -```console -# Show the last ten commits as a graph -$ git log --max-count=10 --graph --oneline -``` - -## Comments earn their maintenance cost - -A comment ships only if it passes all three gates. Fail any: delete or rewrite. -Borderline: delete — borderline means the information is reconstructible, which -is what makes deletion cheap. - -**Loss.** Three years from now, would losing this cost a maintainer real time -rediscovering intent, an invariant, a constraint, or a failure mode the code and -tests do not already make obvious? - -**Elite.** Would SQLite, Redis, the Go standard library, or CPython write this -comment, at this length? Those projects state the constraint and stop. They do -not argue with an imagined objector. - -**Upkeep.** Will it stay true without maintenance? A comment that hand-syncs a -value the code owns — a count, an offset, a line reference, a duplicated -constant — is false the first time that value moves. - -### Ceiling - -One or two lines. A comment reaching four is either carrying several facts, in -which case split it, or arguing, in which case cut it to the fact. - -Rationale, alternatives weighed, and the story of how the code got here belong -in the commit message: timestamped, attached to the exact diff, and free to -maintain. - -A comment often holds both a constraint and the deliberation that found it. Keep -the constraint, cut the deliberation. "Runs at most once per second" survives; -"this is the right trade for now" does not. - -### Keep - -- Why over how: upstream quirks, protocol and compatibility constraints, - performance tradeoffs still part of the contract. -- Invariants, preconditions, ordering, lifetime, and concurrency requirements - that types and tests cannot express. -- Code that looks wrong but is not, so a later cleanup does not reintroduce the - bug. -- A high-level sketch of an algorithm whose local operations do not reveal the - whole. - -### Delete - -- Narration of the next lines; code translated into English. -- Restated names, types, defaults, or control flow. -- Values duplicated from the code and hand-synced. -- Justification, hedging, or apology for a choice. -- Speculation about future requirements. -- History version control already holds, including commented-out code. -- Ticket and issue numbers. They say nothing to a reader without tracker access, - and they rot when the tracker moves. Unfinished work goes in the tracker, not - the source. -- Transient observations — "currently", "for now", "the latest release" — - that go stale with no nearby edit. - -### The upkeep gate in practice - -It reaches values that track our own code. It does not reach frozen external -facts. - -Bad (Delete): - -```python -# There are 321 tests to complete for servers. -``` - -Good (Keep): - -```python -# CPython < 3.11 has no ExceptionGroup, so this branch stays. -``` - -### Documentation exception - -Doctests, minimal usage examples, and param, return, and raises lines on public -API are exempt from the loss gate — they serve the caller, not the maintainer. -They are exempt from nothing else. Ceiling: a good man page entry. - -NumPy-style `Parameters`, `Returns`, and `Attributes` sections and executable -doctests fall under this exception — autodoc ships every field whether or not -you describe it, and a doctest that runs is also a test. - -## AI Slop Prevention - -Treat AI slop as **review-hostile noise**, not as proof that text or -code is wrong. The goal is to maximize information density by removing -artifacts that make the repository harder to trust or navigate. - -### The Anti-Slop Rubric - -Before committing, audit all AI-assisted changes for these noise -patterns: - -- **AI Signatures:** Remove "Generated by", footers, conversational - filler ("Certainly!", "Here is..."), unexplained emojis (🤖, ✨), and - AI-tool metadata. -- **Brittle References:** Avoid hard-coded line numbers, fragile - file/test counts, dated "as of" claims, bare SHAs, and local - absolute paths unless they are strict evidentiary artifacts (e.g., - benchmark logs). -- **Diff Narration:** Do not restate what moved, was renamed, or was - removed in artifacts the downstream reader holds: code, docstrings, - README, CHANGES, PR descriptions, or release notes. The diff and - commit message already carry this history. -- **Branch-Internal Narrative:** Do not mention intermediate branch - states, abandoned approaches, or "no longer" behavior unless users - of a published release actually experienced the old state (**The - Published-Release Test**). -- **Low-Value Scaffolding:** Remove ownerless TODOs (`TODO: revisit`), - unused future-proofing, debug artifacts, and defensive wrappers that - do not protect a currently reachable failure mode. -- **Prose Inflation:** Replace generic AI "tells" like *comprehensive, - robust, seamless, production-ready, leverage, delve, tapestry,* and - *best practices* with concrete descriptions of behavior, - constraints, or trade-offs. -- **Coded Labels:** Write rules, options, and findings as plain - imperatives. Don't tag them with codes like `[R1]`, `A1`, or - `Option B` in artifacts a human reads — the reader shouldn't have to - decode an index. Internal agent bookkeeping may use ids; shipped text - may not. - -### Durable Source Links - -Link to a pinned revision, never to trunk. A pinned permalink is not a -brittle reference; an unlinked SHA dropped into prose is. `blob/master/…` -links rot silently — the file moves, lines shift, and the anchor lands -on unrelated code while still resolving. - -- Prefer a release tag (`blob/v1.4.0/…`). Most durable, and it tells - the reader which released version the claim held for. -- Otherwise use a 7-char commit ref (`blob/9a29b1a/…`) reachable from - trunk. Use when there is no tag or the claim is about unreleased - code. Never a PR-head SHA — it can be rebased or garbage-collected. -- Reserve `blob/master/…` for living documents meant to always show the - latest state, such as a contributing guide. -- Line anchors (`#L120-L145`) are only safe on a pinned ref. - -### Preservation & Context - -Subjective cleanup must never remove load-bearing rationale. Adjudicate -comments with the comment policy above; borderline cases are deleted, not -kept. - -- **Preserve the "Why":** You MUST NOT delete comments that document - invariants, protocol constraints, platform quirks, security - boundaries, and upstream workarounds. -- **Evidence is Immune:** Preserve exact counts, dates, and SHAs when - they serve as evidence in benchmark results, release notes, stack - traces, or lockfiles. -- **Behavior Over Inventory:** A useful description explains what - changed for the *system or user*; it does not provide an inventory - of files or functions the diff already shows. - -### The Published-Release Test - -Long-running branches accumulate tactical decisions — renames, -refactors, attempts-then-reverts. When deciding what counts as -branch-internal, use trunk or the parent branch as the baseline — not -intermediate states inside the current branch. Ask: - -> Did users of the most recently published release ever experience -> this old name, old behavior, or bug? - -If the answer is **no**, it is branch-internal narrative. Move it to -the commit message and describe only the final state in the artifact. - -**Keep in shipped artifacts:** -- Deprecations and migration guides for symbols that actually shipped. -- `### Fixes` entries for bugs that affected users of a published - release. -- Comments explaining *why the current code looks this way* - (invariants, platform quirks) that make sense to a reader who never - saw the previous version. - -### Cleanup in Hindsight - -When applying these rules retroactively from inside a feature branch, -first establish scope by diffing against the parent branch (or trunk) -to identify which commits this branch actually introduced. Then: - -- **In-branch commits:** Prompt the user with two options: `fixup!` - commits with `git rebase --autosquash` to address each causal commit - at its source, or a single cleanup commit at branch tip. -- **Trunk/Parent commits:** Default to leaving them alone. Act only on - explicit user instruction. If the user opts in, fold the cleanup - into a single commit at branch tip; do not rewrite shared history. -- **Scope guard:** If cleaning prior slop would touch a colleague's - work or expand the branch beyond its stated goal, stay in lane: - protect the current goal and leave prior slop alone. - -### Change Discipline - -- Make the smallest coherent change that solves the verified problem; - keep unrelated cleanup out of it. -- Reuse an existing file, component, helper, API, or test before adding - a new one. Modify in place when the change fits the file's - responsibility. -- Keep new APIs private until a caller outside the module needs them. -- Add a file only for a durable boundary — a distinct responsibility, - independent reuse, or splitting an oversized high-touch module — not - for a single-use helper or a one-line re-export. - -### Keep Instructions Lean - -Treat this file like code and prune it. - -- Delete a line whose removal would not cause a mistake. -- Move multi-step procedures into skills, path-specific rules into - nested AGENTS.md files, and hard limits into hooks or CI. -- Keep only non-obvious, broadly applicable defaults here. Anything a - reader can infer from the code, a manifest, or a linter does not - belong. diff --git a/README.md b/README.md index 71ad293..ec2ba6f 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,11 @@ Passthrough to your current directory's VCS. [![Code Coverage](https://codecov.io/gh/vcs-python/g/branch/master/graph/badge.svg)](https://codecov.io/gh/vcs-python/g) [![License](https://img.shields.io/github/license/vcs-python/g.svg)](https://github.com/vcs-python/g/blob/master/LICENSE) -Shortcut / powertool for developers to access current repos' VCS, whether it's -git, subversion (`svn`), or mercurial (`hg`). +A command-line shortcut that runs your current directory's VCS command, +whether it's git, subversion (`svn`), or mercurial (`hg`). Requires Python +3.10 or newer. + +## Install ```console $ pip install --user g @@ -39,7 +42,7 @@ $ g ### Developmental releases -You can test the unpublished version of g before its released. +You can test the unpublished version of g before its release. - [pip](https://pip.pypa.io/en/stable/): @@ -50,15 +53,15 @@ You can test the unpublished version of g before its released. - [uv](https://docs.astral.sh/uv/): ```console - $ uv tool install g + $ uv tool install --prerelease=allow g ``` ```console - $ uv add g + $ uv add g --prerelease allow ``` ```console - $ uvx g + $ uvx --from 'g' --prerelease allow g ``` - [pipx](https://pypa.github.io/pipx/docs/): @@ -69,11 +72,28 @@ You can test the unpublished version of g before its released. Then use `g@next --help`. -# Credits +## Usage + +```console +$ g status +``` + +Inside a git checkout, that runs `git status`; inside svn, `svn status`; +inside mercurial, `hg status`. g intercepts only `-V`/`--version` — +everything else, including `-h`/`--help`, forwards straight to the detected +VCS. The VCS's own stdout and stderr print through unchanged, but g's exit +status does not mirror the wrapped command's: a completed run always exits +`0`. Outside a VCS directory, g prints `No VCS found in current +directory.` to stderr and stops. + +Full detection order and command reference: +. + +## Credits 2021-12-05: Thanks to [John Shanahan](https://github.com/shanahanjrs) ([@\_shanahanjrs](https://twitter.com/_shanahanjrs)) for giving g use [g](https://pypi.org/project/g/) -# Donations +## Donations Your donations fund development of new features, testing and support. Your money will go directly to maintenance and development of the @@ -82,9 +102,9 @@ right for the value you get out of the project. See donation options at . -# More information +## More information -- Python support: >= 3.9, pypy +- Python support: >= 3.10 - VCS supported: git(1), svn(1), hg(1) - Source: - Docs: diff --git a/docs/AGENTS.md b/docs/AGENTS.md deleted file mode 100644 index 8dab30c..0000000 --- a/docs/AGENTS.md +++ /dev/null @@ -1,131 +0,0 @@ -# Documentation voice - -This file covers the *voice* of prose under `docs/` — how to frame a -page so a reader meets the idea before its mechanics. It complements -the repository-root `AGENTS.md`, which already governs doctests, -changelog conventions, MyST roles, and commit messages. When the two -overlap, the root file wins; this one only answers the question it -leaves open: how should the prose sound? - -## Who you are writing for - -The default reader works at a shell prompt inside a checkout and runs -`g` where they would type `git`, `svn`, or `hg`. They are fluent in -their own VCS — status, commit, log, diff — but you cannot assume they -read Python or know g's internals: `find_repo_type()`, the -`vcspath_registry` marker mapping, or the `G_IS_TEST` escape hatch the -tests use. - -A second, smaller reader writes Python: they call `run()` directly, -build on `create_parser()`, or contribute to g itself. Serve them too, -but mark their material opt-in ("for the rarer cases", "internal") so -the default reader knows they can stop. Never make the common case pay -a comprehension tax for the advanced one. - -## Voice - -- **Second person, present tense, active.** "You run `g status`", not - "The command is proxied". Address the reader who is doing the thing. -- **Concept before mechanics.** Open by saying what g *does* for the - reader — one command that becomes the right VCS command. The - generated option listing, the marker table, the exit behavior are - the last details they need, not the first. A page that opens with - argparse output has buried the idea under its plumbing. -- **Say when they can stop.** g has no configuration; most pages owe - the reader one reassurance up front — run `g` where you'd run your - VCS, and you're done. Let a skimmer leave after one sentence. -- **Grant permission, don't demand attention.** "Reach for this - when…", "for the rarer cases" — tell readers they're in the right - place without implying they must read on. -- **Progressive disclosure.** Order by how many readers need it: the - everyday command → the one flag g intercepts (`--version`/`-V`) → - how detection works → the Python entry points. Each step is for a - smaller audience than the last. -- **Lean on the pipeline.** The reader's mental model is a straight - line: current directory → walk up the parents → a `.git`, `.svn`, - or `.hg` marker → the matching VCS command, with your arguments - forwarded. Reinforce that chain when you explain detection or - dispatch; it is the whole tool. -- **Name the trade-off.** If the thinness costs something, say so - plainly: `--version`/`-V` never reaches the underlying VCS, and - outside any repository g prints "No VCS found in current directory." - and stops. State it; don't sell it. -- **Frame by concept, not by mechanism.** Don't headline a feature by - its directory marker or argparse detail in prose; that names the - implementation surface, which is the reader's last concern. Name the - concept — detection, forwarding. The mechanics vocabulary — the - marker-to-command table, the generated option reference — belongs in - the CLI reference, and only there. - -## Keeping examples correct - -Nothing executes the examples under `docs/` — `--doctest-modules` -runs the doctests in `src/g`'s docstrings, and `testpaths` includes -`docs/`, but Markdown prose is never collected. A ```` ```console ```` -block is trusted as written, so it drifts silently. Run a command -before you commit it as an example, and when you reshape a page, -re-check that its examples still match what g prints today. Keep -shell examples in ```` ```console ```` blocks at a `$` prompt, one -command per block, as the existing pages do. - -## What stays precise - -Warm the framing, never the facts. The marker-to-command table, exact -message strings ("No VCS found in current directory."), version -strings, and function cross-references carry meaning in their exact -form — leave them alone. The friendly voice belongs in the sentences -*around* a precise block, introducing it, not inside it paraphrasing -it into vagueness. - -## Cross-references - -Point the curious reader at the deep-dive rather than inlining it, and -put the link where their interest peaks — on the phrase that made them -curious ("how detection works", "call it from Python") — not as a -standalone footnote the eye skips. Use the MyST roles listed in the -root `AGENTS.md` (`{func}`, `{class}`, `{meth}`, `{attr}`, `{exc}`, -`{ref}`, `{doc}`). A `{ref}` must match its target's anchor exactly — -anchors here are lowercase and hyphenated (`cli-main`, -`cli-supported-vcs`, `developmental-releases`). `make build_docs` -catches a broken cross-reference; the test suite does not — so build -the docs before you commit. - -Link the first prose mention of any symbol that has a useful -destination on that page. This includes Python objects, g's API, CLI -reference anchors, project pages, and external tools or projects. Use -the most specific target available: `{func}`, `{class}`, `{meth}`, -`{mod}`, `{exc}`, or `{attr}` for API objects; `{ref}` or `{doc}` for -documentation pages and section anchors; and a Markdown link or -reference link for external projects. After the first linked mention -on a page, later mentions can stay plain unless the distance or -context makes another link useful. - -Do not rely on a later reference section to satisfy the first-mention -rule. If the first occurrence would be a heading, grid-card teaser, or -introductory sentence, link that occurrence or retitle the heading so -the first prose mention can carry the link. Leave command examples, -code blocks, and literal values as code; link the surrounding prose -instead. - -## A page that does this - -`docs/cli/index.md` is the worked example: a concept-first line — g -proxies to your current directory's VCS — a three-step "How it works" -before any generated reference, the `--version`/`-V` interception -stated plainly, everyday usage in ```` ```console ```` blocks, and the -precise directory-marker table left exact, below everything the -everyday reader needs. Read it before reshaping another page. - -## Before you commit - -- Does the page open with what g *does* for the reader, or with how - the command line is parsed? -- Can a reader who only wants `g status` stop after the first - paragraph? -- Is anything framed by its directory marker or argparse surface that - should be named by concept instead? -- Are the Python-only and contributor parts clearly marked opt-in? -- Did you re-run any shell example you touched, and leave every table, - message string, and cross-reference exact? -- Did `make build_docs` stay clean — no new warning, no broken - cross-reference? diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md deleted file mode 120000 index 47dc3e3..0000000 --- a/docs/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -AGENTS.md \ No newline at end of file diff --git a/docs/project/code-style.md b/docs/project/code-style.md index 948cc7d..e96b29f 100644 --- a/docs/project/code-style.md +++ b/docs/project/code-style.md @@ -1,24 +1,7 @@ # Code Style -Use this page when you are changing g itself and want the same local checks CI -expects. - -## Formatting - -Run [ruff](https://github.com/astral-sh/ruff) before committing Python changes. - -```console -$ uv run ruff check . --fix -``` - -```console -$ uv run ruff format . -``` - -## Type Checking - -Run [mypy](https://mypy-lang.org/) for static type checking. - -```console -$ uv run mypy . -``` +This page split in two. The formatting, linting, and type-checking commands +now live in +[.github/CONTRIBUTING.md](https://github.com/vcs-python/g/blob/master/.github/CONTRIBUTING.md#the-gates) +("The gates"). Docstring and comment conventions now live in +[.github/WRITING.md](https://github.com/vcs-python/g/blob/master/.github/WRITING.md#docstrings). diff --git a/docs/project/contributing.md b/docs/project/contributing.md index 047e52c..bd13e1a 100644 --- a/docs/project/contributing.md +++ b/docs/project/contributing.md @@ -1,255 +1,7 @@ # Development -Use this page when you want to change g itself. If you only want to install and -run the command, start with {doc}`/quickstart`. - -## Bootstrap the project - -Install [git] and [uv]. - -Clone: - -```console -$ git clone https://github.com/vcs-python/g.git -``` - -```console -$ cd g -``` - -Install packages: - -```console -$ uv sync --all-extras --dev -``` - -[installation documentation]: https://docs.astral.sh/uv/getting-started/installation/ -[git]: https://git-scm.com/ - -## Tests - -```console -$ uv run py.test -``` - -The Makefile wrapper runs the same test command. - -```console -$ make test -``` - -## Automatically run tests on file save - -Run tests once, then keep watching with [pytest-watcher]: - -```console -$ make start -``` - -Watch through [entr(1)] if you have it installed: - -```console -$ make watch_test -``` - -[pytest-watcher]: https://github.com/olzhasar/pytest-watcher - -## Documentation - -Default preview server: http://localhost:8034 - -[sphinx-autobuild] builds the docs, watches for file changes, and launches a -server. - -From the project root: - -```console -$ make start_docs -``` - -From inside `docs/`: - -```console -$ make start -``` - -[sphinx-autobuild]: https://github.com/executablebooks/sphinx-autobuild - -### Manual documentation - -Enter the docs directory: - -```console -$ cd docs -``` - -Build the docs: - -```console -$ make html -``` - -Start the HTTP server: - -```console -$ make serve -``` - -Project-root helpers run the same docs tasks: - -```console -$ make build_docs -``` - -```console -$ make serve_docs -``` - -Rebuild docs on file change with [entr(1)]: - -```console -$ make watch_docs -``` - -Rebuild docs and run the server through one terminal when your [GNU Make] has -`-J` support: - -```console -$ make dev_docs -``` - -## Formatting / Linting - -### Linting and formatting - -The project uses [ruff] to handle formatting, sorting imports and linting. - -````{tab} Command - -uv: - -```console -$ uv run ruff check . -``` - -If you set up manually: - -```console -$ ruff check . -``` - -```` - -````{tab} make - -```console -$ make ruff -``` - -```` - -````{tab} Watch - -```console -$ make watch_ruff -``` - -requires [`entr(1)`]. - -```` - -````{tab} Fix files - -uv: - -```console -$ uv run ruff check . --fix -``` - -If you set up manually: - -```console -$ ruff check . --fix -``` - -```` - -#### Code formatting - -Use [ruff format] for formatting. - -````{tab} Command - -uv: - -```console -$ uv run ruff format . -``` - -If you set up manually: - -```console -$ ruff format . -``` - -```` - -````{tab} make - -```console -$ make ruff_format -``` - -```` - -### Type checking - -Use [mypy] for static type checking. - -````{tab} Command - -uv: - -```console -$ uv run mypy . -``` - -If you set up manually: - -```console -$ mypy . -``` - -```` - -````{tab} make - -```console -$ make mypy -``` - -```` - -````{tab} Watch - -```console -$ make watch_mypy -``` - -requires [`entr(1)`]. -```` - -## Releasing - -[uv] handles virtualenv creation, package requirements, versioning, -building, and publishing. There is no `setup.py` or requirements file. - -See {doc}`/project/releasing` before preparing a release. - -[uv]: https://github.com/astral-sh/uv -[entr(1)]: http://eradman.com/entrproject/ -[`entr(1)`]: http://eradman.com/entrproject/ -[GNU Make]: https://www.gnu.org/software/make/ -[ruff format]: https://docs.astral.sh/ruff/formatter/ -[ruff]: https://ruff.rs -[mypy]: http://mypy-lang.org/ +Environment setup, the gates, tests, documentation builds, and the release +process now live in +[.github/CONTRIBUTING.md](https://github.com/vcs-python/g/blob/master/.github/CONTRIBUTING.md), +which is also what GitHub shows when you open a pull request. Read it there +— this page only keeps the published URL working.