
Check
- 11.8k installs
- 6.6k repo stars
- Updated July 26, 2026
- tw93/waza
A review output grounded in diff, project context, live command output, and verification evidence. Includes findings (cited file:line, trigger, severity), fixes (safe_auto applied, gated_auto batched, manual presented),
About
Check is a code-review skill that analyzes diffs, PRs, issue queues, and release readiness with safety gates for dirty worktrees. Developers invoke it to review changes before merge ("review", "看看代码", "合并前"), triage issues/PRs in batch, audit project-wide code quality with scorecards, or gate release decisions. It reads project context from public docs (README, AGENTS.md, CI workflows), extracts review constraints, runs verification commands, and outputs findings grounded in diff and live evidence. Modes include default PR review, plan execution follow-through, triage batching, release worthiness analysis, project audits, and ship/release confirmation workflows. Core contract: findings and decisions are traced to worktree status, diff content, and command output; user work (modified/staged/untracked files) is never moved or discarded without explicit approval.
- Worktree-safe review: reads git status, preserves dirty/untracked user work, serializes commits before pushing
- Multi-mode dispatch: default PR review, plan execution, triage batching, release analysis, and project-wide audit modes
- Evidence-grounded findings: every claim cites file:line, trigger condition, and why existing guards do not prevent it
- Release gate matrix: verifies version sync, generated artifacts, package contents, CI state, and registry before shippin
- Pattern-hunt and sibling-sweep: flags recurring bug classes and ensures fixes are applied across all instances in the re
Check by the numbers
- 11,754 all-time installs (skills.sh)
- +611 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #26 of 1,382 Code Review & Quality skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
check capabilities & compatibility
- Capabilities
- parse and analyze diffs for correctness, safety, · extract project constraints from readme, agents. · run verification commands and surface test/build · detect pattern class bugs (injection, missing va · verify generated artifacts, version sync, and re · triage issues and prs with action first disposit · audit project wide code quality via signal extra · gate commits, pushes, tags, and releases with sa · preserve user work (dirty/untracked files) and s
- Works with
- github
- Use cases
- code review · testing · debugging · ci cd · security audit
- Platforms
- macOS · Linux · WSL
- Runs
- Runs locally
- Pricing
- Free
What check says it does
Done when: findings, fixes, shipped state, or blockers are stated with the commands, artifacts, or remote state that prove them.
Every verification claim in the sign-off must point to a command that actually ran in this session.
npx skills add https://github.com/tw93/waza --skill checkAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 11.8k |
|---|---|
| repo stars | ★ 6.6k |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 26, 2026 |
| Repository | tw93/waza ↗ |
What it does
Review code diffs, pull requests, and project quality before merging or releasing.
Who is it for?
Code review workflows, PR gating, release readiness checks, issue triage in batch, project-wide code-quality audits, and pre-commit safety verification.
Skip if: Exploratory debugging (use hunt or debug skill), prose/document review (use /write), root-cause analysis without a fix candidate, or review of code not yet in a git worktree.
When should I use this skill?
User asks: 'review', '看看代码', 'check changes', 'before merge', 'release', 'audit', '项目体检', 'triage', 'commit', 'push', 'publish', or provides a /think plan to implement.
What you get
User gets a reviewed, verified, and (if approved) shipped change with findings grounded in evidence and durable project constraints documented.
- Code review report
- Safe applied fixes
- Merge or release readiness verdict
By the numbers
- Supports 6 dispatch modes: default review, plan execution, triage, release analysis, project audit, and ship follow-thro
- Detects hard stops (injection, credentials, unseen identifiers, version skew, dependency changes, safety-sink validation
- Reviews up to 500+ lines and 10+ files with specialist parallelization (security, architecture, performance, compliance)
Files
Check: Review Before You Ship
Prefix your first line with 🥷 inline, not as its own paragraph.
Update check (non-blocking). Before starting, run bash ../../scripts/check-update.sh once; if it prints a line, relay it to the user, then continue. It runs at most once a day, only reads a public version file, sends no data, and fails silently.
Note:/reviewis a built-in Anthropic plugin command for PR review. Waza uses/check(or the aliascode-review) instead. Do not re-trigger/reviewfrom within this skill.
Read the diff, find the problems, fix what can be fixed safely, ask about the rest. Done means verification ran in this session and passed.
Outcome Contract
- Outcome: a review, release decision, or maintainer action grounded in the current diff, project context, and live evidence.
- Done when: findings, fixes, shipped state, or blockers are stated with the commands, artifacts, or remote state that prove them.
- Evidence: worktree status, diff, public project docs, manifests, CI, package contents, release or registry state, and current command output.
- Output: concise findings first, then verification and shipped-state summary when applicable.
Worktree Safety Preflight
Before any review, triage, ship, release, or PR operation, read the current worktree with:
git status --short --branch -uallTreat modified, staged, and untracked files as user work. You may read them and include them in the review surface, but you must not move, hide, overwrite, clean, or discard them without explicit user approval in the current turn.
Do not run these commands as default review or PR setup: git switch, git checkout, git reset --hard, git clean, git stash -u, git stash --include-untracked, git stash -a, git stash --all, or gh pr checkout. If a branch change or cleanup is genuinely required, stop and ask for that exact operation.
Do not "protect" user work by moving untracked files, generated files, screenshots, or local scratch files into /tmp or another holding directory. Moving someone else's WIP out of the checkout is the same class of interference as stashing it. If a clean tree is required for generation, packaging, or verification, use a separate worktree from a known commit and copy only the artifact or patch you own back into the current checkout.
For commit or push follow-through in a dirty or multi-agent checkout, record git rev-parse HEAD before staging. Re-read git status --short --branch -uall and git rev-parse HEAD immediately before commit and again before push. If HEAD moved, unknown commits appeared, or the worktree changed outside your intended files, stop and report the mismatch instead of rebasing, recommitting, or pushing.
For PR inspection, prefer commands that do not switch the current working tree: gh pr view, gh pr diff, git fetch origin pull/<n>/head:refs/tmp/pr-<n>, and git merge-tree.
Mode Picker
Pick the mode that matches the user's intent, then read that section in full. Modes layer on top of the shared review surface (Scope, Hard Stops, Autofix, Specialist Review, Verification, Sign-off) further down.
| User intent | Mode |
|---|---|
"implement this plan", /think output handed off | Plan Execution |
| Diff or PR ready, "review", "看看代码", "合并前" | Default review (start at Get the Diff) |
| "look at issues", "review PRs", "triage", "批量处理" | Triage Mode |
| "is this worth a release", "值不值得发版" | Release Worthiness Analysis |
| "commit", "push", "publish", "release", "close issue", "发布表情" | Ship / Release Follow-through |
| "audit", "项目体检", "项目评分", "给项目打分", "深入分析项目代码", "scorecard", "linus review" | Project Audit |
| Document, PDF, prose review | Delegate to /write (see Document Review) |
Before any mode, run Project Context Extraction and (if memory is in scope) Durable Context Preflight.
Project Context Extraction
This is Waza's public, standalone code-review capability. It should not depend on private machine paths or unpublished project instructions.
Before reviewing, extract project constraints from repository context:
1. Read the diff and identify changed languages, frameworks, manifests, generated outputs, release files, and CI workflows. 2. Inspect public project files only as needed: README, AGENTS/CLAUDE instructions when present, package manifests, lockfiles, build configs, test configs, workflow files, and release notes. 3. Compress the findings into review context: verification commands, protected or generated files, release artifacts, domain risks, and public reply rules. 4. Apply the stricter rule when project context and this skill overlap. 5. If project docs or CI name a verification command, prefer that over auto-detection.
For the context shape, see references/project-context.md.
For release or maintainer work, also fill the Release Gate 2.0 matrix from references/project-context.md. It covers review base, dirty/staged/untracked state, latest tag, origin sync, version fields, generated artifacts, package/archive contents, release assets, registry/appcast/CI, and public issue/PR state. Missing matrix evidence is a blocker for a "ready to release" claim.
Durable Context Preflight
See rules/durable-context.md for when to read durable context, the read-order budget, and the memory-type mapping.
For /check, private task constraints are decision, preference, and principle entries; review checklists are pattern and learning. Current code, diff, public docs, CI, tests, and remote state override memory. Durable memory can explain user intent and preferred follow-through, but public project rules still come from README files, manifests, CI workflows, release docs, the diff, and explicit instructions in the current thread. Never cite private memory as a public project requirement.
Plan Execution Mode
Activate when the user's message starts with "Implement the following plan", "按计划实施", "按照计划", "整", "可以干", "直接改" followed by a plan body, or links to a /think output.
In this mode, do not run a code review. Instead:
1. State which plan is being executed (first heading or summary line). 2. Check for obvious repo drift: run git status --short --branch -uall and skim any changed files that contradict the plan. If drift makes the plan unsafe, name the specific conflict and stop. 3. Work through each plan item as a to-do. Mark each complete as you go. 4. After all items are done, run the project's verification command. 5. Transition automatically into Ship mode if the project context or current thread indicates review-then-ship.
Default Continuation (review-then-ship)
When the project's AGENTS.md or the current thread explicitly asks to "commit after review", "ship if green", or equivalent, transition directly from review to the Ship flow after a clean review. Do not ask again. State "proceeding to ship" before acting.
Get the Diff
Get the full diff between the current branch and the base branch. If unclear, ask. If already on the base branch, ask which commits to review.
Triage Mode
Activate when the user mentions: issue, PR, "review all", triage, "batch", or "批量处理". Skip the diff flow and run this instead.
Action-first rule: Items with a clear disposition (already fixed, duplicate, already released) get acted on immediately without analysis paragraphs. When analyzing screenshots or images, state what you see and the suggested action in one message. Only ask the user when the disposition is genuinely ambiguous.
Bundled request classification: When one issue, PR, or support thread contains several asks, split them before acting: core bug, existing affordance, cosmetic preference, and out-of-scope request. Fix or close only the validated core bug; answer existing affordances with the current path; defer or decline cosmetic and out-of-scope asks instead of treating the whole report as a to-do list.
Status answer order: For "都解决了吗", "is this fixed", "is this ready", or similar status checks, answer in this order: code or commit state, branch or CI state, release artifact or registry state, then public issue or PR state. Do not collapse fixed-on-main, available in pre-release, next stable release, and already shipped.
Flow: First identify the project's issue/PR host from public context. For GitHub projects, pull open items with gh issue list -R <repo> --state open --limit 20 and gh pr list -R <repo> --state open. For non-GitHub projects, use the platform CLI/API named by the project docs or user request; if none exists, stop and report the missing integration instead of pretending GitHub commands apply. For each item, check if a fix already shipped: git log --oneline <latest-tag>..HEAD | grep -i "<keyword>". If shipped: close with note. If merged but unreleased: reply "已修复,等下一个版本 release" and close. If no fix: analyze and act. Fix now if possible (fix: closes #N commit); when the target project documents a nightly, beta, or pre-release channel that already contains the fix, reply with that exact upgrade path and close; for valid-but-unreleased items acknowledge and leave open; for invalid items give one-two sentence reason and close.
Before final conclusions in a live queue, refresh the issue/PR list once more and re-read any item that changed during the run. If evidence is incomplete, hold the item instead of closing it on a guess.
PR handling: If the PR direction is accepted but the patch needs changes, prefer pushing the maintainer's fixes to the contributor's PR branch and then merging the PR. Check maintainerCanModify first, then confirm the push remote, target branch, and current HEAD immediately before pushing so you do not overwrite contributor work or push maintainer fixes to the wrong repository. If branch edits are not allowed, ask the contributor to enable maintainer edits or push the needed revision; only fall back to a separate maintainer commit when timing or release safety requires it, and say so in the PR. Close without merging only when the direction is rejected, unsafe, no longer needed, or explicitly not part of the project's scope. Do not silently absorb an accepted PR into main and close it.
Public reply shape: load references/public-reply.md for the full template (mention, single thanks, factual paragraphs, next-release step, editing rules, closure criteria). Ship Mode uses the same template; the file is the single source.
Sign-off line (append to standard sign-off):
triage: N reviewed, N closed, N deferredRelease Worthiness Analysis
Activate when the user asks "深入分析 X 是不是值得发新版本", "is this worth a new release", "值不值得发版", or similar.
1. Run git log <last-tag>..HEAD --oneline (find last tag with git tag --sort=-version:refname | head -1). 2. Count and classify commits: feat (new feature), fix (bug fix), chore/docs/refactor (internal). 3. Output:
- Commit summary: N feat, N fix, N chore since last release
- Verdict: release / skip (one line)
- Recommended version bump: patch (fixes only), minor (feat present), major (breaking change)
- Key risk: one sentence on the biggest risk in this batch
4. If verdict is "release", offer to transition into Ship mode.
Ship / Release Follow-through
Activate when the user asks to commit, tag, release, publish, push, reply on an issue/PR, or close an issue after a change is ready.
This mode extends review; it does not skip review. Before any public or irreversible action:
1. Extract release rules from public project context: README, manifests, CI workflows, release notes, package scripts, changelogs, and explicit user instructions in the current thread. 2. Fill the Release Gate 2.0 matrix from references/project-context.md: review base, dirty/staged/untracked state, latest tag, origin sync, version fields, generated artifacts, package/archive contents, release assets, registry/appcast/CI, and public issue/PR state. 3. Verify generated or bundled outputs, version fields, release notes, package contents, and required artifacts are in sync. Prefer dry-run commands when the ecosystem provides them. Generated deliverables include tracked archives, ignored dist files, appcasts, site/download copy, registry packages, checksums, and release assets. If project docs require them, regenerate, inspect, and stage or upload them explicitly even when they are ignored by git; do not infer readiness from source-only tests. For remote assets, prefer downloading or reading back the published artifact and comparing entries, checksums, or manifest contents; release page text, file size, or workflow success alone is not artifact proof. If the project has preview, beta, nightly, stable, or App Store lanes, name the lane explicitly. Do not use a preview or beta artifact to claim stable release readiness, and do not touch stable appcast, registry, or download surfaces when the requested lane is preview-only unless project docs require it. 4. Commit only intended files. Preserve unrelated dirty work, serialize git operations so index locks or overlapping adds do not corrupt the workflow, and re-check HEAD/status before pushing so concurrent agent or maintainer commits are not swept into your ship action. 5. Push, publish, tag, or create a release only when the user has explicitly approved that action. If auth, OTP, CI, registry, or network state blocks the operation, pause and report the exact blocker. 6. For issue/PR follow-through, confirm the item identity with the host's read command before posting. On GitHub, use gh issue view or gh pr view; on other hosts, use the CLI/API named by project docs or the current request. Use references/public-reply.md for the maintainer reply template (mention, single thanks, facts, explicit next release or verification step) and its closure criteria. 7. For GitHub release reaction follow-through, only do it when project context or the current thread asks for it. After the release exists and required assets are verified, resolve the release id from the tag, POST every positive release reaction to repos/<owner>/<repo>/releases/<id>/reactions with gh api or the available GitHub tool, and re-read reactions to confirm. Positive release reactions are +1, laugh, heart, hooray, rocket, and eyes. 8. After network or API failures, re-read the end state instead of assuming success or failure.
Reworked Or Cancelled Release Gate
Activate this gate when a release candidate was cancelled, a preview or beta had repeated bug-fix churn, or the user asks whether a delayed release is finally safe.
1. Lock the review base to the last public stable tag or release artifact, then review through current HEAD. Do not limit the review to recent commits or the latest local diff. 2. Record the exact base, HEAD, dirty state, origin sync, version fields, generated artifacts, release notes, package contents, CI, and remote distribution state. If any state changes mid-review, refresh the range and rerun the fast gates. 3. Review by shipped risk surface: user-reported regressions, crash or hang paths, destructive operations, privilege or permission boundaries, background workers, startup or first-frame work, update feeds, package contents, and public support claims. 4. Output two release decisions, not one: whether the preview or beta can keep taking user testing, and whether stable release prep can start. 5. Every conclusion must name blockers, deferrable maintenance, commands that ran, and runtime or user-smoke coverage. Source tests alone cannot prove a reworked UI/native release ready.
End with the concrete shipped state: commit hash, tag, release URL, registry/version result, pushed branch, release asset state, release reaction state, issue/PR state, and any remaining blockers. Omit fields that do not apply.
Project Audit Mode
Activate when the user asks for a project-wide code-quality scorecard: "audit", "项目体检", "代码质量评分", "scorecard", "linus 风格 review". Distinct from Default Review (PR/diff scoped) and Triage (issue batching). Single-pass project-wide quality assessment.
Flow
1. Run python3 <waza>/skills/check/scripts/audit_signals.py --root <project> from the target repo. The script emits labelled blocks (=== FILE SIZE HOTSPOTS === ... === DENYLIST IN BUILD ===) each ending with status: PASS|WARN|FAIL|N/A. 2. Skim the largest source files surfaced by FILE SIZE HOTSPOTS (typically 3-5; stop sooner if the architecture is already clear). 3. Read CLAUDE.md / AGENTS.md / README.md to learn the project's own stated conventions before judging it against generic ones. 4. Apply the four-axis rubric below. Each axis is independently scored 0-10. Overall = arithmetic mean. 5. Surface 3-7 concrete findings per axis. Each finding: file:line citation when possible, severity (CRIT/STRUCT/INCR), one-line fix. 6. Output to terminal only. Do not create files in the target repo. If the user follows up with "save it", offer ./docs/<project>-audit.md then; default is ephemeral.
Rubric
| Axis | What it covers |
|---|---|
| Architecture | Module boundaries, coupling, abstraction layers vs flat duplication, single source of truth |
| Code Quality | File size discipline, dedup, readability, comments on non-obvious behavior |
| Engineering | Tests, CI gates, version coordination, install URL pinning, packaging posture |
| Perf and Risk | Hazards, scope creep, distribution risk, privacy posture, third-party blast radius |
Scoring anchors
- 9-10: exceptional discipline, polish-only items
- 7-8.5: solid with clear targeted improvements
- 5-7: working but with structural debt
- below 5: significant rework recommended
A WARN that the project has explicitly justified (in its own docs or a comment) is not a finding; cite the justification and skip. Do not mechanically convert WARN to CRIT. A block with status: N/A means the surface does not exist (e.g. no packaging script); treat as silence, not as a positive signal.
Output template (terminal)
Project: <name>
Overall: X.X / 10
Architecture: X / 10 -- one-line summary
Code Quality: X / 10 -- one-line summary
Engineering: X / 10 -- one-line summary
Perf & Risk: X / 10 -- one-line summary
Findings
[CRIT] <file:line> -- <issue>
why: <reason grounded in signal or read>
fix: <concrete action>
[STRUCT] ...
[INCR] ...
Top 3 highest-leverage moves
1. ...
2. ...
3. ...Stop after the report unless the user asks for follow-up implementation. Audit mode does not modify files in the target repo.
Scope
Measure the diff and classify depth:
| Depth | Criteria | Reviewers |
|---|---|---|
| Quick | Under 100 lines, 1-5 files | Base review only |
| Standard | 100-500 lines, or 6-10 files | Base + conditional specialists |
| Deep | 500+ lines, 10+ files, or touches auth/payments/data mutation | Base + all specialists + adversarial pass |
State the depth before proceeding.
Static content diffs can stay quick even when they touch several generated files: version strings, dates, release-copy mirrors, sitemap dates, or one-for-one localization copy changes usually need line-by-line readback plus grep consistency, not a specialist fleet. Escalate only when the diff changes logic, generation rules, public distribution behavior, or user-facing semantics beyond the literal text replacement.
Did We Build What Was Asked?
Before reading code, check scope drift: do the diff and the stated goal match? Label: on target / drift / incomplete.
Also check surgical traceability: every changed file and every new public surface must trace back to the user's stated goal. If a file, dependency, config knob, abstraction, generated artifact, workflow permission, or release behavior cannot be explained in one sentence from the request, label it drift until proven necessary.
Drift signals (examples, not exhaustive -- any one is enough to label drift):
- A changed file has no connection to the stated goal
- The diff includes pure refactoring (renames, formatting, restructuring) when the goal was a bug fix or feature
- A new dependency appears that the goal did not mention
- Code unrelated to the goal was deleted or commented out
- A new abstraction or helper was introduced that is not required by the goal
- A maintainability, review, or cleanup change quietly adds user-visible UI, default config, workflow permissions, or release behavior
Pattern-Fix Completeness
When the diff fixes one instance of a class-of-bug (a missing validation, a wrong selector, an off-by-one, a missing lock), the same shape often lives elsewhere. Extract the pattern signature, grep -rn it across the repo (exclude generated dirs), and confirm sibling instances were also handled. List any unswept sibling: flag it as a hard stop when it carries the same risk, advisory when lower-risk. For a deeper sweep playbook, see hunt's Scope Blast Mode.
Testability Seam For Recurring Bugs
When the diff fixes a visual, layout, timing, or stateful-UI bug that has recurred (the same area broke before, or the fix reads as "tune a number until it looks right"), a code change alone will let the regression return: the logic is entangled with mutable render or UI state, so there is nowhere to assert on it. Flag the fix as incomplete unless it pulls the decision into a pure function -- inputs in, value out, no mutable receiver -- and unit-tests the invariant that was violated (a width never collapses to zero, a hit region stays half-open, an offset stays in bounds). "Verified by running the app" confirms this one instance; only a pinned invariant stops the next one. Reserve this for classes that recur or that runtime checks cannot see; do not demand a seam for one-off logic that already has straightforward coverage.
CLI Command Surface
When a diff touches a CLI entrypoint, installer, completion, config/env handling, package wrapper, or a mutating command such as cleanup, update, uninstall, migration, or cache removal, fill the CLI Command Surface from references/project-context.md before sign-off.
Check command contract and installed-runtime behavior, not just library tests: help/version, subcommands/flags, exit codes, stdout/stderr, JSON/schema output, TTY/non-interactive paths, env/config precedence, shebang/executable bit, PATH shim, and package-manager install path when applicable.
For mutating CLI commands, also run the Safety Sink Review: dry-run or confirmation path, operation log or rollback story, retry/idempotency, signal/partial-failure handling, and test-mode guards for auth prompts or real system changes. For cleanup, uninstall, prune, reset, or cache-removal commands, add two checks before approval: can a normal user verify each selected item is safe, and is the deleted content locally rebuildable rather than a downloaded dependency or user data? If either answer is no, require narrower matching, explicit user selection, or leave the item visible but non-destructive.
Hard Stops (fix before merging)
Examples, not exhaustive -- flag any diff that could cause irreversible harm if merged unreviewed.
- No unverified claims. Do not write "I verified X", "I ran Y", "tests pass", or "this fixes Z" unless the shell output is in this turn's transcript. If you reason about behavior without running, say "based on reading the code" instead of "I verified". Every verification claim in the sign-off must point to a command that actually ran in this session.
- Re-read before citing source-of-truth facts. Before writing a line number, dirty-file count, branch ahead/behind state, fallback behavior, locale coverage, or release artifact state into a handoff or review report, re-read the source in this turn (
git status,git diff, fileRead,rg, command output). Earlier chat context, prior agent's notes, and your own recall from a hundred turns ago are stale by default; restating "the catalog uses en fallback" or "the file is at line 310" without checking has been the recurring failure mode in long sessions. Cite the verification path inline (per current Read of <file>/pergit statusthis turn) so reviewers know which facts are anchored. - String-matching on captured output? When a diff branches on, greps, or classifies an error message or command output, verify what that string actually holds at runtime before approving. A subprocess spawned with
stdio: 'inherit'(or any uncaptured pipe) streams its diagnostics to the terminal, not intoerror.message-- which then contains only the command line. Such a matcher silently matches the command, not the output: it can pass tests, fire on the wrong token, or be dead in production while looking correct. Probe the realerror.message(a one-line repro) instead of assuming, and prefer driving behavior off a structured fact the caller already holds (build target, exit code) over re-parsing a string. - Destructive auto-execution: any task marked "safe" or "auto-run" that modifies user-visible state (history files, config, preferences, installed software) must require explicit confirmation.
- Release artifacts missing: verify every artifact listed in release notes, release templates, or project workflows exists and has been uploaded before declaring done.
- Generated artifact drift: if source changes require generated or bundled outputs, verify the output was regenerated and included.
- Verifier failure layer unclear: if a verifier fails before assertions or due to missing optional dependencies, bootstrap noise, transient build-service crashes, unavailable simulators, or tool setup, classify setup versus product failure. Retry only with new evidence or a narrower environment. Do not call the repo broken until the intended test body or artifact check actually ran.
- Tracked package omissions: if a package script builds from tracked files, allowlists, or generated manifests, verify every new helper module, reference file, template, or script used by the diff is tracked and present in the built archive before sign-off.
- Version skew: release version fields across manifests, package metadata, app configs, changelogs, tags, or lockfiles must stay synchronized.
- Unknown identifiers in diff: any function, variable, or type introduced in the diff that does not exist in the codebase is a hard stop. Grep before writing or approving any reference:
grep -r "name" .-- no results outside the diff = does not exist. - Dead-code or YAGNI deletion without proof: any "zero callers" or "unused" claim must be checked across the whole repository, including top-level entrypoints, docs, tests, generated dispatch tables, scripts, CI, and dynamic lookup patterns. Treat sub-agent or tool reports as leads, not proof. Before deleting, batch-grep all candidates, classify test-only references separately from production references, and chase written variables or data tables that may become orphaned together. If the grep scope is partial, do not delete.
- Injection and validation: SQL, command, path injection at system entry points. Credentials hardcoded, logged, committed, or copied into public docs.
- Dependency changes: unexpected additions or version bumps in package.json, Cargo.toml, go.mod, requirements.txt. Flag any new dependency not obviously required by the diff.
- Safety sinks: destructive file operations, shell or AppleScript construction, cwd/path/symlink traversal, approval or sandbox boundary changes, signing/appcast flows, and auth prompts need explicit review of validation, rollback, and user-confirmation behavior.
- Audit before restore: when the diff re-adds a symbol, string, asset, or config field that recent history removed, grep the rest of the diff and the main branch to confirm anything still uses it. A rule file that names the symbol is not proof of life. If only a parity test references it, the rule is stale and the restore is wrong; reject the restore and flag the stale rule. Specifically suspicious: re-adding an enum case, xcstrings entry, dictionary key, or asset file that the prior commit deleted intentionally.
- AI-generated PR with broad matchers in destructive sinks: any PR that introduces
find-like recursion, mass-delete, sandbox/container traversal, ID-prefix wildcards, or fallback regex branches feeding a destructive sink, and was likely AI-generated, must be reviewed line-by-line for three things: matcher breadth in every branch (fallback paths often regress to broad globs even when the primary branch is correct), protected-path coverage (does the existing guard list include this new entry point?), and whether the change bypasses an existing user-confirmation step. Generic plausibility is not safety. When in doubt, ask the contributor to narrow the matcher to an exact constant (exact bundle ID, exact app name, exact path), not a prefix or wildcard; do not approve "this looks fine." - Migration code for features that did not ship before: reject migration scaffolding, version-gated defaults, or "carry old key forward" logic when the underlying preference / schema / feature was introduced in this same release.
git show v<last-release>:<path>is the gate: if the key is absent from the last tag, no migration is needed; ship the default. Migration code added for a never-shipped key is dead-on-arrival complexity.
Finding Quality Gate
Before writing any finding into the report, run this gate:
Pre-report self-check (four questions, every finding must pass): 1. Can I cite the exact file:line? 2. Can I describe the specific input or state that triggers the bad outcome? 3. Have I read the upstream callers / downstream consumers, not just the function in isolation? 4. Is the severity defensible? Would a senior reviewer raise this at this level in a real PR?
If any answer is "no", drop the finding or downgrade it to advisory. Vague findings train the reader to ignore real ones.
A clean review is a valid review. Do not manufacture findings to justify the invocation. Zero findings with a stated review surface is a complete output. Padding the report with low-confidence noise is a worse outcome than reporting nothing.
HIGH and CRITICAL require three pieces of evidence: 1. The exact file:line where the bug lives. 2. The specific trigger: what input, state, or sequence produces the bad outcome. 3. Why existing guards (validation, type system, upstream catch, framework default) do not already prevent it.
Cannot supply all three? Downgrade to MEDIUM, or drop. "This might break under some condition" is not a HIGH.
Knowledge Sync
After reviewing the diff, check whether it introduces invariants not yet captured in project docs:
- New safety gate or path-guard rule → AGENTS.md
- New UI constraint (layout rule, animation, overlay registration) →
.claude/rules/*.md - New deploy/release step or artifact → AGENTS.md or
docs/ - New cross-file sync requirement (enum ↔ HTML anchors, Swift keys ↔ xcstrings) → AGENTS.md
- One-off review reports or diagnostic snapshots should not become durable docs as-is; extract the stable rule into AGENTS/CLAUDE/rules/references and drop the stale report from the commit.
Snapshot Report Routing
Treat review reports, scorecards, and diagnostic snapshots as evidence, not as source-of-truth docs. Before approving one:
1. Re-read the current diff or repo surface named by the report. If the claim is stale, exclude the report from the commit or rewrite it into a stable rule. 2. Keep project-specific commands, paths, protected areas, release rituals, and safety constraints in that project's public context. Do not promote them into Waza. 3. Promote only transferable review behavior into Waza: e.g. "check untracked files before readiness", "inspect generated package contents", or "turn one-off reports into invariants."
If found, either apply the doc update as safe_auto (when the invariant is clear from the diff) or flag it in the sign-off as doc debt. When no new invariants exist, sign-off says doc debt: none.
Specialist Review (Standard and Deep only)
Load references/persona-catalog.md to determine which specialists activate. Launch all activated specialists in parallel via the environment's agent or sub-agent facility when available, passing the full diff. If no parallel reviewer facility exists, run the specialist passes sequentially in the same session.
Merge findings: when two specialists flag the same code location, keep the higher severity and note cross-reviewer agreement. Findings on different code locations are never duplicates even if they share a theme.
Treat each specialist finding as a claim to verify, not a fact to act on. Before routing a finding to Autofix or sign-off, re-read the cited code this turn and confirm it is real and live: not already handled elsewhere, not consistent-by-design, not a latent-only risk labeled as a live bug. Parallel reviewers over-report from name-based inference and partial context; drop or downgrade what dissolves on direct read, and cite the verification path.
Autofix Routing
| Class | Definition | Action |
|---|---|---|
safe_auto | Unambiguous, risk-free: typos, missing imports, style inconsistencies | Apply immediately |
gated_auto | Likely correct but changes behavior: null checks, error handling additions | Batch into one user confirmation block |
manual | Requires judgment: architecture, behavior changes, security tradeoffs | Present in sign-off |
advisory | Informational only | Note in sign-off |
Apply all safe_auto fixes first. Batch all gated_auto into one confirmation block. Never ask separately about each one.
Adversarial Pass (Deep only)
"If I were trying to break this system through this specific diff, what would I exploit?" Four angles (see references/persona-catalog.md): assumption violation, composition failures, cascade construction, abuse cases. Suppress findings below 0.60 confidence.
Platform Operations
Use the platform tool that matches the project. For GitHub projects, prefer gh or the available GitHub integration and confirm CI passes before merging. For non-GitHub projects, derive the CLI/API from public project docs or the user's explicit platform context; do not force GitHub commands onto other hosts.
Verification
Run bash scripts/run-tests.sh from this skill directory, or the project's known verification command from the target repository. Paste the full output.
If the script exits non-zero or prints (no test command detected): halt. Do not claim done. Ask the user for the verification command before proceeding. If the user also cannot provide one, document this explicitly in the sign-off as verification: none -- no command available and flag it as a structural gap, not a pass.
For bug fixes: a regression test that fails on the old code must exist before the fix is done.
In a dirty or multi-agent checkout, a passing local build or test run is not proof your change is sound: unrelated WIP already in the tree can supply missing symbols, mask a break, or fail for reasons unrelated to you. Verify in isolation -- git worktree add --detach <known-good-commit>, git apply only the diff of the files you own, then build/test there. The clean isolated pass is the real signal; the contaminated local pass is not.
Gotchas
| What happened | Rule |
|---|---|
| Posted a public reply to the wrong issue or PR thread | Re-read the target with gh issue view N or gh pr view N and confirm title, author, and current state before acting |
| PR comment sounded like a report | 1-2 sentences, natural, like a colleague. Not structured, not AI-sounding. |
| PR comment used bullet points | Write as short paragraphs, one thought per paragraph; thank the contributor first |
| New file name duplicated a locale, platform, or suffix convention | Check the target directory's existing naming convention before creating or renaming files |
| Deployed without provider runtime or env checks | Follow the project's public deployment docs and compare provider config with local required env and runtime settings |
| Push failed from auth mismatch | Check git remote -v, current branch, and auth identity before the first push in a new project |
Document Review
For document, PDF, white paper, or prose review, route to /write (Document Review Mode). /check handles code diffs and release artifacts only.
Sign-off
files changed: N (+X -Y)
scope: on target / drift: [what]
review depth: quick / standard / deep
hard stops: N found, N fixed, N deferred
specialists: [security, architecture] or none
new tests: N
doc debt: none / AGENTS.md needs X / rules need Y
verification: [command] -> pass / failArchitecture Reviewer
You are an architecture specialist reviewing a code diff. Your job is finding structural problems that will compound over time: coupling that should not exist, contracts that will break callers, abstractions that leak, and dependencies that point the wrong direction.
You receive a diff. Return a list of findings only. No prose, no praise, no explanation beyond what is in each finding.
Focus Areas
Coupling: New dependencies between modules that should be independent. A component importing from a layer above it. Two features that could evolve independently now sharing state or a direct call.
Interface contracts: Changes to public APIs, exported types, or function signatures that break existing callers without a migration path. Optional parameters added in a position that shifts existing positional arguments.
Abstraction leaks: Implementation details exposed in a public interface. A type that forces callers to know about internal representation. A function that returns a raw database row where a domain object was expected.
Dependency direction: A core module importing from a peripheral one. Business logic importing from infrastructure. A shared utility importing from a feature module.
Scalability concerns: A design that works at current load but has a fixed bottleneck (single lock, single table scan, single process) that will fail under 10x load. Flag only if the bottleneck is introduced by this diff, not pre-existing.
Output Format
Return findings as a plain list. For each finding:
[SEVERITY] file:line -- {what the structural problem is}
Impact: {what gets harder or breaks as the system grows, one sentence}
Fix: {specific corrective action}
Class: architecture
Autofix: manualSeverity: HIGH (will cause a breakage or forces a rewrite), MEDIUM (will slow future development), LOW (worth noting, not urgent).
Scope Rules
Flag only issues introduced or made significantly worse by this diff. Do not re-report pre-existing structural problems unless the diff extends or entrenches them.
Suppress LOW confidence findings. If you cannot articulate a concrete consequence, do not file the finding.
Do not flag: security issues, performance micro-optimizations, missing tests, code style. Those belong to other reviewers.
Security Reviewer
You are a security specialist reviewing a code diff. Your job is finding vulnerabilities that would survive correctness review: injection paths, authentication bypass, credential exposure, and trust boundary violations.
You receive a diff. Return a list of findings only. No prose, no praise, no explanation beyond what is in each finding.
Focus Areas
Injection: SQL, command, path, LDAP, XSS. Trace every user-controlled value from entry point to sink. Flag cases where the value reaches a sink without sanitization or parameterization.
Authentication bypass: Routes or functions accessible without verifying identity. JWT or session checks that can be skipped by header manipulation. Permission checks applied after the sensitive operation rather than before.
Credential exposure: API keys, tokens, passwords in code, comments, log statements, or error messages. Environment variable names that reveal the existence of a secret without protecting its value.
Input validation gaps: Missing length checks, type checks, or format validation on fields that flow to storage or execution. Validation applied at the wrong layer (UI only, not API).
Trust boundary violations: Data from one trust zone (user input, external API, LLM output) used without sanitization in a higher-trust zone (database, shell, filesystem). Output from a lower-trust component treated as authoritative.
Output Format
Return findings as a plain list. For each finding:
[SEVERITY] file:line -- {what the vulnerability is}
Mechanism: {how it can be exploited, one sentence}
Fix: {specific corrective action}
Class: security
Autofix: manualSeverity: CRITICAL (exploitable now), HIGH (exploitable with effort), MEDIUM (hardening gap), LOW (defense-in-depth).
Scope Rules
Flag only issues introduced or made worse by this diff. Do not re-report pre-existing issues unless the diff makes them materially easier to exploit.
Suppress findings below HIGH confidence. A finding without a concrete exploit path is noise. State the exploit path or do not file the finding.
Do not flag: code style, missing tests, performance issues, architectural concerns. Those belong to other reviewers.
Specialist Reviewer Activation Catalog
The orchestrator reads the full diff and uses judgment (not keyword matching) to decide which specialists to activate. This catalog defines the signals to reason about.
Always-On (no condition required)
The base /check skill runs as always-on. Specialist reviewers are additive.
Conditional Specialists
Security Reviewer
Agent file: agents/reviewer-security.md Activate at: Standard or Deep depth
Activate when the diff touches:
- Authentication or authorization logic (middleware, guards, JWT handling, session management)
- Cryptographic operations (hashing, signing, encryption)
- Input handling at trust boundaries (form fields, API request bodies, URL parameters)
- File system operations on user-controlled paths
- Shell or subprocess execution
- Third-party credential or API key handling
- SQL queries or raw database access
Do not activate for: pure UI changes, config file updates, test-only changes, documentation.
Architecture Reviewer
Agent file: agents/reviewer-architecture.md Activate at: Standard or Deep depth
Activate when the diff:
- Adds a new module, package, or service boundary
- Changes a public API, exported type, or function signature
- Introduces a cross-module import that did not exist before
- Modifies more than 10 files across different directories
- Adds or removes a major dependency
- Restructures how components call each other
Do not activate for: single-file bug fixes, test additions, style changes, documentation updates.
Adversarial Pass (Deep only)
No separate agent. The orchestrator runs this as an extra reasoning pass after all findings are collected.
Activate at: Deep depth only (500+ lines changed, or explicit high-risk signals: auth, payments, data mutation, external API integration).
Adversarial pass asks: "If I wanted to break this system through this specific diff, what would I do?"
Four attack angles: 1. Assumption violation -- What does this code assume is always true? (format, ordering, range) What happens when it is not? 2. Composition failures -- What breaks when this new code interacts with the existing system under concurrent load or partial failure? 3. Cascade construction -- What sequence of valid operations leads to an invalid state? 4. Abuse cases -- What happens on the 1000th request, during a deployment, with two users editing the same resource simultaneously?
Report adversarial findings with confidence score. Suppress findings below 0.60.
Project Review Context Template
Use this template to compress repository context before running Waza /check. The context must come from public project files, the diff, CI configuration, or explicit user instructions. Do not depend on private machine paths or unpublished project instructions.
What Belongs In Waza /check
- Diff depth classification.
- Scope drift detection.
- Hard stops such as destructive automation, missing release artifacts, generated artifact drift, version skew, unknown identifiers, injection risks, credential leakage, and dependency surprises.
- Release Gate 2.0 matrix for release readiness.
- Safety sink review for destructive operations, command construction, path boundaries, signing/appcast, sandbox/approval, and auth prompts.
- Security and architecture specialist routing.
- Autofix policy.
- Sign-off format.
- Verification expectations.
What Belongs In Project Context
- Verification commands discovered from public docs, manifests, Makefiles, scripts, or CI workflows.
- Protected files and directories.
- Generated or bundled artifacts that must stay in sync with source changes.
- Packaging source of truth: whether archives are built from
git ls-files, explicit allowlists, generated manifests, or source directories. - Delivery surfaces: whether generated outputs are tracked, ignored, external release assets, registry uploads, appcasts, installer metadata, checksums, or site/download copy; how they are regenerated, inspected, staged, or uploaded.
- Distribution lanes: preview, beta, nightly, stable, App Store, or registry channels, and which generated artifacts belong to each lane.
- CLI command surfaces: entrypoints, subcommands, flags, help/version behavior, exit codes, stdout/stderr contract, TTY and non-interactive paths, config/env precedence, and installed-runtime checks.
- Runtime dependencies introduced by the diff: Python packages, CLIs, network services, package managers, or platform tools that are not already declared in CI/docs.
- Domain-specific safety rules.
- Release artifacts that must exist.
- GitHub release reactions or other public release follow-through expected by the project.
- Release-asset verification method: download, archive entry comparison, checksum manifest, package metadata readback, appcast readback, or registry query.
- Public issue or PR reply conventions.
- Known CI or test flakes documented by the project and how to distinguish them from real failures.
- Release, publish, push, or issue-closure prerequisites documented by the project.
What Does Not Belong In Public Context
- Credential paths, private key filenames, passwords, tokens, or secret values.
- Maintainer-only machine paths.
- One-off personal preferences that do not affect project behavior.
- One-off review reports, scorecards, or diagnostic snapshots copied as guidance instead of distilled into stable project rules.
- Raw memory, chat excerpts, screenshots, private support details, local paths, project-specific commands, issue/PR numbers, release tags, or commit hashes from another project.
- Full copies of Waza
/checksections.
Recommended Context Shape
## Project Commands
- Format: `<command>`
- Fast check: `<command>`
- Full verification: `<command>`
## CLI Command Surface
- Entrypoints: `<command or bin>`.
- Command contract: help/version, subcommands, flags, exit codes, stdout/stderr, JSON/schema output.
- Runtime shape: TTY vs non-interactive behavior, env/config precedence, completion/manpage or shell integration.
- Install/run proof: built package, temp prefix, PATH shim, shebang/executable bit, or package-manager path checked with `<command>`.
- Mutating commands: dry-run/confirmation, operation log, rollback/retry behavior, signal/partial-failure handling.
## Project Hard Stops
- Do not modify `<protected path>` unless explicitly requested.
- If `<artifact>` is generated from `<source>`, verify it was regenerated.
- If `<artifact>` is ignored by git but required for release, verify the regeneration and force-stage, upload, or registry publish path named by the project.
- If `<package script>` builds from tracked files or an allowlist, verify newly introduced helpers, references, templates, and scripts are included in `<archive>`.
- If an installer fetches remote content, verify the default ref is pinned to a release tag or checksum-protected; floating `main` must be an explicit override.
- If a helper introduces a non-stdlib package or external CLI, verify CI installs it or the helper fails with a clear setup path.
- If `<artifact>` is listed in release notes, verify it exists before sign-off.
## Project-Specific Risks
- `<risk>`: `<how to inspect it>`
## Public Replies
See `public-reply.md` for the full reply template (language match, `@user` + thanks, factual paragraphs, ship-state line, closure criteria). It is the single source; do not restate the rules here.
## Release Follow-through
- Version fields to check: `<manifest>`, `<app config>`, `<lockfile>`.
- Generated artifacts to check: `<artifact>` from `<source>`.
- Distribution lane: `<preview/beta/nightly/stable/etc.>` and which public surfaces it is allowed to touch.
- Dry-run command before publishing: `<command>`.
- Remote asset proof: `<download/readback command>` that checks content, manifest, digest, appcast, or registry state.
- GitHub release reactions to add after asset verification: `<+1/laugh/heart/hooray/rocket/eyes or none>`.
- Public state to re-read after publishing or closing: `<registry/release/issue URL or command>`.Keep this context brief. It should guide the review, not replace the review method.
Release Gate 2.0 Matrix
Fill this before claiming a change is release-ready. Use "n/a" only when the project clearly has no such surface.
| Surface | Evidence |
|---|---|
| Review base | Base branch, latest tag, and commit range reviewed |
| Worktree state | Dirty, staged, and untracked files accounted for |
| Remote state | origin/main or release branch sync checked |
| Version fields | Manifest, app config, changelog, appcast, and lockfile versions aligned |
| Distribution lane | Preview, beta, nightly, stable, registry, or app-store lane named, with unrelated lanes left untouched |
| Runtime dependencies | Newly introduced Python packages, CLIs, package managers, and network tools declared and available in CI |
| Generated artifacts | Tracked archives, ignored dist outputs, bundled/minified files, appcasts, installer metadata, checksums, and site/download copy regenerated or proven not needed |
| Package/archive contents | Built package inspected for required files, newly introduced helpers/references, and missing extras |
| Release assets | GitHub release, appcast, download archive, checksum, or installer assets downloaded or read back and verified beyond page text or file size |
| Registry/appcast | npm/crates/Homebrew/appcast/App Store or equivalent state re-read after publish |
| CI status | Latest required checks passed or blocker named |
| Issue/PR state | Target issue or PR re-read before commenting, closing, merging, or saying shipped |
Safety Sink Review
Any diff that touches one of these sinks needs explicit validation and rollback thinking:
- Deleting, moving, or overwriting user files, caches, history, preferences, or generated outputs.
- Building shell, AppleScript, SQL, URL, or filesystem paths from user input.
- Changing cwd handling, symlink resolution, path traversal guards, sandbox permissions, approval checks, or auth prompts.
- Changing signing, notarization, appcast, update, license, payment, or release asset generation.
Review the smallest entry point that reaches the sink, then the downstream call. If validation is missing or rollback is unclear, treat it as a hard stop.
Public Reply Shape (maintainer, issue or PR)
Reusable by both Triage Mode and Ship / Release Follow-through. Default to this shape unless AGENTS.md or CLAUDE.md in the target repo contradicts it.
1. Resolve @<login> from gh issue view / gh pr view --json author before posting. 2. Language: Match the opener's language when it is Chinese or English. If the opener used Japanese or Korean, use English for the maintainer reply unless project docs override. 3. Open with @<login> and at most one short thanks (感谢反馈, thank you for the report, etc.). Do not add closing thanks stacks (再次感谢, Thanks again, long courtesy endings). 4. One or two short paragraphs: factual reason, what shipped or what is blocked, no ceremony. 5. Always give a next step tied to releases or verification: next App Store or GitHub release, nightly upgrade command, cache path to clear once, or exactly what info is still needed. 6. Prefer editing an existing maintainer comment (PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}) when updating wording; avoid delete plus repost unless the old text must disappear from history.
When closing
Close only when the fix is shipped, already available in the latest release, the report is invalid, the report is a duplicate, or the maintainer explicitly asked for closure. Otherwise leave open with the next-release acknowledgement.
#!/usr/bin/env python3
"""Project audit signals (Phase 1) for /check audit mode.
Walks a project root and emits structured signal blocks to stdout.
Each block ends with `status: PASS|WARN|FAIL|N/A` so the LLM driving the
4-axis Linus-style scorecard can skim quickly.
Pure stdlib. Read-only. Exits 0 even on WARN/FAIL so the harness does
not confuse "finding surfaced" with "script broken".
Run as: python3 skills/check/scripts/audit_signals.py --root <path>
"""
from __future__ import annotations
import argparse
import json
import os
import re
import subprocess
import sys
from pathlib import Path
EXCLUDED_DIRS = {
".git", ".hg", ".svn", "node_modules", "dist", "build", ".next",
"__pycache__", ".turbo", "target", ".venv", "venv", "vendor",
"coverage", ".cache", ".parcel-cache", ".pytest_cache", ".mypy_cache",
".ruff_cache", "Pods", "Carthage", ".swiftpm", ".gradle",
}
SOURCE_EXTS = {
".py", ".swift", ".rs", ".go", ".ts", ".tsx", ".js", ".jsx", ".sh",
".bash", ".zsh", ".rb", ".java", ".kt", ".m", ".mm", ".vue", ".c",
".cc", ".cpp", ".h", ".hpp", ".cs",
}
HOTSPOT_LINES = 500
HOTSPOT_FAIL = 1500
HEREDOC_LINES = 100
DRIFT_WARN = 50
DRIFT_FAIL = 150
DUP_JACCARD = 0.70
MARKER_RE = re.compile(r"\b(TODO|FIXME|HACK|XXX)\b")
HEREDOC_OPEN_RE = re.compile(
r"(python3?|node|ruby|perl|php)\b[^|\n]*?<<-?\s*['\"]?(\w+)['\"]?"
)
INSTALL_URL_RE = re.compile(
r"raw\.githubusercontent\.com/[^/\s]+/[^/\s]+/([^/\s]+)/"
)
# --exclude requires = or trailing value to avoid matching git's --exclude-standard
DENYLIST_HINT_RE = re.compile(
r"(^\s*(skip|exclude)\s*=|\s--exclude=|!\*\.\w+|grep\s+-v\b|--ignore=)",
re.IGNORECASE,
)
MINIFIED_RE = re.compile(r"\.min\.[a-z]+$", re.IGNORECASE)
CLI_CONTRACT_BUCKETS: tuple[tuple[str, re.Pattern[str]], ...] = (
("help_or_usage", re.compile(r"(--help|\busage\b|\bhelp output\b)", re.IGNORECASE)),
("version", re.compile(r"(--version|\bversion output\b)", re.IGNORECASE)),
("exit_code", re.compile(r"\b(exit code|exit status|return code|exit_code|\$\?)\b", re.IGNORECASE)),
("stdout", re.compile(r"\b(stdout|standard output)\b|>\s*\"\$?[A-Za-z0-9_./-]*stdout", re.IGNORECASE)),
("stderr", re.compile(r"\b(stderr|standard error)\b|2>\s*\"\$?[A-Za-z0-9_./-]*stderr", re.IGNORECASE)),
("non_interactive_or_tty", re.compile(r"\b(non-interactive|noninteractive|tty|isatty|/dev/null|CI=1)\b", re.IGNORECASE)),
(
"install_run",
re.compile(
r"(\binstall\s+-m\b|\binstalled command\b|\binstalled-runtime\b|"
r"\binstall/run\b|\binstall run\b|\btemp prefix\b|\bPATH shim\b|"
r"\bpackage-manager path\b|\bnpm link\b|\bpipx install\b|"
r"\bcargo install\b|\bbrew install\b|\bmake install\b)",
re.IGNORECASE,
),
),
("json_or_schema", re.compile(r"\b(json|schema)\b", re.IGNORECASE)),
("completion", re.compile(r"\bcompletion\b", re.IGNORECASE)),
)
CLI_CORE_BUCKETS = (
"help_or_usage",
"version",
"exit_code",
"stdout",
"stderr",
"install_run",
)
# The file-walk helpers below are deliberately duplicated in
# skills/health/scripts/check_maintainability.py. Both scripts ship
# standalone (see packaging.allowlist) and run inside an arbitrary target
# project, so they import only stdlib. Do not hoist them into a shared
# scripts/ module: it is dev-only, not on the ship allowlist, and would
# couple a standalone tool to the install layout.
def is_excluded(path: Path, root: Path) -> bool:
try:
parts = path.relative_to(root).parts
except ValueError:
parts = path.parts
if any(p in EXCLUDED_DIRS for p in parts):
return True
return bool(MINIFIED_RE.search(path.name))
def iter_files(root: Path) -> list[Path]:
try:
proc = subprocess.run(
["git", "-C", str(root), "ls-files",
"--cached", "--others", "--exclude-standard"],
text=True, stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL, check=False,
)
if proc.returncode == 0 and proc.stdout.strip():
out = []
for line in proc.stdout.splitlines():
p = root / line
if p.is_file() and not is_excluded(p, root):
out.append(p)
return out
except OSError:
pass
out = []
for dirpath, dirnames, filenames in os.walk(root):
current = Path(dirpath)
dirnames[:] = [d for d in dirnames if d not in EXCLUDED_DIRS]
if is_excluded(current, root):
continue
for fname in filenames:
p = current / fname
if p.is_file() and not is_excluded(p, root):
out.append(p)
return out
def line_count(path: Path) -> int:
try:
with path.open("rb") as fh:
return sum(1 for _ in fh)
except OSError:
return 0
def read_text(path: Path, limit: int = 0) -> str:
try:
data = path.read_text(encoding="utf-8", errors="replace")
except OSError:
return ""
return data[:limit] if limit else data
def rel(path: Path, root: Path) -> str:
try:
return path.relative_to(root).as_posix()
except ValueError:
return path.as_posix()
def header(name: str) -> None:
print(f"=== {name} ===")
def status(label: str) -> None:
print(f"status: {label}")
def block_hotspots(files: list[Path], root: Path) -> None:
header("FILE SIZE HOTSPOTS")
sized = ((p, line_count(p)) for p in files if p.suffix in SOURCE_EXTS)
big = sorted(
(item for item in sized if item[1] >= HOTSPOT_LINES),
key=lambda x: -x[1],
)[:10]
if not big:
print(f"(no source files >= {HOTSPOT_LINES} lines)")
status("PASS")
return
for path, n in big:
print(f" {n:>5} {rel(path, root)}")
status("FAIL" if any(n >= HOTSPOT_FAIL for _, n in big) else "WARN")
def block_heredoc(files: list[Path], root: Path) -> None:
header("HEREDOC BLOAT")
hits: list[tuple[str, int, str, int]] = []
for path in files:
if path.suffix not in {".sh", ".bash", ".zsh"}:
continue
text = read_text(path)
if not text:
continue
lines = text.splitlines()
i = 0
while i < len(lines):
m = HEREDOC_OPEN_RE.search(lines[i])
if not m:
i += 1
continue
lang, marker = m.group(1), m.group(2)
j = i + 1
close = re.compile(r"^\s*" + re.escape(marker) + r"\s*$")
while j < len(lines) and not close.match(lines[j]):
j += 1
size = j - i
if size >= HEREDOC_LINES:
hits.append((rel(path, root), i + 1, lang, size))
i = j + 1
if not hits:
print("(no python/node/ruby/perl/php heredocs >= 100 lines)")
status("PASS")
return
for f, ln, lang, sz in hits:
print(f" {f}:{ln} lang={lang} block_lines={sz}")
status("WARN")
def block_test_ci(files: list[Path], root: Path) -> None:
header("TEST AND CI SURFACE")
test_files = [
p for p in files
if p.suffix in SOURCE_EXTS
and (("test" in p.name.lower()) or ("spec" in p.name.lower()))
]
src_files = [p for p in files if p.suffix in SOURCE_EXTS]
wf_dir = root / ".github" / "workflows"
workflows = []
if wf_dir.is_dir():
workflows = sorted(list(wf_dir.glob("*.yml")) + list(wf_dir.glob("*.yaml")))
job_names: list[str] = []
for wf in workflows:
text = read_text(wf, 50_000)
for m in re.finditer(r"^name:\s*(.+?)\s*$", text, re.MULTILINE):
job_names.append(f"{wf.name}: {m.group(1)[:60]}")
break
ratio = len(test_files) / max(len(src_files), 1)
print(f"tests_count={len(test_files)} source_count={len(src_files)} "
f"ratio={ratio:.1%}")
print(f"ci_workflow_files={len(workflows)}")
for j in job_names[:10]:
print(f" workflow: {j}")
if not test_files and not workflows:
status("FAIL")
elif not test_files or not workflows:
status("WARN")
else:
status("PASS")
def _package_bin_entrypoints(root: Path) -> list[str]:
path = root / "package.json"
if not path.is_file():
return []
text = read_text(path, 200_000)
try:
data = json.loads(text)
except json.JSONDecodeError:
return []
bin_field = data.get("bin")
name = str(data.get("name") or "package")
if isinstance(bin_field, str):
return [f"package.json bin:{name} -> {bin_field}"]
if isinstance(bin_field, dict):
return [
f"package.json bin:{cmd} -> {target}"
for cmd, target in sorted(bin_field.items())
if isinstance(cmd, str) and isinstance(target, str)
]
return []
def _pyproject_script_entrypoints(root: Path) -> list[str]:
path = root / "pyproject.toml"
if not path.is_file():
return []
text = read_text(path, 200_000)
entries: list[str] = []
in_scripts = False
for line in text.splitlines():
stripped = line.strip()
if stripped.startswith("[") and stripped.endswith("]"):
in_scripts = stripped in {
"[project.scripts]",
"[tool.poetry.scripts]",
}
continue
if not in_scripts or not stripped or stripped.startswith("#"):
continue
m = re.match(r'([A-Za-z0-9_.-]+)\s*=\s*["\']([^"\']+)["\']', stripped)
if m:
entries.append(f"pyproject.toml script:{m.group(1)} -> {m.group(2)}")
return entries
def _cargo_entrypoints(root: Path) -> list[str]:
entries: list[str] = []
cargo = root / "Cargo.toml"
if cargo.is_file():
text = read_text(cargo, 200_000)
if "[[bin]]" in text:
names = re.findall(r'(?m)^\s*name\s*=\s*["\']([^"\']+)["\']', text)
if names:
entries.extend(f"Cargo.toml bin:{name}" for name in sorted(set(names)))
else:
entries.append("Cargo.toml [[bin]]")
if (root / "src" / "main.rs").is_file():
entries.append("src/main.rs")
return entries
def cli_entrypoints(files: list[Path], root: Path) -> list[str]:
entries: set[str] = set()
entries.update(_package_bin_entrypoints(root))
entries.update(_pyproject_script_entrypoints(root))
entries.update(_cargo_entrypoints(root))
for path in files:
try:
parts = path.relative_to(root).parts
except ValueError:
continue
if not parts:
continue
if parts[0] == "bin" and len(parts) >= 2:
entries.add("/".join(parts[:2]))
if parts[0] == "cmd" and len(parts) >= 3 and path.suffix == ".go":
entries.add(f"cmd/{parts[1]}")
return sorted(entries)
def _is_cli_contract_candidate(path: Path, root: Path) -> bool:
try:
parts = path.relative_to(root).parts
except ValueError:
return False
if not parts:
return False
lower_parts = tuple(p.lower() for p in parts)
name = lower_parts[-1]
if name in {"readme.md", "readme.txt", "agents.md", "claude.md"}:
return True
if lower_parts[0] in {"tests", "test", "spec", "scripts"}:
return True
if "test" in name or "spec" in name:
return True
if len(lower_parts) >= 3 and lower_parts[:2] == (".github", "workflows"):
return True
return False
def cli_contract_evidence(files: list[Path], root: Path) -> dict[str, list[tuple[str, str]]]:
hits: dict[str, list[tuple[str, str]]] = {}
for path in files:
if not _is_cli_contract_candidate(path, root):
continue
text = read_text(path, 200_000)
if not text:
continue
for bucket, pattern in CLI_CONTRACT_BUCKETS:
m = pattern.search(text)
if m:
hits.setdefault(bucket, []).append((rel(path, root), m.group(0)))
return {bucket: sorted(values) for bucket, values in sorted(hits.items())}
def block_cli_contract_surface(files: list[Path], root: Path) -> None:
header("CLI CONTRACT SURFACE")
entries = cli_entrypoints(files, root)
if not entries:
print("(no CLI entrypoints detected)")
status("N/A")
return
print(f"entrypoints={len(entries)}")
for entry in entries[:12]:
print(f" entry: {entry}")
if len(entries) > 12:
print(f" ... {len(entries) - 12} more")
evidence = cli_contract_evidence(files, root)
covered = tuple(bucket for bucket, _ in CLI_CONTRACT_BUCKETS if bucket in evidence)
missing = tuple(bucket for bucket in CLI_CORE_BUCKETS if bucket not in evidence)
print(f"covered={','.join(covered) if covered else 'none'}")
print(f"missing={','.join(missing) if missing else 'none'}")
printed = 0
for bucket in covered:
for path, signal in evidence[bucket][:3]:
print(f" evidence: {bucket} {path} signal={signal}")
printed += 1
if printed >= 12:
break
if printed >= 12:
break
if not missing:
status("PASS")
else:
status("WARN")
def _grep_version(path: Path, pattern: str) -> str | None:
text = read_text(path, 20_000)
if not text:
return None
m = re.search(pattern, text, re.MULTILINE)
return m.group(1).strip() if m else None
def block_version_sources(root: Path) -> None:
header("VERSION SOURCE COUNT")
found: list[tuple[str, str]] = []
v = root / "VERSION"
if v.is_file():
first = read_text(v).strip().splitlines()
if first:
found.append(("VERSION", first[0]))
probes = [
("package.json", r'"version"\s*:\s*"([^"]+)"'),
("Cargo.toml", r'^\s*version\s*=\s*"([^"]+)"'),
("pyproject.toml", r'^\s*version\s*=\s*"([^"]+)"'),
("setup.py", r"version\s*=\s*['\"]([^'\"]+)['\"]"),
]
for fname, pat in probes:
p = root / fname
if p.is_file():
v_str = _grep_version(p, pat)
if v_str:
found.append((fname, v_str))
for pat in ("*.podspec", "*.csproj"):
for path in root.glob(pat):
v_str = _grep_version(
path, r'(?i)version\s*[:=]\s*["\']?(\d+\.\d+\.\d+[\w.-]*)'
)
if v_str:
found.append((path.name, v_str))
for path in list(root.glob("build.gradle*")):
v_str = _grep_version(
path, r'(?i)version\s*[:=]\s*["\']?(\d+\.\d+\.\d+[\w.-]*)'
)
if v_str:
found.append((path.name, v_str))
if not found:
print("(no declared version source found)")
status("PASS")
return
for f, val in found:
print(f" {f}: {val}")
distinct = {val for _, val in found if val}
print(f"sources={len(found)} distinct_values={len(distinct)}")
if len(found) > 1 and len(distinct) > 1:
status("WARN")
else:
status("PASS")
def block_packaging_posture(root: Path) -> None:
header("PACKAGING FILTER POSTURE")
allowlist_files = list(root.glob("*.allowlist")) + list(root.glob("MANIFEST.in"))
pkg_scripts = (list(root.glob("scripts/package*.sh"))
+ list(root.glob("scripts/release*.sh")))
denylist_hits = 0
for sp in pkg_scripts:
for line in read_text(sp).splitlines():
if DENYLIST_HINT_RE.search(line):
denylist_hits += 1
if allowlist_files:
for f in allowlist_files:
print(f" allowlist: {rel(f, root)}")
print(f"posture=allowlist denylist_hits_in_scripts={denylist_hits}")
status("PASS")
return
if denylist_hits:
for sp in pkg_scripts:
print(f" script: {rel(sp, root)}")
print(f"posture=denylist denylist_hits_in_scripts={denylist_hits}")
status("WARN")
return
print("posture=none (no packaging scripts)")
status("N/A")
def block_install_url(root: Path) -> None:
header("INSTALL URL PINNING")
targets: list[Path] = [root / "README.md"]
targets += list(root.glob("scripts/setup*.sh"))
targets += list(root.glob("scripts/install*.sh"))
findings: list[tuple[str, int, str]] = []
for path in targets:
if not path.is_file():
continue
text = read_text(path, 200_000)
for i, line in enumerate(text.splitlines(), start=1):
for m in INSTALL_URL_RE.finditer(line):
findings.append((rel(path, root), i, m.group(1)))
if not findings:
print("(no raw.githubusercontent.com refs found)")
status("PASS")
return
moving = [f for f in findings if f[2] in ("main", "master", "HEAD")]
for f, ln, ref in findings[:20]:
marker = " [MOVING]" if ref in ("main", "master", "HEAD") else ""
print(f" {f}:{ln} ref={ref}{marker}")
print(f"total={len(findings)} moving={len(moving)}")
if moving:
status("WARN")
else:
status("PASS")
def block_agent_doc_dedup(root: Path) -> None:
header("AGENT DOC DEDUP")
claude = root / "CLAUDE.md"
agents = root / "AGENTS.md"
have_c = claude.exists() or claude.is_symlink()
have_a = agents.exists() or agents.is_symlink()
if not have_c and not have_a:
print("posture=none")
status("PASS")
return
if not (have_c and have_a):
print(f"posture=single-file ({'CLAUDE.md' if have_c else 'AGENTS.md'} only)")
status("PASS")
return
if claude.is_symlink() and claude.resolve(strict=False).name == "AGENTS.md":
print("posture=symlink (CLAUDE.md -> AGENTS.md)")
status("PASS")
return
if agents.is_symlink() and agents.resolve(strict=False).name == "CLAUDE.md":
print("posture=symlink (AGENTS.md -> CLAUDE.md)")
status("PASS")
return
a = read_text(claude)
b = read_text(agents)
if a and a == b:
print("posture=identical (consider symlink to dedup)")
status("WARN")
return
cross = ("AGENTS.md" in a) or ("CLAUDE.md" in b)
a_set = {ln.strip() for ln in a.splitlines()
if ln.strip() and not ln.strip().startswith("#")}
b_set = {ln.strip() for ln in b.splitlines()
if ln.strip() and not ln.strip().startswith("#")}
union = a_set | b_set
jaccard = len(a_set & b_set) / len(union) if union else 0.0
print(f"jaccard={jaccard:.2f} cross_refs={cross}")
if jaccard >= 0.20:
print("posture=divergent-overlap (drift risk; consider symlink)")
status("WARN")
return
if cross:
print("posture=cross-ref (one references the other)")
status("WARN")
return
print("posture=independent")
status("PASS")
def block_drift_markers(files: list[Path], root: Path) -> None:
header("DRIFT MARKERS")
counts: list[tuple[str, int]] = []
total = 0
for path in files:
if path.suffix not in SOURCE_EXTS:
continue
text = read_text(path, 200_000)
if not text:
continue
n = len(MARKER_RE.findall(text))
if n:
counts.append((rel(path, root), n))
total += n
counts.sort(key=lambda x: -x[1])
for f, n in counts[:5]:
print(f" {n:>4} {f}")
print(f"total={total}")
if total >= DRIFT_FAIL:
status("FAIL")
elif total >= DRIFT_WARN:
status("WARN")
else:
status("PASS")
def block_duplicate_setup(root: Path) -> None:
header("DUPLICATE SETUP SCRIPTS")
scripts = (list(root.glob("scripts/setup-*.sh"))
+ list(root.glob("scripts/install-*.sh")))
if len(scripts) < 2:
print("(fewer than 2 setup-* scripts to compare)")
status("N/A")
return
sets: dict[Path, set[str]] = {}
for sp in scripts:
sets[sp] = {ln.strip() for ln in read_text(sp).splitlines()
if ln.strip() and not ln.strip().startswith("#")}
pairs: list[tuple[str, str, float]] = []
names = list(sets.keys())
for i, a in enumerate(names):
for b in names[i + 1:]:
union = sets[a] | sets[b]
if not union:
continue
j = len(sets[a] & sets[b]) / len(union)
if j >= DUP_JACCARD:
pairs.append((rel(a, root), rel(b, root), j))
if not pairs:
print("(no setup pairs with jaccard >= 0.70)")
status("PASS")
return
for a, b, j in pairs:
print(f" {a} vs {b} jaccard={j:.2f}")
status("WARN")
def block_denylist_in_build(root: Path) -> None:
header("DENYLIST IN BUILD")
targets = (list(root.glob("scripts/package*.sh"))
+ list(root.glob("scripts/release*.sh"))
+ [root / "Makefile", root / "Justfile"])
real_targets = [p for p in targets if p.is_file()]
if not real_targets:
print("(no build scripts present)")
status("N/A")
return
hits: list[tuple[str, int, str]] = []
for path in real_targets:
text = read_text(path, 100_000)
for i, line in enumerate(text.splitlines(), start=1):
if DENYLIST_HINT_RE.search(line):
hits.append((rel(path, root), i, line.strip()[:80]))
if not hits:
print("(no denylist patterns found in build scripts)")
status("PASS")
return
for f, ln, s in hits[:20]:
print(f" {f}:{ln} {s}")
status("WARN")
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--root", type=Path, default=Path.cwd(),
help="Project root to audit (default: current working directory)",
)
args = parser.parse_args()
root = args.root.resolve()
if not root.is_dir():
print(f"audit_signals: not a directory: {root}", file=sys.stderr)
return 2
files = iter_files(root)
print(f"project_root: {root}")
print(f"files_scanned: {len(files)}")
print()
block_hotspots(files, root); print()
block_heredoc(files, root); print()
block_test_ci(files, root); print()
block_cli_contract_surface(files, root); print()
block_version_sources(root); print()
block_packaging_posture(root); print()
block_install_url(root); print()
block_agent_doc_dedup(root); print()
block_drift_markers(files, root); print()
block_duplicate_setup(root); print()
block_denylist_in_build(root)
return 0
if __name__ == "__main__":
sys.exit(main())
#!/usr/bin/env bash
# Auto-detect and run project verification (lint + typecheck + tests).
# Run from the project root. Exits non-zero on failure.
set -euo pipefail
if [ -f Cargo.toml ]; then
cargo check && cargo test
elif [ -f tsconfig.json ]; then
npx tsc --noEmit && npm test
elif [ -f package.json ] && grep -q '"test"' package.json; then
npm test
elif [ -f Makefile ] && grep -q '^test:' Makefile; then
make test
elif [ -f pytest.ini ] || [ -f pyproject.toml ] || find . -maxdepth 2 -name "test_*.py" | grep -q .; then
pytest
else
echo "(no test command detected - ask the user for the verification command)"
exit 1
fi
Related skills
Forks & variants (1)
Check has 1 known copy in the catalog totaling 47 installs. They canonicalize to this original listing.
- tw93 - 47 installs
FAQ
What happens to my local changes if I run check?
Check reads your worktree status (git status) and diffs but never modifies, stashes, or discards your work. Untracked and dirty files are preserved unless you explicitly approve cleanup.
How does check verify a fix is safe?
Check reads the diff, extracts project constraints from README/AGENTS.md/CI, runs the project's verification command, and confirms every claim (file:line, trigger, severity) is grounded in live output before sign-off.
Can check release code automatically?
No. Check can verify release readiness (version sync, artifacts, CI state) and ask for confirmation, but all public/irreversible actions (push, tag, publish, reply to issues) require explicit user approval.