diff --git a/.github/awesome-claude-code-submission.md b/.github/awesome-claude-code-submission.md new file mode 100644 index 0000000..9b043d2 --- /dev/null +++ b/.github/awesome-claude-code-submission.md @@ -0,0 +1,88 @@ +# Submission draft: Awesome Claude Code + +Not yet eligible. The list requires **14 days of development on the default branch, or 100 +stars**. First commit to `main` was 2026-08-03, so the earliest submission date is +**2026-08-17**. + +The maintainer requires the submission to be made **by a human, through the web issue form**. +Opening it any other way risks being restricted from the repository: + +> ALL RECOMMENDATIONS MUST BE MADE USING THE WEB UI ISSUE FORM TEMPLATE, OR YOU RISK BEING +> RESTRICTED FROM INTERACTING WITH THIS REPOSITORY TEMPORARILY. + +> Although resources themselves may be partially or entirely written by a coding agent, +> resource recommendations must be created by human beings. + +Form: + +--- + +## Field values + +**Display Name** + +``` +backcheck +``` + +**Category** + +``` +Linting +``` + +Their `Observability & Monitoring` section is live dashboards and session monitors, which this +is not. `Linting` holds tools that validate something and report violations, which is what this +does to an agent's closing summary. The maintainer may recategorise; that is fine. + +**Link** + +``` +https://github.com/VectorInstitute/backcheck +``` + +**Author Name** + +``` +VectorInstitute +``` + +**Author Link** + +``` +https://github.com/VectorInstitute +``` + +**Description** (their style rules: a description not a pitch, no addressing the reader, one +line, no emojis, 10 to 500 characters) + +``` +Reads a Claude Code session transcript and checks the agent's closing claims, such as tests passing or changes being committed, against the tool calls that actually ran. Each claim is reported as supported, qualified, unsupported, or contradicted, together with the line of recorded output the verdict rests on. Runs as a Stop hook, a CLI, or in CI, and makes no model calls. +``` + +**Checklist**: tick the first five. Leave the sixth unchecked; it is a trap for people who do not +read the form. + +--- + +## Before submitting + +- Confirm the repo still has a detectable licence. GitHub currently reports `Apache-2.0`. +- Re-read the entry against the list's existing `Linting` entries so the description matches + their register. +- Check the resource is not already listed, and that no near-duplicate was added in the interim. + +## Worth knowing first + +The maintainer is explicit that the list is not a growth channel: + +> Too many people think like this: (i) Build something awesome; (ii) Submit to Awesome Claude +> Code; (iii) Get accepted, because of being awesome; (iv) Get users. However, a more likely +> chain of events is: (i) Build something awesome; (ii) Get users; (iii) Submit it to Awesome +> Claude Code. + +If approved, the list invites a badge: + +```markdown +[![Mentioned in Awesome Claude Code](https://awesome.re/mentioned-badge.svg)](https://github.com/hesreallyhim/awesome-claude-code) +``` diff --git a/README.md b/README.md index 8cc18f7..e239889 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,7 @@ Verification only works for commands `backcheck` can read the result of, so the | **Tests** | pytest · unittest · tox · nox · cargo test · cargo nextest · go test · jest · vitest · mocha · ava · bun test · npm / yarn / pnpm test · rspec · phpunit · dotnet test · maven · gradle · ctest · make test | | **CI** | `gh pr checks` · `gh run list` · `gh run view` · `gh run watch` | | **Types** | mypy · pyright · tsc · cargo check | -| **Lint** | ruff · eslint · clippy · flake8 · pylint · golangci-lint · biome · pre-commit · import-linter · npm lint · cargo fmt · gofmt | +| **Lint** | ruff · eslint · clippy · flake8 · pylint · golangci-lint · biome · shellcheck · pre-commit · import-linter · npm lint · cargo fmt · gofmt | | **Build** | cargo build · go build · npm / vite / Next build · `python -m build` · docker build · mkdocs · sphinx | It sees through the wrappers these arrive in: `uv run`, `poetry run`, `npx`, `pnpm exec`, diff --git a/src/runners/classify.rs b/src/runners/classify.rs index 8b596fe..1f67884 100644 --- a/src/runners/classify.rs +++ b/src/runners/classify.rs @@ -97,6 +97,7 @@ pub fn classify(segment: &str) -> Option<(CheckKind, String)> { ("pylint", "pylint"), ("golangci-lint", "golangci-lint"), ("biome", "biome"), + ("shellcheck", "shellcheck"), ] { if head == needle { return Some((CheckKind::Lint, name.to_string())); diff --git a/src/runners/mod.rs b/src/runners/mod.rs index 530f7eb..9162a83 100644 --- a/src/runners/mod.rs +++ b/src/runners/mod.rs @@ -390,6 +390,34 @@ mod tests { ); } + #[test] + fn shellcheck_pass_fail_and_ambiguous_output() { + let ok = one("shellcheck scripts/check.sh", ""); + assert_eq!(ok.kind, CheckKind::Lint); + assert_eq!(ok.runner, "shellcheck"); + assert_eq!(ok.outcome, Outcome::Passed); + + let bad = one( + "shellcheck scripts/check.sh", + "In scripts/check.sh line 3:\n\ +echo $name\n\ + ^---^ SC2086 (info): Double quote to prevent globbing and word splitting.\n\ +\n\ +For more information:\n\ + https://www.shellcheck.net/wiki/SC2086 -- Double quote to prevent globbing and word splitting.", + ); + assert_eq!(bad.outcome, Outcome::Failed); + let evidence = bad.evidence_line.as_deref().unwrap_or_default(); + assert!( + evidence.contains("SC2086") && !evidence.contains("shellcheck.net"), + "evidence should quote the finding, not the wiki URL: {evidence}" + ); + assert_eq!(bad.failed, Some(1)); + + let ambiguous = one("shellcheck scripts/check.sh", "Checking scripts/check.sh"); + assert_eq!(ambiguous.outcome, Outcome::Unknown); + } + #[test] fn suppressed_failure_is_caveated_only_when_it_could_hide_something() { // No readable summary: `|| true` really could be concealing the outcome. diff --git a/src/runners/outcome.rs b/src/runners/outcome.rs index f63009f..983758c 100644 --- a/src/runners/outcome.rs +++ b/src/runners/outcome.rs @@ -174,6 +174,25 @@ pub(crate) fn parse_outcome( // their own marker (`&& echo RUFF CLEAN`). Let the generic reading find it. generic_outcome(output, kind, exclusive) } + "shellcheck" => { + if output.trim().is_empty() { + return (Outcome::Passed, None, Some(0), None); + } + static DIAGNOSTIC: OnceLock = OnceLock::new(); + let diagnostic = re(&DIAGNOSTIC, r"\bSC\d{4}\b"); + if diagnostic.is_match(output) { + let evidence = output + .lines() + .find(|line| diagnostic.is_match(line) && !line.contains("shellcheck.net")) + .map(|line| line.trim().to_string()); + let count = output + .lines() + .filter(|line| diagnostic.is_match(line) && !line.contains("shellcheck.net")) + .count() as u32; + return (Outcome::Failed, None, Some(count), evidence); + } + (Outcome::Unknown, None, None, None) + } "clippy" | "cargo check" | "cargo build" => { static E: OnceLock = OnceLock::new(); if let Some(c) = find(r"error(?:\[E\d+\])?:", &E) {