agents: add coverity-audit, sonarqube-audit, graphql-audit skills (#22296)
* agents: add coverity-audit, sonarqube-audit, graphql-audit skills Adds three repo-scoped AI agent skills under .agents/skills/ for the recurring static-analysis triage workflows: coverity-audit/ - Coverity Scan defect triage sonarqube-audit/ - SonarCloud findings triage graphql-audit/ - GitHub Code Scanning (CodeQL) triage Each skill is self-contained: a SKILL.md with frontmatter (name, description) plus a scripts/ directory with the shell helpers that drive each platform's API. Scripts auto-detect the repo root and read per-user secrets from the gitignored /.env at the repo root. They write run-time artifacts under the gitignored /.local/audits/ tree. AGENTS.md gains three new sections documenting the conventions: - AI agent skills: where skills live and the rule that any new knowledge must be committed back into SKILL.md - Local-only directory: the gitignored /.local/ for runtime artifacts - Per-user secrets: the gitignored /.env for tokens, cookies, keys The placeholder .agents/skills/.gitkeep is removed since the directory now has real content. The shell scripts share several conventions: - ASCII-only enforcement on comment bodies (Coverity and SonarCloud sit behind Cloudflare, which 403s non-ASCII payloads) - Repo-relative paths via git rev-parse, so scripts work from any cwd - Token / cookie masking in transparency output - Idempotent fetch loops (skip outputs that already exist on disk) - Read-only search/list helpers separated from write actions For Coverity specifically, the keepalive.sh script exits non-zero the moment a ping fails so the orchestrator's background-task notification fires immediately and the agent can recapture the cookie. * agents: auto-create .local/audits/<skill>/ on access The *_audit_dir() helpers in each skill's _lib.sh now run mkdir -p before returning the path. Callers can redirect output into subpaths (e.g. .local/audits/sonarqube/queue.json) without having to mkdir the parent first. * agents/coverity-audit: capture full operational knowledge The first cut of this skill was thin -- it had operational scaffolding but lost the API knowledge accumulated over hundreds of CIDs. This rewrite captures it. SKILL.md additions: - Server-side view-state cursor: documented, plus the explicit warning that the user must NOT touch the Coverity UI while a fetch runs (they would move the cursor out from under the scripts). - Coverity views (queue scopes): how to find a viewId, what the common view types mean (Outstanding, All in project, Dismissed, Fixed, Unclassified non-outstanding), how to switch without confusing the cursor. - Coverity attribute reference: classification (3), severity (1), action (2), external-reference (4) ID tables -- previously only in inline script comments. - displayImpact -> severity-ID mapping. - Suggested verdict vocabulary (TRUE_BUG_*, FALSE_POSITIVE_*, COSMETIC, CODE_GONE, NEEDS_HUMAN) with the classification/action it maps to. - CID vs defectInstanceId distinction and the resolve-cid-to-diid.sh primitive. - Project-specific FP guardrails (z-allocators, freez(NULL), DOUBLE_LINKED_LIST_*, buffer_*, STRING, ARAL, DICTIONARY, custom locks, glibc/musl, gcc/clang, daemon/plugin/streaming trust model). - Input trust boundaries table for FALSE_POSITIVE_TRUSTED_INPUT decisions. - Per-defect workdir convention. - Failure-mode quick-diagnosis table. The skill explicitly does NOT prescribe a review pipeline. Reviews are adhoc -- agree the approach with the user up front. For small batches (1-3 defects, the common case) a single agent or the user reading the bundle directly is usually fastest. Multi-model setups make sense only for large batches; the user picks the actual CLIs/models. New scripts (model-agnostic primitives): - resolve-cid-to-diid.sh -- CID -> defectInstanceId via /reports/defects.json - prepare-defect.sh -- bundle one defect (summary + details + source context + scratch TODO) under .local/audits/coverity/triage/<scope>/cid-<N>/ * agents: add pr-reviews skill for iterating PR comments Adds a fourth repo-scoped skill under .agents/skills/pr-reviews/ for the iterative PR comment / review handling workflow. The skill captures the rules that experience showed are non-negotiable: - Pagination paranoia. GitHub paginates everything; round-number counts (100, 200, 300) almost always mean the previous client missed the next page. fetch-all.sh auto-probes for an extra page when the count is a multiple of 100. - Address every comment, no exceptions. The bar is project performance, stability, and long-term maintainability. - Reply per-thread, one by one. No bulk replies, no mechanical 'fixed'. - When a bot finds a legit issue, search the whole PR for similar issues before pushing. AI reviewers surface their top 3-7 findings, not the full set; fixing only what they pointed at means dozens of round-trips. - Check CI before every push but never wait for CI between iterations. Throughput matters; CI lag does not block AI reviewer round-trips. - Re-trigger AI reviewers explicitly. They do not react to thread replies or pushed commits. Copilot needs a re-add as reviewer; cubic-dev-ai needs a top-level mention comment. - Some bots stop responding -- accept it, do not loop forever. Scripts: - fetch-all.sh -- paranoid-paginated fetch of issue comments, review comments, reviews, and review threads (GraphQL for thread isResolved). Writes a summary.txt with per-author counts and the open thread list. - list-open-threads.sh -- read from cached fetch; show open threads either full-body or as a one-line table. - reply-thread.sh -- post a reply inside an existing thread. - resolve-thread.sh -- mark a thread resolved (GraphQL). - ci-status.sh -- check current CI for failures before pushing. - trigger-cubic.sh -- post the @cubic-dev-ai mention comment. - trigger-copilot.sh -- re-add @copilot as a requested reviewer. - wait-for-activity.sh -- block until new activity (new comment / new review / new push) or 30-min timeout. - _lib.sh -- shared helpers, author classification regex. Author classes are handled differently: - AI bots -- iterate autonomously. - Informational bots (sonarqubecloud quality gate, etc.) -- read for signal, no reply needed. - Humans -- consult the user; do not reply on the user's behalf. AGENTS.md is updated to list the new skill alongside the other three. * agents/skills: portability, input validation, doc consistency, shellcheck Bundled fixes across the four skills, addressing both the cubic-dev-ai findings on PR 22296 and a sweep for similar patterns elsewhere. Portability (BSD/macOS): - Replace 'grep -qP "[^\\x00-\\x7F]"' with a portable 'tr -d "\\000-\\177"' pipeline in three places: coverity _lib.sh, sonarqube _lib.sh, and coverity update-triage.sh. 'grep -P' is GNU-only and breaks ASCII validation on BSD/macOS. - Replace 'mktemp --tmpdir' (GNU-only) with 'mktemp "${TMPDIR:-/tmp}/..."' in three places: coverity finalize-defect.sh, coverity update-triage.sh, pr-reviews fetch-all.sh. Input validation: - Add cov_require_numeric_cid() helper in coverity _lib.sh; call it from finalize-defect.sh, prepare-defect.sh, update-triage.sh, resolve-cid-to-diid.sh before interpolating CID into jq filters, URL params, or paths. Earlier versions trusted raw CLI input. - Switch jq filters in finalize-defect.sh and prepare-defect.sh from raw '${CID}' interpolation to jq '--argjson cid' for defense-in-depth even after the numeric guard. Repo-slug parsing: - The previous gh_repo_slug regex used '[^/.]+' which truncated repo names containing dots ('my.repo'). Replace with bash parameter expansion in graphql-audit _lib.sh and pr-reviews _lib.sh -- handles dots correctly and avoids regex altogether. Wildcard merge: - coverity fetch-table.sh combined pages with '${PREFIX}-page*.json', which would silently include stale page files from a prior larger run (e.g. PAGES=5 after a previous PAGES=7). Enumerate the requested page files explicitly. sonar-mark.sh: - list_open_hotspots_for_rule() embedded ${rule} inside a Python -c script via single-quoted bash interpolation -- shell metacharacters in the rule id could break parsing. Replace with jq '--arg rule' for the filter; consistent with the rest of the script's jq usage. - list_open_issues_for_rule() and list_open_hotspots_for_rule() were capped at the first 500 findings (a single ps=500 page) -- rules with more than 500 open findings would be silently truncated. Both now paginate via Sonar's p=N until paging.total is reached. - URL-encode the rule id before placing it in the issue-search query string (rule ids contain ':' which must be %3A). Documentation: - sonarqube SKILL.md documented 'SONAR_MARK_DRY_RUN' but the script actually reads 'SONAR_DRY_RUN'. The shown dry-run command would have performed real mutations. Renamed in the doc (two locations) to match the script. - sonarqube SKILL.md dry-run section now clarifies which calls are suppressed in dry-run (writes) vs not (reads -- needed so family-mode can show what it would have acted on). - coverity SKILL.md finalize-defect.sh invocation used '<SCOPE>' as the third arg placeholder where the script read '<phase>'. Renamed PHASE -> SCOPE in finalize-defect.sh for consistency with prepare-defect.sh (which already used 'scope') and updated the doc to match. shellcheck: - Mark color vars with 'shellcheck disable=SC2034' across all four _lib.sh files (they are referenced from sourcing scripts; shellcheck cannot see that). - Restructure 'set -a; source ...; set +a' across two lines so the shellcheck source-disable directive has a clear target line. - Drop unused 'http' and 'DIR' locals. - Replace 'printf >&2 "${COLOR}"' (variables in the format string) with 'printf >&2 "%s" "${COLOR}"' in sonarqube _lib.sh. shellcheck --severity=warning is now clean across all skill scripts. * agents/pr-reviews: emphasize per-thread reply-then-resolve cadence The 'one by one' rule is in the mandatory rules list, but the workflow section was loose enough that batch replies (prepare 14 reply texts, loop) felt acceptable. Tighten the wording: walk threads one at a time, reply, resolve, then move on. Bulk reply+resolve looks mechanical and erodes trust in the address pass. * agents/pr-reviews: pull SonarCloud findings, address all PR finding sources The pr-reviews skill was missing SonarCloud as a finding source and was not explicit about its job being broader than just review-comment threads. SonarCloud only posts a QualityGate summary to GitHub -- the actual issue list lives behind /api/issues/search?pullRequest=<N>. Without an explicit fetch, the agent has no way to see them. SKILL.md changes: - New top-level 'Your role on a PR' section that states the agent's job: bring the PR into merge-ready shape, addressing every legitimate finding from every source, not just GitHub review comments. Lists the five sources in priority order: humans, AI bot review comments, SonarCloud PR findings, CI failures relevant to the PR, anything else (Codacy, custom workflows). Documents the 'unrelated CI failures get noted but not fixed' rule. - New Step 1b 'Fetch SonarCloud PR findings' invokes the new fetch-sonar-findings.sh; new Step 1c notes CI signal as a third source. - New Step 3b walks each Sonar issue / hotspot, with a similar-pattern sweep and the option to mark FP via sonar-mark.sh from the sonarqube-audit skill (cross-skill integration). - Loop completion criteria now include zero PR-introduced Sonar findings and no CI failures attributable to the PR. - Final-report step lists what to summarize for the user, including any unrelated CI failures noted but not fixed. New script: - pr-reviews/scripts/fetch-sonar-findings.sh Pulls /api/issues/search and /api/hotspots/search filtered by pullRequest=<N>. Paginated up to total. Reuses SONAR_TOKEN / SONAR_HOST_URL / SONAR_PROJECT from .env (same vars sonarqube-audit uses). Writes sonar-issues.json and sonar-hotspots.json under .local/audits/pr-reviews/pr-<N>/, plus a stdout summary by rule and severity. coverity finalize-defect.sh: - Add explicit '*) ;;' default branch to the early-exit case statement for VERDICT. Sonar S131 (CRITICAL) flagged this. The default arm is a no-op; the next case statement handles all other verdicts. Documented the fall-through with a comment. sonarqube _lib.sh: - printf format-string fix from the earlier shellcheck pass dropped the ANSI escape interpretation; the literal backslash-octal-three-three was being printed verbatim instead of becoming ESC. Switch to %b (interprets backslash escapes) -- still safe with respect to SC2059 because the format string is a constant literal. The two Sonar S1135 'TODO' findings on prepare-defect.sh:18 and :109 were marked False Positive directly on SonarCloud (not in this commit): both reference the 'TODO.md' filename convention used by prepare-defect to scope per-defect notes inside the defect directory, not literal unfinished-task markers. * agents: holistic pre-push review fixes Second iteration after addressing the 14 cubic-dev-ai threads + the 3 SonarCloud findings on PR 22296. The skill's mandatory pre-push subagent review surfaced these: - pr-reviews now mandates a subagent holistic review BEFORE every push (Step 4a, plus rule #12 in the mandatory rules list). The skill context is biased toward the recent fix; a clean-context subagent re-reviewing the WHOLE diff is what catches similar patterns and new issues introduced by the fixes themselves. - pr-reviews scripts now validate PR numbers via pr_require_numeric() before interpolating them into REST paths or gh arguments. Eight scripts wired: fetch-all, fetch-sonar-findings, ci-status, list-open-threads, reply-thread, trigger-cubic, trigger-copilot, wait-for-activity. - graphql codeql-dismiss validates the alert number is a positive integer. - coverity update-triage.sh switched --arg cid + |tonumber to --argjson cid (consistent with finalize-defect.sh / prepare-defect.sh now); same for projectId. Numeric-validation guards added. - coverity _lib.sh:cov_load_env now validates COVERITY_PROJECT_ID is a positive integer at load time. - All numeric guards use ^[1-9][0-9]*$ rather than ^[0-9]+$ -- the error message says 'positive integer' so reject zero accordingly. - Color vars across all four _lib.sh redefined with $'...' so they hold real ESC bytes -- printf '%s' works and shellcheck SC2059 stays happy. Earlier change to printf '%s' had silently broken color rendering; rule #11 (smoke-test every fix, do not trust the linter alone) added to the mandatory rules. - sonarqube SKILL.md: replaced the 'shelldre / godre' language list with the actual SonarCloud language keys (c, cpp, go, javascript, py, shell, ...) and clarified that rule-id namespaces (the prefix before the colon in rule keys) are different from language keys -- the qualityprofile API takes the language key, not the rule-id namespace. - sonarqube _lib.sh: SONAR_ORG no longer required at env-load (no script uses it today; documented why it stays as an optional default). - Documented the audit-dir naming convention in AGENTS.md (skill '<topic>-audit/' writes to '.local/audits/<topic>/'). New tooling: - pr-reviews/scripts/fetch-sonar-findings.sh -- pulls PR-specific SonarCloud findings via /api/issues/search?pullRequest=<N> and /api/hotspots/search?pullRequest=<N>. Paginated. Writes sonar-issues.json + sonar-hotspots.json under the per-PR cache. The pr-reviews SKILL.md gained a 'Your role on a PR' section listing the five finding sources (humans, AI bots, Sonar, CI, others) and the rule that the agent's job is to bring the PR to merge-ready shape by addressing legitimate findings from every channel. * agents: address iteration-3 cubic findings + 4 holistic-review rounds Iteration 3 of the address-review cycle on PR 22296. Cubic-dev-ai returned 13 new findings on the previous push and the pre-push holistic subagent reviews (rounds 3 + 4) found another 8 real bugs the AI bots had not surfaced. Bundled fixes: Pagination correctness (the largest class): - pr-reviews/fetch-all.sh fetch_paranoid: 'gh api --paginate' writes per-page arrays back-to-back, NOT a single JSON array; 'jq length' on that input only counted the first page. Pipe through 'jq -s "add // []"' so all pages slurp into one array. Same fix applied to graphql-audit/codeql-list.sh which had the identical pattern. - pr-reviews/wait-for-activity.sh snapshot: switch from per-page '--jq length' summed via awk to defensive 'if type=="array" then length else 0 end' so a malformed page (object instead of array) does not get its key-count inflated into the sum. - pr-reviews/wait-for-activity.sh snapshot also now cursor-paginates the GraphQL reviewThreads connection -- PRs with >100 threads (the topology-maps PR has hundreds) would otherwise miss resolve/unresolve transitions on every thread past the first page. - sonarqube-audit: introduced sq_paginate helper that walks /api/*/search until paging.total. Refactored sonar-search.sh, sonar-mark.sh family-mode helpers, and pr-reviews/fetch-sonar-findings.sh to use it. fetch-sonar-findings.sh now sources sonarqube-audit/_lib.sh for cross-skill helper reuse rather than duplicating the paginator. - pr-reviews/fetch-all.sh GraphQL nested comments: bumped first:50 -> first:100 + added pageInfo and totalCount; logs a warning naming any thread whose comments truncated. Robustness: - sq_paginate validates each page is a JSON object with .paging and a recognised array key (issues/hotspots/components/rules/users); bails loudly on unknown payload rather than silently returning zero rows. - sq_run_read added: read-only API call helper that masks the token in the transparency log but does NOT skip in dry-run (read-only enumeration must run so the caller sees what would be acted on). - pr-reviews/wait-for-activity.sh GraphQL block always emits a threads_resolved=...open=... line, falling back to ERR placeholders on transient failure. Without this the line vanished intermittently and the snapshot diff falsely flagged 'new activity' on each return. - pr-reviews/wait-for-activity.sh diff in success branch wrapped with '|| true' so the diff exit-1-on-difference does not trip pipefail. Input validation: - pr-reviews/_lib.sh + graphql-audit/_lib.sh: the gh_repo_slug / pr_repo_slug helpers now return empty for non-github.com remotes (the skills are GitHub-only). PR_REPO_SLUG override validated as ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$. - pr_require_slug + gh_require_slug helpers added; all 8 sites that derive a slug now use them so the failure mode is one consistent error. - coverity-audit/fetch-table.sh validates VIEW_ID and PAGES as positive integers before the loop; otherwise non-numeric PAGES would make seq produce no output and the final jq merge would block on stdin. - coverity-audit/prepare-defect.sh: source-context check now requires non-empty displayFile AND a regular file at ${ROOT}/${repo_file}; previously an empty displayFile resolved to ${ROOT}/ which IS a readable directory (the -r test passed) and awk would crash. - pr-reviews/reply-thread.sh: empty-body guard strips ALL whitespace ([[:space:]]) not just spaces. - sonarqube-audit/sonar-search.sh: --resolved and --status whitelisted. URL encoding consistency: - sq_url_encode lifted out of sonar-mark.sh into _lib.sh. sonar-search.sh now uses it on --rule values too -- previously the rule string was concatenated into the URL raw, so any reserved character (& = % space) could inject extra query parameters or be rejected by Cloudflare. ci-status.sh: - Header comment + WARNING text were saying 'pushing now will lose CI results' which contradicted the SKILL.md policy ('push anyway, fresh CI runs on the new code'). Reworded as informational. Doc fixes: - pr-reviews/SKILL.md: 'no AI tool names in commit messages / PR bodies' rule clarified to NOT ban operational mentions like @cubic-dev-ai (which the trigger script enforces). - pr-reviews/SKILL.md: ci-status exit-2 entry in the failure-modes table reconciled with the don't-wait policy. - pr-reviews/SKILL.md: wait-for-activity.sh now also detects review thread resolve/unresolve transitions (documented). - sonarqube-audit/SKILL.md: removed stale 'family-mode hits ps=500 ceiling' workaround (sq_paginate handles it). Style/naming: - local_cursor_arg renamed to cursor_arg (top-level scope; misleading prefix). - reviewer name fallback in pr.json summary: '.login // .name // "?"'. - trigger-copilot.sh comment about remove-reviewer rephrased to describe what we actually rely on. * agents: address iteration-4 cubic findings (3 P2/P3) + similar-pattern sweep Cubic-dev-ai's review of the previous push surfaced three more findings: - sonar-mark.sh confirm_family used ${ans,,} which is a bash 4+ feature and breaks on macOS bash 3.2. Replaced with portable 'tr [:upper:] [:lower:]' lowercase conversion. - coverity fetch-details.sh interpolated 'cid' (read from input JSON) into the output filename without numeric validation. Corrupted/manipulated input could escape OUT_DIR. cid + defectInstanceId now validated as positive integers before path construction; non-numeric rows are skipped with a clear log line. - coverity prepare-defect.sh: SCOPE arg landed in a path component (.local/audits/coverity/triage/<scope>/cid-N/) without validation. Added regex check ^[a-z][a-z0-9_-]*$ -- matches every documented scope (outstanding, dismissed, fixed, unclassified, all-in-project) and rejects path-escaping inputs. Cross-skill sweeps (rule #10): - No other ${var,,} / ${var,^^} sites anywhere in .agents/skills/. - No other JSON-string-to-filesystem-path interpolation outside the one fetched-details.sh site that was just fixed. - CLI-arg-to-path: SCOPE in prepare-defect.sh was the only validation gap; OUT_DIR/PREFIX are by-design user-controlled paths. Cubic finding on fetch-sonar-findings.sh:62 'group_by needs sort_by' verified false positive: jq's group_by sorts internally (see https://stedolan.github.io/jq/manual/#group_by(path_expression)). The thread reply documents this with the spec citation; no code change. Holistic pre-push review #5 ran on the resulting tree and recommended SHIP -- all CLI/JSON path sinks now validated, all bash 4+ idioms removed, all documented scopes still parse. * agents: iteration-5 + iteration-6 hardening (input validation, portability, doc fixes) Bundled fixes from cubic-dev-ai's last two review rounds, the holistic-review subagents, and the cross-skill sweeps. All addressed in one push per the new sync-barrier rule (Step 4-pre): re-fetch all finding sources immediately before push, fold in anything that arrived during the iteration, only push when fetches are clean. Pre-push sync barrier added to pr-reviews/SKILL.md (Step 4-pre + rule #13). Without it, reviewers post in parallel and findings that arrive mid-iteration get attributed to the next push, leaving the orchestrator and reviewers chronically one round out of sync. Hardening: - All 7 ' -r ' / '! -r ' path tests across the 4 skills upgraded to ' -f ... && -r ... ' / '! -f ... || ! -r ... ', so a directory or symlink-to-directory cannot pass the readability gate and crash on the read. - pr_repo_slug + gh_repo_slug: tightened the github.com host check from substring '*github.com*' (matched 'notgithub.com' and 'github.com.attacker.example.com') to three explicit prefixes covering SCP-style ssh, URL-style ssh, anonymous https, and credentialed https with x-access-token. Verified against fake-remote smoke tests. - pr-reviews/_lib.sh: PR_REPO_SLUG override now validated as ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ so an env var can't smuggle whitespace or shell metacharacters. - coverity/keepalive.sh: COVERITY_VIEW_OUTSTANDING validated as a positive integer before going into PING_URL. - coverity/fetch-details.sh: cid + defectInstanceId from input JSON validated as positive integers before path/URL interpolation. Non-numeric rows are skipped with a clear log line, counted in failed total. - coverity/fetch-table.sh: GET response now validated to be JSON with '.resultSet.results | type=="array"' before being treated as cached. A 200-with-HTML-body (Cloudflare challenge) no longer caches as 'valid' to confuse subsequent runs. - coverity/prepare-defect.sh: repo_file (from Coverity displayFile) now also rejects '..' segments to prevent path traversal escaping the repo root. - pr-reviews/list-open-threads.sh + reply-thread.sh: input file checks now require ' -f -r -s '. reply-thread.sh comment-id validated as positive integer; @file body must be readable non-empty regular file. - pr-reviews/resolve-thread.sh: thread-node-id validated against '^PRRT_[A-Za-z0-9_-]+$' before being passed to the GraphQL mutation. - pr-reviews/wait-for-activity.sh: timeout + poll args validated as positive integers. Pagination + concatenation correctness: - pr-reviews/fetch-all.sh: 'gh api --paginate' writes per-page arrays back-to-back, NOT one JSON array; previous 'jq length' on the concatenated stream only saw the first page. Now piped through 'jq -s "add // []"' to slurp + concatenate. - graphql-audit/codeql-list.sh: same fix applied (had identical bug). - pr-reviews/wait-for-activity.sh snapshot: switched to per-page '--jq length' summed via awk; defensive 'if type=="array" then length else 0 end' so a malformed page doesn't get key-counted. - pr-reviews/wait-for-activity.sh: GraphQL reviewThreads now cursor-paginated; 'threads_resolved=N_open=M' line emitted unconditionally with ERR fallback so transient GraphQL failure doesn't drop the line and falsely flag 'new activity'. - pr-reviews/fetch-all.sh GraphQL nested comments: bumped first:50 to first:100 + added pageInfo.hasNextPage + totalCount; warning logged per truncated thread. Sonar pagination + URL safety: - sonarqube-audit/_lib.sh: introduced sq_paginate that walks every page until paging.total, validates each page is an object with .paging and a recognised array key (issues/hotspots/components/ rules/users), bails loudly on unknown payload. Used by sonar-mark.sh family-mode helpers, sonar-search.sh, and pr-reviews/ fetch-sonar-findings.sh (which sources sonarqube-audit/_lib.sh cross-skill rather than duplicating). - sonarqube-audit/_lib.sh: added sq_run_read for read-only API calls (token-masked log line, no dry-run skip) so reads in family-mode still execute under SONAR_DRY_RUN. - sonarqube-audit/_lib.sh: lifted _url_encode out of sonar-mark.sh into the lib as sq_url_encode. sonar-search.sh now URL-encodes --rule values; previously a rule with reserved characters (':', '&', '=') could inject extra params or trigger Cloudflare. - sonar-search.sh: --rule arg now requires a value (was crashing under set -u when missing); positional params copied to local vars (Sonar S7679); --resolved/--status whitelisted. Portability: - sonar-mark.sh confirm_family: replaced ${ans,,} (bash 4+) with 'tr [:upper:] [:lower:]'; works on macOS bash 3.2. Doc fixes: - AGENTS.md /.local/ convention text + naming rule reconciled into a single statement (was written twice with inconsistent placeholders). - pr-reviews/SKILL.md: ci-status.sh referenced with the explicit repo-relative path. - graphql-audit/SKILL.md: codeql-list.sh / codeql-dismiss.sh examples use the explicit 'bash .agents/skills/.../...' paths instead of bare script names that assume PATH setup. - sonarqube-audit/SKILL.md: removed leading '+' that was rendering as a markdown list bullet. - pr-reviews/SKILL.md: failure-modes table entry for ci-status exit 2 reconciled with the don't-wait policy. - pr-reviews/SKILL.md: 'no AI tool names in commit messages / PR bodies' clarified to NOT ban operational mentions like @cubic-dev-ai (which the trigger script always prepends). - pr-reviews/SKILL.md: wait-for-activity.sh now lists thread-count as a tracked signal. - sonarqube-audit/SKILL.md: removed stale 'family-mode hits ps=500 ceiling' workaround; sq_paginate handles it. - coverity-audit/SKILL.md: finalize-defect.sh invocation uses <scope> consistently (matches prepare-defect.sh terminology); the script's third arg renamed PHASE -> SCOPE so doc and code agree. Style/structure: - Color vars across all four _lib.sh redefined with $'...' so they hold real ESC bytes; printf '%s' renders correctly without violating shellcheck SC2059. The earlier change to bare '%s' had silently broken color rendering -- rule #11 (smoke-test every fix) added. - shellcheck SC2034 silenced for color vars; SC1090 directive moved to its own line; unused locals dropped. - Naming: local_cursor_arg renamed to cursor_arg. - Reviewer name fallback in pr.json summary uses '.login // .name // "?"'. - finalize-defect.sh early-exit case has explicit '*) ;;' default (Sonar S131); two false-positive S1135 'TODO' findings on prepare-defect.sh marked False Positive directly on SonarCloud. * agents: clean shellcheck info-level findings + 2 cubic round-7 fixes CI's reviewdog/action-shellcheck reporter (github-pr-check) fails the check on ANY shellcheck finding regardless of severity, so info-level findings block the PR. Local shellcheck --severity=warning was clean, but CI was failing on: - SC1091 'Not following: ./_lib.sh' across 16 source-loading scripts. The 'shellcheck source=./_lib.sh' directive helps shellcheck try to follow but the relative path doesn't resolve under CI's working dir. Added an explicit 'shellcheck disable=SC1091' next to each source directive so the warning is suppressed without losing the source-path hint for local developers running 'shellcheck -x'. - SC2016 'Expressions don't expand in single quotes' on three GraphQL queries (fetch-all.sh, wait-for-activity.sh, resolve-thread.sh). The single-quoted body contains $owner / $name / $number / $threadId / $after as GraphQL placeholders, NOT shell variables. Suppressed with 'shellcheck disable=SC2016' on its own line above each call (combined comment+directive trips SC1072). Plus two findings cubic raised after the previous push: - pr-reviews/scripts/resolve-thread.sh: added 'pr_require_slug >/dev/null' for fail-fast on non-GitHub remotes. The GraphQL mutation operates by node-id and doesn't need the slug, but failing entry-point checks catch misconfiguration earlier than letting the mutation hit a non-github API. - pr-reviews/SKILL.md Step 4-pre (sync barrier): the prose mentioned 'comments, Sonar, CI' as the three sources to re-check, but only showed fetch-all.sh + fetch-sonar-findings.sh commands. Added ci-status.sh to the command list with a note that it's the third source and that in-scope CI failures (caused by the PR's diff) get folded into the same push, while unrelated CI failures are noted but not fixed. Sync barrier ran one more time before this push -- confirmed no new findings beyond the two above.