Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 75 additions & 20 deletions .claude/skills/gemini-task/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ second opinions call Gemini through Vertex AI instead (`/zweitmeinung`).
**Good Gemini tasks** need no credentials and no production access, can be
verified by commands that run locally, and are worth an hour of work or more.
Claude does a quick fix directly; the hand-over costs more than it saves there.
Propose this route yourself: when planning turns up a longer task that fits,
offer it to the owner as an option (owner, 2026-10-22).
The longer Gemini is to run, the more planning goes in first: for a large task,
get a second opinion on the plan (`/zweitmeinung`) and write the owner's
decisions into the issue. Examples:
Expand All @@ -55,6 +57,19 @@ decisions into the issue. Examples:

- Never write `@claude` in it: the util-claude workflow reacts to that mention.
- Never put secrets or personal data in it. The repository is public.
- For a change to a rule or heuristic, Acceptance also lists cases where the
existing rules must still win, not only the new rule's positive and negative
cases. #12224 listed none, and Gemini built a currency rule to spec that
overrode a German file's own decimal-comma column; only the independent
review caught it.
- Constraints name the files the gates make Gemini touch as well. For example,
a new parser warning needs its code in `agents/anyplot/data/warning_codes.py`
and a sample in its test, because an autouse fixture in
`tests/unit/agents/data/conftest.py` fails every test that emits an unknown
warning. A "change only" list without them contradicts the gates.
- Wherever you tell Gemini or the owner to read an issue, write
`gh issue view <number> --json title,body,comments`: a bare `gh issue view`
and `gh pr edit` fail on this repository (Projects classic).
- Put the body in a private temp file, then create the issue:

```bash
Expand All @@ -74,10 +89,12 @@ Repo-relative paths, related PRs or issues, and the facts Gemini cannot look up.
- [ ] One checkbox per reviewable step; a long refactor gets several.

## Constraints
Scope boundaries, what must not change, and the repository rules that matter most here.
Scope boundaries, what must not change, the files the gates make you touch as
well, and the repository rules that matter most here.

## Acceptance
The exact commands that must pass, and the behavior to check by hand, if any.
The exact commands that must pass, the cases where the existing rules must still
win, and the behavior to check by hand, if any.

## Decide yourself / report back
The routine choices Gemini makes and documents, and the questions it returns
Expand Down Expand Up @@ -120,9 +137,11 @@ the issue.

This section is for the Gemini agent in the owner's agy session.

1. **Read the task.** Use `gh issue view <number>`; with no number given, list
the open tasks with `gh issue list --label gemini --state open` and ask the
owner which one to take. Reading GitHub is fine. Your only writes are your
1. **Read the task.** Use `gh issue view <number> --json title,body,comments`
(without `--json` it fails on this repository: Projects classic); with no
number given, list the open tasks with
`gh issue list --label gemini --state open` and ask the owner which one to
take. Reading GitHub is fine. Your only writes are your
own branch and your own draft PR (step 7); never create, edit, comment on,
label, or close anything else.
2. **Work in the dedicated clone on your own branch.** That clone is
Expand All @@ -135,6 +154,11 @@ This section is for the Gemini agent in the owner's agy session.
git switch -c gemini/<number>-<slug> origin/main
```

In a fresh clone, or after `pyproject.toml` or `uv.lock` changed, sync the
environment once with the extras CI and the gates use; without `agents` the
tests under `tests/unit/agents/` fail at collection:
`uv sync --extra test --extra dev --extra typecheck --extra agents`.

To continue a task from an earlier session, switch to its branch and bring
in what Claude may have pushed to it with
`git pull --ff-only origin gemini/<number>-<slug>`. Then run `git status`,
Expand All @@ -154,17 +178,22 @@ This section is for the Gemini agent in the owner's agy session.
`AGENTS.md` apply: English everywhere, files edited with your edit tool, no
lint-disable comments, and documentation updated with behavior. A change
that is not exempt adds a changelog fragment under `changelog.d/` without
any `(#...)` reference; Claude adds the PR number later.
any `(#...)` reference; Claude adds the PR number later. A file the gates
make you change as well, such as a registry a test fixture enforces, is in
scope even when the issue's Constraints miss it; name it in the hand-over.
5. **Run the acceptance commands and the gates for what you changed:**

```bash
PYTHON_DOTENV_DISABLED=1 uv run ruff check . && uv run ruff format --check .
PYTHON_DOTENV_DISABLED=1 uv run --extra typecheck mypy api core --pretty # Python changes
PYTHON_DOTENV_DISABLED=1 uv run pytest tests/unit tests/integration -n auto # or the narrower set the issue names
PYTHON_DOTENV_DISABLED=1 uv run python -m tools.changelog check --base origin/main
PYTHON_DOTENV_DISABLED=1 uv run python -m tools.changelog check --base origin/main # after committing the fragment
cd app && yarn lint && yarn fm:check && yarn type-check && yarn test && yarn build # app/ changes
```

The changelog check reads `origin/main...HEAD`, as CI does, so it fails
until the fragment is committed; staging it is not enough.

6. **Commit each finished step** on your branch with a conventional message.
Its body names the step (for example `Step 2 of 5: …`) and ends with
`Refs #<number>`, followed by a co-author trailer crediting yourself, the
Expand All @@ -179,20 +208,38 @@ This section is for the Gemini agent in the owner's agy session.

7. **Hand over** when you finish or stop, whether on a blocker or at the end of
the session. Push your branch and open a draft PR, or update the one you
opened before. Write the body into a temp file first. It carries
`Closes #<number>` and lists:
- what changed and why,
- the commands you ran and their results,
- the decisions you made,
- the steps still open,
- your questions.
opened before. Write the body into a temp file first, in the repository's
PR format, so Claude can ship it without rewriting it:

```markdown
Closes #<number>

## Summary
- What changed and why, one bullet per change; name every file outside
the issue's Constraints and the gate that made you touch it.

## Plan
Issue #<number>.

## Test plan
- [x] Each command you ran, with its result line.

## Decisions
- The choices the issue left to you, and what you chose.

## Open steps and questions
- The steps still open and your questions, or "None".
```

```bash
git push -u origin gemini/<number>-<slug>
gh pr create --draft --label gemini --title "<type>: <summary>" --body-file <file> # first hand-over
gh pr edit <pr> --body-file <file> # later ones
gh api -X PATCH repos/{owner}/{repo}/pulls/<pr> -F body=@<file> # later ones
```

`gh pr edit` fails on this repository (Projects classic), so a later
hand-over updates the body through `gh api`.

Then tell the owner the PR number, and stop.

**Never** do any of these:
Expand All @@ -213,14 +260,20 @@ This section is for the Gemini agent in the owner's agy session.
1. **Find the drafts.** When the owner says Gemini is done, or at the start of a
session, list them with `gh pr list --draft --label gemini`. Read the PR body
(the hand-over) and the CI results on the draft. Open steps or questions go
back to the owner first.
back to the owner first. Skills load from the shared checkout, so when its
`main` is behind `origin/main` and clean, fast-forward it
(`git pull --ff-only origin main`) before you follow these steps; on
2026-10-22 a stale checkout still described the take-over from the clone.
2. **Check it out** into a fresh worktree of your own checkout:

```bash
git fetch origin gemini/<number>-<slug>:gemini/<number>-<slug>
git worktree add .claude/worktrees/gemini-<number> gemini/<number>-<slug>
```

A new worktree has no environment; sync it with the extras from section 2,
step 2, before you run anything in it.

When Gemini hands over again after step 4, the worktree exists and Git
refuses to fetch into its checked-out branch, so bring in its new commits
there instead:
Expand All @@ -239,10 +292,12 @@ This section is for the Gemini agent in the owner's agy session.
continue. Bring in a newer `origin/main` by merging it into the branch, never
by rebasing a pushed branch.
5. **Ship.** Run the open-pr gates, add the PR number to the changelog
fragment, and add one Summary bullet to the PR body: the change was
implemented by Gemini 4 Argon in the owner's interactive agy session and
reviewed by Claude. This is provenance for the reviewer, not a "Generated
with" footer. Then `gh pr ready <pr>` starts the Copilot review; follow the
fragment, and add one Summary bullet to the PR body, which Gemini wrote in
the repository's format (section 2, step 7): the change was implemented by
Gemini 4 Argon in the owner's interactive agy session and reviewed by
Claude, with the defects the review found and the commits that fixed them.
This is provenance for the reviewer, not a "Generated with" footer. Then
`gh pr ready <pr>` starts the Copilot review; follow the
PR through as `/open-pr` describes. Merge only on the owner's explicit OK in
the session.
6. **Clean up.** After the merge, remove your worktree. Deleting a local
Expand Down
9 changes: 6 additions & 3 deletions .claude/skills/verify-core/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,12 @@ To check against **real** data, don't reach for the DB from a script
(PostgreSQL in prod, SQLite in tests). When adding a column type,
verify it on both: an integration test (SQLite) plus one e2e run
or a careful review of the PostgreSQL branch.
- **Plain `uv run ruff` can fail on a fresh venv** — dev tools live
in extras; `uv sync --extra dev` (or `--all-extras`) first, and
mypy always needs `--extra typecheck`.
- **A fresh venv (a new worktree or clone) lacks the extras** — dev
tools live in extras, and the tests under `tests/unit/agents/` fail
at collection without `google-adk`. Sync once with what CI and the
gates use: `uv sync --extra test --extra dev --extra typecheck --extra agents`
(CI's "Run Tests" syncs `--extra test --extra agents`); mypy always
needs `--extra typecheck`.

## Troubleshooting

Expand Down
9 changes: 9 additions & 0 deletions changelog.d/gemini-task-friction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
### Changed

- **Gemini tasks after the first pilot.** The `gemini-task` skill now reads
issues with `gh issue view --json` and updates PR bodies through `gh api`,
because both bare commands fail on this repository; asks issue authors for
cases where the existing rules must still win and for the files the gates
couple; names the environment sync with the `agents` extra; and has Gemini
hand over in the repository's PR format. The changelog check names a
fragment that is on disk but not yet committed. (#12229)
10 changes: 10 additions & 0 deletions tests/unit/tools/test_changelog_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,16 @@ def test_a_pr_without_a_fragment_fails_and_names_the_way_out(repo: Path) -> None
assert cl.SKIP_LABEL in problem


def test_an_uncommitted_fragment_is_named_in_the_failure(repo: Path) -> None:
"""The gate reads commits, as CI does; a fragment still on disk only gets a pointer, not a pass."""
(repo / "core.py").write_text("x = 2\n", encoding="utf-8")
_commit_all(repo, "change")
(repo / "changelog.d" / "topic.md").write_text(FRAGMENT, encoding="utf-8")
(problem,) = cl.check_pr("main", root=repo)
assert "no changelog fragment" in problem
assert "changelog.d/topic.md is not committed yet" in problem


def test_a_catalogue_only_pr_needs_no_fragment(repo: Path) -> None:
"""What the automated plot pipeline produces, and what a release summarizes
in one italic *Catalog* line rather than per PR."""
Expand Down
15 changes: 12 additions & 3 deletions tools/changelog/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,9 +458,18 @@ def check_pr(base: str, *, root: Path = REPO_ROOT) -> list[str]:
touches_fragments = any(p.startswith(f"{FRAGMENT_DIR_NAME}/") for p in changed)
exempt = bool(changed) and all(p.startswith(EXEMPT_PREFIXES) for p in changed)
if changed and not (touches_fragments or release_cut or exempt):
problems.insert(
0,
problem = (
f"no changelog fragment: add {FRAGMENT_DIR_NAME}/<slug>.md (format: {FRAGMENT_DIR_NAME}/README.md), "
f"or label the PR '{SKIP_LABEL}' if it truly changes nothing worth a line",
f"or label the PR '{SKIP_LABEL}' if it truly changes nothing worth a line"
)
if pending := _uncommitted_fragments(root):
problem += f"; {', '.join(pending)} is not committed yet, and the check reads commits only"
problems.insert(0, problem)
return problems


def _uncommitted_fragments(root: Path) -> list[str]:
"""Fragments in the working tree that no commit holds yet: the gate, like CI, reads only `base...HEAD`."""
out = _git(root, "status", "--porcelain", "--untracked-files=all", "--", FRAGMENT_DIR_NAME)
paths = [line[3:] for line in out.splitlines()]
return [p for p in paths if p.endswith(".md") and Path(p).name != "README.md"]
Loading