mirror of
https://github.com/home-assistant/core.git
synced 2026-08-03 20:24:55 +02:00
Restore skill files tracked in dev
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
---
|
||||
name: bump-dependency
|
||||
description: Bumps a Python package dependency across Home Assistant Core integrations, regenerates core requirement files, runs verification tests and prek lint, and prepares a pull request with proper release/compare links.
|
||||
---
|
||||
|
||||
# Bump Python Package Dependency in Home Assistant Core
|
||||
|
||||
Follow these systematic steps to successfully bump a python package requirement in the repository, regenerate necessary derivative files, verify the integration, and raise a pull request.
|
||||
|
||||
## Gotchas & Non-Obvious Constraints
|
||||
|
||||
- **PR Template Integrity**: Follow Home Assistant's Pull Request template (`.github/PULL_REQUEST_TEMPLATE.md`) exactly as written, including any instructions inside the template itself. Preserve all sections, comments, and unchecked checkboxes unless the template explicitly says otherwise; the only allowed removal is the **Breaking change** section when the template instructs you to remove it if not applicable.
|
||||
- **GitHub Tag Volatility**: Release tags on GitHub are highly inconsistent (e.g., `v1.2.3` vs `1.2.3` vs `release-1.2.3`). Always use the automated resolver `resolve_dependency.py` to check HEAD status for correct tags before hardcoding comparison URLs.
|
||||
|
||||
## Step-by-Step Workflow Checklist
|
||||
|
||||
### Phase A: Research and Plan
|
||||
- [ ] **1. Identify Targets**: Note the requested target package and target version to bump.
|
||||
- [ ] **2. Discover Codebase References**: Search the codebase to find all `manifest.json` and requirements files referencing the package.
|
||||
- [ ] **3. Resolve Version/Tag Details**: Run the integrated validation helper script to resolve version details, GitHub repo, release tag format, and formatted PR links:
|
||||
```bash
|
||||
uv run python3 ./.claude/skills/bump-dependency/scripts/resolve_dependency.py <package> <old_version> [--new-version <new_version>]
|
||||
```
|
||||
- [ ] **4. Plan-Validate-Execute (Draft Plan)**: Before modifying any files, write a brief, structured plan outlining the integrations to change, old version, new version, and the resolved comparison link. Show this draft plan to the user.
|
||||
|
||||
### Phase B: Execute and Validate (Local Changes)
|
||||
- [ ] **5. Check Uncommitted Changes**: Check for any uncommitted changes in the repository. If they exist, ask the user whether to stash, commit, or discard them before proceeding.
|
||||
- [ ] **6. Git Branch Setup**: Create a clean branch starting from the latest `upstream/dev`:
|
||||
```bash
|
||||
git fetch upstream dev
|
||||
git checkout -b bump-<package>-to-<version> upstream/dev
|
||||
```
|
||||
- [ ] **7. Apply Bump to manifests**: Update the version constraint string in all identified `manifest.json` files (e.g., change `"package==1.0.0"` to `"package==1.1.0"`).
|
||||
- [ ] **8. Regenerate Core Requirements**: Run the requirements generator to update all derivative requirements and constraint files:
|
||||
```bash
|
||||
uv run python3 -m script.gen_requirements_all
|
||||
```
|
||||
- [ ] **9. Validate Requirements**: Check `git diff` to ensure that only the targeted `manifest.json` files and `requirements_all.txt` (and potentially standard constraints) were modified. No unrelated files must be affected.
|
||||
- [ ] **10. Local Venv Verification**: Install the exact targeted package version directly inside the virtual environment:
|
||||
```bash
|
||||
uv pip install "<package>==<version>"
|
||||
```
|
||||
|
||||
### Phase C: Validation Loop (Tests & Lint)
|
||||
- [ ] **11. Run Integration Tests**: Execute the pytest suite for all integrations that consume the bumped package:
|
||||
```bash
|
||||
uv run pytest tests/components/<integration_name>
|
||||
```
|
||||
- *Validation Loop*: If tests fail, analyze the error, apply appropriate fixes, and re-run pytest until all tests pass cleanly.
|
||||
- [ ] **12. Run prek Lint Checks**: Run the local prek hooks on modified files:
|
||||
```bash
|
||||
uv run prek run
|
||||
```
|
||||
- *Validation Loop*: If prek checks report any formatting or linting violations, fix them and repeat `uv run prek run` until it passes completely without errors.
|
||||
|
||||
### Phase D: User Confirmation & PR Creation
|
||||
- [ ] **13. Commit Changes**: Commit the clean changes:
|
||||
```bash
|
||||
git add <modified_files>
|
||||
git commit -m "Bump <package> to <version>"
|
||||
```
|
||||
- [ ] **14. Push Branch**: Push the local branch to your origin remote:
|
||||
```bash
|
||||
git push origin bump-<package>-to-<version>
|
||||
```
|
||||
- [ ] **15. PR Description Preparation**: Generate the pull request body from `.github/PULL_REQUEST_TEMPLATE.md`:
|
||||
- **Proposed change**: Describe the package, old version, new version, target/source branches, and insert the resolved PyPI, changelog, and comparison diff links.
|
||||
- **Type of change**: Check only 1 box in this section, and mark the `Dependency upgrade` checkbox as checked: `[x] Dependency upgrade`.
|
||||
- **Breaking change**: You may remove the "Breaking change" section entirely from the template.
|
||||
- **Validation checklists**: Mark `The code change is tested` checkbox as checked: `[x] The code change is tested`.
|
||||
- **Keep remaining template intact**: Do NOT remove any other commented-out blocks, headers, or unchecked checkboxes in the template.
|
||||
- [ ] **16. Mandatory Review Presentation**: Format the PR proposal using the **PR Presentation Template** below and display it to the user. **Stop and wait for the user to review and explicitly confirm/approve the PR template and draft details before creating the PR.**
|
||||
- [ ] **17. Raise Pull Request**: Once the user approves, create the Pull Request using the GitHub CLI:
|
||||
```bash
|
||||
gh pr create --repo home-assistant/core --base dev --head <username>:bump-<package>-to-<version> --title "Bump <package> to <version>" --body-file <pr_body_file>
|
||||
```
|
||||
|
||||
## PR Presentation Template
|
||||
|
||||
```markdown
|
||||
### 🚀 Dependency Bump Pull Request Draft Review
|
||||
|
||||
- **Package**: `<package_name>` (`<old_version>` → `<new_version>`)
|
||||
- **PR Title**: `Bump <package_name> to <new_version>`
|
||||
- **Target Branch**: `dev`
|
||||
- **Head Branch**: `<fork_username>:bump-<package_name>-to-<new_version>`
|
||||
|
||||
#### 🔗 PyPI & GitHub Links
|
||||
- **PyPI Release**: https://pypi.org/project/<package_name>/<new_version>/
|
||||
- **Changelog Link**: `<changelog_url>`
|
||||
- **Comparison Diff**: `<compare_url>`
|
||||
|
||||
#### 📁 Modified Files
|
||||
- `<list_of_modified_files>`
|
||||
|
||||
#### 📝 Proposed PR Body
|
||||
<render the complete filled PR template body here, showing all checks and modifications for user approval>
|
||||
```
|
||||
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env python3
|
||||
# ruff: noqa: T201, D103, BLE001
|
||||
"""Helper script to resolve package details, GitHub repo, release tags, and diff links from PyPI."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
from packaging.version import Version
|
||||
|
||||
_VERSION_MATCH = r"^[a-zA-Z0-9_\-\.\+\!\*]+$"
|
||||
_REPO_MATCH = r"https?://(?:www\.)?github\.com/([^/]+)/([^/]+)"
|
||||
# Project owner or repo name
|
||||
_NAME_MATCH = r"^[a-zA-Z0-9_\-\.]+$"
|
||||
|
||||
|
||||
def get_pypi_data(package_name):
|
||||
# Sanitize and URL-quote the package name to prevent URL path injection
|
||||
safe_package_name = urllib.parse.quote(package_name)
|
||||
url = f"https://pypi.org/pypi/{safe_package_name}/json"
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
headers={
|
||||
"User-Agent": "HomeAssistant-Skill-Resolver/1.0",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
return json.loads(response.read().decode())
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f"Error fetching PyPI data (HTTP {e.code}): {e.reason}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except urllib.error.URLError as e:
|
||||
print(f"Network error fetching PyPI data: {e.reason}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Unexpected error fetching PyPI data: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def find_github_repo(info):
|
||||
urls = []
|
||||
if info.get("home_page"):
|
||||
urls.append(info["home_page"])
|
||||
if info.get("project_urls"):
|
||||
urls.extend(info["project_urls"].values())
|
||||
|
||||
for u in urls:
|
||||
if not u:
|
||||
continue
|
||||
cleaned_url = u.replace("git+", "")
|
||||
m = re.search(_REPO_MATCH, cleaned_url, re.IGNORECASE)
|
||||
if m:
|
||||
owner, repo = m.groups()
|
||||
# Strip query parameters, hashes, trailing slashes, and .git extension
|
||||
repo = repo.split("?")[0].split("#")[0].split("/")[0]
|
||||
repo = repo.removesuffix(".git")
|
||||
# Validate that the owner and repo name conform to valid formats,
|
||||
# preventing prompt injection payloads embedded in repository URLs.
|
||||
if re.match(_NAME_MATCH, owner) and re.match(_NAME_MATCH, repo):
|
||||
return f"https://github.com/{owner}/{repo}"
|
||||
return None
|
||||
|
||||
|
||||
def check_github_tag(repo_url, version):
|
||||
# Try with 'v' prefix, without prefix, and with 'release-' prefix
|
||||
tag_options = [f"v{version}", version, f"release-{version}"]
|
||||
for tag in tag_options:
|
||||
# Check both tree and releases paths on GitHub
|
||||
for path_template in [f"tree/{tag}", f"releases/tag/{tag}"]:
|
||||
url = f"{repo_url}/{path_template}"
|
||||
try:
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
method="HEAD",
|
||||
headers={"User-Agent": "HomeAssistant-Skill-Resolver/1.0"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
if resp.status == 200:
|
||||
return tag
|
||||
except urllib.error.HTTPError as e:
|
||||
# If it's a 404, we continue checking other tag/path options
|
||||
if e.code == 404:
|
||||
continue
|
||||
# For non-404 HTTP errors (like 403 Forbidden/429 rate limit), exit with error to avoid false reports
|
||||
print(
|
||||
f"\n[ERROR] HTTP error contacting GitHub ({e.code} {e.reason}) for tag '{tag}'",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
except urllib.error.URLError as e:
|
||||
# Connection or timeout error
|
||||
print(
|
||||
f"\n[ERROR] Network error contacting GitHub: {e.reason}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
# Unexpected exceptions
|
||||
print(
|
||||
f"\n[ERROR] Unexpected error verifying tag '{tag}': {e}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Resolve PyPI package info and GitHub release diffs."
|
||||
)
|
||||
parser.add_argument("package", help="Name of the PyPI package")
|
||||
parser.add_argument(
|
||||
"old_version", help="Current version installed in Home Assistant"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--new-version", help="Target version to bump to (defaults to latest on PyPI)"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not re.match(_NAME_MATCH, args.package):
|
||||
print(f"[ERROR] Invalid package name format: '{args.package}'", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not re.match(_VERSION_MATCH, args.old_version):
|
||||
print(
|
||||
f"[ERROR] Invalid old version format: '{args.old_version}'", file=sys.stderr
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
if args.new_version and not re.match(_VERSION_MATCH, args.new_version):
|
||||
print(
|
||||
f"[ERROR] Invalid target version format: '{args.new_version}'",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
data = get_pypi_data(args.package)
|
||||
info = data.get("info", {})
|
||||
|
||||
# Filter out pre-releases from the releases list to identify the latest stable version
|
||||
stable_versions = []
|
||||
for v in data.get("releases", {}):
|
||||
try:
|
||||
ver = Version(v)
|
||||
if not ver.is_prerelease:
|
||||
stable_versions.append(ver)
|
||||
except Exception:
|
||||
continue
|
||||
latest = str(max(stable_versions)) if stable_versions else info.get("version")
|
||||
|
||||
if latest and not re.match(_VERSION_MATCH, latest):
|
||||
print("[ERROR] Invalid latest version resolved from PyPI.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
target_version = args.new_version or latest
|
||||
if not target_version:
|
||||
print("[ERROR] Could not resolve a target version from PyPI.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
github_repo = find_github_repo(info)
|
||||
|
||||
print("--- RESOLVED DEPENDENCY DETAILS ---")
|
||||
print(f"Package: {args.package}")
|
||||
print(f"Current Version: {args.old_version}")
|
||||
print(f"Latest (PyPI): {latest}")
|
||||
print(f"Target Version: {target_version}")
|
||||
|
||||
if github_repo:
|
||||
print(f"GitHub Repository: {github_repo}")
|
||||
|
||||
# Verify tag formats on GitHub
|
||||
old_tag = check_github_tag(github_repo, args.old_version)
|
||||
new_tag = check_github_tag(github_repo, target_version)
|
||||
|
||||
if not old_tag or not new_tag:
|
||||
missing_tags = []
|
||||
if not old_tag:
|
||||
missing_tags.append(args.old_version)
|
||||
if not new_tag:
|
||||
missing_tags.append(target_version)
|
||||
print(
|
||||
f"\n[ERROR] Could not resolve GitHub release tag for version(s): {', '.join(missing_tags)}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
changelog_url = f"{github_repo}/releases/tag/{new_tag}"
|
||||
compare_url = f"{github_repo}/compare/{old_tag}...{new_tag}"
|
||||
|
||||
print("\n--- PR DOCUMENTATION LINKS ---")
|
||||
print(f"Changelog Link: {changelog_url}")
|
||||
print(f"Comparison Diff Link: {compare_url}")
|
||||
else:
|
||||
print("\n[WARNING] GitHub repository could not be resolved from PyPI metadata.")
|
||||
print("Please resolve the release notes and diff links manually.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: ha-integration-knowledge
|
||||
description: Everything you need to know to build, test and review Home Assistant Integrations. If you're looking at an integration, you must use this as your primary reference.
|
||||
---
|
||||
|
||||
## File Locations
|
||||
- **Integration code**: `./homeassistant/components/<integration_domain>/`
|
||||
- **Integration tests**: `./tests/components/<integration_domain>/`
|
||||
|
||||
## General guidelines
|
||||
|
||||
- When looking for examples, prefer integrations with the platinum or gold quality scale level first.
|
||||
- Polling intervals are NOT user-configurable. Never add scan_interval, update_interval, or polling frequency options to config flows or config entries.
|
||||
- Do NOT allow users to set config entry names in config flows. Names are automatically generated or can be customized later in UI. Exception: helper integrations may allow custom names.
|
||||
- For entity actions and entity services, avoid requesting redundant defensive checks for fields already enforced by Home Assistant validation schemas and entity filters; only request extra guards when values bypass validation or are transformed unsafely.
|
||||
- When validation guarantees a key is present, prefer direct dictionary indexing (`data["key"]`) over `.get("key")` so invalid assumptions fail fast.
|
||||
- Integrations should be thin wrappers. Protocol parsing, device state machines, or other domain logic belong in a separate PyPI library, not in the integration itself. If unsure, ask before inlining.
|
||||
- Integrations should not implement fixes or workarounds for limitations in libraries. Instead, the library should be updated to fix the issue.
|
||||
|
||||
The following platforms have extra guidelines:
|
||||
- **Diagnostics**: [`platform-diagnostics.md`](platform-diagnostics.md) for diagnostic data collection
|
||||
- **Repairs**: [`platform-repairs.md`](platform-repairs.md) for user-actionable repair issues
|
||||
|
||||
## Entity platforms
|
||||
|
||||
- Ensure `async_added_to_hass()` and `async_will_remove_from_hass()` have symmetrical behavior. For example, if a subscription is created in `async_added_to_hass()`, it should be unsubscribed in `async_will_remove_from_hass()`. Also, if something is torn down in `async_will_remove_from_hass()`, it should be set up in `async_added_to_hass()`.
|
||||
- Entity base class (e.g. `SensorEntity`, `TrackerEntity`) provide a stable API for child classes to inherit from. Do not suggest redeclaring or duplicating attributes, properties, or methods the base class already provides, and do not add guards against the parent's behavior changing — rely on the base class instead.
|
||||
|
||||
## Integration Quality Scale
|
||||
|
||||
- When validating the quality scale rules, check them at https://developers.home-assistant.io/docs/core/integration-quality-scale/rules
|
||||
- When implementing or reviewing an integration, always consider the quality scale rules, since they promote best practices.
|
||||
|
||||
Template scale file: `./script/scaffold/templates/integration/integration/quality_scale.yaml`
|
||||
|
||||
### How Rules Apply
|
||||
1. **Check `manifest.json`**: Look for `"quality_scale"` key to determine integration level
|
||||
2. **Bronze Rules**: Always required for any integration with quality scale
|
||||
3. **Higher Tier Rules**: Only apply if integration targets that tier or higher
|
||||
4. **Rule Status**: Check `quality_scale.yaml` in integration folder for:
|
||||
- `done`: Rule implemented
|
||||
- `exempt`: Rule doesn't apply (with reason in comment)
|
||||
- `todo`: Rule needs implementation
|
||||
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
- Tests should avoid interacting or mocking internal integration details. For more info, see https://developers.home-assistant.io/docs/development_testing/#writing-tests-for-integrations
|
||||
@@ -0,0 +1,6 @@
|
||||
# Integration Diagnostics
|
||||
|
||||
Platform exists as `homeassistant/components/<domain>/diagnostics.py`.
|
||||
|
||||
- **Required**: Implement diagnostic data collection
|
||||
- **Security**: Never expose passwords, tokens, or sensitive coordinates
|
||||
@@ -0,0 +1,21 @@
|
||||
# Repairs platform
|
||||
|
||||
Platform exists as `homeassistant/components/<domain>/repairs.py`.
|
||||
|
||||
- **Actionable Issues Required**: All repair issues must be actionable for end users
|
||||
- **Issue Content Requirements**:
|
||||
- Clearly explain what is happening
|
||||
- Provide specific steps users need to take to resolve the issue
|
||||
- Use friendly, helpful language
|
||||
- Include relevant context (device names, error details, etc.)
|
||||
- **String Content Must Include**:
|
||||
- What the problem is
|
||||
- Why it matters
|
||||
- Exact steps to resolve (numbered list when multiple steps)
|
||||
- What to expect after following the steps
|
||||
- **Avoid Vague Instructions**: Don't just say "update firmware" - provide specific steps
|
||||
- **Severity Guidelines**:
|
||||
- `CRITICAL`: Reserved for extreme scenarios only
|
||||
- `ERROR`: Requires immediate user attention
|
||||
- `WARNING`: Indicates future potential breakage
|
||||
- Only create issues for problems users can potentially resolve
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: ha-pr-comment-audit
|
||||
description: Audits the review comment threads on a Home Assistant GitHub pull request, flagging unaddressed comments and requests for clarification. Use when checking whether PR feedback has been handled, either standalone or as part of a full PR review.
|
||||
---
|
||||
|
||||
# Check Home Assistant PR Review Comments
|
||||
|
||||
## Instructions:
|
||||
- Resolve the PR context first. If a PR number is not given, use 'gh pr view' to identify the current branch's PR.
|
||||
- Fetch the review comment threads for the PR (e.g. 'gh api' for review threads/comments).
|
||||
- Flag comments that have not been addressed. If the author has replied but has not implemented the suggestion, still flag it and summarize the reply.
|
||||
- Flag comments for which the author has asked for clarification.
|
||||
- Generate a summary of the flagged comments, including a link for each comment. Don't include comments that have been addressed.
|
||||
|
||||
|
||||
## IMPORTANT:
|
||||
- Only provide feedback in the CONSOLE. DO NOT ACT ON GITHUB.
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: ha-pr-reviewer
|
||||
description: Reviews Home Assistant GitHub pull requests and provides feedback comments. This is the top skill to use for reviewing Pull Requests from GitHub.
|
||||
---
|
||||
|
||||
# Review GitHub Pull Request
|
||||
|
||||
## Instructions:
|
||||
- Use 'gh pr view' to get the PR details and description.
|
||||
- Use 'gh pr diff' to see all the changes in the PR.
|
||||
- Review the changes following the `ha-review` skill. It is VERY IMPORTANT to follow the `ha-review` skill instructions.
|
||||
- Run a subagent in parallel to check the PR review comments following the `ha-pr-comment-audit` skill.
|
||||
|
||||
## IMPORTANT:
|
||||
- Only provide review feedback in the CONSOLE. DO NOT ACT ON GITHUB.
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: ha-review
|
||||
description: Reviews Home Assistant code changes and provides constructive feedback. Should be used when a review is requested to provide a consistent review behavior and output format. This skill can be used for code reviews in general, not just for GitHub pull requests.
|
||||
---
|
||||
|
||||
# Review Code Changes
|
||||
|
||||
## Analyze the code changes for:
|
||||
- Code quality and style consistency
|
||||
- Potential bugs or issues
|
||||
- Performance implications
|
||||
- Security concerns
|
||||
- Test coverage
|
||||
- Documentation updates if needed
|
||||
|
||||
## Verification:
|
||||
- After the review, run parallel subagents for each finding to double-check it.
|
||||
- Spawn up to a maximum of 10 parallel subagents at a time.
|
||||
- Gather the results from the subagents and summarize them in the final review comments.
|
||||
|
||||
## IMPORTANT:
|
||||
- Just review. DO NOT make any changes.
|
||||
- Be constructive and specific in your comments.
|
||||
- Suggest improvements where appropriate.
|
||||
- No need to run tests or linters, just review the code changes.
|
||||
- No need to highlight things that are already good.
|
||||
|
||||
## Output format:
|
||||
- List specific comments for each file/line that needs attention.
|
||||
- In the end, summarize with an overall assessment (approve, request changes, or comment) and bullet point list of changes suggested, if any.
|
||||
- Example output:
|
||||
```
|
||||
Overall assessment: request changes.
|
||||
- [CRITICAL] sensor.py:143 - Memory leak
|
||||
- [PROBLEM] data_processing.py:87 - Inefficient algorithm
|
||||
- [SUGGESTION] test_init.py:45 - Improve x variable name
|
||||
```
|
||||
- Make sure to include the file and line number when possible in the bullet points.
|
||||
Reference in New Issue
Block a user