diff --git a/.claude/settings.json b/.claude/settings.json index d4564235..5c2b4e44 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -10,6 +10,14 @@ "Bash(ruff check *)", "Bash(ruff format *)", "Bash(mypy *)", + "Bash(pyright *)", + "Bash(vulture *)", + "Bash(deptry *)", + "Bash(lint-imports *)", + "Bash(xenon *)", + "Bash(diff-cover *)", + "Bash(prettier *)", + "Bash(swiftlint *)", "Bash(pytest *)", "Bash(pre-commit run *)", "Bash(./scripts/check.sh)", @@ -36,7 +44,18 @@ "mcp__assemblyai-docs__search_docs", "mcp__assemblyai-docs__get_pages", "mcp__assemblyai-docs__get_api_reference", - "mcp__assemblyai-docs__list_sections" + "mcp__assemblyai-docs__list_sections", + "mcp__github__pull_request_read", + "mcp__github__list_pull_requests", + "mcp__github__search_pull_requests", + "mcp__github__get_commit", + "mcp__github__list_commits", + "mcp__github__get_file_contents", + "mcp__github__actions_list", + "mcp__github__actions_get", + "mcp__github__get_job_logs", + "mcp__github__issue_read", + "mcp__github__list_issues" ], "deny": [ "Read(.env)", diff --git a/.claude/skills/check/SKILL.md b/.claude/skills/check/SKILL.md index 1e0efd9e..93bec6a1 100644 --- a/.claude/skills/check/SKILL.md +++ b/.claude/skills/check/SKILL.md @@ -16,7 +16,9 @@ Run the project's canonical verification gate and report the result. ./scripts/check.sh ``` - This runs, in order: `ruff check` → `ruff format --check` → `mypy` (src + tests) → `markdownlint` (excludes generated `docs/`) → `shellcheck install.sh` → `pytest` with a **90% branch-coverage gate** (`--cov-fail-under=90`, excluding `e2e` and `install_script` markers) → `uv build` + `twine check --strict`. Everything runs through `uv run` against the locked environment. + This runs, in order: `uv lock --check` → `ruff check` → `ruff format --check` → `mypy` (src + tests) → `pyright` (src strict, then tests) → `vulture` (dead code) → `deptry` (dependency hygiene) → `lint-imports` (architecture contracts) → `xenon` (cyclomatic complexity, max grade B / project avg A) → `swiftlint` + swift compile (macOS only) → `markdownlint` (excludes generated `docs/`) → `prettier` (init template JS/CSS) → `shellcheck install.sh scripts/check.sh` → generated `--show-code` compile gate → init template contract gate → `pytest` with a **90% branch-coverage gate** (`--cov-fail-under=90`, excluding `e2e`/`install`/`install_script` markers) → `diff-cover` (100% patch coverage vs `origin/main`) → a "no new escape hatches" diff gate → `uv build` + `twine check --strict`. Everything Python runs through `uv run` against the locked environment. + + Heads-up on the stages `ruff`+`mypy` don't cover: `vulture` flags unused code, `deptry` flags unused/missing/misplaced dependencies, `lint-imports` enforces the import-architecture contracts in `.importlinter`, and `xenon` fails any function over cyclomatic-complexity grade B (CC > 10). These are the ones that most often surprise an otherwise-clean change. 2. If anything fails, fix it and re-run `./scripts/check.sh` until it passes. Do not claim success until the script prints `All checks passed.` @@ -31,5 +33,6 @@ uv run pytest -m install_script # builds a wheel and runs install.sh for real; ## Notes -- If `shellcheck` isn't installed locally, `check.sh` skips it with a notice (CI still runs it) — that's expected, not a failure. +- External linters that aren't Python deps — `shellcheck`, `prettier`, `swiftlint`/`swiftc` — self-skip with a notice when not installed (CI still runs them); that's expected, not a failure. `swiftlint`/`swiftc` also no-op off macOS. +- `diff-cover` and the escape-hatch gate self-skip when `origin/main` isn't present (e.g. a shallow branch-only clone); CI provides the base ref. - Report the final outcome with the actual tail of the output, not a summary from memory. diff --git a/AGENTS.md b/AGENTS.md index e2298080..be6a6f00 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,9 +11,11 @@ This project uses [uv](https://docs.astral.sh/uv/). **Run every Python tool thro ```sh uv sync --extra dev # create/refresh the venv with dev dependencies uv run aai --help # run the CLI from the locked environment -./scripts/check.sh # the full gate CI runs: ruff + mypy + markdownlint + prettier + shellcheck + pytest(+coverage) + build/twine +./scripts/check.sh # the full gate CI runs (scripts/check.sh is the source of truth) ``` +`scripts/check.sh` is the authoritative gate; keep this list in sync with it. It runs, in order: `uv lock --check` → `ruff check` → `ruff format --check` → `mypy` → `pyright` (src strict) → `pyright` (tests) → `vulture` (dead code) → `deptry` (dependency hygiene) → `lint-imports` (import-linter architecture contracts) → `xenon` (cyclomatic complexity, max grade B / project avg A) → `swiftlint` + swift compile (macOS only, skipped elsewhere) → `markdownlint` → `prettier` (init template JS/CSS) → `shellcheck` → generated `--show-code` compile gate → init template contract gate → `pytest` (90% branch coverage) → `diff-cover` (100% patch coverage vs `origin/main`) → a "no new escape hatches" diff gate (`# type: ignore` / `# noqa` / `pragma: no cover` / net-new `Any` / `cast(`) → `uv build` + `twine check --strict`. The `vulture`/`deptry`/`lint-imports`/`xenon` and patch-coverage stages catch the failures that `ruff`+`mypy` alone won't — don't claim the gate is green until the script prints `All checks passed.` + Individual tools (all via `uv run`): ```sh @@ -37,6 +39,8 @@ uv run pytest -m install_script # builds a wheel and runs install.sh for real; `check.sh` runs `-m "not e2e and not install_script"` with a **90% branch-coverage gate** (`--cov-fail-under=90`). New code generally needs tests to clear that gate. +CLI output is pinned by **syrupy snapshot tests** (`tests/__snapshots__/*.ambr`). Changing help text, tables, or rendered output will fail those tests until you regenerate them with `uv run pytest --snapshot-update` and commit the updated `.ambr` files. The auto-format hook only touches `*.py`, and pre-commit's whitespace fixers deliberately skip `tests/__snapshots__/` (syrupy's indentation must stay byte-for-byte), so never hand-edit a snapshot — always regenerate. + ## Naming & packaging gotchas - The **package/module** is `aai_cli`; the **distribution** name is `aai-cli`; the **console command** is `aai` (`[project.scripts] aai = "aai_cli.main:run"`). diff --git a/scripts/check.sh b/scripts/check.sh index eb696d56..8fe87b71 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -93,7 +93,13 @@ echo "==> markdownlint (docs/ is generated, so excluded)" markdownlint "**/*.md" --ignore docs --ignore node_modules --ignore .pytest_cache echo "==> prettier (init template JS/CSS)" -prettier --check "aai_cli/init/templates/**/*.{js,css}" +# CI's runner has prettier on PATH; locally it's skipped with a notice if not +# installed, matching how shellcheck/swiftlint self-skip above. +if command -v prettier >/dev/null 2>&1; then + prettier --check "aai_cli/init/templates/**/*.{js,css}" +else + echo " prettier not found; skipping (CI runs it)" +fi echo "==> shellcheck (install.sh)" # Static-lint the public install script and this gate script. CI's ubuntu runner ships shellcheck;