Skip to content
Open
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
3 changes: 2 additions & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<!-- How did you test your changes? -->

- [ ] Tested locally with `uv run specify --help`
- [ ] Ran existing tests with `uv sync && uv run pytest`
- [ ] Ran existing tests with `uv sync && uv run pytest` (optionally `uv run pytest --parallel --parallel-tier medium`)
- [ ] Tested with a sample project (if applicable)

## AI Disclosure
Expand All @@ -17,6 +17,7 @@

- [ ] I **did not** use AI assistance for this contribution
- [ ] I **did** use AI assistance (describe below)
- [ ] If AI posted PR comments on my behalf, each comment includes explicit "Posted on behalf of @<me> by <agent> (model: <model>)" attribution

<!-- If you used AI, briefly describe how (e.g., "Code generated by Copilot", "Consulted ChatGPT for approach"): -->

33 changes: 33 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ On [GitHub Codespaces](https://github.com/features/codespaces) it's even simpler
1. Fork and clone the repository
1. Configure and install the dependencies: `uv sync --extra test`
1. Make sure the CLI works on your machine: `uv run specify --help`
1. Run tests: `uv run pytest` (optional faster path: `uv run pytest --parallel`)
1. Create a new branch: `git checkout -b <type>/<number>-<short-slug>` (see [Branch naming](#branch-naming) below)
1. Make your change, add tests, and make sure everything still works
1. Test the CLI functionality with a sample project if relevant
Expand Down Expand Up @@ -87,6 +88,32 @@ For the smoothest review experience, validate changes in this order:

### Automated checks

#### Optional parallel test execution

```bash
uv run pytest --parallel
```

`--parallel` is opt-in and auto-selects a conservative worker count using CPU, memory, and OS caps. Use `--parallel-max-workers N` to set a stricter upper bound.

Worker settings are calculated from effective CPU capacity (including affinity/container quotas where available) and currently available memory, then bounded by platform caps.

Use `--parallel-tier low|medium|high` to tune aggressiveness:

- `low` keeps more headroom (best for laptops or multitasking)
- `medium` is the default balance
- `high` favors throughput on dedicated dev/CI machines

Recommended starting points:

| Environment | Suggested tier | Example command |
|---|---|---|
| Laptop / shared desktop | low | `uv run pytest --parallel --parallel-tier low` |
| Developer workstation | medium | `uv run pytest --parallel --parallel-tier medium` |
| Dedicated CI runner | high | `uv run pytest --parallel --parallel-tier high` |

If system load is high or tests become unstable, step down one tier and/or set `--parallel-max-workers`.

#### Agent configuration and wiring consistency

```bash
Expand Down Expand Up @@ -190,6 +217,12 @@ That being said, if you are using any kind of AI assistance (e.g., agents, ChatG

If your PR responses or comments are being generated by an AI, disclose that as well.

When AI-generated PR comments are posted on your behalf, use an explicit attribution line in the comment body, for example:

> Posted on behalf of @<your-handle> by GitHub Copilot (model: GPT-5.3-Codex).

Keep one top-level review-round summary comment per round (instead of replying to every thread), and do not resolve reviewer conversations yourself.

As an exception, trivial spacing or typo fixes don't need to be disclosed, so long as the changes are limited to small parts of the code or short phrases.

An example disclosure:
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ packages = ["src/specify_cli"]
test = [
"pytest>=7.0",
"pytest-cov>=4.0",
"pytest-xdist>=3.6.1",
]

[tool.pytest.ini_options]
Expand Down
29 changes: 17 additions & 12 deletions scripts/bash/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -433,12 +433,16 @@ resolve_template() {
local presets_dir="$repo_root/.specify/presets"
if [ -d "$presets_dir" ]; then
local registry_file="$presets_dir/.registry"
if [ -f "$registry_file" ] && command -v python3 >/dev/null 2>&1; then
if [ -f "$registry_file" ] && (command -v python3 >/dev/null 2>&1 || command -v python >/dev/null 2>&1); then
# Read preset IDs sorted by priority (lower number = higher precedence).
# The python3 call is wrapped in an if-condition so that set -e does not
# abort the function when python3 exits non-zero (e.g. invalid JSON).
# The python call is wrapped in an if-condition so that set -e does not
# abort the function when the interpreter exits non-zero (e.g. invalid JSON).
local sorted_presets=""
if sorted_presets=$(SPECKIT_REGISTRY="$registry_file" python3 -c "
local python_cmd="python3"
if ! command -v "$python_cmd" >/dev/null 2>&1; then
python_cmd="python"
fi
Comment thread
LahkLeKey marked this conversation as resolved.
if sorted_presets=$(SPECKIT_REGISTRY="$registry_file" "$python_cmd" -c "
Comment on lines +436 to +445
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved by ba72907.

import json, sys, os
try:
with open(os.environ['SPECKIT_REGISTRY']) as f:
Expand All @@ -451,28 +455,29 @@ except Exception:
sys.exit(1)
" 2>/dev/null); then
if [ -n "$sorted_presets" ]; then
# python3 succeeded and returned preset IDs — search in priority order
# Python interpreter succeeded and returned preset IDs — search in priority order
while IFS= read -r preset_id; do
preset_id="${preset_id%$'\r'}"
local candidate="$presets_dir/$preset_id/templates/${template_name}.md"
[ -f "$candidate" ] && echo "$candidate" && return 0
done <<< "$sorted_presets"
Comment on lines +458 to 463
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved by ba72907.

fi
# python3 succeeded but registry has no presets — nothing to search
# Python interpreter succeeded but registry has no presets — nothing to search
else
# python3 failed (missing, or registry parse error) — fall back to unordered directory scan
for preset in "$presets_dir"/*/; do
# Interpreter invocation failed (missing, or registry parse error) — fall back to deterministic directory scan
while IFS= read -r preset; do
[ -d "$preset" ] || continue
local candidate="$preset/templates/${template_name}.md"
[ -f "$candidate" ] && echo "$candidate" && return 0
done
done < <(find "$presets_dir" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | LC_ALL=C sort)
Comment on lines +467 to +472
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved by ba72907.

fi
else
# Fallback: alphabetical directory order (no python3 available)
for preset in "$presets_dir"/*/; do
# Fallback: alphabetical directory order (no usable python interpreter available)
while IFS= read -r preset; do
[ -d "$preset" ] || continue
local candidate="$preset/templates/${template_name}.md"
[ -f "$candidate" ] && echo "$candidate" && return 0
done
done < <(find "$presets_dir" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | LC_ALL=C sort)
fi
Comment thread
LahkLeKey marked this conversation as resolved.
fi

Expand Down
Loading