Skip to content

Commit e71e9a5

Browse files
Merge branch 'main' into copilot/add-no-hook-flag-to-tools
2 parents a725407 + 13f5208 commit e71e9a5

10 files changed

Lines changed: 202 additions & 22 deletions

File tree

.agents/skills/usethis-cli-modify/SKILL.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: Modify the usethis CLI layer (commands, options, help text) and kee
44
compatibility: usethis, Python, typer, markdown
55
license: MIT
66
metadata:
7-
version: "1.0"
7+
version: "1.1"
88
---
99

1010
# Modifying the CLI
@@ -34,6 +34,20 @@ After making CLI changes, review and update each of the following as needed:
3434

3535
The command reference page documents every command with its full description, supported options, and behavior. Update it to match any changes you made to commands, options, defaults, or descriptions.
3636

37+
Keep descriptions **factual and concise**. The reference is not the place for extended rationale, usage tips, or explanations of why an option exists. If important context is needed, put it in a separate documentation page and link to it from the reference.
38+
39+
Good — factual, concise:
40+
41+
```markdown
42+
- `--output-file` to write the output to a file instead of stdout.
43+
```
44+
45+
Bad — excessive rationale embedded in the reference:
46+
47+
```markdown
48+
- `--output-file` to write the output to a file instead of stdout. This is useful to avoid issues when shell redirects (e.g. `> file.txt`) create the file before the command runs, which can influence the behaviour of `usethis show`.
49+
```
50+
3751
### Command overview
3852

3953
The command overview page lists all commands organized by category with brief descriptions. Update it if you added, removed, renamed, or recategorized a command.

.agents/skills/usethis-qa-static-checks/SKILL.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: Perform static code checks
44
compatibility: usethis, Python, prek, basedpyright
55
license: MIT
66
metadata:
7-
version: "1.4"
7+
version: "1.5"
88
---
99

1010
# Static Checks
@@ -21,3 +21,7 @@ Note that we are interested in both errors and warnings from these tools - we sh
2121
## When to run these checks
2222

2323
Before submitting changes for review, **always** run these static checks. This should be done every time, even for small changes, to avoid slowing down the code review process unnecessarily.
24+
25+
## What to do when prek checks fail
26+
27+
It's quite common for minor cosmetic changes to be made automatically when running the prek checks, even by linters such as Ruff and mkdownlint-cli2. Since auto-fixes may have been applied during the first run, if checks fail, you should re-run a second time to see if any issues remain. Only then should you proceed to fix any remaining issues manually.

.agents/skills/usethis-skills-external-add/SKILL.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: Add an external (community) skill to the project from a third-party
44
compatibility: usethis, agent skills, npx, markdown
55
license: MIT
66
metadata:
7-
version: "1.1"
7+
version: "1.2"
88
---
99

1010
# Adding External Skills
@@ -14,10 +14,10 @@ External skills are sourced from third-party repositories rather than written lo
1414
## Procedure
1515

1616
1. Set the line-ending environment variables (see "Line endings and reproducible hashes" below).
17-
2. Install the skills using `npx skills add <source> --skill '*' --agent github-copilot --yes` (e.g. `npx skills add CodSpeedHQ/codspeed --skill '*' --agent github-copilot --yes`).
17+
2. Install the skill(s) — see "Installing the skill" below for the correct `--skill` flag.
1818
3. Note the skill name(s) added to `skills-lock.json`.
1919
4. Add each new skill to the external skills registry in `AGENTS.md`.
20-
5. Verify the hook passes: `python hooks/check-skills-documented.py`.
20+
5. Verify the hook passes: `uv run prek check-skills-documented`.
2121

2222
## Line endings and reproducible hashes
2323

@@ -29,13 +29,19 @@ Due to a [bug in the skills CLI](https://github.com/vercel-labs/skills/issues/78
2929

3030
## Installing the skill
3131

32-
To **add a new** external skill, run from the repository root (with the line-ending environment variables set as described above):
32+
To **add a specific skill** from a source, run from the repository root (with the line-ending environment variables set as described above):
33+
34+
```commandline
35+
npx skills add <github-org>/<repo> --skill '<skill-name>' --agent github-copilot --yes
36+
```
37+
38+
To **add all skills** from a source (only when you want every skill the source offers):
3339

3440
```commandline
3541
npx skills add <github-org>/<repo> --skill '*' --agent github-copilot --yes
3642
```
3743

38-
This adds the skill entry to `skills-lock.json`. Multiple skills may be added from a single source.
44+
**Warning:** `--skill '*'` installs _every_ skill from the source repository, which can be hundreds of unwanted skills. Always prefer `--skill '<skill-name>'` unless you genuinely want all of them.
3945

4046
## Documenting in AGENTS.md
4147

AGENTS.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,20 @@ The `.agents/skills` directory contains agent skills.
4343

4444
External skills can be installed if they are not present — see the `usethis-skills-external-install` skill.
4545

46-
| Skill | Source | Description |
47-
| ------------------------ | --------------------- | ----------------------------------------------------------------------- |
48-
| `codspeed-optimize` | `CodSpeedHQ/codspeed` | Optimize code for performance using CodSpeed benchmarks and flamegraphs |
49-
| `codspeed-setup-harness` | `CodSpeedHQ/codspeed` | Set up performance benchmarks and the CodSpeed harness for a project |
46+
| Skill | Source | Description |
47+
| ------------------------ | --------------------- | ------------------------------------------------------------------------------------- |
48+
| `codspeed-optimize` | `CodSpeedHQ/codspeed` | Optimize code for performance using CodSpeed benchmarks and flamegraphs |
49+
| `codspeed-setup-harness` | `CodSpeedHQ/codspeed` | Set up performance benchmarks and the CodSpeed harness for a project |
50+
| `find-skills` | `vercel-labs/skills` | Discover and install agent skills from the open skills ecosystem for new capabilities |
5051

5152
### Important Instructions about Skills usage
5253

5354
- ALWAYS use possibly relevant agent skills when they are available. Eagerly use skills, if in doubt, assume a skill is relevant.
55+
- ALWAYS use `find-skills` to research new skill capabilities if there are difficult tasks, tasks in an unfamiliar domain, if you believe there is a lack of clarity or direction around precisely how to proceed, or if you get stuck or find something surprisingly challenging. When using this skill, please be sure to use the `usethis-skills-external-install` skill when deciding to install a new external skill.
5456
- ALWAYS consider the `usethis-qa-static-checks` to be relevant: if you think your task
5557
is complete, always run this skill to check for any issues before finishing.
5658
- ALWAYS mention which skills you've used after completing any task, in PR descriptions, and comments.
59+
60+
## Lessons
61+
62+
When you are working on a problem, you are almost always going to encounter a difficulty. This is great - it's an opportunity for learning. ALWAYS make a note explicitly of what lessons you are drawing as you complete a task or when receiving user feedback. Try and keep this structured: consider the root cause of the difficulty, and how you overcame it. After finishing work on a task, report back all your lessons. Finally, try and update the relevant skills with any new insights you've drawn, to help future agents, and/or create a new skill.

docs/cli/reference.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,28 @@ Currently supported subcommands:
457457

458458
- `usethis show backend` to show the inferred project manager backend, e.g. 'uv' or 'none'. This is the default backend used, i.e. when `--backend=auto` is specified.
459459
- `usethis show name` to show the name of the project.
460-
- `usethis show sonarqube` to show appropriate contents of a `sonar-projects.properties` file for SonarQube analysis.
460+
- `usethis show sonarqube` to show appropriate contents of a `sonar-project.properties` file for SonarQube analysis.
461+
462+
### `usethis show sonarqube`
463+
464+
Show the contents of a `sonar-project.properties` file for SonarQube analysis.
465+
466+
If a `sonar-project.properties` file already exists in the project root, its contents are returned as-is. In this case, the `--project-key` option and `tool.usethis.sonarqube.project-key` in `pyproject.toml` are both ignored.
467+
468+
If no `sonar-project.properties` file exists, the contents are constructed from `pyproject.toml` configuration. In this case, a project key is required:
469+
470+
- If `--project-key` is provided, it is used.
471+
- Otherwise, `tool.usethis.sonarqube.project-key` from `pyproject.toml` is used.
472+
473+
Additional configuration in `pyproject.toml`:
474+
475+
- `tool.usethis.sonarqube.verbose` (bool, default `false`) — sets `sonar.verbose`.
476+
- `tool.usethis.sonarqube.exclusions` (list of strings, default `[]`) — sets `sonar.exclusions`.
477+
- `tool.coverage.xml.output` (string, required) — sets `sonar.python.coverage.reportPaths`.
478+
479+
Supported options:
480+
481+
- `--output-file` to write the output to a file instead of stdout.
461482

462483
## `usethis browse pypi <package>`
463484

skills-lock.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
"source": "CodSpeedHQ/codspeed",
1111
"sourceType": "github",
1212
"computedHash": "587a58bc7498635347ee7a7eba66ac17c75430db7543be4af7eaab74533a6714"
13+
},
14+
"find-skills": {
15+
"source": "vercel-labs/skills",
16+
"sourceType": "github",
17+
"computedHash": "d31e234f0c90694a670222cdd1dafa853e051d7066beda389f1097c22dadd461"
1318
}
1419
}
1520
}

src/usethis/_core/show.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,36 @@
11
from __future__ import annotations
22

3+
from typing import TYPE_CHECKING
4+
35
from usethis._backend.dispatch import get_backend
46
from usethis._console import plain_print
57
from usethis._integrations.project.name import get_project_name
68
from usethis._integrations.sonarqube.config import get_sonar_project_properties
79

10+
if TYPE_CHECKING:
11+
from pathlib import Path
12+
13+
14+
def show_backend(*, output_file: Path | None = None) -> None:
15+
_output(get_backend().value, output_file=output_file)
16+
817

9-
def show_backend() -> None:
10-
plain_print(get_backend().value)
18+
def show_name(*, output_file: Path | None = None) -> None:
19+
_output(get_project_name(), output_file=output_file)
1120

1221

13-
def show_name() -> None:
14-
plain_print(get_project_name())
22+
def show_sonarqube_config(
23+
*, project_key: str | None = None, output_file: Path | None = None
24+
) -> None:
25+
_output(
26+
get_sonar_project_properties(project_key=project_key), output_file=output_file
27+
)
1528

1629

17-
def show_sonarqube_config(*, project_key: str | None = None) -> None:
18-
plain_print(get_sonar_project_properties(project_key=project_key))
30+
def _output(content: str, *, output_file: Path | None = None) -> None:
31+
if output_file is not None:
32+
if not content.endswith("\n"):
33+
content += "\n"
34+
output_file.write_text(content, encoding="utf-8")
35+
else:
36+
plain_print(content)

src/usethis/_ui/interface/show.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
from pathlib import Path
2+
13
import typer
24

35
from usethis._config import usethis_config
4-
from usethis._ui.options import offline_opt, quiet_opt
6+
from usethis._ui.options import offline_opt, output_file_opt, quiet_opt
57

68
app = typer.Typer(
79
help="Show information about the current project.", add_completion=False
@@ -19,6 +21,7 @@
1921
def backend(
2022
offline: bool = offline_opt,
2123
quiet: bool = quiet_opt,
24+
output_file: Path | None = output_file_opt,
2225
) -> None:
2326
from usethis._config_file import files_manager
2427
from usethis._console import err_print
@@ -27,7 +30,7 @@ def backend(
2730

2831
with usethis_config.set(offline=offline, quiet=quiet), files_manager():
2932
try:
30-
show_backend()
33+
show_backend(output_file=output_file)
3134
except UsethisError as err:
3235
err_print(err)
3336
raise typer.Exit(code=1) from None
@@ -37,6 +40,7 @@ def backend(
3740
def name(
3841
offline: bool = offline_opt,
3942
quiet: bool = quiet_opt,
43+
output_file: Path | None = output_file_opt,
4044
) -> None:
4145
from usethis._config_file import files_manager
4246
from usethis._console import err_print
@@ -45,7 +49,7 @@ def name(
4549

4650
with usethis_config.set(offline=offline, quiet=quiet), files_manager():
4751
try:
48-
show_name()
52+
show_name(output_file=output_file)
4953
except UsethisError as err:
5054
err_print(err)
5155
raise typer.Exit(code=1) from None
@@ -59,6 +63,7 @@ def sonarqube(
5963
offline: bool = offline_opt,
6064
quiet: bool = quiet_opt,
6165
project_key: str | None = project_key_opt,
66+
output_file: Path | None = output_file_opt,
6267
) -> None:
6368
from usethis._config_file import files_manager
6469
from usethis._console import err_print
@@ -67,7 +72,7 @@ def sonarqube(
6772

6873
with usethis_config.set(offline=offline, quiet=quiet), files_manager():
6974
try:
70-
show_sonarqube_config(project_key=project_key)
75+
show_sonarqube_config(project_key=project_key, output_file=output_file)
7176
except UsethisError as err:
7277
err_print(err)
7378
raise typer.Exit(code=1) from None

src/usethis/_ui/options.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@
109109
# status command options
110110
status_arg = typer.Argument(default=..., help="Docstring style to enforce.")
111111

112+
# show command options
113+
output_file_opt = typer.Option(
114+
None,
115+
"--output-file",
116+
help="Write output to this file instead of stdout.",
117+
)
118+
112119
# ruff command options
113120
linter_opt = typer.Option(
114121
True,

tests/usethis/_ui/interface/test_show.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,22 @@ def test_none_backend(self, tmp_path: Path):
3232
assert result.exit_code == 0, result.output
3333
assert result.output == "none\n"
3434

35+
def test_output_file(self, tmp_path: Path):
36+
# Arrange
37+
(tmp_path / "uv.lock").touch()
38+
output_file = tmp_path / "backend.txt"
39+
40+
# Act
41+
runner = CliRunner()
42+
with change_cwd(tmp_path):
43+
result = runner.invoke_safe(
44+
app, ["backend", "--output-file", str(output_file)]
45+
)
46+
47+
# Assert
48+
assert result.exit_code == 0, result.output
49+
assert output_file.read_text(encoding="utf-8") == "uv\n"
50+
3551

3652
class TestName:
3753
def test_output(self, tmp_path: Path):
@@ -60,6 +76,23 @@ def test_invalid_pyproject(self, tmp_path: Path):
6076
# Assert
6177
assert result.exit_code == 1, result.output
6278

79+
def test_output_file(self, tmp_path: Path):
80+
# Arrange
81+
path = tmp_path / "fun"
82+
path.mkdir()
83+
output_file = path / "name.txt"
84+
85+
# Act
86+
runner = CliRunner()
87+
with change_cwd(path):
88+
result = runner.invoke_safe(
89+
app, ["name", "--output-file", str(output_file)]
90+
)
91+
92+
# Assert
93+
assert result.exit_code == 0, result.output
94+
assert output_file.read_text(encoding="utf-8") == "fun\n"
95+
6396

6497
class TestSonarqube:
6598
def test_runs(self, tmp_path: Path):
@@ -162,3 +195,64 @@ def test_invalid_pyproject(self, tmp_path: Path):
162195

163196
# Assert
164197
assert result.exit_code == 1, result.output
198+
199+
def test_output_file(self, tmp_path: Path):
200+
# Arrange
201+
(tmp_path / "pyproject.toml").write_text(
202+
"""
203+
[tool.usethis.sonarqube]
204+
project-key = "fun"
205+
206+
[tool.coverage.xml.output]
207+
"""
208+
)
209+
output_file = tmp_path / "sonar-project.properties"
210+
211+
# Act
212+
runner = CliRunner()
213+
with change_cwd(tmp_path):
214+
result = runner.invoke_safe(
215+
app, ["sonarqube", "--output-file", str(output_file)]
216+
)
217+
218+
# Assert
219+
assert result.exit_code == 0, result.output
220+
content = output_file.read_text(encoding="utf-8")
221+
assert "sonar.projectKey=fun" in content
222+
223+
def test_output_file_not_detected_as_existing(self, tmp_path: Path):
224+
"""Using --output-file avoids the redirect problem.
225+
226+
When using shell redirect (`> file`), the file is created empty before
227+
the command runs, which causes sonarqube to read that empty file.
228+
With --output-file, the file is written after generation.
229+
"""
230+
# Arrange
231+
(tmp_path / "pyproject.toml").write_text(
232+
"""
233+
[tool.usethis.sonarqube]
234+
project-key = "fun"
235+
236+
[tool.coverage.xml.output]
237+
"""
238+
)
239+
# Simulate what happens with shell redirect: an empty file pre-exists
240+
output_file = tmp_path / "sonar-project.properties"
241+
output_file.write_text("", encoding="utf-8")
242+
243+
# Act
244+
# Despite sonar-project.properties existing (empty), --output-file
245+
# still causes the config to be read from that file (by design of
246+
# get_sonar_project_properties), then overwrites it with that content.
247+
runner = CliRunner()
248+
with change_cwd(tmp_path):
249+
result = runner.invoke_safe(
250+
app, ["sonarqube", "--output-file", str(output_file)]
251+
)
252+
253+
# Assert
254+
assert result.exit_code == 0, result.output
255+
content = output_file.read_text(encoding="utf-8")
256+
# With --output-file, the file is written after content generation,
257+
# so even if it was previously empty, it will have the content now
258+
assert content != ""

0 commit comments

Comments
 (0)