diff --git a/.github/agents/designer.agent.md b/.github/agents/designer.agent.md index 78a339c..2d2476f 100644 --- a/.github/agents/designer.agent.md +++ b/.github/agents/designer.agent.md @@ -152,5 +152,6 @@ Apply all relevant disciplines — a fullstack integration system needs API cont - `@#docs` — keep design artifacts and related docs aligned with delivered changes - `@#explore` — codebase discovery and mapping - `@#analyse` — impact analysis, tradeoffs, feasibility +- `@#openapi` — OpenAPI 3.1 spec writing and review diff --git a/.github/agents/engineer.agent.md b/.github/agents/engineer.agent.md index 44af4fd..3c5497f 100644 --- a/.github/agents/engineer.agent.md +++ b/.github/agents/engineer.agent.md @@ -144,5 +144,10 @@ Only delegate when workstreams are genuinely independent. - `@#performance` — performance investigation - `@#container` — Dockerfile and docker-compose authoring - `@#cicd` — GitHub Actions CI/CD workflow configuration +- `@#migrate` — database migration review and authoring +- `@#refactor` — structured refactoring without behavior change +- `@#openapi` — OpenAPI 3.1 spec writing and review +- `@#dependency` — dependency health audit +- `@#incident` — incident analysis and post-mortem writing diff --git a/.github/agents/product.agent.md b/.github/agents/product.agent.md index 5afb2a0..3a24aa1 100644 --- a/.github/agents/product.agent.md +++ b/.github/agents/product.agent.md @@ -142,6 +142,7 @@ Before PR merge, consolidate relevant content into baseline docs and remove the - `@#explore` — codebase discovery and mapping (brownfield intake) - `@#analyse` — impact analysis, tradeoffs, feasibility - `@#adr` — architecture decision record writing (if significant decisions) +- `@#onboard` — contributor onboarding guide generation ## gate moments diff --git a/.github/agents/tester.agent.md b/.github/agents/tester.agent.md index 586049c..0f7b2fe 100644 --- a/.github/agents/tester.agent.md +++ b/.github/agents/tester.agent.md @@ -176,5 +176,8 @@ Browser/E2E tests: only if the product scope includes a frontend UI. - `@#explore` — codebase discovery and mapping - `@#analyse` — impact analysis, tradeoffs, feasibility - `@#code-review` — pre-merge review before release +- `@#migrate` — database migration safety review +- `@#dependency` — dependency vulnerability and health audit +- `@#incident` — incident analysis and post-mortem writing diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 8c54e05..b9594c9 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -17,17 +17,31 @@ vstack provides structured skills for backend/microservice development, executab ``` src/vstack/ ← Python package (source of truth) src/vstack/_templates/ -├── skills//template.md ← skill source templates (edit these) +├── skills// +│ ├── template.md ← skill body (edit these) +│ └── config.yaml ├── skills/_partials/ ← shared partial snippets -└── agents// - ├── template.md ← agent instructions body - └── config.yaml ← agent frontmatter fields +├── agents// +│ ├── template.md ← agent instructions body +│ └── config.yaml ← agent frontmatter fields +├── instructions// +│ ├── template.md ← instruction file body +│ └── config.yaml +└── prompts// + ├── template.md ← prompt file body + └── config.yaml docs/ ← architecture.md, design.md, skills.md, workflow.md, roadmap.md, adr/ -.github/ ← generated output (never edit directly) +.github/ ← generated output (never edit directly*) ├── skills//SKILL.md -└── agents/.agent.md +├── agents/.agent.md +├── instructions/.instructions.md +├── prompts/.prompt.md +└── vstack.json ← install manifest (generated) ``` +*Hand-authored exceptions in `.github/`: `copilot-instructions.md`, `CODEOWNERS`, +`pull_request_template.md`, `ISSUE_TEMPLATE/`, `workflows/`. + Generated files are written to `.github/` at install time: ```bash @@ -44,14 +58,16 @@ When vstack is installed inside its own repo (`.github/agents/` and `.github/ski |---|---| | `src/vstack/_templates/agents//config.yaml` or `template.md` | `python3 -m vstack install` | | `src/vstack/_templates/skills//template.md` or `_partials/*.md` | `python3 -m vstack install` | +| `src/vstack/_templates/instructions//template.md` | `python3 -m vstack install` | +| `src/vstack/_templates/prompts//template.md` | `python3 -m vstack install` | -A single command handles both: +A single command handles all artifact types: ```bash python3 -m vstack install ``` -Never edit `.github/agents/` or `.github/skills/` directly — changes will be overwritten. +Never edit `.github/agents/`, `.github/skills/`, `.github/instructions/`, or `.github/prompts/` directly — changes will be overwritten. ## Execution Model diff --git a/.github/skills/dependency/SKILL.md b/.github/skills/dependency/SKILL.md new file mode 100644 index 0000000..2523d65 --- /dev/null +++ b/.github/skills/dependency/SKILL.md @@ -0,0 +1,319 @@ +--- +name: dependency +description: 'Dependency health audit. Covers vulnerability scanning, outdated packages, licence compliance, transitive risk, pinning policy, and supply chain hygiene. Goes beyond the vulnerability gate in `security` — covers upgrade strategy, licence obligations, and long-term dependency health. Use when asked to "audit dependencies", "check for outdated packages", "licence compliance", "pin versions", or "dependency health check".' +license: 'MIT' +compatibility: 'Requires a skills-compatible agent with repository file access and terminal command execution when needed.' +metadata: + owner: vstack + maturity: stable +argument-hint: '[project or package manifest to audit]' +user-invocable: true +disable-model-invocation: false +--- +## Skill Context + +This skill is part of **vstack** — a VS Code-native AI engineering workflow system. + +### AskUserQuestion Format + +When you need clarification, use this exact format — never invent or guess: + +> **Question:** [The specific question] +> **Options:** A) … | B) … | C) … +> **Default if no response:** [What you'll do] + +Never ask more than one question at a time without waiting for the answer. + +# dependency — Dependency Health Audit + +Audit the health, security, and compliance of project dependencies. Covers +vulnerability scanning, outdated packages, licence obligations, transitive +risk, pinning policy, and supply chain hygiene. + +## Scope vs related skills + +- **This skill** — full dependency health: vulnerabilities, freshness, licences, + pinning policy, supply chain +- **`security`** — includes a lightweight vulnerability gate as part of OWASP A06; + for a full dependency audit use this skill instead +- **`verify`** — includes a quick vuln check in the quality gate loop; escalates + to this skill for deeper investigation + +**Golden rule: A dependency is owned code you didn't write. Treat it with the +same scrutiny as your own code.** + +______________________________________________________________________ + +## Step 0: Detect the Stack + +```bash +# Identify package manifests +ls pyproject.toml poetry.lock requirements*.txt \ + package.json package-lock.json yarn.lock pnpm-lock.yaml \ + go.mod go.sum Cargo.toml Cargo.lock \ + pom.xml build.gradle Gemfile Gemfile.lock 2>/dev/null + +# Detect Python version and package manager +cat pyproject.toml 2>/dev/null | grep -E 'requires-python|tool\.poetry' | head -5 +cat .python-version 2>/dev/null +which poetry && poetry --version 2>/dev/null || true +which pip-audit && pip-audit --version 2>/dev/null || true +``` + +Document: + +```text +Stack: [Python | Node | Go | Rust | Java | other] +Manager: [Poetry | pip | npm | yarn | pnpm | cargo | go modules | other] +Manifests: [list of files found] +Lock file: [present | absent — flag if absent] +``` + +______________________________________________________________________ + +## Part 1: Vulnerability Scan + +Run the appropriate scanner for each detected stack: + +```bash +# Python — pip-audit (preferred) or safety +if [ -f pyproject.toml ] || [ -f requirements.txt ]; then + pip-audit 2>/dev/null \ + || safety check --full-report 2>/dev/null \ + || echo "No Python vuln scanner found — install pip-audit: pip install pip-audit" +fi + +# Node +[ -f package.json ] && npm audit --json 2>/dev/null | head -100 + +# Go +if [ -f go.mod ]; then + govulncheck ./... 2>/dev/null \ + || echo "govulncheck not found — install: go install golang.org/x/vuln/cmd/govulncheck@latest" +fi + +# Rust +if [ -f Cargo.toml ]; then + cargo audit 2>/dev/null \ + || echo "cargo-audit not found — install: cargo install cargo-audit" +fi + +# Java (Maven) +[ -f pom.xml ] && mvn dependency-check:check -q 2>/dev/null || true +``` + +Triage findings by severity: + +```text +Vulnerabilities found: + 🔴 CRITICAL / HIGH: [package] [version] — [CVE] — [description] + 🟡 MEDIUM: [package] [version] — [CVE] — [description] + 🟢 LOW / INFO: [count] low-severity findings +``` + +**Remediation rule:** CRITICAL and HIGH must be resolved before release. MEDIUM +should be tracked and resolved within the sprint. LOW may be deferred with +documented rationale. + +______________________________________________________________________ + +## Part 2: Outdated Packages + +```bash +# Python (Poetry) +poetry show --outdated 2>/dev/null | head -40 + +# Python (pip) +pip list --outdated 2>/dev/null | head -40 + +# Node +npm outdated 2>/dev/null | head -40 + +# Go — check go.sum and go.mod for pinned versions +go list -m -u all 2>/dev/null | grep '\[' | head -30 + +# Rust +cargo outdated 2>/dev/null | head -30 +``` + +Classify each outdated package: + +| Package | Current | Latest | Type | Action | +| ------- | ------- | ------ | ----- | ------------------------- | +| `foo` | 1.2.0 | 1.2.5 | patch | update now | +| `bar` | 2.1.0 | 3.0.0 | major | evaluate breaking changes | +| `baz` | 0.9.0 | 0.9.8 | patch | update now | + +**Update priority:** + +- Patch updates: update immediately (no breaking changes expected) +- Minor updates: update soon (check changelog for deprecations) +- Major updates: plan upgrade (read migration guide, test thoroughly) + +______________________________________________________________________ + +## Part 3: Licence Compliance + +Check licence obligations for all direct and transitive dependencies: + +```bash +# Python +if pip-licenses --version >/dev/null 2>&1; then + pip-licenses --format=markdown --with-urls 2>/dev/null | head -60 +elif python3 -m pip_licenses --help >/dev/null 2>&1; then + python3 -m pip_licenses 2>/dev/null | head -60 +else + echo "Install pip-licenses: pip install pip-licenses" +fi + +# Node +npx license-checker --summary 2>/dev/null | head -40 + +# Go +if go-licenses --help >/dev/null 2>&1; then + go-licenses report ./... 2>/dev/null | head -40 +else + echo "Install go-licenses: go install github.com/google/go-licenses@latest" +fi +``` + +Classify licences by risk: + +| Risk | Licences | Requirement | +| ------ | ---------------------------------- | -------------------------------------------------- | +| Low | MIT, BSD-2, BSD-3, Apache-2.0, ISC | Can use freely, attribution in docs | +| Medium | LGPL-2.1, LGPL-3.0 | Dynamic linking OK; static linking requires review | +| High | GPL-2.0, GPL-3.0, AGPL-3.0 | May require open-sourcing your code | +| Review | Commercial, proprietary, unknown | Requires legal review before use | + +Flag any High or Review licences: + +```text +Licence issues: + 🔴 [package] — [licence] — [risk] — [recommendation] +``` + +______________________________________________________________________ + +## Part 4: Pinning Policy + +A healthy dependency policy requires reproducible builds: + +**Check for lock files:** + +- [ ] `poetry.lock` / `package-lock.json` / `yarn.lock` / `Cargo.lock` / `go.sum` exists +- [ ] Lock file is committed to version control +- [ ] Lock file is up to date with the manifest + +**Check for version constraints:** + +```bash +# Python — look for unpinned deps +cat pyproject.toml 2>/dev/null | grep -E '^\s+[a-z]' | grep -v '^#' | head -30 + +# Flag overly loose constraints (e.g. "*", ">=1.0" with no upper bound in prod deps) +``` + +| Pattern | Risk | Recommendation | +| --------------------------- | ------ | -------------------------------------------- | +| `package = "*"` | High | Pin to a compatible range | +| `package = ">=1.0"` | Medium | Add upper bound: `>=1.0,<3.0` | +| `package = "^1.0"` (Poetry) | Low | Acceptable for non-critical deps | +| `package = "1.2.3"` (exact) | Low | Fine for direct deps; brittle for transitive | +| No lock file | High | Add lock file and commit it | + +______________________________________________________________________ + +## Part 5: Transitive Risk + +Identify high-risk transitive (indirect) dependencies: + +```bash +# Python — show full dependency tree +if poetry --version >/dev/null 2>&1; then + poetry show --tree 2>/dev/null | head -60 +else + pip install pipdeptree 2>/dev/null && pipdeptree 2>/dev/null | head -60 +fi + +# Node +npm list --depth=2 2>/dev/null | head -60 + +# Go +go mod graph 2>/dev/null | head -40 +``` + +Flags to look for: + +- [ ] No single dependency with > 20 transitive deps (blast radius risk) +- [ ] No abandoned packages (last release > 2 years, no recent commits) +- [ ] No packages with a single maintainer for critical functionality +- [ ] Core dependencies have active security policies (CVE response time < 30 days) + +```bash +# Check for abandoned packages — look at last release dates +# (manual step: check PyPI / npm registry for each critical dep) +``` + +______________________________________________________________________ + +## Part 6: Supply Chain Hygiene + +```bash +# Python — check if packages are installed from PyPI or custom source +cat pyproject.toml 2>/dev/null | grep -E '\[\[tool\.poetry\.source\]\]' -A 5 + +# Node — check for private registry config +cat .npmrc 2>/dev/null +cat package.json 2>/dev/null | grep -E '"registry"' + +# Check for dependency confusion risk (private package names published on public registry) +# (manual step: search PyPI/npm for any internal package names) +``` + +Check: + +- [ ] All packages sourced from trusted, official registries +- [ ] No `--extra-index-url` pointing to untrusted sources (Python) +- [ ] Private package names are not also available on public registries (confusion attack) +- [ ] `pip install` / `npm install` output reviewed for unexpected packages +- [ ] CI pipeline pins the package manager version itself + +______________________________________________________________________ + +## Output + +```text +Dependency Audit Report +═══════════════════════ + +Stack: [stack + manager] +Scanned: [N direct, N transitive dependencies] + +Vulnerabilities: + 🔴 Critical/High: [N] — [list or "none"] + 🟡 Medium: [N] — [list or "none"] + 🟢 Low: [N] + +Outdated: + Patch updates available: [N packages] + Minor updates available: [N packages] + Major updates available: [N packages] + +Licence issues: + 🔴 High-risk licences: [list or "none"] + 🟡 Review required: [list or "none"] + +Pinning: + Lock file: [present | absent] + Loose pins: [list or "none"] + +Supply chain: + [clean | issues found — details] + +Action items (priority order): + 1. [action] — [package] — [severity] + 2. ... +``` + + diff --git a/.github/skills/incident/SKILL.md b/.github/skills/incident/SKILL.md new file mode 100644 index 0000000..525ca3e --- /dev/null +++ b/.github/skills/incident/SKILL.md @@ -0,0 +1,319 @@ +--- +name: incident +description: 'Incident analysis and post-mortem writing. Guides a structured investigation from timeline reconstruction through root cause identification to a blameless post-mortem document with action items. Use when asked to "write a post-mortem", "incident review", "root cause analysis for this outage", "what went wrong?", or "blameless post-mortem". Produces a docs/postmortems/{date}-{slug}.md.' +license: 'MIT' +compatibility: 'Requires a skills-compatible agent with repository file access and terminal command execution when needed.' +metadata: + owner: vstack + maturity: stable +argument-hint: '[incident or outage to analyse and document]' +user-invocable: true +disable-model-invocation: false +--- +## Skill Context + +This skill is part of **vstack** — a VS Code-native AI engineering workflow system. + +### AskUserQuestion Format + +When you need clarification, use this exact format — never invent or guess: + +> **Question:** [The specific question] +> **Options:** A) … | B) … | C) … +> **Default if no response:** [What you'll do] + +Never ask more than one question at a time without waiting for the answer. + +# incident — Incident Analysis & Post-Mortem + +Guide a structured incident investigation and produce a blameless post-mortem +document. The goal is learning and prevention — not blame. + +## Out of scope + +- Live incident response / on-call triage (this skill is for retrospective analysis) +- Root-cause debugging of code bugs (use `debug`) +- Security audit of vulnerabilities (use `security`) +- Performance benchmarking (use `performance`) + +**Golden rule: Incidents are system failures, not human failures. Every finding +must be framed as a system improvement opportunity, never as individual blame.** + +______________________________________________________________________ + +## Step 0: Gather Incident Context + +Before analysis, collect all available evidence: + +> **Questions to answer:** +> +> - When did the incident start and end? (UTC timestamps) +> - What was the user-visible impact? (errors, latency, data loss, downtime) +> - What services were affected? +> - Who detected it and how? (alert, user report, monitoring) +> - What was done to resolve it? +> - Is there a severity classification? (P0/P1/P2 or SEV1/SEV2/SEV3) + +```bash +# Gather git history around the incident window +git log --oneline --since="YYYY-MM-DD" --until="YYYY-MM-DD" 2>/dev/null | head -30 + +# Check recent deploys +git log --oneline --merges --since="YYYY-MM-DD" 2>/dev/null | head -20 + +# Find relevant config or infra changes +git log --oneline --since="YYYY-MM-DD" -- '*.yaml' '*.yml' '*.toml' '*.env*' 2>/dev/null | head -20 +``` + +Document: + +```text +Incident ID: [INC-NNNN or date-slug] +Severity: [P0 | P1 | P2 | SEV1 | SEV2 | SEV3] +Start: [YYYY-MM-DD HH:MM UTC] +End: [YYYY-MM-DD HH:MM UTC] +Duration: [N hours N minutes] +Detected by: [alert | user report | manual check] +Services: [list of affected services] +Impact: [user-facing description] +``` + +______________________________________________________________________ + +## Step 1: Reconstruct the Timeline + +Build a precise, chronological timeline of events. Include: + +- System events (deploys, config changes, traffic spikes) +- Detection events (alerts fired, pages sent) +- Response actions (who did what, when) +- Resolution events (rollback, fix deployed, service restored) + +```text +Timeline (all times UTC): + +HH:MM — [event description] — [who / what system] +HH:MM — [event description] — [who / what system] +... + +Key markers: + Impact start: HH:MM + Detection: HH:MM (+N min after impact start) + Response start: HH:MM (+N min after detection) + Mitigation: HH:MM (+N min after response) + Full resolution: HH:MM + Total duration: N hours N minutes +``` + +______________________________________________________________________ + +## Step 2: Identify Contributing Factors + +List ALL factors that contributed to the incident — not just the "trigger". +Incidents are never caused by a single thing. Use the 5-Whys technique: + +**5-Whys template:** + +```text +Why did [impact] happen? + Because [immediate cause]. + +Why did [immediate cause] happen? + Because [contributing factor 1]. + +Why did [contributing factor 1] exist? + Because [deeper cause]. + +Why did [deeper cause] exist? + Because [systemic gap]. + +Why did [systemic gap] exist? + Because [root systemic condition]. +``` + +Categorize contributing factors: + +| Category | Factor | +| ------------- | -------------------------------------------------------- | +| Code / logic | [e.g. missing error handling, race condition] | +| Configuration | [e.g. incorrect timeout, missing feature flag] | +| Deployment | [e.g. no canary, missing rollback plan] | +| Monitoring | [e.g. alert threshold too high, missing metric] | +| Process | [e.g. no review for config changes, unclear runbook] | +| External | [e.g. upstream dependency failure, cloud provider issue] | +| Knowledge | [e.g. undocumented behaviour, tribal knowledge gap] | + +______________________________________________________________________ + +## Step 3: Determine Root Cause + +The root cause is the deepest systemic condition that, if addressed, would +prevent this class of incident from recurring. + +**Root cause is NOT:** + +- "Human error" (humans make mistakes — the system must be resilient to them) +- "We forgot to test X" (why was it possible to ship without testing X?) +- The deployment that triggered it (that is the trigger, not the cause) + +```text +Root cause: + [One clear, specific statement of the systemic condition] + +Evidence: + [What evidence supports this conclusion] + +Class of incident: + [Deploy regression | Configuration drift | Dependency failure | + Capacity / traffic | Data corruption | Security breach | Other] +``` + +______________________________________________________________________ + +## Step 4: Assess Impact + +Quantify the impact precisely: + +```text +User impact: + Affected users: [N users | N% of traffic | all users] + Error rate: [N% of requests returned errors] + Latency increase: [p99 increased from Nms to Nms] + Data loss: [none | N records | describe scope] + Feature unavailable:[list features] + +Business impact: + Revenue: [estimated impact if known] + SLA breach: [yes — N minutes over limit | no] + Customer comms: [status page update | direct notification | none] + +Detection gap: + Time to detect: [N minutes] + How detected: [alert | user complaint | manual] + Why not faster: [threshold too high | missing alert | other] +``` + +______________________________________________________________________ + +## Step 5: Write Action Items + +Action items must be: + +- **Specific** — not "improve monitoring" but "add alert on p99 > 500ms for /checkout" +- **Owned** — assigned to a person or team +- **Time-bound** — target date or sprint +- **Categorized** — prevention, detection, or response improvement + +```text +Action items: + +Prevention (stop this from happening again): + [ ] [specific action] — owner: [name/team] — due: [date/sprint] + +Detection (catch it faster next time): + [ ] [specific action] — owner: [name/team] — due: [date/sprint] + +Response (resolve it faster next time): + [ ] [specific action] — owner: [name/team] — due: [date/sprint] + [ ] Write or update runbook for this failure class — owner: [name/team] — due: [date/sprint] + +Process (improve how we handle incidents): + [ ] [specific action] — owner: [name/team] — due: [date/sprint] +``` + +______________________________________________________________________ + +## Step 6: Produce the Post-Mortem Document + +Write the post-mortem to `docs/postmortems/YYYY-MM-DD-.md`: + +```markdown +# Post-Mortem: [Short Title] + +**Date:** YYYY-MM-DD +**Severity:** [P0 | P1 | P2] +**Duration:** N hours N minutes +**Status:** [Draft | In Review | Closed] +**Author(s):** [names] + +--- + +## Summary + +[2–3 sentences: what happened, what was the impact, how was it resolved. +Written for a non-technical audience.] + +## Impact + +| Dimension | Details | +|---|---| +| Duration | N hours N minutes (HH:MM–HH:MM UTC) | +| Users affected | [N users / N% of traffic] | +| Error rate | [N%] | +| SLA breach | [yes / no] | +| Data loss | [none / description] | + +## Timeline + +| Time (UTC) | Event | +|---|---| +| HH:MM | [event] | +| HH:MM | [event] | +| ... | ... | + +## Root Cause + +[One paragraph. Specific, systemic, blameless.] + +## Contributing Factors + +- [factor 1] +- [factor 2] +- [factor 3] + +## What Went Well + +- [thing 1 — e.g. alert fired within 2 minutes] +- [thing 2 — e.g. rollback completed in 4 minutes] +- [thing 3] + +## What Went Poorly + +- [thing 1 — e.g. no runbook for this failure mode] +- [thing 2] + +## Action Items + +| # | Action | Category | Owner | Due | +|---|---|---|---|---| +| 1 | [action] | Prevention | [owner] | [date] | +| 2 | [action] | Detection | [owner] | [date] | +| 3 | [action] | Response | [owner] | [date] | + +## Lessons Learned + +[2–4 sentences summarizing the key takeaways for the team and organization. +What does this incident teach us about our system, processes, or culture?] +``` + +______________________________________________________________________ + +## Output + +```text +Incident Analysis Complete +══════════════════════════ + +Incident: [ID / title] +Severity: [P0 | P1 | P2] +Duration: [N hours N minutes] +Root cause: [one-line summary] + +Contributing factors: [N identified] +Action items: [N total — N prevention, N detection, N response] + +Post-mortem written: docs/postmortems/YYYY-MM-DD-.md +Status: [Draft — ready for team review] +``` + + diff --git a/.github/skills/migrate/SKILL.md b/.github/skills/migrate/SKILL.md new file mode 100644 index 0000000..0484601 --- /dev/null +++ b/.github/skills/migrate/SKILL.md @@ -0,0 +1,331 @@ +--- +name: migrate +description: 'Database migration review and authoring. Covers forwards/backwards compatibility, zero-downtime strategies, rollback plans, data integrity, and index safety. Use when asked to "write a migration", "review this migration", "is this migration safe?", or "zero-downtime schema change". Proactively suggest before any DDL change ships to production.' +license: 'MIT' +compatibility: 'Requires a skills-compatible agent with repository file access and terminal command execution when needed.' +metadata: + owner: vstack + maturity: stable +argument-hint: '[migration file or schema change to review]' +user-invocable: true +disable-model-invocation: false +--- +## Skill Context + +This skill is part of **vstack** — a VS Code-native AI engineering workflow system. + +### AskUserQuestion Format + +When you need clarification, use this exact format — never invent or guess: + +> **Question:** [The specific question] +> **Options:** A) … | B) … | C) … +> **Default if no response:** [What you'll do] + +Never ask more than one question at a time without waiting for the answer. + +```bash +# Detect base branch (main / master / develop / trunk) +BASE=$(gh pr view --json baseRefName -q .baseRefName 2>/dev/null) \ + || BASE=$(git remote show origin 2>/dev/null | grep 'HEAD branch' | awk '{print $NF}') \ + || BASE=$(git branch -r 2>/dev/null | grep -E '/(main|master|develop|trunk)' | head -1 | sed 's|.*origin/||') \ + || BASE="main" +echo "Base branch: $BASE" +``` + +# migrate — Database Migration Review & Authoring + +Review or write database migrations for safety, correctness, and zero-downtime +deployability. No migration ships without a rollback plan. + +## Out of scope + +- ORM model design (use `design`) +- Performance benchmarking of queries (use `performance`) +- Full security audit (use `security`) +- Architecture decisions (use `architecture` + `adr`) + +**Golden rule: Every migration must be reversible or explicitly documented as +irreversible with a data-recovery plan.** + +______________________________________________________________________ + +## Step 0: Understand the Change + +Before reviewing or writing anything, gather context: + +> **Question:** What schema change is needed and why? +> +> - What table(s) / collection(s) are affected? +> - Approximate row count and data size? +> - Is this service actively serving production traffic? +> - What deployment strategy is used (blue/green, rolling, big-bang)? +> **Default:** Assume rolling deployment, production traffic, safety-first. + +```bash +# Find existing migration files +find . -type f \( -name '*.sql' -o -name '*migration*' -o -name '*migrate*' \) \ + --exclude-dir=.venv --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=build \ + 2>/dev/null | sort | tail -20 + +# Show migration files changed in this branch +git diff --stat -- '*.sql' '**migration**' '**migrate**' 2>/dev/null | head -20 + +# Check migration framework in use +[ -f alembic.ini ] && echo "alembic" || true +[ -f flyway.conf ] && echo "flyway" || true +grep -r "migrate\|liquibase\|goose\|dbmate" pyproject.toml package.json go.mod 2>/dev/null | head -5 +``` + +Document: + +```text +Framework: [alembic | flyway | goose | dbmate | raw SQL | other] +Table: [affected table(s)] +Row estimate: [< 1k | 10k | 100k | 1M | 10M+ ] +Deployment: [rolling | blue-green | big-bang | maintenance window] +Direction: [new migration | review existing | both] +``` + +______________________________________________________________________ + +## Step 1: Classify the Migration + +Classify every DDL operation by risk level: + +| Operation | Risk | Notes | +| ---------------------------------------- | --------------- | ---------------------------------- | +| `CREATE TABLE` | Low | Safe at any time | +| `ADD COLUMN` nullable, no default | Low | Safe in rolling deploy | +| `ADD COLUMN` with default (non-volatile) | Medium | May lock on large tables | +| `ADD COLUMN NOT NULL` without default | **High** | Breaks old app version | +| `DROP COLUMN` | **High** | Must remove all references first | +| `RENAME COLUMN` | **High** | Breaks old app version immediately | +| `ALTER COLUMN` type change | **High** | May require data rewrite | +| `CREATE INDEX CONCURRENTLY` | Low | Safe, non-blocking | +| `CREATE INDEX` (without CONCURRENTLY) | **High** | Full table lock | +| `DROP INDEX` | Low | Safe | +| `ADD CONSTRAINT` | **High** | Validates all existing rows | +| `TRUNCATE` / `DROP TABLE` | **Destructive** | Requires explicit confirmation | + +Assign risk to each operation in the migration: + +```text +Operations: + 1. [operation] — [Low | Medium | High | Destructive] + 2. ... +Overall risk: [Low | Medium | High | Destructive] +``` + +______________________________________________________________________ + +## Step 2: Zero-Downtime Analysis + +**Rolling deployments require that the schema be compatible with BOTH the old and +new version of the application simultaneously.** + +Check each operation: + +- [ ] Old app version can read/write with the new schema +- [ ] New app version can read/write with the old schema (before migration runs) +- [ ] No `NOT NULL` columns added without a default or backfill step +- [ ] No column renames (use add → backfill → switch → drop in separate deploys) +- [ ] No type changes that are incompatible with existing data + +**Expand/Contract pattern (required for breaking changes):** + +```text +Phase 1 — Expand: Add new column/table alongside old one +Phase 2 — Migrate: Backfill data; dual-write in application +Phase 3 — Switch: Application reads from new column +Phase 4 — Contract: Drop old column in a separate deploy +``` + +If the migration violates zero-downtime, flag it: + +```text +⚠ ZERO-DOWNTIME VIOLATION + Operation: [operation] + Problem: [what breaks] + Fix: [expand/contract steps or maintenance window required] +``` + +______________________________________________________________________ + +## Step 3: Rollback Plan + +Every migration must have a defined rollback: + +```sql +-- Forward migration +ALTER TABLE orders ADD COLUMN discount_cents INTEGER; + +-- Rollback +ALTER TABLE orders DROP COLUMN discount_cents; +``` + +For destructive operations (DROP, TRUNCATE), rollback is not possible — document this explicitly: + +```text +⚠ IRREVERSIBLE OPERATION + Operation: DROP TABLE legacy_sessions + Pre-condition: Confirm legacy_sessions is unused (grep all references) + Backup required: Yes — take a snapshot before deploying + Recovery: Restore from snapshot (RTO: [estimate]) +``` + +______________________________________________________________________ + +## Step 4: Data Integrity + +Check: + +- [ ] Foreign key constraints are correct and indexed +- [ ] `NOT NULL` columns have sensible defaults for existing rows +- [ ] `UNIQUE` constraints won't fail on existing duplicates +- [ ] `CHECK` constraints won't reject existing data +- [ ] Enum additions are safe (additions OK; removals break existing data) +- [ ] Cascade behavior is intentional (`ON DELETE CASCADE` vs `RESTRICT`) + +```sql +-- Before adding a NOT NULL column: verify no NULLs exist +SELECT COUNT(*) FROM table WHERE column IS NULL; + +-- Before adding UNIQUE constraint: verify no duplicates +SELECT column, COUNT(*) FROM table GROUP BY column HAVING COUNT(*) > 1; + +-- Before adding CHECK constraint: verify no violations +SELECT COUNT(*) FROM table WHERE NOT (constraint_expression); +``` + +______________________________________________________________________ + +## Step 5: Index Safety + +**Never create an index without `CONCURRENTLY` on a live table.** + +```sql +-- BAD: acquires ACCESS EXCLUSIVE lock +CREATE INDEX idx_orders_user_id ON orders(user_id); + +-- GOOD: non-blocking, runs in background +CREATE INDEX CONCURRENTLY idx_orders_user_id ON orders(user_id); +``` + +Caveats for `CONCURRENTLY`: + +- Cannot run inside a transaction block +- Takes longer to build +- May fail if the table is modified heavily during build + +Check: + +- [ ] All new indexes use `CONCURRENTLY` (unless in a migration transaction that can afford a lock) +- [ ] Composite index column order matches query patterns +- [ ] No redundant indexes (subset of existing composite index) +- [ ] Partial indexes considered for filtered queries + +______________________________________________________________________ + +## Step 6: Performance on Large Tables + +For tables with > 100k rows: + +```bash +# Estimate table size +# PostgreSQL: +psql -c "SELECT pg_size_pretty(pg_total_relation_size('table_name'));" 2>/dev/null || true +# MySQL: +# SELECT table_name, ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'Size (MB)' +# FROM information_schema.TABLES WHERE table_schema = DATABASE(); +``` + +For large tables, prefer: + +- **Online DDL** (MySQL 8+, PostgreSQL with `CONCURRENTLY`) +- **Batched backfills** instead of single `UPDATE` statements +- **Maintenance window** for operations that cannot be made non-blocking + +Batched backfill pattern: + +```sql +-- Backfill in batches to avoid long-running locks +DO $$ +DECLARE + batch_size INT := 10000; + offset_val INT := 0; + rows_updated INT; +BEGIN + LOOP + UPDATE table SET new_col = + WHERE id IN ( + SELECT id FROM table WHERE new_col IS NULL LIMIT batch_size + ); + GET DIAGNOSTICS rows_updated = ROW_COUNT; + EXIT WHEN rows_updated = 0; + PERFORM pg_sleep(0.1); -- brief pause between batches + END LOOP; +END $$; +``` + +______________________________________________________________________ + +## Step 7: Testing + +```bash +# Run migration tests +# Framework-specific: +alembic upgrade head && alembic downgrade -1 2>/dev/null || true +flyway migrate && flyway undo 2>/dev/null || true + +# Verify the schema matches models +# (adjust for your ORM) +python -c "from app.models import Base; Base.metadata.create_all(engine)" 2>/dev/null || true +``` + +Check: + +- [ ] Migration applies cleanly on a fresh database +- [ ] Migration applies cleanly on a database with production-representative data +- [ ] Rollback/downgrade works cleanly +- [ ] Application tests pass with the new schema +- [ ] No model/schema drift detected + +______________________________________________________________________ + +## Output + +Produce a structured migration review: + +```text +Migration Review +════════════════ + +File: [migration filename] +Framework: [framework] +Risk level: [Low | Medium | High | Destructive] + +Operations: + 1. [operation] — [risk] — [safe / unsafe for rolling deploy] + 2. ... + +Zero-downtime: [SAFE | UNSAFE — reason] +Rollback: [SQL rollback statement | IRREVERSIBLE — reason] + +Issues found: + 🔴 CRITICAL: [issue + fix] + 🟡 WARNING: [issue + recommendation] + 🟢 OK: [what is correct] + +Recommended migration: + [corrected or approved SQL] + +Pre-deploy checklist: + [ ] Backup taken + [ ] Migration tested on staging with production data volume + [ ] Rollback script validated + [ ] Feature flag in place (if needed for expand/contract) + [ ] Monitoring alert set for migration duration +``` + + diff --git a/.github/skills/onboard/SKILL.md b/.github/skills/onboard/SKILL.md new file mode 100644 index 0000000..5745ea2 --- /dev/null +++ b/.github/skills/onboard/SKILL.md @@ -0,0 +1,315 @@ +--- +name: onboard +description: 'Generate a contributor onboarding guide for a repository. Covers project purpose, architecture overview, local dev setup, test commands, contribution workflow, and first-task suggestions. Use when asked to "write an onboarding guide", "create a contributor guide", "help new devs get started", or "document how to contribute". Produces or updates CONTRIBUTING.md and supplements README with a dev setup section.' +license: 'MIT' +compatibility: 'Requires a skills-compatible agent with repository file access and terminal command execution when needed.' +metadata: + owner: vstack + maturity: stable +argument-hint: '[repository or service to document]' +user-invocable: true +disable-model-invocation: false +--- +## Skill Context + +This skill is part of **vstack** — a VS Code-native AI engineering workflow system. + +### AskUserQuestion Format + +When you need clarification, use this exact format — never invent or guess: + +> **Question:** [The specific question] +> **Options:** A) … | B) … | C) … +> **Default if no response:** [What you'll do] + +Never ask more than one question at a time without waiting for the answer. + +# onboard — Contributor Onboarding Guide + +Generate or update onboarding documentation so a new contributor can go from +clone to first PR with zero tribal knowledge required. + +## Out of scope + +- API design documentation (use `design` or `openapi`) +- Architecture documentation (use `architecture`) +- Release notes (use `release-notes`) +- Test execution (use `verify`) + +**Golden rule: If a new contributor needs to ask a question that isn't answered +by the docs, that is a documentation gap — not a knowledge problem.** + +______________________________________________________________________ + +## Step 0: Audit Existing Documentation + +```bash +# Find existing contributor docs +ls -la README.md CONTRIBUTING.md DEVELOPMENT.md docs/ 2>/dev/null + +# Check for setup scripts +ls -la Makefile scripts/ bin/ 2>/dev/null + +# Check what tooling is defined +cat Makefile 2>/dev/null | grep -E '^[a-z].*:' | head -20 + +# Detect tech stack +ls pyproject.toml package.json go.mod Cargo.toml pom.xml 2>/dev/null +cat pyproject.toml 2>/dev/null | head -30 +cat package.json 2>/dev/null | grep -E '"scripts"' -A 20 | head -25 +``` + +Document gaps: + +```text +Existing docs: [list of files found] +Missing: [what's absent — setup steps, test commands, etc.] +Tech stack: [Python | Node | Go | other] +Build tool: [Poetry | npm | make | other] +``` + +______________________________________________________________________ + +## Step 1: Understand the Project + +Read the codebase to extract onboarding-relevant facts: + +```bash +# Project purpose +head -50 README.md 2>/dev/null + +# Project structure +find . -maxdepth 3 -type d \ + | grep -v node_modules | grep -v .venv | grep -v __pycache__ \ + | grep -v .git | grep -v dist | grep -v build \ + | sort | head -40 + +# Dependencies and Python version +cat pyproject.toml 2>/dev/null | grep -E 'python|requires|dependencies' | head -20 +cat .python-version 2>/dev/null +cat .nvmrc 2>/dev/null +cat .node-version 2>/dev/null + +# CI configuration — what does CI run? +cat .github/workflows/*.yml 2>/dev/null | grep -E 'run:|uses:' | head -30 +``` + +______________________________________________________________________ + +## Step 2: Verify the Setup Steps Work + +Before documenting setup steps, verify they actually work: + +```bash +# Attempt setup from scratch perspective +# (Do not actually destroy the current environment — read and verify commands) + +# Check prerequisites are documented +which python3 || which python && python --version +which poetry && poetry --version +which node && node --version +which make && make --version +``` + +For each setup step, confirm: + +- [ ] The command exists and works +- [ ] Dependencies are version-pinned or constrained +- [ ] Environment variables are documented (use `.env.example` if present) +- [ ] The setup completes in < 5 minutes on a fresh machine + +______________________________________________________________________ + +## Step 3: Extract Test Commands + +```bash +# Find all test commands +cat Makefile 2>/dev/null | grep -E 'test|lint|check|verify' | head -20 +cat pyproject.toml 2>/dev/null | grep -E '\[tool\.' -A 5 | head -40 +cat package.json 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); [print(k,':',v) for k,v in d.get('scripts',{}).items()]" 2>/dev/null +``` + +Document the minimal set a contributor needs: + +```text +Quick check (before every commit): [command] +Full test suite: [command] +Lint only: [command] +Type check only: [command] +Single test: [command pattern] +``` + +______________________________________________________________________ + +## Step 4: Identify "Good First Issues" + +```bash +# Find TODOs and FIXMEs +grep -r -n "TODO\|FIXME\|HACK\|good.first" \ + --include='*.py' --include='*.ts' --include='*.go' --include='*.md' \ + --exclude-dir=node_modules --exclude-dir=.venv --exclude-dir=.git \ + . 2>/dev/null | head -20 + +# Check GitHub issues if available +# (manual step — list any open "good first issue" labels) +``` + +______________________________________________________________________ + +## Step 5: Write the Onboarding Guide + +Produce or update `CONTRIBUTING.md` with the following sections: + +______________________________________________________________________ + +````markdown +# Contributing to [Project Name] + +Welcome. This guide gets you from zero to a merged PR. + +## Prerequisites + +| Tool | Version | Install | +|------|---------|---------| +| Python | >= X.Y | [pyenv](https://github.com/pyenv/pyenv) | +| Poetry | >= X.Y | `pip install poetry` | +| make | any | system package manager | + +## Setup + +```bash +git clone https://github.com/org/repo +cd repo +[setup command — e.g. make bootstrap or poetry install] +``` + +Verify setup: + +```bash +[verify command — e.g. make check or poetry run pytest] +``` + +Expected output: `[N tests passed]` + +## Project Structure + +``` +[directory tree — top 2 levels with brief descriptions] +``` + +## Development Workflow + +### Making changes + +1. Create a branch: `git checkout -b [type]/[short-description]` + - `feat/` — new feature + - `fix/` — bug fix + - `chore/` — maintenance +1. Make your change +1. Run checks: `[check command]` +1. Commit: `git commit -m "[type]: [description]"` +1. Push and open a PR + +### Before every commit + +```bash +[pre-commit or check command] +``` + +This runs: [lint, type check, tests — describe what is checked] + +## Testing + +```bash +# Run all tests +[full test command] + +# Run a single test file +[single test command] + +# Run with coverage +[coverage command] +``` + +Tests live in `tests/`. Mirror the source structure: `src/foo/bar.py` → `tests/foo/test_bar.py`. + +## Environment Variables + +Copy `.env.example` to `.env` and fill in values: + +```bash +cp .env.example .env +``` + +| Variable | Required | Description | +| ---------- | -------- | ------------- | +| `VAR_NAME` | Yes | [description] | + +## Architecture Overview + +\[2–4 sentences describing the main components and how they interact. +Link to docs/architecture/architecture.md for details.\] + +## Good First Issues + +\[List 3–5 concrete starting points: + +- A TODO in the code +- A missing test +- A documentation gap +- A small enhancement\] + +## Getting Help + +[Slack channel / GitHub Discussions / email — whatever is appropriate] + +```` + +______________________________________________________________________ + +______________________________________________________________________ + +## Step 6: Supplement README (if needed) + +If README lacks a dev setup section, add a minimal one linking to CONTRIBUTING.md: + +````markdown +## development + +See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, testing, and contribution guidelines. + +Quick start: + +```bash +[one-liner setup command] +[one-liner test command] +``` + +```` + +______________________________________________________________________ + +## Output + +```text +Onboarding Guide Summary +════════════════════════ + +Files produced/updated: + ✅ CONTRIBUTING.md — [new | updated] + ✅ README.md — [updated dev section | no change needed] + +Coverage: + [ ] Prerequisites documented + [ ] Setup steps verified + [ ] Test commands documented + [ ] Environment variables documented + [ ] Project structure explained + [ ] Contribution workflow explained + [ ] Good first issues listed + +Gaps remaining (if any): + [anything that could not be determined automatically] +``` + + diff --git a/.github/skills/openapi/SKILL.md b/.github/skills/openapi/SKILL.md new file mode 100644 index 0000000..907c55b --- /dev/null +++ b/.github/skills/openapi/SKILL.md @@ -0,0 +1,408 @@ +--- +name: openapi +description: 'Write and review OpenAPI 3.1 specifications. Covers resource naming, HTTP method semantics, status codes, error conventions, pagination, versioning, security schemes, and schema validation. Use when asked to "write an OpenAPI spec", "review this API spec", "add an endpoint to the spec", or "validate this OpenAPI file".' +license: 'MIT' +compatibility: 'Requires a skills-compatible agent with repository file access and terminal command execution when needed.' +metadata: + owner: vstack + maturity: stable +argument-hint: '[API or spec file to write or review]' +user-invocable: true +disable-model-invocation: false +--- +## Skill Context + +This skill is part of **vstack** — a VS Code-native AI engineering workflow system. + +### AskUserQuestion Format + +When you need clarification, use this exact format — never invent or guess: + +> **Question:** [The specific question] +> **Options:** A) … | B) … | C) … +> **Default if no response:** [What you'll do] + +Never ask more than one question at a time without waiting for the answer. + +# openapi — OpenAPI 3.1 Specification + +Write or review OpenAPI 3.1 specifications. The spec is the contract — it must +be precise, complete, and implementable without ambiguity. + +## Out of scope + +- Service architecture decisions (use `architecture`) +- Full API design from scratch (use `design`) +- Security audit of the implementation (use `security`) +- Contract compliance of the implementation (use `verify`) + +**Golden rule: The spec is the source of truth. Code must conform to the spec, +not the other way around.** + +______________________________________________________________________ + +## Step 0: Understand the Task + +> **Question:** What needs to be done? +> +> **Options:** +> A) Write a new OpenAPI spec from scratch +> B) Review and improve an existing spec +> C) Add endpoints to an existing spec +> D) Validate spec for correctness and completeness +> **Default:** B — review existing spec + +```bash +# Locate existing spec files +find . -name 'openapi*.yaml' -o -name 'openapi*.json' \ + -o -name 'swagger*.yaml' -o -name 'swagger*.json' \ + 2>/dev/null | grep -v node_modules | grep -v .venv | head -10 + +# Validate spec if tooling is available +[ -f openapi.yaml ] && npx @redocly/cli lint openapi.yaml 2>/dev/null || true +[ -f openapi.yaml ] && npx swagger-cli validate openapi.yaml 2>/dev/null || true +``` + +______________________________________________________________________ + +## Part 1: Spec Structure + +Every OpenAPI 3.1 spec must have: + +```yaml +openapi: "3.1.0" + +info: + title: Service Name API + version: "1.0.0" + description: | + One paragraph describing the service purpose. + contact: + name: Team Name + email: team@example.com + +servers: + - url: https://api.example.com/v1 + description: Production + - url: https://api.staging.example.com/v1 + description: Staging + +tags: + - name: users + description: User management + +paths: {} + +components: + schemas: {} + securitySchemes: {} + +security: [] +``` + +Check: + +- [ ] `openapi` field is `"3.1.0"` (not 3.0.x or 2.x) +- [ ] `info.version` follows semver +- [ ] At least one server defined +- [ ] Tags defined at root level and used consistently on operations +- [ ] `components` section exists for reusable schemas + +______________________________________________________________________ + +## Part 2: Resource & Path Design + +### Naming conventions + +| Correct | Incorrect | Rule | +| -------------------------- | -------------------- | --------------------------- | +| `/users` | `/getUsers`, `/user` | Plural nouns, no verbs | +| `/users/{userId}` | `/users/{id}` | Descriptive path parameters | +| `/users/{userId}/orders` | `/userOrders` | Nested for ownership | +| `/orders/{orderId}/cancel` | `/cancelOrder/{id}` | Sub-resource for actions | + +### HTTP method semantics + +| Method | Semantics | Idempotent | Body | +| -------- | ------------------------------- | ---------- | ---- | +| `GET` | Read, no side effects | Yes | No | +| `POST` | Create or non-idempotent action | No | Yes | +| `PUT` | Full replace | Yes | Yes | +| `PATCH` | Partial update | No | Yes | +| `DELETE` | Remove | Yes | No | + +Check each path: + +- [ ] Path uses plural noun, no verbs +- [ ] Path parameters are descriptive (`userId` not `id`) +- [ ] HTTP method matches semantics above +- [ ] `GET` operations have no request body +- [ ] `DELETE` operations return `204 No Content` or `200` with body, never `201` +- [ ] Every operation has a unique `operationId` (camelCase, e.g. `listUsers`, `createOrder`) +- [ ] Every operation has a `summary` (short title, ≤ 80 chars) and `tags` + +______________________________________________________________________ + +## Part 3: Status Codes + +Use exactly these status codes — no others unless justified: + +| Code | Meaning | When to use | +| --------------------------- | ----------------------------- | ------------------------------------- | +| `200 OK` | Success with body | `GET`, `PUT`, `PATCH` success | +| `201 Created` | Resource created | `POST` creating a resource | +| `202 Accepted` | Accepted for async processing | Background jobs | +| `204 No Content` | Success, no body | `DELETE`, `POST` with no return | +| `400 Bad Request` | Validation error | Invalid input | +| `401 Unauthorized` | Not authenticated | Missing/invalid token | +| `403 Forbidden` | Not authorized | Valid token, insufficient permissions | +| `404 Not Found` | Resource missing | ID doesn't exist | +| `409 Conflict` | State conflict | Duplicate create, optimistic lock | +| `422 Unprocessable Entity` | Semantic validation | Business rule violation | +| `429 Too Many Requests` | Rate limited | Include `Retry-After` header | +| `500 Internal Server Error` | Unexpected error | Never expose internals | + +Check: + +- [ ] Every operation documents all realistic status codes +- [ ] `200` vs `201` vs `204` used correctly +- [ ] `401` and `403` are distinct and documented +- [ ] `500` is documented but never includes stack traces + +______________________________________________________________________ + +## Part 4: Error Response Schema + +Every error response must use a consistent schema: + +```yaml +components: + schemas: + Error: + type: object + required: [code, message] + properties: + code: + type: string + description: Machine-readable error code + example: "VALIDATION_ERROR" + message: + type: string + description: Human-readable description + example: "email must be a valid email address" + details: + type: array + description: Field-level validation errors + items: + type: object + required: [field, message] + properties: + field: + type: string + example: "email" + message: + type: string + example: "must be a valid email address" + request_id: + type: string + description: Correlation ID for tracing + example: "req_01HZ..." +``` + +Check: + +- [ ] All `4xx` and `5xx` responses reference `$ref: '#/components/schemas/Error'` +- [ ] Error schema has a machine-readable `code` field +- [ ] `request_id` for correlation is present +- [ ] Field-level errors included for `400`/`422` + +______________________________________________________________________ + +## Part 5: Pagination + +Standard cursor-based pagination (preferred for large datasets): + +```yaml +# Query parameters +parameters: + - name: cursor + in: query + schema: + type: string + description: Opaque cursor from previous response + - name: limit + in: query + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + +# Response envelope +components: + schemas: + PaginatedUsers: + type: object + required: [data, pagination] + properties: + data: + type: array + items: + $ref: '#/components/schemas/User' + pagination: + type: object + required: [has_more] + properties: + has_more: + type: boolean + next_cursor: + type: string + nullable: true + total: + type: integer + description: Total count (expensive — omit if not needed) +``` + +Check: + +- [ ] Pagination is cursor-based (not offset for large collections) +- [ ] `limit` has a maximum and default +- [ ] Response includes `has_more` and `next_cursor` +- [ ] `total` is optional (expensive query — only include if needed) + +______________________________________________________________________ + +## Part 6: Schema Quality + +For each schema in `components/schemas`: + +```yaml +components: + schemas: + User: + type: object + required: [id, email, created_at] # ← explicit required list + properties: + id: + type: string + format: uuid + readOnly: true # ← readOnly for server-generated fields + example: "01HZ..." + email: + type: string + format: email + example: "user@example.com" + name: + type: string + minLength: 1 + maxLength: 255 + example: "Alice" + created_at: + type: string + format: date-time + readOnly: true + example: "2026-01-01T00:00:00Z" +``` + +Check: + +- [ ] All schemas have a `required` list (no implicit optionals) +- [ ] `id`, `created_at`, `updated_at` are `readOnly: true` +- [ ] All string fields have `minLength`/`maxLength` where appropriate +- [ ] All fields have an `example` +- [ ] `format` used for `uuid`, `email`, `date-time`, `uri` +- [ ] No `type: object` without properties (use `additionalProperties` explicitly) +- [ ] No circular `$ref` without a nullable break + +______________________________________________________________________ + +## Part 7: Security Schemes + +```yaml +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + apiKey: + type: apiKey + in: header + name: X-API-Key + +security: + - bearerAuth: [] # Global default +``` + +Override at operation level where needed: + +```yaml +paths: + /health: + get: + security: [] # Public endpoint — no auth required +``` + +Check: + +- [ ] Security scheme is defined in `components.securitySchemes` +- [ ] Global `security` set at root level +- [ ] Public endpoints explicitly override with `security: []` +- [ ] OAuth2 scopes are defined if using OAuth +- [ ] No API keys in query parameters (use headers) + +______________________________________________________________________ + +## Part 8: Versioning + +URI versioning is the recommended approach: + +```yaml +servers: + - url: https://api.example.com/v1 +``` + +Check: + +- [ ] Version in server URL (`/v1`, `/v2`) +- [ ] No minor versions in URL (`/v1.1` is wrong — use headers for minor) +- [ ] Deprecated operations tagged with `deprecated: true` +- [ ] Deprecated operations have a `x-sunset` date + +```yaml +/users/{userId}: + get: + deprecated: true + x-sunset: "2027-01-01" + description: "Deprecated. Use /v2/users/{userId} instead." +``` + +______________________________________________________________________ + +## Output + +Produce a review report or the corrected spec: + +**Review report format:** + +```text +OpenAPI Spec Review +═══════════════════ + +Spec: [filename] +Version: [openapi version] +Paths: [count] +Schemas: [count] + +Issues: + 🔴 CRITICAL: [issue] — [path/operation] + 🟡 WARNING: [issue] — [path/operation] + 🟢 INFO: [suggestion] + +Summary: [N critical, N warnings, N info] +``` + +**If writing or correcting the spec:** produce the complete corrected YAML, using +`$ref` for all reusable schemas, and validate it passes linting. + + diff --git a/.github/skills/refactor/SKILL.md b/.github/skills/refactor/SKILL.md new file mode 100644 index 0000000..81569bf --- /dev/null +++ b/.github/skills/refactor/SKILL.md @@ -0,0 +1,379 @@ +--- +name: refactor +description: 'Structured refactoring for backend services, APIs, and libraries. Identifies code smells, plans incremental changes, executes without altering observable behavior, and verifies correctness. Use when asked to "refactor this", "clean up this module", "reduce duplication", or "improve structure without changing behavior". Never changes behavior — if behavior must change, stop and use the engineering role.' +license: 'MIT' +compatibility: 'Requires a skills-compatible agent with repository file access and terminal command execution when needed.' +metadata: + owner: vstack + maturity: stable +argument-hint: '[module, file, or area to refactor]' +user-invocable: true +disable-model-invocation: false +--- +## Skill Context + +This skill is part of **vstack** — a VS Code-native AI engineering workflow system. + +### AskUserQuestion Format + +When you need clarification, use this exact format — never invent or guess: + +> **Question:** [The specific question] +> **Options:** A) … | B) … | C) … +> **Default if no response:** [What you'll do] + +Never ask more than one question at a time without waiting for the answer. + +```bash +# Detect base branch (main / master / develop / trunk) +BASE=$(gh pr view --json baseRefName -q .baseRefName 2>/dev/null) \ + || BASE=$(git remote show origin 2>/dev/null | grep 'HEAD branch' | awk '{print $NF}') \ + || BASE=$(git branch -r 2>/dev/null | grep -E '/(main|master|develop|trunk)' | head -1 | sed 's|.*origin/||') \ + || BASE="main" +echo "Base branch: $BASE" +``` + +# refactor — Structured Refactoring + +Improve the internal structure of code without changing its observable behavior. +Refactoring is not rewriting. Every step must leave tests green. + +## Out of scope + +- Fixing bugs (use `debug`) +- Adding features (engineering role) +- Performance optimizations that change behavior (use `performance`) +- Architecture redesign (use `architecture`) +- Security fixes (use `security`) + +**Golden rule: If all tests pass before and after each step, the refactor is +correct. If behavior changes, stop — that is a feature or bug fix, not a refactor.** + +______________________________________________________________________ + +## Step 0: Define the Scope + +> **Question:** What needs refactoring and why? +> +> - Which file(s), module(s), or area(s)? +> - What is the motivation? (duplication, complexity, naming, coupling, size) +> - What must NOT change? (public API, behavior, performance characteristics) +> **Default:** Identify smells in the specified area and propose a plan. + +```bash +# Understand the current state +git log --oneline -10 +git diff --stat 2>/dev/null | head -20 + +# Find the files to refactor +# Read the area before touching anything +``` + +Document: + +```text +Scope: [file(s) or module(s)] +Motivation: [duplication | complexity | naming | coupling | size | other] +Constraints: [what must not change] +``` + +______________________________________________________________________ + +## Step 1: Establish a Baseline + +**Never start refactoring without a green test baseline.** + +```bash +# Detect test runner and run tests +if [ -f package.json ]; then + if grep -q '"vitest"' package.json 2>/dev/null; then + npx vitest run + elif grep -q '"jest"' package.json 2>/dev/null; then + npx jest + elif grep -q '"bun"' package.json 2>/dev/null; then + bun test + else + npm test + fi +elif [ -f pyproject.toml ] || [ -f setup.py ]; then + python -m pytest -v +elif [ -f go.mod ]; then + go test ./... +elif [ -f Cargo.toml ]; then + cargo test +else + echo "No recognized test framework detected." +fi +``` + +If tests are red before you start: + +```text +⛔ STOP: Tests are failing before the refactor begins. + Fix failing tests first, or confirm with the user that the failures are + pre-existing and unrelated to this refactor scope. +``` + +Record the baseline: + +```text +Baseline: + Tests: [N passed, N failed, N skipped] + Coverage: [N%] + Lint: [clean | N warnings] +``` + +______________________________________________________________________ + +## Step 2: Identify Code Smells + +Scan the target area for common smells: + +```bash +# Long files +wc -l $(find . -name '*.py' -o -name '*.ts' -o -name '*.go' \ + 2>/dev/null | grep -v node_modules | grep -v .venv) 2>/dev/null | sort -rn | head -20 + +# Duplicated patterns +grep -r -n "TODO\|FIXME\|HACK\|XXX" \ + --include='*.py' --include='*.ts' --include='*.go' \ + --exclude-dir=node_modules --exclude-dir=.venv --exclude-dir=.git \ + . 2>/dev/null | head -20 +``` + +| Smell | Description | Refactoring | +| ----------------------- | ------------------------------------- | --------------------------- | +| Long function | > 30 lines, multiple responsibilities | Extract function | +| Long file | > 300 lines | Extract module | +| Duplicate code | Same logic in 2+ places | Extract shared function | +| Magic numbers/strings | Unnamed literals | Named constant | +| Deep nesting | > 3 levels | Early return / guard clause | +| Long parameter list | > 4 parameters | Parameter object | +| Inappropriate naming | Misleading or vague names | Rename | +| Dead code | Unused functions/variables | Remove | +| Comment explaining code | Code needs a comment to be understood | Rewrite the code | +| Mutable global state | Module-level mutable variables | Encapsulate | + +Produce a prioritized smell list: + +```text +Smells found: + P1 (high impact): [smell] — [location] + P2 (medium): [smell] — [location] + P3 (low): [smell] — [location] +``` + +______________________________________________________________________ + +## Step 3: Plan the Refactoring + +Break the refactoring into small, independent steps. Each step must: + +1. Change exactly one thing +1. Leave tests green +1. Be reviewable in isolation + +```text +Refactoring plan: + Step 1: [specific change] — [file] — [smell addressed] + Step 2: [specific change] — [file] — [smell addressed] + Step 3: ... + +Estimated scope: [N files, N functions] +Risk: [Low | Medium — reason] +``` + +**Stop and confirm with user if:** + +- The plan requires changing a public API or exported interface +- The plan requires changing database schema +- More than 10 files are affected + +______________________________________________________________________ + +## Step 4: Execute — One Step at a Time + +For each planned step: + +1. Make the change +1. Run tests immediately +1. Confirm green before moving to the next step + +```bash +# Detect test runner and run tests +if [ -f package.json ]; then + if grep -q '"vitest"' package.json 2>/dev/null; then + npx vitest run + elif grep -q '"jest"' package.json 2>/dev/null; then + npx jest + elif grep -q '"bun"' package.json 2>/dev/null; then + bun test + else + npm test + fi +elif [ -f pyproject.toml ] || [ -f setup.py ]; then + python -m pytest -v +elif [ -f go.mod ]; then + go test ./... +elif [ -f Cargo.toml ]; then + cargo test +else + echo "No recognized test framework detected." +fi +``` + +### Common refactoring patterns + +**Extract function (Python):** + +```python +# Before +def process_order(order): + # validate + if not order.get("id"): + raise ValueError("missing id") + if order.get("amount", 0) <= 0: + raise ValueError("amount must be positive") + # process + ... + +# After +def _validate_order(order: dict) -> None: + if not order.get("id"): + raise ValueError("missing id") + if order.get("amount", 0) <= 0: + raise ValueError("amount must be positive") + +def process_order(order: dict) -> None: + _validate_order(order) + ... +``` + +**Guard clause (replace nested conditionals):** + +```python +# Before +def process(item): + if item is not None: + if item.active: + if item.value > 0: + return item.value * 2 + return None + +# After +def process(item): + if item is None: + return None + if not item.active: + return None + if item.value <= 0: + return None + return item.value * 2 +``` + +**Named constant:** + +```python +# Before +if status_code == 429: + time.sleep(60) + +# After +HTTP_TOO_MANY_REQUESTS = 429 +RATE_LIMIT_BACKOFF_SECONDS = 60 + +if status_code == HTTP_TOO_MANY_REQUESTS: + time.sleep(RATE_LIMIT_BACKOFF_SECONDS) +``` + +**Parameter object:** + +```python +# Before +def create_user(name, email, role, department, manager_id, start_date): + ... + +# After +@dataclass +class CreateUserRequest: + name: str + email: str + role: str + department: str + manager_id: str + start_date: date + +def create_user(request: CreateUserRequest) -> User: + ... +``` + +______________________________________________________________________ + +## Step 5: Verify + +After all steps are complete, run the full verification suite: + +```bash +# Detect test runner and run tests +if [ -f package.json ]; then + if grep -q '"vitest"' package.json 2>/dev/null; then + npx vitest run + elif grep -q '"jest"' package.json 2>/dev/null; then + npx jest + elif grep -q '"bun"' package.json 2>/dev/null; then + bun test + else + npm test + fi +elif [ -f pyproject.toml ] || [ -f setup.py ]; then + python -m pytest -v +elif [ -f go.mod ]; then + go test ./... +elif [ -f Cargo.toml ]; then + cargo test +else + echo "No recognized test framework detected." +fi +``` + +Check: + +- [ ] All tests still pass (same count as baseline) +- [ ] Coverage has not dropped +- [ ] No new lint warnings introduced +- [ ] No public interfaces changed (unless explicitly in scope) +- [ ] No behavior changes (verify with diff) + +```bash +# Review what changed +git diff --stat +git diff +``` + +______________________________________________________________________ + +## Output + +```text +Refactoring Summary +═══════════════════ + +Scope: [file(s) / module(s)] +Smells: [N identified] +Steps: [N completed] + +Changes made: + ✅ [Step 1 description] — [file] + ✅ [Step 2 description] — [file] + ... + +Result: + Tests: [N passed — same as baseline] + Coverage: [N% — unchanged or improved] + Lint: [clean] + +Public API changed: No +Behavior changed: No +``` + + diff --git a/.github/vstack.json b/.github/vstack.json index 947c705..9dc7425 100644 --- a/.github/vstack.json +++ b/.github/vstack.json @@ -1,6 +1,6 @@ { - "vstack_version": "0.0.0", - "installed_at": "2026-04-16T23:34:10.546914+00:00", + "vstack_version": "0.0.0.post3.dev0+df3fe6e", + "installed_at": "2026-04-19T19:50:07.239266+00:00", "artifacts": { "skills": [ { @@ -43,6 +43,11 @@ "file": "skills/debug/SKILL.md", "version": "1.0.1" }, + { + "name": "dependency", + "file": "skills/dependency/SKILL.md", + "version": "1.0.0" + }, { "name": "design", "file": "skills/design/SKILL.md", @@ -63,11 +68,31 @@ "file": "skills/guardrails/SKILL.md", "version": "1.0.1" }, + { + "name": "incident", + "file": "skills/incident/SKILL.md", + "version": "1.0.0" + }, { "name": "inspect", "file": "skills/inspect/SKILL.md", "version": "1.0.1" }, + { + "name": "migrate", + "file": "skills/migrate/SKILL.md", + "version": "1.0.0" + }, + { + "name": "onboard", + "file": "skills/onboard/SKILL.md", + "version": "1.0.0" + }, + { + "name": "openapi", + "file": "skills/openapi/SKILL.md", + "version": "1.0.0" + }, { "name": "performance", "file": "skills/performance/SKILL.md", @@ -78,6 +103,11 @@ "file": "skills/pr/SKILL.md", "version": "1.0.1" }, + { + "name": "refactor", + "file": "skills/refactor/SKILL.md", + "version": "1.0.0" + }, { "name": "release-notes", "file": "skills/release-notes/SKILL.md", diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a13c091..d8fb74c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -8,14 +8,14 @@ repos: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.4 + rev: v0.15.11 hooks: - id: ruff args: [--fix] - id: ruff-format - repo: https://github.com/executablebooks/mdformat - rev: 0.7.22 + rev: 1.0.0 hooks: - id: mdformat additional_dependencies: @@ -24,7 +24,7 @@ repos: exclude: ^\.github/ - repo: https://github.com/DavidAnson/markdownlint-cli2 - rev: v0.18.1 + rev: v0.22.0 hooks: - id: markdownlint-cli2 args: diff --git a/CHANGELOG.md b/CHANGELOG.md index a902763..c6f4f77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,58 @@ # Changelog -## [Unreleased] +## [Unreleased] — 1.0.4 -- No unreleased changes yet. +Skill expansion and documentation alignment update. + +### Added in 1.0.4 + +- Six new skills: `migrate`, `openapi`, `refactor`, `onboard`, `dependency`, `incident`. + - `migrate` — database migration review: zero-downtime analysis, expand/contract, rollback plans, index safety, batched backfills. + - `openapi` — OpenAPI 3.1 spec writing and review: resource naming, HTTP semantics, status codes, error conventions, pagination, security schemes, versioning. + - `refactor` — structured refactoring without behavior change: smell identification, incremental plan, step-by-step execution with test verification at each step. + - `onboard` — contributor onboarding guide generation: prerequisites, setup, test commands, env vars, architecture overview, good first issues → `CONTRIBUTING.md`. + - `dependency` — full dependency health audit: vulnerability scanning, outdated packages, licence compliance, transitive risk, pinning policy, supply chain hygiene. + - `incident` — incident analysis and blameless post-mortem writing: timeline reconstruction, 5-Whys root cause, contributing factors matrix, action items → `docs/postmortems/YYYY-MM-DD-*.md`. + +### Fixed in 1.0.4 + +- `refactor` skill: removed outer ```` ```bash ```` fences wrapping `{{RUN_TESTS}}` partial (which already includes its own fence). +- `onboard` skill: fixed nested fence issues in step 5 CONTRIBUTING.md example and step 6 README snippet. + +### Changed in 1.0.4 + +- `engineer`, `designer`, `tester`, `product` agent templates updated with skill references for all new skills. +- `docs/design/skills.md` updated with full skill table including all new skills and their primary roles. +- `README.md` role–skill table updated to reflect new primary skills per role. +- `README.md` project structure diagram updated to include `instructions/` and `prompts/` template directories and the correct `docs/` subdirectory layout. +- `.github/copilot-instructions.md` updated: system structure diagram now includes all four template artifact types (`skills`, `agents`, `instructions`, `prompts`); hand-authored `.github/` exceptions listed explicitly; install table extended with `instructions` and `prompts` rows. + +## 1.0.3 — 2026-04-19 + +Community health and release workflow update. + +### Added in 1.0.3 + +- `CODEOWNERS` file. +- GitHub issue templates: `bug_report.yml`, `feature_request.yml`, `config.yml`. +- Pull request template (`.github/pull_request_template.md`). +- `CODE_OF_CONDUCT.md`, `CONTRIBUTING.md`, `SECURITY.md` community health files. + +### Changed in 1.0.3 + +- Release workflow title format updated to `Release vX.Y.Z (YYYY-MM-DD)`. + +______________________________________________________________________ + +## 1.0.2 — 2026-04-17 + +Release workflow fix. + +### Fixed in 1.0.2 + +- Release workflow: corrected Poetry setup order and opted in to Node 24 to resolve runner deprecation warnings. + +______________________________________________________________________ ## 1.0.1 — 2026-04-17 diff --git a/README.md b/README.md index bb66dd5..fe89c11 100644 --- a/README.md +++ b/README.md @@ -69,14 +69,14 @@ You can also ask a role to use a specific skill: ### 4. Available roles and their primary skills -| Role | Invocation | Primary skills | -| --------- | ------------ | ------------------------------------- | -| product | `@product` | vision, requirements, docs | -| architect | `@architect` | architecture, adr | -| designer | `@designer` | design, consult, docs | -| engineer | `@engineer` | code-review, debug, performance, docs | -| tester | `@tester` | verify, inspect, security, docs | -| release | `@release` | release-notes, pr, docs | +| Role | Invocation | Primary skills | +| --------- | ------------ | ------------------------------------------------------- | +| product | `@product` | vision, requirements, onboard, docs | +| architect | `@architect` | architecture, adr | +| designer | `@designer` | design, openapi, consult, docs | +| engineer | `@engineer` | code-review, debug, refactor, migrate, dependency, docs | +| tester | `@tester` | verify, inspect, security, incident, dependency, docs | +| release | `@release` | release-notes, pr, docs | Full skill index: [docs/design/skills.md](docs/design/skills.md) @@ -182,11 +182,12 @@ make tox # pytest on py311, py312, py313, py314 make tox-all # all tox envs: tests + lint + type ``` -**Editing skills:** source of truth is `src/vstack/_templates/skills//{config.yaml,template.md}`. Never edit generated files. +**Editing templates:** source of truth is always `src/vstack/_templates///{config.yaml,template.md}`. Never edit generated files in `.github/`. ```bash -vim src/vstack/_templates/skills/verify/config.yaml vim src/vstack/_templates/skills/verify/template.md +vim src/vstack/_templates/agents/engineer/template.md +vim src/vstack/_templates/instructions/python/template.md poetry run vstack validate poetry run pytest ``` @@ -206,18 +207,27 @@ vstack/ │ ├── prompts/ ← prompt artifact config + generator │ ├── cli/ ← commands, parser, constants │ └── _templates/ -│ ├── skills//config.yaml ← skill frontmatter fields -│ ├── skills//template.md ← skill instructions body -│ ├── skills/_partials/ ← shared partials -│ └── agents// -│ ├── template.md ← agent instructions body -│ └── config.yaml ← agent frontmatter fields +│ ├── skills//config.yaml ← skill frontmatter fields +│ ├── skills//template.md ← skill instructions body +│ ├── skills/_partials/ ← shared partials +│ ├── agents//config.yaml ← agent frontmatter fields +│ ├── agents//template.md ← agent instructions body +│ ├── instructions//config.yaml ← instruction frontmatter +│ ├── instructions//template.md ← instruction body +│ ├── prompts//config.yaml ← prompt frontmatter +│ └── prompts//template.md ← prompt body ├── docs/ -│ ├── architecture.md ← system structure -│ ├── roadmap.md ← milestones -│ ├── skills.md ← full skill index -│ ├── workflow.md ← execution model -│ └── adr/ ← architecture decision records +│ ├── architecture/ +│ │ ├── architecture.md ← system structure +│ │ └── adr/ ← architecture decision records +│ ├── design/ +│ │ ├── design.md ← generator and builder internals +│ │ ├── skills.md ← full skill index +│ │ └── workflow.md ← execution model +│ └── product/ +│ ├── requirements.md +│ ├── roadmap.md ← milestones +│ └── vision.md ├── tests/ │ └── vstack/ └── Makefile ← generic local dev tasks diff --git a/docs/design/skills.md b/docs/design/skills.md index 9015891..27d62f3 100644 --- a/docs/design/skills.md +++ b/docs/design/skills.md @@ -59,6 +59,12 @@ ______________________________________________________________________ | `pr` | Commit, push, and open a pull request from the current branch to main. | release | commit + PR | | `docs` | Post-release documentation alignment for README/API docs/migrations and related artifacts (no CHANGELOG ownership). | product, architect, designer, engineer, tester, release | updated docs artifacts | | `guardrails` | Activate safety guardrails for the session. Requires explicit confirmation before any destructive action. | — | (mode activation) | +| `migrate` | Database migration review and authoring. Forwards/backwards compatibility, zero-downtime strategies, rollback plans, data integrity, index safety. | engineer, tester | reviewed/corrected migration SQL | +| `openapi` | Write and review OpenAPI 3.1 specifications. Resource naming, HTTP semantics, status codes, error conventions, pagination, security schemes. | designer, engineer | `openapi.yaml` | +| `refactor` | Structured refactoring without behavior change. Identify smells, plan incremental steps, execute, verify correctness. | engineer | refactored code + green tests | +| `onboard` | Generate a contributor onboarding guide. Prerequisites, setup, tests, env vars, architecture overview, good first issues. | product | `CONTRIBUTING.md` + README dev section | +| `dependency` | Dependency health audit. Vulnerability scanning, outdated packages, licence compliance, transitive risk, pinning policy, supply chain hygiene. | engineer, tester | dependency audit report | +| `incident` | Incident analysis and blameless post-mortem writing. Timeline reconstruction, 5-Whys root cause, contributing factors, action items. | tester, engineer | `docs/postmortems/YYYY-MM-DD-*.md` | ______________________________________________________________________ diff --git a/poetry.lock b/poetry.lock index 214bd8c..046f3d2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -169,26 +169,26 @@ files = [ [[package]] name = "filelock" -version = "3.28.0" +version = "3.29.0" description = "A platform independent file lock." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "filelock-3.28.0-py3-none-any.whl", hash = "sha256:de9af6712788e7171df1b28b15eba2446c69721433fa427a9bee07b17820a9db"}, - {file = "filelock-3.28.0.tar.gz", hash = "sha256:4ed1010aae813c4ee8d9c660e4792475ee60c4a0ba76073ceaf862bd317e3ca6"}, + {file = "filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258"}, + {file = "filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90"}, ] [[package]] name = "identify" -version = "2.6.18" +version = "2.6.19" description = "File identification library for Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737"}, - {file = "identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd"}, + {file = "identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a"}, + {file = "identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842"}, ] [package.extras] @@ -208,157 +208,163 @@ files = [ [[package]] name = "librt" -version = "0.8.1" +version = "0.9.0" description = "Mypyc runtime library" optional = false python-versions = ">=3.9" groups = ["dev"] markers = "platform_python_implementation != \"PyPy\"" files = [ - {file = "librt-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81fd938344fecb9373ba1b155968c8a329491d2ce38e7ddb76f30ffb938f12dc"}, - {file = "librt-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5db05697c82b3a2ec53f6e72b2ed373132b0c2e05135f0696784e97d7f5d48e7"}, - {file = "librt-0.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d56bc4011975f7460bea7b33e1ff425d2f1adf419935ff6707273c77f8a4ada6"}, - {file = "librt-0.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdc0f588ff4b663ea96c26d2a230c525c6fc62b28314edaaaca8ed5af931ad0"}, - {file = "librt-0.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c2b54ff6717a7a563b72627990bec60d8029df17df423f0ed37d56a17a176b"}, - {file = "librt-0.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f1125e6bbf2f1657d9a2f3ccc4a2c9b0c8b176965bb565dd4d86be67eddb4b6"}, - {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f4bb453f408137d7581be309b2fbc6868a80e7ef60c88e689078ee3a296ae71"}, - {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c336d61d2fe74a3195edc1646d53ff1cddd3a9600b09fa6ab75e5514ba4862a7"}, - {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eb5656019db7c4deacf0c1a55a898c5bb8f989be904597fcb5232a2f4828fa05"}, - {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c25d9e338d5bed46c1632f851babf3d13c78f49a225462017cf5e11e845c5891"}, - {file = "librt-0.8.1-cp310-cp310-win32.whl", hash = "sha256:aaab0e307e344cb28d800957ef3ec16605146ef0e59e059a60a176d19543d1b7"}, - {file = "librt-0.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:56e04c14b696300d47b3bc5f1d10a00e86ae978886d0cee14e5714fafb5df5d2"}, - {file = "librt-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:681dc2451d6d846794a828c16c22dc452d924e9f700a485b7ecb887a30aad1fd"}, - {file = "librt-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3b4350b13cc0e6f5bec8fa7caf29a8fb8cdc051a3bae45cfbfd7ce64f009965"}, - {file = "librt-0.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ac1e7817fd0ed3d14fd7c5df91daed84c48e4c2a11ee99c0547f9f62fdae13da"}, - {file = "librt-0.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:747328be0c5b7075cde86a0e09d7a9196029800ba75a1689332348e998fb85c0"}, - {file = "librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e"}, - {file = "librt-0.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d480de377f5b687b6b1bc0c0407426da556e2a757633cc7e4d2e1a057aa688f3"}, - {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d0ee06b5b5291f609ddb37b9750985b27bc567791bc87c76a569b3feed8481ac"}, - {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e2c6f77b9ad48ce5603b83b7da9ee3e36b3ab425353f695cba13200c5d96596"}, - {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:439352ba9373f11cb8e1933da194dcc6206daf779ff8df0ed69c5e39113e6a99"}, - {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:82210adabbc331dbb65d7868b105185464ef13f56f7f76688565ad79f648b0fe"}, - {file = "librt-0.8.1-cp311-cp311-win32.whl", hash = "sha256:52c224e14614b750c0a6d97368e16804a98c684657c7518752c356834fff83bb"}, - {file = "librt-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b"}, - {file = "librt-0.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:f7cdf7f26c2286ffb02e46d7bac56c94655540b26347673bea15fa52a6af17e9"}, - {file = "librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a"}, - {file = "librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9"}, - {file = "librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb"}, - {file = "librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d"}, - {file = "librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7"}, - {file = "librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440"}, - {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9"}, - {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972"}, - {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921"}, - {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0"}, - {file = "librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a"}, - {file = "librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444"}, - {file = "librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d"}, - {file = "librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35"}, - {file = "librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583"}, - {file = "librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c"}, - {file = "librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04"}, - {file = "librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363"}, - {file = "librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0"}, - {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012"}, - {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb"}, - {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b"}, - {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d"}, - {file = "librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a"}, - {file = "librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79"}, - {file = "librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0"}, - {file = "librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f"}, - {file = "librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c"}, - {file = "librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc"}, - {file = "librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c"}, - {file = "librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3"}, - {file = "librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14"}, - {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7"}, - {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6"}, - {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071"}, - {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78"}, - {file = "librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023"}, - {file = "librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730"}, - {file = "librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3"}, - {file = "librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1"}, - {file = "librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee"}, - {file = "librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7"}, - {file = "librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040"}, - {file = "librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e"}, - {file = "librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732"}, - {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624"}, - {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4"}, - {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382"}, - {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994"}, - {file = "librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a"}, - {file = "librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4"}, - {file = "librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61"}, - {file = "librt-0.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3dff3d3ca8db20e783b1bc7de49c0a2ab0b8387f31236d6a026597d07fcd68ac"}, - {file = "librt-0.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08eec3a1fc435f0d09c87b6bf1ec798986a3544f446b864e4099633a56fcd9ed"}, - {file = "librt-0.8.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e3f0a41487fd5fad7e760b9e8a90e251e27c2816fbc2cff36a22a0e6bcbbd9dd"}, - {file = "librt-0.8.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bacdb58d9939d95cc557b4dbaa86527c9db2ac1ed76a18bc8d26f6dc8647d851"}, - {file = "librt-0.8.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d7ab1f01aa753188605b09a51faa44a3327400b00b8cce424c71910fc0a128"}, - {file = "librt-0.8.1-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4998009e7cb9e896569f4be7004f09d0ed70d386fa99d42b6d363f6d200501ac"}, - {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2cc68eeeef5e906839c7bb0815748b5b0a974ec27125beefc0f942715785b551"}, - {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0bf69d79a23f4f40b8673a947a234baeeb133b5078b483b7297c5916539cf5d5"}, - {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:22b46eabd76c1986ee7d231b0765ad387d7673bbd996aa0d0d054b38ac65d8f6"}, - {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:237796479f4d0637d6b9cbcb926ff424a97735e68ade6facf402df4ec93375ed"}, - {file = "librt-0.8.1-cp39-cp39-win32.whl", hash = "sha256:4beb04b8c66c6ae62f8c1e0b2f097c1ebad9295c929a8d5286c05eae7c2fc7dc"}, - {file = "librt-0.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:64548cde61b692dc0dc379f4b5f59a2f582c2ebe7890d09c1ae3b9e66fa015b7"}, - {file = "librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73"}, + {file = "librt-0.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f8e12706dcb8ff6b3ed57514a19e45c49ad00bcd423e87b2b2e4b5f64578443"}, + {file = "librt-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4e3dda8345307fd7306db0ed0cb109a63a2c85ba780eb9dc2d09b2049a931f9c"}, + {file = "librt-0.9.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:de7dac64e3eb832ffc7b840eb8f52f76420cde1b845be51b2a0f6b870890645e"}, + {file = "librt-0.9.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22a904cbdb678f7cb348c90d543d3c52f581663d687992fee47fd566dcbf5285"}, + {file = "librt-0.9.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:224b9727eb8bc188bc3bcf29d969dba0cd61b01d9bac80c41575520cc4baabb2"}, + {file = "librt-0.9.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e94cbc6ad9a6aeea46d775cbb11f361022f778a9cc8cc90af653d3a594b057ce"}, + {file = "librt-0.9.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7bc30ad339f4e1a01d4917d645e522a0bc0030644d8973f6346397c93ba1503f"}, + {file = "librt-0.9.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:56d65b583cf43b8cf4c8fbe1e1da20fa3076cc32a1149a141507af1062718236"}, + {file = "librt-0.9.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0a1be03168b2691ba61927e299b352a6315189199ca18a57b733f86cb3cc8d38"}, + {file = "librt-0.9.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:63c12efcd160e1d14da11af0c46c0217473e1e0d2ae1acbccc83f561ea4c2a7b"}, + {file = "librt-0.9.0-cp310-cp310-win32.whl", hash = "sha256:e9002e98dcb1c0a66723592520decd86238ddcef168b37ff6cfb559200b4b774"}, + {file = "librt-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:9fcb461fbf70654a52a7cc670e606f04449e2374c199b1825f754e16dacfedd8"}, + {file = "librt-0.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90904fac73c478f4b83f4ed96c99c8208b75e6f9a8a1910548f69a00f1eaa671"}, + {file = "librt-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:789fff71757facc0738e8d89e3b84e4f0251c1c975e85e81b152cdaca927cc2d"}, + {file = "librt-0.9.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1bf465d1e5b0a27713862441f6467b5ab76385f4ecf8f1f3a44f8aa3c695b4b6"}, + {file = "librt-0.9.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f819e0c6413e259a17a7c0d49f97f405abadd3c2a316a3b46c6440b7dbbedbb1"}, + {file = "librt-0.9.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0785c2fb4a81e1aece366aa3e2e039f4a4d7d21aaaded5227d7f3c703427882"}, + {file = "librt-0.9.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:80b25c7b570a86c03b5da69e665809deb39265476e8e21d96a9328f9762f9990"}, + {file = "librt-0.9.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d4d16b608a1c43d7e33142099a75cd93af482dadce0bf82421e91cad077157f4"}, + {file = "librt-0.9.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:194fc1a32e1e21fe809d38b5faea66cc65eaa00217c8901fbdb99866938adbdb"}, + {file = "librt-0.9.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8c6bc1384d9738781cfd41d09ad7f6e8af13cfea2c75ece6bd6d2566cdea2076"}, + {file = "librt-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15cb151e52a044f06e54ac7f7b47adbfc89b5c8e2b63e1175a9d587c43e8942a"}, + {file = "librt-0.9.0-cp311-cp311-win32.whl", hash = "sha256:f100bfe2acf8a3689af9d0cc660d89f17286c9c795f9f18f7b62dd1a6b247ae6"}, + {file = "librt-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:0b73e4266307e51c95e09c0750b7ec383c561d2e97d58e473f6f6a209952fbb8"}, + {file = "librt-0.9.0-cp311-cp311-win_arm64.whl", hash = "sha256:bc5518873822d2faa8ebdd2c1a4d7c8ef47b01a058495ab7924cb65bdbf5fc9a"}, + {file = "librt-0.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b3e3bc363f71bda1639a4ee593cb78f7fbfeacc73411ec0d4c92f00730010a4"}, + {file = "librt-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a09c2f5869649101738653a9b7ab70cf045a1105ac66cbb8f4055e61df78f2d"}, + {file = "librt-0.9.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5ca8e133d799c948db2ab1afc081c333a825b5540475164726dcbf73537e5c2f"}, + {file = "librt-0.9.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:603138ee838ee1583f1b960b62d5d0007845c5c423feb68e44648b1359014e27"}, + {file = "librt-0.9.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4003f70c56a5addd6aa0897f200dd59afd3bf7bcd5b3cce46dd21f925743bc2"}, + {file = "librt-0.9.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:78042f6facfd98ecb25e9829c7e37cce23363d9d7c83bc5f72702c5059eb082b"}, + {file = "librt-0.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a361c9434a64d70a7dbb771d1de302c0cc9f13c0bffe1cf7e642152814b35265"}, + {file = "librt-0.9.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:dd2c7e082b0b92e1baa4da28163a808672485617bc855cc22a2fd06978fa9084"}, + {file = "librt-0.9.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7e6274fd33fc5b2a14d41c9119629d3ff395849d8bcbc80cf637d9e8d2034da8"}, + {file = "librt-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5093043afb226ecfa1400120d1ebd4442b4f99977783e4f4f7248879009b227f"}, + {file = "librt-0.9.0-cp312-cp312-win32.whl", hash = "sha256:9edcc35d1cae9fd5320171b1a838c7da8a5c968af31e82ecc3dff30b4be0957f"}, + {file = "librt-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc2917258e131ae5f958a4d872e07555b51cb7466a43433218061c74ef33745"}, + {file = "librt-0.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:90e6d5420fc8a300518d4d2288154ff45005e920425c22cbbfe8330f3f754bd9"}, + {file = "librt-0.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29b68cd9714531672db62cc54f6e8ff981900f824d13fa0e00749189e13778e"}, + {file = "librt-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d5c8a5929ac325729f6119802070b561f4db793dffc45e9ac750992a4ed4d22"}, + {file = "librt-0.9.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:756775d25ec8345b837ab52effee3ad2f3b2dfd6bbee3e3f029c517bd5d8f05a"}, + {file = "librt-0.9.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b8f5d00b49818f4e2b1667db994488b045835e0ac16fe2f924f3871bd2b8ac5"}, + {file = "librt-0.9.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c81aef782380f0f13ead670aae01825eb653b44b046aa0e5ebbb79f76ed4aa11"}, + {file = "librt-0.9.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66b58fed90a545328e80d575467244de3741e088c1af928f0b489ebec3ef3858"}, + {file = "librt-0.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e78fb7419e07d98c2af4b8567b72b3eaf8cb05caad642e9963465569c8b2d87e"}, + {file = "librt-0.9.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c3786f0f4490a5cd87f1ed6cefae833ad6b1060d52044ce0434a2e85893afd0"}, + {file = "librt-0.9.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8494cfc61e03542f2d381e71804990b3931175a29b9278fdb4a5459948778dc2"}, + {file = "librt-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:07cf11f769831186eeac424376e6189f20ace4f7263e2134bdb9757340d84d4d"}, + {file = "librt-0.9.0-cp313-cp313-win32.whl", hash = "sha256:850d6d03177e52700af605fd60db7f37dcb89782049a149674d1a9649c2138fd"}, + {file = "librt-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:a5af136bfba820d592f86c67affcef9b3ff4d4360ac3255e341e964489b48519"}, + {file = "librt-0.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:4c4d0440a3a8e31d962340c3e1cc3fc9ee7febd34c8d8f770d06adb947779ea5"}, + {file = "librt-0.9.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:3f05d145df35dca5056a8bc3838e940efebd893a54b3e19b2dda39ceaa299bcb"}, + {file = "librt-0.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1c587494461ebd42229d0f1739f3aa34237dd9980623ecf1be8d3bcba79f4499"}, + {file = "librt-0.9.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0a2040f801406b93657a70b72fa12311063a319fee72ce98e1524da7200171f"}, + {file = "librt-0.9.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f38bc489037eca88d6ebefc9c4d41a4e07c8e8b4de5188a9e6d290273ad7ebb1"}, + {file = "librt-0.9.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3fd278f5e6bf7c75ccd6d12344eb686cc020712683363b66f46ac79d37c799f"}, + {file = "librt-0.9.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fcbdf2a9ca24e87bbebb47f1fe34e531ef06f104f98c9ccfc953a3f3344c567a"}, + {file = "librt-0.9.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e306d956cfa027fe041585f02a1602c32bfa6bb8ebea4899d373383295a6c62f"}, + {file = "librt-0.9.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:465814ab157986acb9dfa5ccd7df944be5eefc0d08d31ec6e8d88bc71251d845"}, + {file = "librt-0.9.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:703f4ae36d6240bfe24f542bac784c7e4194ec49c3ba5a994d02891649e2d85b"}, + {file = "librt-0.9.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3be322a15ee5e70b93b7a59cfd074614f22cc8c9ff18bd27f474e79137ea8d3b"}, + {file = "librt-0.9.0-cp314-cp314-win32.whl", hash = "sha256:b8da9f8035bb417770b1e1610526d87ad4fc58a2804dc4d79c53f6d2cf5a6eb9"}, + {file = "librt-0.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:b8bd70d5d816566a580d193326912f4a76ec2d28a97dc4cd4cc831c0af8e330e"}, + {file = "librt-0.9.0-cp314-cp314-win_arm64.whl", hash = "sha256:fc5758e2b7a56532dc33e3c544d78cbaa9ecf0a0f2a2da2df882c1d6b99a317f"}, + {file = "librt-0.9.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f24b90b0e0c8cc9491fb1693ae91fe17cb7963153a1946395acdbdd5818429a4"}, + {file = "librt-0.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3fe56e80badb66fdcde06bef81bbaa5bfcf6fbd7aefb86222d9e369c38c6b228"}, + {file = "librt-0.9.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:527b5b820b47a09e09829051452bb0d1dd2122261254e2a6f674d12f1d793d54"}, + {file = "librt-0.9.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d429bdd4ac0ab17c8e4a8af0ed2a7440b16eba474909ab357131018fe8c7e71"}, + {file = "librt-0.9.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7202bdcac47d3a708271c4304a474a8605a4a9a4a709e954bf2d3241140aa938"}, + {file = "librt-0.9.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0d620e74897f8c2613b3c4e2e9c1e422eb46d2ddd07df540784d44117836af3"}, + {file = "librt-0.9.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d69fc39e627908f4c03297d5a88d9284b73f4d90b424461e32e8c2485e21c283"}, + {file = "librt-0.9.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c2640e23d2b7c98796f123ffd95cf2022c7777aa8a4a3b98b36c570d37e85eee"}, + {file = "librt-0.9.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:451daa98463b7695b0a30aa56bf637831ea559e7b8101ac2ef6382e8eb15e29c"}, + {file = "librt-0.9.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:928bd06eca2c2bbf4349e5b817f837509b0604342e65a502de1d50a7570afd15"}, + {file = "librt-0.9.0-cp314-cp314t-win32.whl", hash = "sha256:a9c63e04d003bc0fb6a03b348018b9a3002f98268200e22cc80f146beac5dc40"}, + {file = "librt-0.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f162af66a2ed3f7d1d161a82ca584efd15acd9c1cff190a373458c32f7d42118"}, + {file = "librt-0.9.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a4b25c6c25cac5d0d9d6d6da855195b254e0021e513e0249f0e3b444dc6e0e61"}, + {file = "librt-0.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5112c2fb7c2eefefaeaf5c97fec81343ef44ee86a30dcfaa8223822fba6467b4"}, + {file = "librt-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a81eea9b999b985e4bacc650c4312805ea7008fd5e45e1bf221310176a7bcb3a"}, + {file = "librt-0.9.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eea1b54943475f51698f85fa230c65ccac769f1e603b981be060ac5763d90927"}, + {file = "librt-0.9.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:81107843ed1836874b46b310f9b1816abcb89912af627868522461c3b7333c0f"}, + {file = "librt-0.9.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa95738a68cedd3a6f5492feddc513e2e166b50602958139e47bbdd82da0f5a7"}, + {file = "librt-0.9.0-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6788207daa0c19955d2b668f3294a368d19f67d9b5f274553fd073c1260cbb9f"}, + {file = "librt-0.9.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f48c963a76d71b9d7927eb817b543d0dccd52ab6648b99d37bd54f4cd475d856"}, + {file = "librt-0.9.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:42ff8a962554c350d4a83cf47d9b7b78b0e6ff7943e87df7cdfc97c07f3c016f"}, + {file = "librt-0.9.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:657f8ba7b9eaaa82759a104137aed2a3ef7bc46ccfd43e0d89b04005b3e0a4cc"}, + {file = "librt-0.9.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d03fa4fd277a7974c1978c92c374c57f44edeee163d147b477b143446ad1bf6"}, + {file = "librt-0.9.0-cp39-cp39-win32.whl", hash = "sha256:d9da80e5b04acce03ced8ba6479a71c2a2edf535c2acc0d09c80d2f80f3bad15"}, + {file = "librt-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:54d412e47c21b85865676ed0724e37a89e9593c2eee1e7367adf85bfad56ffb1"}, + {file = "librt-0.9.0.tar.gz", hash = "sha256:a0951822531e7aee6e0dfb556b30d5ee36bbe234faf60c20a16c01be3530869d"}, ] [[package]] name = "mypy" -version = "1.19.1" +version = "1.20.1" description = "Optional static typing for Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"}, - {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"}, - {file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"}, - {file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"}, - {file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"}, - {file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"}, - {file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"}, - {file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"}, - {file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"}, - {file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"}, - {file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"}, - {file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"}, - {file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"}, - {file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"}, - {file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"}, - {file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"}, - {file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"}, - {file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"}, - {file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"}, - {file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"}, - {file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"}, - {file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"}, - {file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"}, - {file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"}, - {file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"}, - {file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"}, - {file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"}, - {file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"}, - {file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"}, - {file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"}, - {file = "mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3"}, - {file = "mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a"}, - {file = "mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67"}, - {file = "mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e"}, - {file = "mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376"}, - {file = "mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24"}, - {file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"}, - {file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"}, + {file = "mypy-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3ba5d1e712ada9c3b6223dcbc5a31dac334ed62991e5caa17bcf5a4ddc349af0"}, + {file = "mypy-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e731284c117b0987fb1e6c5013a56f33e7faa1fce594066ab83876183ce1c66"}, + {file = "mypy-1.20.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8e945b872a05f4fbefabe2249c0b07b6b194e5e11a86ebee9edf855de09806c"}, + {file = "mypy-1.20.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fc88acef0dc9b15246502b418980478c1bfc9702057a0e1e7598d01a7af8937"}, + {file = "mypy-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:14911a115c73608f155f648b978c5055d16ff974e6b1b5512d7fedf4fa8b15c6"}, + {file = "mypy-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:76d9b4c992cca3331d9793ef197ae360ea44953cf35beb2526e95b9e074f2866"}, + {file = "mypy-1.20.1-cp310-cp310-win_arm64.whl", hash = "sha256:b408722f80be44845da555671a5ef3a0c63f51ca5752b0c20e992dc9c0fbd3cd"}, + {file = "mypy-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c01eb9bac2c6a962d00f9d23421cd2913840e65bba365167d057bd0b4171a92e"}, + {file = "mypy-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55d12ddbd8a9cac5b276878bd534fa39fff5bf543dc6ae18f25d30c8d7d27fca"}, + {file = "mypy-1.20.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0aa322c1468b6cdfc927a44ce130f79bb44bcd34eb4a009eb9f96571fd80955"}, + {file = "mypy-1.20.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f8bc95899cf676b6e2285779a08a998cc3a7b26f1026752df9d2741df3c79e8"}, + {file = "mypy-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:47c2b90191a870a04041e910277494b0d92f0711be9e524d45c074fe60c00b65"}, + {file = "mypy-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:9857dc8d2ec1a392ffbda518075beb00ac58859979c79f9e6bdcb7277082c2f2"}, + {file = "mypy-1.20.1-cp311-cp311-win_arm64.whl", hash = "sha256:09d8df92bb25b6065ab91b178da843dda67b33eb819321679a6e98a907ce0e10"}, + {file = "mypy-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:36ee2b9c6599c230fea89bbd79f401f9f9f8e9fcf0c777827789b19b7da90f51"}, + {file = "mypy-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fba3fb0968a7b48806b0c90f38d39296f10766885a94c83bd21399de1e14eb28"}, + {file = "mypy-1.20.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef1415a637cd3627d6304dfbeddbadd21079dafc2a8a753c477ce4fc0c2af54f"}, + {file = "mypy-1.20.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef3461b1ad5cd446e540016e90b5984657edda39f982f4cc45ca317b628f5a37"}, + {file = "mypy-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:542dd63c9e1339b6092eb25bd515f3a32a1453aee8c9521d2ddb17dacd840237"}, + {file = "mypy-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:1d55c7cd8ca22e31f93af2a01160a9e95465b5878de23dba7e48116052f20a8d"}, + {file = "mypy-1.20.1-cp312-cp312-win_arm64.whl", hash = "sha256:f5b84a79070586e0d353ee07b719d9d0a4aa7c8ee90c0ea97747e98cbe193019"}, + {file = "mypy-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f3886c03e40afefd327bd70b3f634b39ea82e87f314edaa4d0cce4b927ddcc1"}, + {file = "mypy-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e860eb3904f9764e83bafd70c8250bdffdc7dde6b82f486e8156348bf7ceb184"}, + {file = "mypy-1.20.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4b5aac6e785719da51a84f5d09e9e843d473170a9045b1ea7ea1af86225df4b"}, + {file = "mypy-1.20.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f37b6cd0fe2ad3a20f05ace48ca3523fc52ff86940e34937b439613b6854472e"}, + {file = "mypy-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4bbb0f6b54ce7cc350ef4a770650d15fa70edd99ad5267e227133eda9c94218"}, + {file = "mypy-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:c3dc20f8ec76eecd77148cdd2f1542ed496e51e185713bf488a414f862deb8f2"}, + {file = "mypy-1.20.1-cp313-cp313-win_arm64.whl", hash = "sha256:a9d62bbac5d6d46718e2b0330b25e6264463ed832722b8f7d4440ff1be3ca895"}, + {file = "mypy-1.20.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:12927b9c0ed794daedcf1dab055b6c613d9d5659ac511e8d936d96f19c087d12"}, + {file = "mypy-1.20.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:752507dd481e958b2c08fc966d3806c962af5a9433b5bf8f3bdd7175c20e34fe"}, + {file = "mypy-1.20.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c614655b5a065e56274c6cbbe405f7cf7e96c0654db7ba39bc680238837f7b08"}, + {file = "mypy-1.20.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c3f6221a76f34d5100c6d35b3ef6b947054123c3f8d6938a4ba00b1308aa572"}, + {file = "mypy-1.20.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4bdfc06303ac06500af71ea0cdbe995c502b3c9ba32f3f8313523c137a25d1b6"}, + {file = "mypy-1.20.1-cp314-cp314-win_amd64.whl", hash = "sha256:0131edd7eba289973d1ba1003d1a37c426b85cdef76650cd02da6420898a5eb3"}, + {file = "mypy-1.20.1-cp314-cp314-win_arm64.whl", hash = "sha256:33f02904feb2c07e1fdf7909026206396c9deeb9e6f34d466b4cfedb0aadbbe4"}, + {file = "mypy-1.20.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:168472149dd8cc505c98cefd21ad77e4257ed6022cd5ed2fe2999bed56977a5a"}, + {file = "mypy-1.20.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:eb674600309a8f22790cca883a97c90299f948183ebb210fbef6bcee07cb1986"}, + {file = "mypy-1.20.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef2b2e4cc464ba9795459f2586923abd58a0055487cbe558cb538ea6e6bc142a"}, + {file = "mypy-1.20.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee461d396dd46b3f0ed5a098dbc9b8860c81c46ad44fa071afcfbc149f167c9"}, + {file = "mypy-1.20.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e364926308b3e66f1361f81a566fc1b2f8cd47fc8525e8136d4058a65a4b4f02"}, + {file = "mypy-1.20.1-cp314-cp314t-win_amd64.whl", hash = "sha256:a0c17fbd746d38c70cbc42647cfd884f845a9708a4b160a8b4f7e70d41f4d7fa"}, + {file = "mypy-1.20.1-cp314-cp314t-win_arm64.whl", hash = "sha256:db2cb89654626a912efda69c0d5c1d22d948265e2069010d3dde3abf751c7d08"}, + {file = "mypy-1.20.1-py3-none-any.whl", hash = "sha256:1aae28507f253fe82d883790d1c0a0d35798a810117c88184097fe8881052f06"}, + {file = "mypy-1.20.1.tar.gz", hash = "sha256:6fc3f4ecd52de81648fed1945498bf42fa2993ddfad67c9056df36ae5757f804"}, ] [package.dependencies] -librt = {version = ">=0.6.2", markers = "platform_python_implementation != \"PyPy\""} +librt = {version = ">=0.8.0", markers = "platform_python_implementation != \"PyPy\""} mypy_extensions = ">=1.0.0" -pathspec = ">=0.9.0" +pathspec = ">=1.0.0" typing_extensions = ">=4.6.0" [package.extras] @@ -366,6 +372,7 @@ dmypy = ["psutil (>=4.0)"] faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] +native-parser = ["ast-serialize (>=0.1.1,<1.0.0)"] reports = ["lxml"] [[package]] @@ -394,14 +401,14 @@ files = [ [[package]] name = "packaging" -version = "26.0" +version = "26.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, - {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, + {file = "packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f"}, + {file = "packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de"}, ] [[package]] @@ -471,14 +478,14 @@ virtualenv = ">=20.10.0" [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, ] [package.extras] @@ -505,14 +512,14 @@ testing = ["covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytes [[package]] name = "pytest" -version = "9.0.2" +version = "9.0.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"}, - {file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"}, + {file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"}, + {file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"}, ] [package.dependencies] @@ -650,30 +657,30 @@ files = [ [[package]] name = "ruff" -version = "0.15.8" +version = "0.15.11" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7"}, - {file = "ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570"}, - {file = "ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3"}, - {file = "ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94"}, - {file = "ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3"}, - {file = "ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762"}, - {file = "ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a"}, - {file = "ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8"}, - {file = "ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1"}, - {file = "ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec"}, - {file = "ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6"}, - {file = "ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb"}, - {file = "ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8"}, - {file = "ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49"}, - {file = "ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34"}, - {file = "ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89"}, - {file = "ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2"}, - {file = "ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e"}, + {file = "ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7"}, + {file = "ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e"}, + {file = "ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb"}, + {file = "ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4"}, + {file = "ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb"}, + {file = "ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d"}, + {file = "ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7"}, + {file = "ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e"}, + {file = "ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431"}, + {file = "ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19"}, + {file = "ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890"}, + {file = "ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5"}, + {file = "ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0"}, + {file = "ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c"}, + {file = "ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3"}, + {file = "ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3"}, + {file = "ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4"}, + {file = "ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33"}, ] [[package]] diff --git a/src/vstack/_templates/agents/designer/template.md b/src/vstack/_templates/agents/designer/template.md index 4432d31..ea8695d 100644 --- a/src/vstack/_templates/agents/designer/template.md +++ b/src/vstack/_templates/agents/designer/template.md @@ -124,3 +124,4 @@ Apply all relevant disciplines — a fullstack integration system needs API cont - `@#docs` — keep design artifacts and related docs aligned with delivered changes - `@#explore` — codebase discovery and mapping - `@#analyse` — impact analysis, tradeoffs, feasibility +- `@#openapi` — OpenAPI 3.1 spec writing and review diff --git a/src/vstack/_templates/agents/engineer/template.md b/src/vstack/_templates/agents/engineer/template.md index 5dbe625..0acab8e 100644 --- a/src/vstack/_templates/agents/engineer/template.md +++ b/src/vstack/_templates/agents/engineer/template.md @@ -115,3 +115,8 @@ Only delegate when workstreams are genuinely independent. - `@#performance` — performance investigation - `@#container` — Dockerfile and docker-compose authoring - `@#cicd` — GitHub Actions CI/CD workflow configuration +- `@#migrate` — database migration review and authoring +- `@#refactor` — structured refactoring without behavior change +- `@#openapi` — OpenAPI 3.1 spec writing and review +- `@#dependency` — dependency health audit +- `@#incident` — incident analysis and post-mortem writing diff --git a/src/vstack/_templates/agents/product/template.md b/src/vstack/_templates/agents/product/template.md index 6f0fea3..79e9aa4 100644 --- a/src/vstack/_templates/agents/product/template.md +++ b/src/vstack/_templates/agents/product/template.md @@ -111,6 +111,7 @@ Before PR merge, consolidate relevant content into baseline docs and remove the - `@#explore` — codebase discovery and mapping (brownfield intake) - `@#analyse` — impact analysis, tradeoffs, feasibility - `@#adr` — architecture decision record writing (if significant decisions) +- `@#onboard` — contributor onboarding guide generation ## gate moments diff --git a/src/vstack/_templates/agents/tester/template.md b/src/vstack/_templates/agents/tester/template.md index 0cd7dec..d4c7a8a 100644 --- a/src/vstack/_templates/agents/tester/template.md +++ b/src/vstack/_templates/agents/tester/template.md @@ -144,3 +144,6 @@ Browser/E2E tests: only if the product scope includes a frontend UI. - `@#explore` — codebase discovery and mapping - `@#analyse` — impact analysis, tradeoffs, feasibility - `@#code-review` — pre-merge review before release +- `@#migrate` — database migration safety review +- `@#dependency` — dependency vulnerability and health audit +- `@#incident` — incident analysis and post-mortem writing diff --git a/src/vstack/_templates/skills/dependency/config.yaml b/src/vstack/_templates/skills/dependency/config.yaml new file mode 100644 index 0000000..df0a991 --- /dev/null +++ b/src/vstack/_templates/skills/dependency/config.yaml @@ -0,0 +1,18 @@ +name: dependency +version: 1.0.0 +description: | + Dependency health audit. Covers vulnerability scanning, outdated packages, + licence compliance, transitive risk, pinning policy, and supply chain hygiene. + Goes beyond the vulnerability gate in `security` — covers upgrade strategy, + licence obligations, and long-term dependency health. Use when asked to + "audit dependencies", "check for outdated packages", "licence compliance", + "pin versions", or "dependency health check". +argument-hint: '[project or package manifest to audit]' + +license: MIT +compatibility: Requires a skills-compatible agent with repository file access and terminal command execution when needed. +metadata: + owner: vstack + maturity: stable +user-invocable: true +disable-model-invocation: false diff --git a/src/vstack/_templates/skills/dependency/template.md b/src/vstack/_templates/skills/dependency/template.md new file mode 100644 index 0000000..202ea74 --- /dev/null +++ b/src/vstack/_templates/skills/dependency/template.md @@ -0,0 +1,293 @@ +{{SKILL_CONTEXT}} + +# dependency — Dependency Health Audit + +Audit the health, security, and compliance of project dependencies. Covers +vulnerability scanning, outdated packages, licence obligations, transitive +risk, pinning policy, and supply chain hygiene. + +## Scope vs related skills + +- **This skill** — full dependency health: vulnerabilities, freshness, licences, + pinning policy, supply chain +- **`security`** — includes a lightweight vulnerability gate as part of OWASP A06; + for a full dependency audit use this skill instead +- **`verify`** — includes a quick vuln check in the quality gate loop; escalates + to this skill for deeper investigation + +**Golden rule: A dependency is owned code you didn't write. Treat it with the +same scrutiny as your own code.** + +______________________________________________________________________ + +## Step 0: Detect the Stack + +```bash +# Identify package manifests +ls pyproject.toml poetry.lock requirements*.txt \ + package.json package-lock.json yarn.lock pnpm-lock.yaml \ + go.mod go.sum Cargo.toml Cargo.lock \ + pom.xml build.gradle Gemfile Gemfile.lock 2>/dev/null + +# Detect Python version and package manager +cat pyproject.toml 2>/dev/null | grep -E 'requires-python|tool\.poetry' | head -5 +cat .python-version 2>/dev/null +which poetry && poetry --version 2>/dev/null || true +which pip-audit && pip-audit --version 2>/dev/null || true +``` + +Document: + +```text +Stack: [Python | Node | Go | Rust | Java | other] +Manager: [Poetry | pip | npm | yarn | pnpm | cargo | go modules | other] +Manifests: [list of files found] +Lock file: [present | absent — flag if absent] +``` + +______________________________________________________________________ + +## Part 1: Vulnerability Scan + +Run the appropriate scanner for each detected stack: + +```bash +# Python — pip-audit (preferred) or safety +if [ -f pyproject.toml ] || [ -f requirements.txt ]; then + pip-audit 2>/dev/null \ + || safety check --full-report 2>/dev/null \ + || echo "No Python vuln scanner found — install pip-audit: pip install pip-audit" +fi + +# Node +[ -f package.json ] && npm audit --json 2>/dev/null | head -100 + +# Go +if [ -f go.mod ]; then + govulncheck ./... 2>/dev/null \ + || echo "govulncheck not found — install: go install golang.org/x/vuln/cmd/govulncheck@latest" +fi + +# Rust +if [ -f Cargo.toml ]; then + cargo audit 2>/dev/null \ + || echo "cargo-audit not found — install: cargo install cargo-audit" +fi + +# Java (Maven) +[ -f pom.xml ] && mvn dependency-check:check -q 2>/dev/null || true +``` + +Triage findings by severity: + +```text +Vulnerabilities found: + 🔴 CRITICAL / HIGH: [package] [version] — [CVE] — [description] + 🟡 MEDIUM: [package] [version] — [CVE] — [description] + 🟢 LOW / INFO: [count] low-severity findings +``` + +**Remediation rule:** CRITICAL and HIGH must be resolved before release. MEDIUM +should be tracked and resolved within the sprint. LOW may be deferred with +documented rationale. + +______________________________________________________________________ + +## Part 2: Outdated Packages + +```bash +# Python (Poetry) +poetry show --outdated 2>/dev/null | head -40 + +# Python (pip) +pip list --outdated 2>/dev/null | head -40 + +# Node +npm outdated 2>/dev/null | head -40 + +# Go — check go.sum and go.mod for pinned versions +go list -m -u all 2>/dev/null | grep '\[' | head -30 + +# Rust +cargo outdated 2>/dev/null | head -30 +``` + +Classify each outdated package: + +| Package | Current | Latest | Type | Action | +| ------- | ------- | ------ | ----- | ------------------------- | +| `foo` | 1.2.0 | 1.2.5 | patch | update now | +| `bar` | 2.1.0 | 3.0.0 | major | evaluate breaking changes | +| `baz` | 0.9.0 | 0.9.8 | patch | update now | + +**Update priority:** + +- Patch updates: update immediately (no breaking changes expected) +- Minor updates: update soon (check changelog for deprecations) +- Major updates: plan upgrade (read migration guide, test thoroughly) + +______________________________________________________________________ + +## Part 3: Licence Compliance + +Check licence obligations for all direct and transitive dependencies: + +```bash +# Python +if pip-licenses --version >/dev/null 2>&1; then + pip-licenses --format=markdown --with-urls 2>/dev/null | head -60 +elif python3 -m pip_licenses --help >/dev/null 2>&1; then + python3 -m pip_licenses 2>/dev/null | head -60 +else + echo "Install pip-licenses: pip install pip-licenses" +fi + +# Node +npx license-checker --summary 2>/dev/null | head -40 + +# Go +if go-licenses --help >/dev/null 2>&1; then + go-licenses report ./... 2>/dev/null | head -40 +else + echo "Install go-licenses: go install github.com/google/go-licenses@latest" +fi +``` + +Classify licences by risk: + +| Risk | Licences | Requirement | +| ------ | ---------------------------------- | -------------------------------------------------- | +| Low | MIT, BSD-2, BSD-3, Apache-2.0, ISC | Can use freely, attribution in docs | +| Medium | LGPL-2.1, LGPL-3.0 | Dynamic linking OK; static linking requires review | +| High | GPL-2.0, GPL-3.0, AGPL-3.0 | May require open-sourcing your code | +| Review | Commercial, proprietary, unknown | Requires legal review before use | + +Flag any High or Review licences: + +```text +Licence issues: + 🔴 [package] — [licence] — [risk] — [recommendation] +``` + +______________________________________________________________________ + +## Part 4: Pinning Policy + +A healthy dependency policy requires reproducible builds: + +**Check for lock files:** + +- [ ] `poetry.lock` / `package-lock.json` / `yarn.lock` / `Cargo.lock` / `go.sum` exists +- [ ] Lock file is committed to version control +- [ ] Lock file is up to date with the manifest + +**Check for version constraints:** + +```bash +# Python — look for unpinned deps +cat pyproject.toml 2>/dev/null | grep -E '^\s+[a-z]' | grep -v '^#' | head -30 + +# Flag overly loose constraints (e.g. "*", ">=1.0" with no upper bound in prod deps) +``` + +| Pattern | Risk | Recommendation | +| --------------------------- | ------ | -------------------------------------------- | +| `package = "*"` | High | Pin to a compatible range | +| `package = ">=1.0"` | Medium | Add upper bound: `>=1.0,<3.0` | +| `package = "^1.0"` (Poetry) | Low | Acceptable for non-critical deps | +| `package = "1.2.3"` (exact) | Low | Fine for direct deps; brittle for transitive | +| No lock file | High | Add lock file and commit it | + +______________________________________________________________________ + +## Part 5: Transitive Risk + +Identify high-risk transitive (indirect) dependencies: + +```bash +# Python — show full dependency tree +if poetry --version >/dev/null 2>&1; then + poetry show --tree 2>/dev/null | head -60 +else + pip install pipdeptree 2>/dev/null && pipdeptree 2>/dev/null | head -60 +fi + +# Node +npm list --depth=2 2>/dev/null | head -60 + +# Go +go mod graph 2>/dev/null | head -40 +``` + +Flags to look for: + +- [ ] No single dependency with > 20 transitive deps (blast radius risk) +- [ ] No abandoned packages (last release > 2 years, no recent commits) +- [ ] No packages with a single maintainer for critical functionality +- [ ] Core dependencies have active security policies (CVE response time < 30 days) + +```bash +# Check for abandoned packages — look at last release dates +# (manual step: check PyPI / npm registry for each critical dep) +``` + +______________________________________________________________________ + +## Part 6: Supply Chain Hygiene + +```bash +# Python — check if packages are installed from PyPI or custom source +cat pyproject.toml 2>/dev/null | grep -E '\[\[tool\.poetry\.source\]\]' -A 5 + +# Node — check for private registry config +cat .npmrc 2>/dev/null +cat package.json 2>/dev/null | grep -E '"registry"' + +# Check for dependency confusion risk (private package names published on public registry) +# (manual step: search PyPI/npm for any internal package names) +``` + +Check: + +- [ ] All packages sourced from trusted, official registries +- [ ] No `--extra-index-url` pointing to untrusted sources (Python) +- [ ] Private package names are not also available on public registries (confusion attack) +- [ ] `pip install` / `npm install` output reviewed for unexpected packages +- [ ] CI pipeline pins the package manager version itself + +______________________________________________________________________ + +## Output + +```text +Dependency Audit Report +═══════════════════════ + +Stack: [stack + manager] +Scanned: [N direct, N transitive dependencies] + +Vulnerabilities: + 🔴 Critical/High: [N] — [list or "none"] + 🟡 Medium: [N] — [list or "none"] + 🟢 Low: [N] + +Outdated: + Patch updates available: [N packages] + Minor updates available: [N packages] + Major updates available: [N packages] + +Licence issues: + 🔴 High-risk licences: [list or "none"] + 🟡 Review required: [list or "none"] + +Pinning: + Lock file: [present | absent] + Loose pins: [list or "none"] + +Supply chain: + [clean | issues found — details] + +Action items (priority order): + 1. [action] — [package] — [severity] + 2. ... +``` diff --git a/src/vstack/_templates/skills/incident/config.yaml b/src/vstack/_templates/skills/incident/config.yaml new file mode 100644 index 0000000..7bba5cc --- /dev/null +++ b/src/vstack/_templates/skills/incident/config.yaml @@ -0,0 +1,17 @@ +name: incident +version: 1.0.0 +description: | + Incident analysis and post-mortem writing. Guides a structured investigation + from timeline reconstruction through root cause identification to a blameless + post-mortem document with action items. Use when asked to "write a post-mortem", + "incident review", "root cause analysis for this outage", "what went wrong?", + or "blameless post-mortem". Produces a docs/postmortems/{date}-{slug}.md. +argument-hint: '[incident or outage to analyse and document]' + +license: MIT +compatibility: Requires a skills-compatible agent with repository file access and terminal command execution when needed. +metadata: + owner: vstack + maturity: stable +user-invocable: true +disable-model-invocation: false diff --git a/src/vstack/_templates/skills/incident/template.md b/src/vstack/_templates/skills/incident/template.md new file mode 100644 index 0000000..0a62082 --- /dev/null +++ b/src/vstack/_templates/skills/incident/template.md @@ -0,0 +1,293 @@ +{{SKILL_CONTEXT}} + +# incident — Incident Analysis & Post-Mortem + +Guide a structured incident investigation and produce a blameless post-mortem +document. The goal is learning and prevention — not blame. + +## Out of scope + +- Live incident response / on-call triage (this skill is for retrospective analysis) +- Root-cause debugging of code bugs (use `debug`) +- Security audit of vulnerabilities (use `security`) +- Performance benchmarking (use `performance`) + +**Golden rule: Incidents are system failures, not human failures. Every finding +must be framed as a system improvement opportunity, never as individual blame.** + +______________________________________________________________________ + +## Step 0: Gather Incident Context + +Before analysis, collect all available evidence: + +> **Questions to answer:** +> +> - When did the incident start and end? (UTC timestamps) +> - What was the user-visible impact? (errors, latency, data loss, downtime) +> - What services were affected? +> - Who detected it and how? (alert, user report, monitoring) +> - What was done to resolve it? +> - Is there a severity classification? (P0/P1/P2 or SEV1/SEV2/SEV3) + +```bash +# Gather git history around the incident window +git log --oneline --since="YYYY-MM-DD" --until="YYYY-MM-DD" 2>/dev/null | head -30 + +# Check recent deploys +git log --oneline --merges --since="YYYY-MM-DD" 2>/dev/null | head -20 + +# Find relevant config or infra changes +git log --oneline --since="YYYY-MM-DD" -- '*.yaml' '*.yml' '*.toml' '*.env*' 2>/dev/null | head -20 +``` + +Document: + +```text +Incident ID: [INC-NNNN or date-slug] +Severity: [P0 | P1 | P2 | SEV1 | SEV2 | SEV3] +Start: [YYYY-MM-DD HH:MM UTC] +End: [YYYY-MM-DD HH:MM UTC] +Duration: [N hours N minutes] +Detected by: [alert | user report | manual check] +Services: [list of affected services] +Impact: [user-facing description] +``` + +______________________________________________________________________ + +## Step 1: Reconstruct the Timeline + +Build a precise, chronological timeline of events. Include: + +- System events (deploys, config changes, traffic spikes) +- Detection events (alerts fired, pages sent) +- Response actions (who did what, when) +- Resolution events (rollback, fix deployed, service restored) + +```text +Timeline (all times UTC): + +HH:MM — [event description] — [who / what system] +HH:MM — [event description] — [who / what system] +... + +Key markers: + Impact start: HH:MM + Detection: HH:MM (+N min after impact start) + Response start: HH:MM (+N min after detection) + Mitigation: HH:MM (+N min after response) + Full resolution: HH:MM + Total duration: N hours N minutes +``` + +______________________________________________________________________ + +## Step 2: Identify Contributing Factors + +List ALL factors that contributed to the incident — not just the "trigger". +Incidents are never caused by a single thing. Use the 5-Whys technique: + +**5-Whys template:** + +```text +Why did [impact] happen? + Because [immediate cause]. + +Why did [immediate cause] happen? + Because [contributing factor 1]. + +Why did [contributing factor 1] exist? + Because [deeper cause]. + +Why did [deeper cause] exist? + Because [systemic gap]. + +Why did [systemic gap] exist? + Because [root systemic condition]. +``` + +Categorize contributing factors: + +| Category | Factor | +| ------------- | -------------------------------------------------------- | +| Code / logic | [e.g. missing error handling, race condition] | +| Configuration | [e.g. incorrect timeout, missing feature flag] | +| Deployment | [e.g. no canary, missing rollback plan] | +| Monitoring | [e.g. alert threshold too high, missing metric] | +| Process | [e.g. no review for config changes, unclear runbook] | +| External | [e.g. upstream dependency failure, cloud provider issue] | +| Knowledge | [e.g. undocumented behaviour, tribal knowledge gap] | + +______________________________________________________________________ + +## Step 3: Determine Root Cause + +The root cause is the deepest systemic condition that, if addressed, would +prevent this class of incident from recurring. + +**Root cause is NOT:** + +- "Human error" (humans make mistakes — the system must be resilient to them) +- "We forgot to test X" (why was it possible to ship without testing X?) +- The deployment that triggered it (that is the trigger, not the cause) + +```text +Root cause: + [One clear, specific statement of the systemic condition] + +Evidence: + [What evidence supports this conclusion] + +Class of incident: + [Deploy regression | Configuration drift | Dependency failure | + Capacity / traffic | Data corruption | Security breach | Other] +``` + +______________________________________________________________________ + +## Step 4: Assess Impact + +Quantify the impact precisely: + +```text +User impact: + Affected users: [N users | N% of traffic | all users] + Error rate: [N% of requests returned errors] + Latency increase: [p99 increased from Nms to Nms] + Data loss: [none | N records | describe scope] + Feature unavailable:[list features] + +Business impact: + Revenue: [estimated impact if known] + SLA breach: [yes — N minutes over limit | no] + Customer comms: [status page update | direct notification | none] + +Detection gap: + Time to detect: [N minutes] + How detected: [alert | user complaint | manual] + Why not faster: [threshold too high | missing alert | other] +``` + +______________________________________________________________________ + +## Step 5: Write Action Items + +Action items must be: + +- **Specific** — not "improve monitoring" but "add alert on p99 > 500ms for /checkout" +- **Owned** — assigned to a person or team +- **Time-bound** — target date or sprint +- **Categorized** — prevention, detection, or response improvement + +```text +Action items: + +Prevention (stop this from happening again): + [ ] [specific action] — owner: [name/team] — due: [date/sprint] + +Detection (catch it faster next time): + [ ] [specific action] — owner: [name/team] — due: [date/sprint] + +Response (resolve it faster next time): + [ ] [specific action] — owner: [name/team] — due: [date/sprint] + [ ] Write or update runbook for this failure class — owner: [name/team] — due: [date/sprint] + +Process (improve how we handle incidents): + [ ] [specific action] — owner: [name/team] — due: [date/sprint] +``` + +______________________________________________________________________ + +## Step 6: Produce the Post-Mortem Document + +Write the post-mortem to `docs/postmortems/YYYY-MM-DD-.md`: + +```markdown +# Post-Mortem: [Short Title] + +**Date:** YYYY-MM-DD +**Severity:** [P0 | P1 | P2] +**Duration:** N hours N minutes +**Status:** [Draft | In Review | Closed] +**Author(s):** [names] + +--- + +## Summary + +[2–3 sentences: what happened, what was the impact, how was it resolved. +Written for a non-technical audience.] + +## Impact + +| Dimension | Details | +|---|---| +| Duration | N hours N minutes (HH:MM–HH:MM UTC) | +| Users affected | [N users / N% of traffic] | +| Error rate | [N%] | +| SLA breach | [yes / no] | +| Data loss | [none / description] | + +## Timeline + +| Time (UTC) | Event | +|---|---| +| HH:MM | [event] | +| HH:MM | [event] | +| ... | ... | + +## Root Cause + +[One paragraph. Specific, systemic, blameless.] + +## Contributing Factors + +- [factor 1] +- [factor 2] +- [factor 3] + +## What Went Well + +- [thing 1 — e.g. alert fired within 2 minutes] +- [thing 2 — e.g. rollback completed in 4 minutes] +- [thing 3] + +## What Went Poorly + +- [thing 1 — e.g. no runbook for this failure mode] +- [thing 2] + +## Action Items + +| # | Action | Category | Owner | Due | +|---|---|---|---|---| +| 1 | [action] | Prevention | [owner] | [date] | +| 2 | [action] | Detection | [owner] | [date] | +| 3 | [action] | Response | [owner] | [date] | + +## Lessons Learned + +[2–4 sentences summarizing the key takeaways for the team and organization. +What does this incident teach us about our system, processes, or culture?] +``` + +______________________________________________________________________ + +## Output + +```text +Incident Analysis Complete +══════════════════════════ + +Incident: [ID / title] +Severity: [P0 | P1 | P2] +Duration: [N hours N minutes] +Root cause: [one-line summary] + +Contributing factors: [N identified] +Action items: [N total — N prevention, N detection, N response] + +Post-mortem written: docs/postmortems/YYYY-MM-DD-.md +Status: [Draft — ready for team review] +``` diff --git a/src/vstack/_templates/skills/migrate/config.yaml b/src/vstack/_templates/skills/migrate/config.yaml new file mode 100644 index 0000000..04b989d --- /dev/null +++ b/src/vstack/_templates/skills/migrate/config.yaml @@ -0,0 +1,17 @@ +name: migrate +version: 1.0.0 +description: | + Database migration review and authoring. Covers forwards/backwards compatibility, + zero-downtime strategies, rollback plans, data integrity, and index safety. + Use when asked to "write a migration", "review this migration", "is this + migration safe?", or "zero-downtime schema change". Proactively suggest before + any DDL change ships to production. +argument-hint: '[migration file or schema change to review]' + +license: MIT +compatibility: Requires a skills-compatible agent with repository file access and terminal command execution when needed. +metadata: + owner: vstack + maturity: stable +user-invocable: true +disable-model-invocation: false diff --git a/src/vstack/_templates/skills/migrate/template.md b/src/vstack/_templates/skills/migrate/template.md new file mode 100644 index 0000000..d17c89e --- /dev/null +++ b/src/vstack/_templates/skills/migrate/template.md @@ -0,0 +1,298 @@ +{{SKILL_CONTEXT}} + +{{BASE_BRANCH}} + +# migrate — Database Migration Review & Authoring + +Review or write database migrations for safety, correctness, and zero-downtime +deployability. No migration ships without a rollback plan. + +## Out of scope + +- ORM model design (use `design`) +- Performance benchmarking of queries (use `performance`) +- Full security audit (use `security`) +- Architecture decisions (use `architecture` + `adr`) + +**Golden rule: Every migration must be reversible or explicitly documented as +irreversible with a data-recovery plan.** + +______________________________________________________________________ + +## Step 0: Understand the Change + +Before reviewing or writing anything, gather context: + +> **Question:** What schema change is needed and why? +> +> - What table(s) / collection(s) are affected? +> - Approximate row count and data size? +> - Is this service actively serving production traffic? +> - What deployment strategy is used (blue/green, rolling, big-bang)? +> **Default:** Assume rolling deployment, production traffic, safety-first. + +```bash +# Find existing migration files +find . -type f \( -name '*.sql' -o -name '*migration*' -o -name '*migrate*' \) \ + --exclude-dir=.venv --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=build \ + 2>/dev/null | sort | tail -20 + +# Show migration files changed in this branch +git diff --stat -- '*.sql' '**migration**' '**migrate**' 2>/dev/null | head -20 + +# Check migration framework in use +[ -f alembic.ini ] && echo "alembic" || true +[ -f flyway.conf ] && echo "flyway" || true +grep -r "migrate\|liquibase\|goose\|dbmate" pyproject.toml package.json go.mod 2>/dev/null | head -5 +``` + +Document: + +```text +Framework: [alembic | flyway | goose | dbmate | raw SQL | other] +Table: [affected table(s)] +Row estimate: [< 1k | 10k | 100k | 1M | 10M+ ] +Deployment: [rolling | blue-green | big-bang | maintenance window] +Direction: [new migration | review existing | both] +``` + +______________________________________________________________________ + +## Step 1: Classify the Migration + +Classify every DDL operation by risk level: + +| Operation | Risk | Notes | +| ---------------------------------------- | --------------- | ---------------------------------- | +| `CREATE TABLE` | Low | Safe at any time | +| `ADD COLUMN` nullable, no default | Low | Safe in rolling deploy | +| `ADD COLUMN` with default (non-volatile) | Medium | May lock on large tables | +| `ADD COLUMN NOT NULL` without default | **High** | Breaks old app version | +| `DROP COLUMN` | **High** | Must remove all references first | +| `RENAME COLUMN` | **High** | Breaks old app version immediately | +| `ALTER COLUMN` type change | **High** | May require data rewrite | +| `CREATE INDEX CONCURRENTLY` | Low | Safe, non-blocking | +| `CREATE INDEX` (without CONCURRENTLY) | **High** | Full table lock | +| `DROP INDEX` | Low | Safe | +| `ADD CONSTRAINT` | **High** | Validates all existing rows | +| `TRUNCATE` / `DROP TABLE` | **Destructive** | Requires explicit confirmation | + +Assign risk to each operation in the migration: + +```text +Operations: + 1. [operation] — [Low | Medium | High | Destructive] + 2. ... +Overall risk: [Low | Medium | High | Destructive] +``` + +______________________________________________________________________ + +## Step 2: Zero-Downtime Analysis + +**Rolling deployments require that the schema be compatible with BOTH the old and +new version of the application simultaneously.** + +Check each operation: + +- [ ] Old app version can read/write with the new schema +- [ ] New app version can read/write with the old schema (before migration runs) +- [ ] No `NOT NULL` columns added without a default or backfill step +- [ ] No column renames (use add → backfill → switch → drop in separate deploys) +- [ ] No type changes that are incompatible with existing data + +**Expand/Contract pattern (required for breaking changes):** + +```text +Phase 1 — Expand: Add new column/table alongside old one +Phase 2 — Migrate: Backfill data; dual-write in application +Phase 3 — Switch: Application reads from new column +Phase 4 — Contract: Drop old column in a separate deploy +``` + +If the migration violates zero-downtime, flag it: + +```text +⚠ ZERO-DOWNTIME VIOLATION + Operation: [operation] + Problem: [what breaks] + Fix: [expand/contract steps or maintenance window required] +``` + +______________________________________________________________________ + +## Step 3: Rollback Plan + +Every migration must have a defined rollback: + +```sql +-- Forward migration +ALTER TABLE orders ADD COLUMN discount_cents INTEGER; + +-- Rollback +ALTER TABLE orders DROP COLUMN discount_cents; +``` + +For destructive operations (DROP, TRUNCATE), rollback is not possible — document this explicitly: + +```text +⚠ IRREVERSIBLE OPERATION + Operation: DROP TABLE legacy_sessions + Pre-condition: Confirm legacy_sessions is unused (grep all references) + Backup required: Yes — take a snapshot before deploying + Recovery: Restore from snapshot (RTO: [estimate]) +``` + +______________________________________________________________________ + +## Step 4: Data Integrity + +Check: + +- [ ] Foreign key constraints are correct and indexed +- [ ] `NOT NULL` columns have sensible defaults for existing rows +- [ ] `UNIQUE` constraints won't fail on existing duplicates +- [ ] `CHECK` constraints won't reject existing data +- [ ] Enum additions are safe (additions OK; removals break existing data) +- [ ] Cascade behavior is intentional (`ON DELETE CASCADE` vs `RESTRICT`) + +```sql +-- Before adding a NOT NULL column: verify no NULLs exist +SELECT COUNT(*) FROM table WHERE column IS NULL; + +-- Before adding UNIQUE constraint: verify no duplicates +SELECT column, COUNT(*) FROM table GROUP BY column HAVING COUNT(*) > 1; + +-- Before adding CHECK constraint: verify no violations +SELECT COUNT(*) FROM table WHERE NOT (constraint_expression); +``` + +______________________________________________________________________ + +## Step 5: Index Safety + +**Never create an index without `CONCURRENTLY` on a live table.** + +```sql +-- BAD: acquires ACCESS EXCLUSIVE lock +CREATE INDEX idx_orders_user_id ON orders(user_id); + +-- GOOD: non-blocking, runs in background +CREATE INDEX CONCURRENTLY idx_orders_user_id ON orders(user_id); +``` + +Caveats for `CONCURRENTLY`: + +- Cannot run inside a transaction block +- Takes longer to build +- May fail if the table is modified heavily during build + +Check: + +- [ ] All new indexes use `CONCURRENTLY` (unless in a migration transaction that can afford a lock) +- [ ] Composite index column order matches query patterns +- [ ] No redundant indexes (subset of existing composite index) +- [ ] Partial indexes considered for filtered queries + +______________________________________________________________________ + +## Step 6: Performance on Large Tables + +For tables with > 100k rows: + +```bash +# Estimate table size +# PostgreSQL: +psql -c "SELECT pg_size_pretty(pg_total_relation_size('table_name'));" 2>/dev/null || true +# MySQL: +# SELECT table_name, ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'Size (MB)' +# FROM information_schema.TABLES WHERE table_schema = DATABASE(); +``` + +For large tables, prefer: + +- **Online DDL** (MySQL 8+, PostgreSQL with `CONCURRENTLY`) +- **Batched backfills** instead of single `UPDATE` statements +- **Maintenance window** for operations that cannot be made non-blocking + +Batched backfill pattern: + +```sql +-- Backfill in batches to avoid long-running locks +DO $$ +DECLARE + batch_size INT := 10000; + offset_val INT := 0; + rows_updated INT; +BEGIN + LOOP + UPDATE table SET new_col = + WHERE id IN ( + SELECT id FROM table WHERE new_col IS NULL LIMIT batch_size + ); + GET DIAGNOSTICS rows_updated = ROW_COUNT; + EXIT WHEN rows_updated = 0; + PERFORM pg_sleep(0.1); -- brief pause between batches + END LOOP; +END $$; +``` + +______________________________________________________________________ + +## Step 7: Testing + +```bash +# Run migration tests +# Framework-specific: +alembic upgrade head && alembic downgrade -1 2>/dev/null || true +flyway migrate && flyway undo 2>/dev/null || true + +# Verify the schema matches models +# (adjust for your ORM) +python -c "from app.models import Base; Base.metadata.create_all(engine)" 2>/dev/null || true +``` + +Check: + +- [ ] Migration applies cleanly on a fresh database +- [ ] Migration applies cleanly on a database with production-representative data +- [ ] Rollback/downgrade works cleanly +- [ ] Application tests pass with the new schema +- [ ] No model/schema drift detected + +______________________________________________________________________ + +## Output + +Produce a structured migration review: + +```text +Migration Review +════════════════ + +File: [migration filename] +Framework: [framework] +Risk level: [Low | Medium | High | Destructive] + +Operations: + 1. [operation] — [risk] — [safe / unsafe for rolling deploy] + 2. ... + +Zero-downtime: [SAFE | UNSAFE — reason] +Rollback: [SQL rollback statement | IRREVERSIBLE — reason] + +Issues found: + 🔴 CRITICAL: [issue + fix] + 🟡 WARNING: [issue + recommendation] + 🟢 OK: [what is correct] + +Recommended migration: + [corrected or approved SQL] + +Pre-deploy checklist: + [ ] Backup taken + [ ] Migration tested on staging with production data volume + [ ] Rollback script validated + [ ] Feature flag in place (if needed for expand/contract) + [ ] Monitoring alert set for migration duration +``` diff --git a/src/vstack/_templates/skills/onboard/config.yaml b/src/vstack/_templates/skills/onboard/config.yaml new file mode 100644 index 0000000..92eedaf --- /dev/null +++ b/src/vstack/_templates/skills/onboard/config.yaml @@ -0,0 +1,18 @@ +name: onboard +version: 1.0.0 +description: | + Generate a contributor onboarding guide for a repository. Covers project + purpose, architecture overview, local dev setup, test commands, contribution + workflow, and first-task suggestions. Use when asked to "write an onboarding + guide", "create a contributor guide", "help new devs get started", or + "document how to contribute". Produces or updates CONTRIBUTING.md and + supplements README with a dev setup section. +argument-hint: '[repository or service to document]' + +license: MIT +compatibility: Requires a skills-compatible agent with repository file access and terminal command execution when needed. +metadata: + owner: vstack + maturity: stable +user-invocable: true +disable-model-invocation: false diff --git a/src/vstack/_templates/skills/onboard/template.md b/src/vstack/_templates/skills/onboard/template.md new file mode 100644 index 0000000..4269860 --- /dev/null +++ b/src/vstack/_templates/skills/onboard/template.md @@ -0,0 +1,289 @@ +{{SKILL_CONTEXT}} + +# onboard — Contributor Onboarding Guide + +Generate or update onboarding documentation so a new contributor can go from +clone to first PR with zero tribal knowledge required. + +## Out of scope + +- API design documentation (use `design` or `openapi`) +- Architecture documentation (use `architecture`) +- Release notes (use `release-notes`) +- Test execution (use `verify`) + +**Golden rule: If a new contributor needs to ask a question that isn't answered +by the docs, that is a documentation gap — not a knowledge problem.** + +______________________________________________________________________ + +## Step 0: Audit Existing Documentation + +```bash +# Find existing contributor docs +ls -la README.md CONTRIBUTING.md DEVELOPMENT.md docs/ 2>/dev/null + +# Check for setup scripts +ls -la Makefile scripts/ bin/ 2>/dev/null + +# Check what tooling is defined +cat Makefile 2>/dev/null | grep -E '^[a-z].*:' | head -20 + +# Detect tech stack +ls pyproject.toml package.json go.mod Cargo.toml pom.xml 2>/dev/null +cat pyproject.toml 2>/dev/null | head -30 +cat package.json 2>/dev/null | grep -E '"scripts"' -A 20 | head -25 +``` + +Document gaps: + +```text +Existing docs: [list of files found] +Missing: [what's absent — setup steps, test commands, etc.] +Tech stack: [Python | Node | Go | other] +Build tool: [Poetry | npm | make | other] +``` + +______________________________________________________________________ + +## Step 1: Understand the Project + +Read the codebase to extract onboarding-relevant facts: + +```bash +# Project purpose +head -50 README.md 2>/dev/null + +# Project structure +find . -maxdepth 3 -type d \ + | grep -v node_modules | grep -v .venv | grep -v __pycache__ \ + | grep -v .git | grep -v dist | grep -v build \ + | sort | head -40 + +# Dependencies and Python version +cat pyproject.toml 2>/dev/null | grep -E 'python|requires|dependencies' | head -20 +cat .python-version 2>/dev/null +cat .nvmrc 2>/dev/null +cat .node-version 2>/dev/null + +# CI configuration — what does CI run? +cat .github/workflows/*.yml 2>/dev/null | grep -E 'run:|uses:' | head -30 +``` + +______________________________________________________________________ + +## Step 2: Verify the Setup Steps Work + +Before documenting setup steps, verify they actually work: + +```bash +# Attempt setup from scratch perspective +# (Do not actually destroy the current environment — read and verify commands) + +# Check prerequisites are documented +which python3 || which python && python --version +which poetry && poetry --version +which node && node --version +which make && make --version +``` + +For each setup step, confirm: + +- [ ] The command exists and works +- [ ] Dependencies are version-pinned or constrained +- [ ] Environment variables are documented (use `.env.example` if present) +- [ ] The setup completes in < 5 minutes on a fresh machine + +______________________________________________________________________ + +## Step 3: Extract Test Commands + +```bash +# Find all test commands +cat Makefile 2>/dev/null | grep -E 'test|lint|check|verify' | head -20 +cat pyproject.toml 2>/dev/null | grep -E '\[tool\.' -A 5 | head -40 +cat package.json 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); [print(k,':',v) for k,v in d.get('scripts',{}).items()]" 2>/dev/null +``` + +Document the minimal set a contributor needs: + +```text +Quick check (before every commit): [command] +Full test suite: [command] +Lint only: [command] +Type check only: [command] +Single test: [command pattern] +``` + +______________________________________________________________________ + +## Step 4: Identify "Good First Issues" + +```bash +# Find TODOs and FIXMEs +grep -r -n "TODO\|FIXME\|HACK\|good.first" \ + --include='*.py' --include='*.ts' --include='*.go' --include='*.md' \ + --exclude-dir=node_modules --exclude-dir=.venv --exclude-dir=.git \ + . 2>/dev/null | head -20 + +# Check GitHub issues if available +# (manual step — list any open "good first issue" labels) +``` + +______________________________________________________________________ + +## Step 5: Write the Onboarding Guide + +Produce or update `CONTRIBUTING.md` with the following sections: + +______________________________________________________________________ + +````markdown +# Contributing to [Project Name] + +Welcome. This guide gets you from zero to a merged PR. + +## Prerequisites + +| Tool | Version | Install | +|------|---------|---------| +| Python | >= X.Y | [pyenv](https://github.com/pyenv/pyenv) | +| Poetry | >= X.Y | `pip install poetry` | +| make | any | system package manager | + +## Setup + +```bash +git clone https://github.com/org/repo +cd repo +[setup command — e.g. make bootstrap or poetry install] +``` + +Verify setup: + +```bash +[verify command — e.g. make check or poetry run pytest] +``` + +Expected output: `[N tests passed]` + +## Project Structure + +``` +[directory tree — top 2 levels with brief descriptions] +``` + +## Development Workflow + +### Making changes + +1. Create a branch: `git checkout -b [type]/[short-description]` + - `feat/` — new feature + - `fix/` — bug fix + - `chore/` — maintenance +1. Make your change +1. Run checks: `[check command]` +1. Commit: `git commit -m "[type]: [description]"` +1. Push and open a PR + +### Before every commit + +```bash +[pre-commit or check command] +``` + +This runs: [lint, type check, tests — describe what is checked] + +## Testing + +```bash +# Run all tests +[full test command] + +# Run a single test file +[single test command] + +# Run with coverage +[coverage command] +``` + +Tests live in `tests/`. Mirror the source structure: `src/foo/bar.py` → `tests/foo/test_bar.py`. + +## Environment Variables + +Copy `.env.example` to `.env` and fill in values: + +```bash +cp .env.example .env +``` + +| Variable | Required | Description | +| ---------- | -------- | ------------- | +| `VAR_NAME` | Yes | [description] | + +## Architecture Overview + +\[2–4 sentences describing the main components and how they interact. +Link to docs/architecture/architecture.md for details.\] + +## Good First Issues + +\[List 3–5 concrete starting points: + +- A TODO in the code +- A missing test +- A documentation gap +- A small enhancement\] + +## Getting Help + +[Slack channel / GitHub Discussions / email — whatever is appropriate] + +```` + +______________________________________________________________________ + +______________________________________________________________________ + +## Step 6: Supplement README (if needed) + +If README lacks a dev setup section, add a minimal one linking to CONTRIBUTING.md: + +````markdown +## development + +See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, testing, and contribution guidelines. + +Quick start: + +```bash +[one-liner setup command] +[one-liner test command] +``` + +```` + +______________________________________________________________________ + +## Output + +```text +Onboarding Guide Summary +════════════════════════ + +Files produced/updated: + ✅ CONTRIBUTING.md — [new | updated] + ✅ README.md — [updated dev section | no change needed] + +Coverage: + [ ] Prerequisites documented + [ ] Setup steps verified + [ ] Test commands documented + [ ] Environment variables documented + [ ] Project structure explained + [ ] Contribution workflow explained + [ ] Good first issues listed + +Gaps remaining (if any): + [anything that could not be determined automatically] +``` diff --git a/src/vstack/_templates/skills/openapi/config.yaml b/src/vstack/_templates/skills/openapi/config.yaml new file mode 100644 index 0000000..324d922 --- /dev/null +++ b/src/vstack/_templates/skills/openapi/config.yaml @@ -0,0 +1,17 @@ +name: openapi +version: 1.0.0 +description: | + Write and review OpenAPI 3.1 specifications. Covers resource naming, HTTP + method semantics, status codes, error conventions, pagination, versioning, + security schemes, and schema validation. Use when asked to "write an OpenAPI + spec", "review this API spec", "add an endpoint to the spec", or "validate + this OpenAPI file". +argument-hint: '[API or spec file to write or review]' + +license: MIT +compatibility: Requires a skills-compatible agent with repository file access and terminal command execution when needed. +metadata: + owner: vstack + maturity: stable +user-invocable: true +disable-model-invocation: false diff --git a/src/vstack/_templates/skills/openapi/template.md b/src/vstack/_templates/skills/openapi/template.md new file mode 100644 index 0000000..3c02211 --- /dev/null +++ b/src/vstack/_templates/skills/openapi/template.md @@ -0,0 +1,382 @@ +{{SKILL_CONTEXT}} + +# openapi — OpenAPI 3.1 Specification + +Write or review OpenAPI 3.1 specifications. The spec is the contract — it must +be precise, complete, and implementable without ambiguity. + +## Out of scope + +- Service architecture decisions (use `architecture`) +- Full API design from scratch (use `design`) +- Security audit of the implementation (use `security`) +- Contract compliance of the implementation (use `verify`) + +**Golden rule: The spec is the source of truth. Code must conform to the spec, +not the other way around.** + +______________________________________________________________________ + +## Step 0: Understand the Task + +> **Question:** What needs to be done? +> +> **Options:** +> A) Write a new OpenAPI spec from scratch +> B) Review and improve an existing spec +> C) Add endpoints to an existing spec +> D) Validate spec for correctness and completeness +> **Default:** B — review existing spec + +```bash +# Locate existing spec files +find . -name 'openapi*.yaml' -o -name 'openapi*.json' \ + -o -name 'swagger*.yaml' -o -name 'swagger*.json' \ + 2>/dev/null | grep -v node_modules | grep -v .venv | head -10 + +# Validate spec if tooling is available +[ -f openapi.yaml ] && npx @redocly/cli lint openapi.yaml 2>/dev/null || true +[ -f openapi.yaml ] && npx swagger-cli validate openapi.yaml 2>/dev/null || true +``` + +______________________________________________________________________ + +## Part 1: Spec Structure + +Every OpenAPI 3.1 spec must have: + +```yaml +openapi: "3.1.0" + +info: + title: Service Name API + version: "1.0.0" + description: | + One paragraph describing the service purpose. + contact: + name: Team Name + email: team@example.com + +servers: + - url: https://api.example.com/v1 + description: Production + - url: https://api.staging.example.com/v1 + description: Staging + +tags: + - name: users + description: User management + +paths: {} + +components: + schemas: {} + securitySchemes: {} + +security: [] +``` + +Check: + +- [ ] `openapi` field is `"3.1.0"` (not 3.0.x or 2.x) +- [ ] `info.version` follows semver +- [ ] At least one server defined +- [ ] Tags defined at root level and used consistently on operations +- [ ] `components` section exists for reusable schemas + +______________________________________________________________________ + +## Part 2: Resource & Path Design + +### Naming conventions + +| Correct | Incorrect | Rule | +| -------------------------- | -------------------- | --------------------------- | +| `/users` | `/getUsers`, `/user` | Plural nouns, no verbs | +| `/users/{userId}` | `/users/{id}` | Descriptive path parameters | +| `/users/{userId}/orders` | `/userOrders` | Nested for ownership | +| `/orders/{orderId}/cancel` | `/cancelOrder/{id}` | Sub-resource for actions | + +### HTTP method semantics + +| Method | Semantics | Idempotent | Body | +| -------- | ------------------------------- | ---------- | ---- | +| `GET` | Read, no side effects | Yes | No | +| `POST` | Create or non-idempotent action | No | Yes | +| `PUT` | Full replace | Yes | Yes | +| `PATCH` | Partial update | No | Yes | +| `DELETE` | Remove | Yes | No | + +Check each path: + +- [ ] Path uses plural noun, no verbs +- [ ] Path parameters are descriptive (`userId` not `id`) +- [ ] HTTP method matches semantics above +- [ ] `GET` operations have no request body +- [ ] `DELETE` operations return `204 No Content` or `200` with body, never `201` +- [ ] Every operation has a unique `operationId` (camelCase, e.g. `listUsers`, `createOrder`) +- [ ] Every operation has a `summary` (short title, ≤ 80 chars) and `tags` + +______________________________________________________________________ + +## Part 3: Status Codes + +Use exactly these status codes — no others unless justified: + +| Code | Meaning | When to use | +| --------------------------- | ----------------------------- | ------------------------------------- | +| `200 OK` | Success with body | `GET`, `PUT`, `PATCH` success | +| `201 Created` | Resource created | `POST` creating a resource | +| `202 Accepted` | Accepted for async processing | Background jobs | +| `204 No Content` | Success, no body | `DELETE`, `POST` with no return | +| `400 Bad Request` | Validation error | Invalid input | +| `401 Unauthorized` | Not authenticated | Missing/invalid token | +| `403 Forbidden` | Not authorized | Valid token, insufficient permissions | +| `404 Not Found` | Resource missing | ID doesn't exist | +| `409 Conflict` | State conflict | Duplicate create, optimistic lock | +| `422 Unprocessable Entity` | Semantic validation | Business rule violation | +| `429 Too Many Requests` | Rate limited | Include `Retry-After` header | +| `500 Internal Server Error` | Unexpected error | Never expose internals | + +Check: + +- [ ] Every operation documents all realistic status codes +- [ ] `200` vs `201` vs `204` used correctly +- [ ] `401` and `403` are distinct and documented +- [ ] `500` is documented but never includes stack traces + +______________________________________________________________________ + +## Part 4: Error Response Schema + +Every error response must use a consistent schema: + +```yaml +components: + schemas: + Error: + type: object + required: [code, message] + properties: + code: + type: string + description: Machine-readable error code + example: "VALIDATION_ERROR" + message: + type: string + description: Human-readable description + example: "email must be a valid email address" + details: + type: array + description: Field-level validation errors + items: + type: object + required: [field, message] + properties: + field: + type: string + example: "email" + message: + type: string + example: "must be a valid email address" + request_id: + type: string + description: Correlation ID for tracing + example: "req_01HZ..." +``` + +Check: + +- [ ] All `4xx` and `5xx` responses reference `$ref: '#/components/schemas/Error'` +- [ ] Error schema has a machine-readable `code` field +- [ ] `request_id` for correlation is present +- [ ] Field-level errors included for `400`/`422` + +______________________________________________________________________ + +## Part 5: Pagination + +Standard cursor-based pagination (preferred for large datasets): + +```yaml +# Query parameters +parameters: + - name: cursor + in: query + schema: + type: string + description: Opaque cursor from previous response + - name: limit + in: query + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + +# Response envelope +components: + schemas: + PaginatedUsers: + type: object + required: [data, pagination] + properties: + data: + type: array + items: + $ref: '#/components/schemas/User' + pagination: + type: object + required: [has_more] + properties: + has_more: + type: boolean + next_cursor: + type: string + nullable: true + total: + type: integer + description: Total count (expensive — omit if not needed) +``` + +Check: + +- [ ] Pagination is cursor-based (not offset for large collections) +- [ ] `limit` has a maximum and default +- [ ] Response includes `has_more` and `next_cursor` +- [ ] `total` is optional (expensive query — only include if needed) + +______________________________________________________________________ + +## Part 6: Schema Quality + +For each schema in `components/schemas`: + +```yaml +components: + schemas: + User: + type: object + required: [id, email, created_at] # ← explicit required list + properties: + id: + type: string + format: uuid + readOnly: true # ← readOnly for server-generated fields + example: "01HZ..." + email: + type: string + format: email + example: "user@example.com" + name: + type: string + minLength: 1 + maxLength: 255 + example: "Alice" + created_at: + type: string + format: date-time + readOnly: true + example: "2026-01-01T00:00:00Z" +``` + +Check: + +- [ ] All schemas have a `required` list (no implicit optionals) +- [ ] `id`, `created_at`, `updated_at` are `readOnly: true` +- [ ] All string fields have `minLength`/`maxLength` where appropriate +- [ ] All fields have an `example` +- [ ] `format` used for `uuid`, `email`, `date-time`, `uri` +- [ ] No `type: object` without properties (use `additionalProperties` explicitly) +- [ ] No circular `$ref` without a nullable break + +______________________________________________________________________ + +## Part 7: Security Schemes + +```yaml +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + apiKey: + type: apiKey + in: header + name: X-API-Key + +security: + - bearerAuth: [] # Global default +``` + +Override at operation level where needed: + +```yaml +paths: + /health: + get: + security: [] # Public endpoint — no auth required +``` + +Check: + +- [ ] Security scheme is defined in `components.securitySchemes` +- [ ] Global `security` set at root level +- [ ] Public endpoints explicitly override with `security: []` +- [ ] OAuth2 scopes are defined if using OAuth +- [ ] No API keys in query parameters (use headers) + +______________________________________________________________________ + +## Part 8: Versioning + +URI versioning is the recommended approach: + +```yaml +servers: + - url: https://api.example.com/v1 +``` + +Check: + +- [ ] Version in server URL (`/v1`, `/v2`) +- [ ] No minor versions in URL (`/v1.1` is wrong — use headers for minor) +- [ ] Deprecated operations tagged with `deprecated: true` +- [ ] Deprecated operations have a `x-sunset` date + +```yaml +/users/{userId}: + get: + deprecated: true + x-sunset: "2027-01-01" + description: "Deprecated. Use /v2/users/{userId} instead." +``` + +______________________________________________________________________ + +## Output + +Produce a review report or the corrected spec: + +**Review report format:** + +```text +OpenAPI Spec Review +═══════════════════ + +Spec: [filename] +Version: [openapi version] +Paths: [count] +Schemas: [count] + +Issues: + 🔴 CRITICAL: [issue] — [path/operation] + 🟡 WARNING: [issue] — [path/operation] + 🟢 INFO: [suggestion] + +Summary: [N critical, N warnings, N info] +``` + +**If writing or correcting the spec:** produce the complete corrected YAML, using +`$ref` for all reusable schemas, and validate it passes linting. diff --git a/src/vstack/_templates/skills/refactor/config.yaml b/src/vstack/_templates/skills/refactor/config.yaml new file mode 100644 index 0000000..6cce8a9 --- /dev/null +++ b/src/vstack/_templates/skills/refactor/config.yaml @@ -0,0 +1,18 @@ +name: refactor +version: 1.0.0 +description: | + Structured refactoring for backend services, APIs, and libraries. Identifies + code smells, plans incremental changes, executes without altering observable + behavior, and verifies correctness. Use when asked to "refactor this", + "clean up this module", "reduce duplication", or "improve structure without + changing behavior". Never changes behavior — if behavior must change, stop + and use the engineering role. +argument-hint: '[module, file, or area to refactor]' + +license: MIT +compatibility: Requires a skills-compatible agent with repository file access and terminal command execution when needed. +metadata: + owner: vstack + maturity: stable +user-invocable: true +disable-model-invocation: false diff --git a/src/vstack/_templates/skills/refactor/template.md b/src/vstack/_templates/skills/refactor/template.md new file mode 100644 index 0000000..8b38eb6 --- /dev/null +++ b/src/vstack/_templates/skills/refactor/template.md @@ -0,0 +1,283 @@ +{{SKILL_CONTEXT}} + +{{BASE_BRANCH}} + +# refactor — Structured Refactoring + +Improve the internal structure of code without changing its observable behavior. +Refactoring is not rewriting. Every step must leave tests green. + +## Out of scope + +- Fixing bugs (use `debug`) +- Adding features (engineering role) +- Performance optimizations that change behavior (use `performance`) +- Architecture redesign (use `architecture`) +- Security fixes (use `security`) + +**Golden rule: If all tests pass before and after each step, the refactor is +correct. If behavior changes, stop — that is a feature or bug fix, not a refactor.** + +______________________________________________________________________ + +## Step 0: Define the Scope + +> **Question:** What needs refactoring and why? +> +> - Which file(s), module(s), or area(s)? +> - What is the motivation? (duplication, complexity, naming, coupling, size) +> - What must NOT change? (public API, behavior, performance characteristics) +> **Default:** Identify smells in the specified area and propose a plan. + +```bash +# Understand the current state +git log --oneline -10 +git diff --stat 2>/dev/null | head -20 + +# Find the files to refactor +# Read the area before touching anything +``` + +Document: + +```text +Scope: [file(s) or module(s)] +Motivation: [duplication | complexity | naming | coupling | size | other] +Constraints: [what must not change] +``` + +______________________________________________________________________ + +## Step 1: Establish a Baseline + +**Never start refactoring without a green test baseline.** + +{{RUN_TESTS}} + +If tests are red before you start: + +```text +⛔ STOP: Tests are failing before the refactor begins. + Fix failing tests first, or confirm with the user that the failures are + pre-existing and unrelated to this refactor scope. +``` + +Record the baseline: + +```text +Baseline: + Tests: [N passed, N failed, N skipped] + Coverage: [N%] + Lint: [clean | N warnings] +``` + +______________________________________________________________________ + +## Step 2: Identify Code Smells + +Scan the target area for common smells: + +```bash +# Long files +wc -l $(find . -name '*.py' -o -name '*.ts' -o -name '*.go' \ + 2>/dev/null | grep -v node_modules | grep -v .venv) 2>/dev/null | sort -rn | head -20 + +# Duplicated patterns +grep -r -n "TODO\|FIXME\|HACK\|XXX" \ + --include='*.py' --include='*.ts' --include='*.go' \ + --exclude-dir=node_modules --exclude-dir=.venv --exclude-dir=.git \ + . 2>/dev/null | head -20 +``` + +| Smell | Description | Refactoring | +| ----------------------- | ------------------------------------- | --------------------------- | +| Long function | > 30 lines, multiple responsibilities | Extract function | +| Long file | > 300 lines | Extract module | +| Duplicate code | Same logic in 2+ places | Extract shared function | +| Magic numbers/strings | Unnamed literals | Named constant | +| Deep nesting | > 3 levels | Early return / guard clause | +| Long parameter list | > 4 parameters | Parameter object | +| Inappropriate naming | Misleading or vague names | Rename | +| Dead code | Unused functions/variables | Remove | +| Comment explaining code | Code needs a comment to be understood | Rewrite the code | +| Mutable global state | Module-level mutable variables | Encapsulate | + +Produce a prioritized smell list: + +```text +Smells found: + P1 (high impact): [smell] — [location] + P2 (medium): [smell] — [location] + P3 (low): [smell] — [location] +``` + +______________________________________________________________________ + +## Step 3: Plan the Refactoring + +Break the refactoring into small, independent steps. Each step must: + +1. Change exactly one thing +1. Leave tests green +1. Be reviewable in isolation + +```text +Refactoring plan: + Step 1: [specific change] — [file] — [smell addressed] + Step 2: [specific change] — [file] — [smell addressed] + Step 3: ... + +Estimated scope: [N files, N functions] +Risk: [Low | Medium — reason] +``` + +**Stop and confirm with user if:** + +- The plan requires changing a public API or exported interface +- The plan requires changing database schema +- More than 10 files are affected + +______________________________________________________________________ + +## Step 4: Execute — One Step at a Time + +For each planned step: + +1. Make the change +1. Run tests immediately +1. Confirm green before moving to the next step + +{{RUN_TESTS}} + +### Common refactoring patterns + +**Extract function (Python):** + +```python +# Before +def process_order(order): + # validate + if not order.get("id"): + raise ValueError("missing id") + if order.get("amount", 0) <= 0: + raise ValueError("amount must be positive") + # process + ... + +# After +def _validate_order(order: dict) -> None: + if not order.get("id"): + raise ValueError("missing id") + if order.get("amount", 0) <= 0: + raise ValueError("amount must be positive") + +def process_order(order: dict) -> None: + _validate_order(order) + ... +``` + +**Guard clause (replace nested conditionals):** + +```python +# Before +def process(item): + if item is not None: + if item.active: + if item.value > 0: + return item.value * 2 + return None + +# After +def process(item): + if item is None: + return None + if not item.active: + return None + if item.value <= 0: + return None + return item.value * 2 +``` + +**Named constant:** + +```python +# Before +if status_code == 429: + time.sleep(60) + +# After +HTTP_TOO_MANY_REQUESTS = 429 +RATE_LIMIT_BACKOFF_SECONDS = 60 + +if status_code == HTTP_TOO_MANY_REQUESTS: + time.sleep(RATE_LIMIT_BACKOFF_SECONDS) +``` + +**Parameter object:** + +```python +# Before +def create_user(name, email, role, department, manager_id, start_date): + ... + +# After +@dataclass +class CreateUserRequest: + name: str + email: str + role: str + department: str + manager_id: str + start_date: date + +def create_user(request: CreateUserRequest) -> User: + ... +``` + +______________________________________________________________________ + +## Step 5: Verify + +After all steps are complete, run the full verification suite: + +{{RUN_TESTS}} + +Check: + +- [ ] All tests still pass (same count as baseline) +- [ ] Coverage has not dropped +- [ ] No new lint warnings introduced +- [ ] No public interfaces changed (unless explicitly in scope) +- [ ] No behavior changes (verify with diff) + +```bash +# Review what changed +git diff --stat +git diff +``` + +______________________________________________________________________ + +## Output + +```text +Refactoring Summary +═══════════════════ + +Scope: [file(s) / module(s)] +Smells: [N identified] +Steps: [N completed] + +Changes made: + ✅ [Step 1 description] — [file] + ✅ [Step 2 description] — [file] + ... + +Result: + Tests: [N passed — same as baseline] + Coverage: [N% — unchanged or improved] + Lint: [clean] + +Public API changed: No +Behavior changed: No +``` diff --git a/src/vstack/cli/constants.py b/src/vstack/cli/constants.py index 4cd865e..c734404 100644 --- a/src/vstack/cli/constants.py +++ b/src/vstack/cli/constants.py @@ -23,4 +23,10 @@ "guardrails", "container", "cicd", + "migrate", + "openapi", + "refactor", + "onboard", + "dependency", + "incident", ] diff --git a/tests/conftest.py b/tests/conftest.py index f9b8ac9..359cd93 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,6 +34,12 @@ "guardrails", "container", "cicd", + "migrate", + "openapi", + "refactor", + "onboard", + "dependency", + "incident", ]