devtools: fold local review experiment into planner

Why:
The old AI local-review workflow was experimental, duplicated newer
validation guidance, and still carried stale review behavior on main.

Impact:
Local validation keeps useful checks in the maintained planner and
removes the obsolete experiment while retaining shared prompt assets.

Before/After:
Before, local review policy lived partly in an unused AI script and a
deleted workflow entry point. After, the planner and skills own the
reusable checks and prompt guidance.

Technical Overview:
Remove ai/local-review-workflow.sh and .agent/workflows/audit.md.
Teach devtools/local-validation-plan.sh to derive its default base from
RSYSLOG_LOCAL_VALIDATION_BASE, rsyslog.localValidationBase, or the
worktree HEAD reflog baseline.
Use the same base for local Cubic review so committed branch changes are
reviewed against the worktree creation point.
Add advisory raw allocation and test antipattern scans, and run fast mock
distcheck for distribution-risk test/build changes.
Fold the old audit prompt guidance into the local container testing skill
as late manual prompt audits without launching another AI CLI.
Document ai/ as the central shared prompt-asset library and remove stale
references to deleted workflow paths.

With the help of AI-Agents: OpenAI Codex
This commit is contained in:
Rainer Gerhards 2026-06-02 09:55:54 +02:00
parent 372e6e8a8c
commit ed91aebc42
8 changed files with 301 additions and 495 deletions

View File

@ -41,7 +41,10 @@ This skill standardizes the final step of the development workflow: committing a
- **Validation**: Ensure the relevant build/test path passed. For PR-ready
implementation work, prefer the local container-testing skill's change-gated
validation over unconditional full-suite local runs.
- **Multi-Pass AI Audit**: Run the `/audit` workflow for a rigorous, persona-based review (Memory, Concurrency, Standards) using the project's canned prompts.
- **Late Prompt Audits**: For non-trivial C/H, concurrency, or test/build
plumbing changes, follow the local container-testing skill's late
prompt-audit stage. Read and apply the project's canned prompts directly;
do not launch another AI CLI from repository scripts.
- **Local Cubic**: Run local Cubic review for code changes when `cubic` is
installed and reachable. Skip Cubic for documentation-only changes. For
tests, workflows, build tooling, and mixed changes, use Cubic when the

View File

@ -1,6 +1,6 @@
---
name: rsyslog_local_container_testing
description: Mirror rsyslog run_checks.yml container validation locally, including Cubic review where applicable, the clang static analyzer job, the change-gated Ubuntu 26.04 run-ci.sh check run, service-skip validation, clean-tree rules, and container path caveats.
description: Mirror rsyslog run_checks.yml container validation locally, including the change-gated Ubuntu 26.04 run-ci.sh check run, the clang static analyzer job, late prompt-based audit passes, Cubic review where applicable, service-skip validation, clean-tree rules, and container path caveats.
---
# rsyslog_local_container_testing
@ -54,6 +54,14 @@ Optional local linters still remain tool-presence guarded, so missing optional
developer tools do not turn a documentation or tooling-only change into a
container validation failure.
By default the helper compares committed branch changes against the worktree's
creation baseline: `RSYSLOG_LOCAL_VALIDATION_BASE` when set, otherwise
`rsyslog.localValidationBase` from git config when present, otherwise the
oldest `HEAD` reflog entry for the worktree. This keeps local Cubic and local
diff classification tied to the commit that was `HEAD` when the dedicated
worktree was created, even after `origin/main` moves. Use `--base REF` for an
intentional one-off override.
Before starting any heavy local validation, run the relevant cheap local checks
that are available in the environment. These checks should be diff-scoped and
tool-presence guarded:
@ -77,6 +85,14 @@ tool-presence guarded:
fuller local environment to cover the gap. CI will not pass with improperly
formatted C/H code. Use `devtools/format-code.sh --git-changed` separately
when you intentionally want to rewrite local files.
- changed C sources or headers also get an advisory raw-allocation scan in
`devtools/local-validation-plan.sh --run`. Treat matches for `malloc`,
`calloc`, `realloc`, `strdup`, and `strndup` as review prompts; prefer
rsyslog allocation helpers where practical, but inspect low-level exceptions
before changing code.
- changed shell tests should run `devtools/check-test-antipatterns.sh` through
the helper. Its findings are advisory prompts for flake-prone constructs such
as fixed sleeps, port preselection, fixed ports, and ad-hoc assertions.
Do not run `cppcheck` routinely unless a maintainer explicitly asks for it; it
is too noisy for routine rsyslog PR validation.
@ -98,11 +114,12 @@ is too noisy for routine rsyslog PR validation.
edits. Internal docs that are not rendered into the user manual, such as
`doc/ai/**`, repository agent guides, and skill files, do not require this
docs build unless they also change rendered Sphinx inputs.
- **Tier 1: default PR-ready gate for code or testbench changes**. Run the
local Cubic review when applicable, the existing static-analyzer pass, and a
change-gated Ubuntu 26.04 `devtools/run-ci.sh` check. This is the normal
local confidence gate for C, parser, module, runtime, `tests/*.sh`,
`diag.sh`, `Makefile.am`, `configure.ac`, and `run_checks.yml` changes.
- **Tier 1: default PR-ready gate for code or testbench changes**. Run a
change-gated Ubuntu 26.04 `devtools/run-ci.sh` check first, then the existing
static-analyzer pass, then late prompt-based audit passes and local Cubic
review when applicable. This is the normal local confidence gate for C,
parser, module, runtime, `tests/*.sh`, `diag.sh`, `Makefile.am`,
`configure.ac`, and `run_checks.yml` changes.
- **Tier 2: compile portability gate for non-trivial C/header changes**. Add
the two build-only compile lanes from `run_checks.yml`:
`clang21-ndebug` and `gcc15-gnu23-debug`. Use this when a change touches
@ -147,11 +164,48 @@ It should be set by the user or machine profile, for example in `.bashrc`.
Deflake and overload experiments are prompt-driven and must use explicit
one-off `-jN` values instead of changing this normal validation knob.
## Late Prompt Audits
Run prompt-based audits late in the process, after cheap deterministic checks,
mock distcheck when needed, the change-gated Ubuntu 26.04 container run, and
the static analyzer have either passed or the implementation is otherwise
stable enough that no major rewrite is expected. This keeps review tokens spent
on the candidate that is likely to ship instead of on intermediate churn.
Do not launch `codex`, `copilot`, or another AI CLI from repository scripts.
The active agent must read the canned prompt and apply it to the current diff,
changed functions, and nearby lifecycle paths. When a separate reviewer session
or subagent is explicitly available, it may be used, but local validation must
not depend on unknown local AI tooling, authentication, or model availability.
Use these prompt passes when they match the change:
- changed C sources or headers: read
`ai/rsyslog_memory_auditor/base_prompt.txt` and audit changed functions plus
nearby allocation, ownership, and cleanup paths. For focused allocation
changes, also consult `audit_leaks.txt`, `audit_lifecycle.txt`, and
`audit_null_checks.txt` as needed.
- lock, queue, worker, thread, timer, atomic, shutdown, retry, or resource
ownership changes: read `ai/rsyslog_bug_finder/base_prompt.txt` and perform a
path-sensitive resource and concurrency audit, including lock ordering and
branch-specific counterexamples for any non-clean findings.
- testbench, build, or distribution plumbing changes: perform a project
standards audit: check `tests/Makefile.am` registration, `EXTRA_DIST`,
"define at top, distribute unconditionally, register conditionally" patterns,
RainerScript syntax in touched tests, and mock distcheck coverage.
Report the prompt audits in the validation summary: which prompts were applied,
which findings were fixed, which findings were dismissed with rationale, or
that the audit was clean. Skip prompt audits for documentation-only and
instruction-only changes unless the edited instructions themselves change audit
or validation behavior.
## Cubic Review
Run the local `cubic` CLI as an AI review gate when it applies. Cubic is a small
local shim that forwards the review request to the configured cloud AI service,
so it can normally run in parallel with the local static analyzer.
so it should run late on the stabilized candidate. It is a broad review pass,
not a fast compile detector.
- **Docs-only changes**: do not run Cubic.
- **Code changes (`*.c`, `*.h`, grammar/parser/runtime/module logic)**: always
@ -190,7 +244,10 @@ change-gated Ubuntu 26.04 check would not expose. Do not add lanes by habit.
- **Distribution checks (`ubuntu_22_distcheck`, mock distcheck, or
`kafka_distcheck_CI`)**: run when adding, renaming, or deleting files/tests,
changing `Makefile.am`, `configure.ac`, `m4`, packaging lists, generated
artifacts, or Kafka build/test plumbing.
artifacts, or Kafka build/test plumbing. The local validation helper runs the
fast `make distcheck TEST_RUN_TYPE=MOCK-OK` variant for changed, added,
renamed, or deleted test and build-manifest distribution risks before
escalating to heavier container lanes.
- **Service-specific lanes**: run only when directly relevant to the touched
module, tests, helpers, build plumbing, or relevance logic. This includes
Kafka, Elasticsearch, VictoriaLogs/omhttp, MySQL/libdbi, imfile, and similar

View File

@ -126,7 +126,10 @@ Certain modules (Kafka, Elasticsearch, Journald) have heavy integration tests re
### 8. Memory Lifecycle Validation (Mental Audit)
Before committing C changes, agents SHOULD perform a self-audit of memory ownership and lifecycle.
- **Rule**: Run the `/audit` workflow. It orchestrates multiple specialized passes (Memory Guardian, Concurrency Architect) using the [Canned Prompts](../../../ai/).
- **Rule**: Follow the late prompt-audit stage in
[`rsyslog_local_container_testing`](../rsyslog_local_container_testing/SKILL.md).
Read and apply the relevant canned prompts under [`ai/`](../../../ai/)
directly; do not depend on another local AI CLI being installed.
- **Critical Patterns**:
- **NULL Checks**: `es_str2cstr()` can return `NULL`. Every call MUST be followed by a `NULL` check.
- **Macro Usage**: Prefer `CHKmalloc()` for allocations as it automatically handles the `NULL` check and jumps to `finalize_it`.

View File

@ -1,35 +0,0 @@
---
description: Perform a comprehensive, multi-pass AI-driven review of current code changes.
---
// turbo-all
1. **Gather Changes**: Run `git diff` to identify all uncommitted changes.
2. **Specialized Persona Audits**:
- **Pass 1: The Memory Guardian (Ownership & Leaks)**:
- Persona: Senior C Security Auditor.
- Canned Prompt: `ai/rsyslog_memory_auditor/base_prompt.txt`
- Focus: `RS_RET` paths, `es_str2cstr()` NULL checks, `strdup/free` balance, `CHKmalloc` usage.
- **Pass 2: The Concurrency Architect (Races & Lock Logic)**:
- Persona: Systems Architect.
- Canned Prompt: `ai/rsyslog_bug_finder/base_prompt.txt`
- Focus: Data races, lock balance, structured path analysis, counterexamples for non-OK findings.
- **Pass 3: The Project Standards Guard (Build & Distribution)**:
- Persona: rsyslog Maintainer.
- Focus:
- `tests/Makefile.am`: "Define at Top, Distribute Unconditionally, Register Conditionally" pattern.
- `EXTRA_DIST` completeness.
- RainerScript syntax correctness (rsyslog Assistant mode from `ai/support_gpt/base_prompt-gpt4.txt`).
- **Pass 4: The Structural & Formatting Auditor**:
- Persona: Documentation & Quality Engineer.
- Focus:
- Check for duplicate section numbering (e.g., two "### 6.").
- Check for redundant or duplicate guidance lines (e.g., identical `- Focus:` bullets).
- Verify all file links and internal cross-references are valid.
3. **Adversarial Pass (Simulation)**:
- Persona: Adversarial Tester.
- Final Pass: Assume a different set of constraints (e.g., "Review this as if you are a strict static analysis tool like Clang Tidy or Coverity") to find any lingering edge cases.
4. **Consolidated Report**:
- **CRITICAL**: Functional bugs, leaks, or failing distribution rules (VPATH failures).
- **ADVISORY**: Pattern improvements, macro suggestions (`CHKmalloc`), or style nits.
- **CLEAN**: Verification of correctly implemented rsyslog patterns.

View File

@ -16,7 +16,7 @@ To ensure consistency and high-quality contributions, AI agents SHOULD use the f
|-------|---------|
| [`rsyslog_build`](.agent/skills/rsyslog_build/SKILL.md) | Environment setup and incremental parallel builds. |
| [`rsyslog_test`](.agent/skills/rsyslog_test/SKILL.md) | Standardized validation and debugging via `diag.sh`. |
| [`rsyslog_local_container_testing`](.agent/skills/rsyslog_local_container_testing/SKILL.md) | CI-style local dev-container validation, analyzer-first flow, service-skip checks, and clean-tree rules. |
| [`rsyslog_local_container_testing`](.agent/skills/rsyslog_local_container_testing/SKILL.md) | CI-style local dev-container validation, change-gated Ubuntu 26.04 first, late prompt audits, service-skip checks, and clean-tree rules. |
| [`rsyslog_pr_babysitting`](.agent/skills/rsyslog_pr_babysitting/SKILL.md) | Post-push PR monitoring, including CI failures, reruns, and unresolved review-thread checks. |
| [`rsyslog_changelog`](.agent/skills/rsyslog_changelog/SKILL.md) | Selective ChangeLog maintenance that follows release-note style and avoids low-signal churn. |
| [`rsyslog_doc`](.agent/skills/rsyslog_doc/SKILL.md) | Structured, RAG-optimized documentation and metadata. |
@ -103,11 +103,12 @@ validation as the final validation gate when container tooling is available.
[`rsyslog_local_container_testing`](.agent/skills/rsyslog_local_container_testing/SKILL.md)
skill's PR-ready local validation before reporting the task complete.
- PR-ready local container validation means the skill's ordered
change-gated sequence: local Cubic where applicable, the Ubuntu 26.04 static
analyzer, and the Ubuntu 26.04 `run-ci.sh` check run using the same relevance
gates as regular PR CI. Focused container tests are useful targeted evidence,
but they are not the final gate unless the skill explicitly allows the
reduced lane for the touched area.
change-gated sequence: the Ubuntu 26.04 `run-ci.sh` check run using the same
relevance gates as regular PR CI, the Ubuntu 26.04 static analyzer where
applicable, late prompt-based audit passes where applicable, and local Cubic
where applicable. Focused container tests are useful targeted evidence, but
they are not the final gate unless the skill explicitly allows the reduced
lane for the touched area.
- Use the skill's configured CI-equivalent dev image, including Docker Hub dev
images when appropriate. Use a locally built image only when validating that
local image or the runtime container produced by the task.

View File

@ -1,30 +1,29 @@
# AI and Machine Learning Tooling for rsyslog
# AI Prompt Assets for rsyslog
This directory contains AI and Machine Learning (ML) tools that
This directory contains maintained prompt assets that agents may read during
rsyslog development, review, documentation, and support workflows.
---
## Purpose
The `ai` directory is designated for a new generation of tooling that will
interface with rsyslog. It will house external AI and ML models and
applications. This approach ensures the rsyslog core remains lean and
robust while allowing for flexible and powerful extensions.
The `ai` directory keeps reusable AI-facing context outside the rsyslog core.
These files are not executable validation scripts and should not launch local
AI tooling by themselves. Active agents read the relevant prompt text and apply
it to the current diff or task.
---
## Current Status
## Current Assets
**No tools exist yet.**
- `rsyslog_memory_auditor/`: memory ownership, cleanup, leak, and NULL-check
review prompts for C changes.
- `rsyslog_bug_finder/`: resource, concurrency, lock-order, and lifecycle
review prompt.
- `rsyslog_commit_assistant/`: commit message guidance.
- `rsyslog_doc_assistant/` and `rsyslog_code_doc_builder/`: documentation
prompt assets.
- `rsyslog_issue_assistant/` and `support_gpt/`: issue and support prompt
assets.
This directory and its `README.md` have been created to provide a
consistent and official location for this work as it progresses.
Active development is ongoing, and tools will be added here once they
reach a stable state.
## Workflow
---
## Vision
Future tools in this directory will leverage artificial intelligence and
machine learning for tasks like advanced log analysis, anomaly detection,
and intelligent system monitoring. The goal is to enhance rsyslog's
capabilities without integrating complex models directly into the core
daemon.
Prompt-based audits are intentionally manual from repository tooling. See
`.agent/skills/rsyslog_local_container_testing/SKILL.md` for when agents should
apply these prompts as late validation passes.

View File

@ -1,407 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# copilot-policy-alloc.sh
#
# Purpose:
# Multi-stage rsyslog policy check pipeline using GitHub Copilot CLI.
# Runs checks from least to most costly, stopping at first issue.
#
# Validation role:
# This script runs local review stages, including local Cubic when available.
# Hosted Cubic/Gemini PR comments are useful follow-up review feedback, but
# they do not substitute for this local Cubic stage. Likewise, local Cubic
# does not substitute for the full local container validation sequence in
# .agent/skills/rsyslog_local_container_testing/SKILL.md.
#
# Default diff:
# main...HEAD
#
# Overrides:
# ./copilot-policy-alloc.sh origin/main...HEAD
# ./copilot-policy-alloc.sh --cached
#
# Output:
# JSON only (machine-consumable)
#
# Exit codes:
# 0 all checks passed (or no changes)
# 1 policy violations found
# 3 tool/setup error
# Configuration
POLICY_CHECK_MODEL="gpt-5.1-codex-mini"
CUBIC_BASE_BRANCH="main"
DEBUG=false
SKIP_STAGES=()
FORCE_SUBCLI=""
die() {
echo "error: $*" >&2
exit 3
}
show_help() {
cat <<'HELP'
Usage: copilot-policy-alloc.sh [OPTIONS] [DIFF_SPEC]
Multi-stage rsyslog policy check pipeline using AI code review tools.
Runs checks from least to most costly, stopping at first issue.
OPTIONS:
--debug Enable debug output to stderr
--skip <stage> Skip a pipeline stage (repeatable)
Values: alloc-policy, mock-distcheck, cubic
--subcli <tool> Force specific CLI tool
Values: copilot, codex
--cached Use staged changes instead of commits
-h, --help Show this help message
DIFF_SPEC:
Default: main...HEAD
Examples: origin/main...HEAD, HEAD~1..HEAD
PIPELINE STAGES:
1. alloc-policy - Scans .c/.h files for raw malloc/calloc/realloc/strdup calls
2. mock-distcheck - Runs 'make distcheck TEST_RUN_TYPE=MOCK-OK' for packaging validation
3. cubic - Runs local cubic code review (final stage)
NOTE: Agent should build/test working tree BEFORE running this script. For
implementation PRs, also run the rsyslog_local_container_testing skill's
full ordered container gate when container tooling is available. A focused
run-ci.sh invocation is targeted container testing, not full validation,
unless that skill explicitly permits the reduced lane.
EXIT CODES:
0 All checks passed (or no changes/skipped)
1 Policy violations found
3 Tool/setup error
EXAMPLES:
copilot-policy-alloc.sh --debug
copilot-policy-alloc.sh --skip cubic
copilot-policy-alloc.sh --skip alloc-policy --skip cubic
copilot-policy-alloc.sh --skip mock-distcheck
copilot-policy-alloc.sh --subcli copilot --debug
copilot-policy-alloc.sh --subcli codex origin/main...HEAD
HELP
exit 0
}
debug() {
if $DEBUG; then
echo "[DEBUG] $*" >&2
fi
}
should_skip() {
local stage="$1"
for skip in "${SKIP_STAGES[@]}"; do
if [[ "$skip" == "$stage" ]]; then
return 0
fi
done
return 1
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"
}
need_cmd git
# Either codex or copilot required (check later)
has_issues() {
local output="$1"
# Try JSON first (cubic, codex output)
if has_issues_array "$output"; then
return 0
fi
# For text streams, check if output is non-empty (skip common "success" patterns)
if [[ -n "$output" ]] && ! echo "$output" | grep -qE '^(\s*|\{\s*"issues"\s*:\s*\[\s*\]\s*\})$'; then
return 0
fi
return 1
}
has_issues_array() {
local json="$1"
# Check if issues array has elements: "issues":[...] with content
echo "$json" | grep -q '"issues"\s*:\s*\[[^]]\+\]'
}
check_allocation_policy() {
local diff_file="$1"
# Read diff content
local diff_content
diff_content=$(<"$diff_file")
# Compact, CLI-safe policy prompt
local PROMPT=$'IMPORTANT: Do NOT read AGENTS.md, DEVELOPING.md, or other repo guidance files. This is a simple pattern-matching task.\n\n'
PROMPT+=$'Workflow: rsyslog policy-alloc\n\n'
PROMPT+=$'Task: Scan ONLY added/modified lines in the diff for raw allocation calls:\n'
PROMPT+=$'malloc(, calloc(, realloc(, strdup(, strndup(\n'
PROMPT+=$'Regex: \\b(malloc|calloc|realloc|strdup|strndup)\\s*\\(\n\n'
PROMPT+=$'A match is a violation unless the same statement/expression clearly uses an approved project wrapper/macro '
PROMPT+=$'(e.g. CHKmalloc/CHKrealloc or project strdup wrapper). '
PROMPT+=$'If uncertain, report as warning with lower confidence.\n\n'
PROMPT+=$'Output JSON ONLY (no prose). Match this exact format:\n'
PROMPT+=$'{\n'
PROMPT+=$' "issues": [\n'
PROMPT+=$' {\n'
PROMPT+=$' "file": "<path>",\n'
PROMPT+=$' "line": <int>,\n'
PROMPT+=$' "severity": "error|warning|info",\n'
PROMPT+=$' "code": "ALLOC_RAW_CALL",\n'
PROMPT+=$' "message": "<description>",\n'
PROMPT+=$' "suggestion": "<recommended fix>"\n'
PROMPT+=$' }\n'
PROMPT+=$' ]\n'
PROMPT+=$'}\n\n'
PROMPT+=$'If no issues found, return: {"issues":[]}\n\n'
PROMPT+=$'Analyze this diff:\n\n'
PROMPT+="$diff_content"
if $DEBUG; then
debug "=== Prompt content (first 500 chars) ==="
echo "${PROMPT:0:500}..." >&2
debug "=== Diff file size: $(wc -l < "$diff_file") lines ==="
debug "=== Diff file content preview (first 20 lines) ==="
head -20 "$diff_file" >&2
fi
# Prefer codex (faster), fall back to copilot, or force via --subcli
if [[ "$FORCE_SUBCLI" == "codex" ]] || { [[ -z "$FORCE_SUBCLI" ]] && command -v codex >/dev/null 2>&1; }; then
if [[ "$FORCE_SUBCLI" == "codex" ]] && ! command -v codex >/dev/null 2>&1; then
die "Forced --subcli codex but codex not found"
fi
debug "Using codex for policy check"
debug "Command: codex exec --model $POLICY_CHECK_MODEL --dangerously-bypass-approvals-and-sandbox <PROMPT>"
if $DEBUG; then
codex exec \
--model "$POLICY_CHECK_MODEL" \
--dangerously-bypass-approvals-and-sandbox \
"$PROMPT"
else
# Suppress codex's verbose output (stderr) when not in debug mode
codex exec \
--model "$POLICY_CHECK_MODEL" \
--dangerously-bypass-approvals-and-sandbox \
"$PROMPT" 2>/dev/null
fi
elif [[ "$FORCE_SUBCLI" == "copilot" ]] || { [[ -z "$FORCE_SUBCLI" ]] && command -v copilot >/dev/null 2>&1; }; then
if [[ "$FORCE_SUBCLI" == "copilot" ]] && ! command -v copilot >/dev/null 2>&1; then
die "Forced --subcli copilot but copilot not found"
fi
debug "Using copilot for policy check"
debug "Command: copilot --model $POLICY_CHECK_MODEL --add-dir /tmp --allow-all-tools --prompt <PROMPT>"
copilot \
--model "$POLICY_CHECK_MODEL" \
--add-dir /tmp \
--allow-all-tools \
--prompt "$PROMPT"
else
debug "Neither codex nor copilot CLI found, skipping check"
# Return valid empty JSON matching cubic format
echo '{"issues":[]}'
fi
}
run_cubic_review() {
if ! command -v cubic >/dev/null 2>&1; then
return 0
fi
debug "Command: cubic review --base $CUBIC_BASE_BRANCH --json"
cubic review --base "$CUBIC_BASE_BRANCH" --json 2>&1 || true
}
run_mock_distcheck() {
debug "Mock distcheck: Running make distcheck TEST_RUN_TYPE=MOCK-OK..."
# Run distcheck - let output stream directly (don't capture)
# Return exit code for caller to check
if ! make distcheck TEST_RUN_TYPE=MOCK-OK -j$(nproc) 2>&1; then
return 1 # Signal failure
fi
return 0 # Success
}
# Parse arguments
DIFF_ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
show_help
;;
--debug)
DEBUG=true
shift
;;
--skip)
if [[ $# -lt 2 ]]; then
die "--skip requires an argument (alloc-policy|mock-distcheck|cubic)"
fi
SKIP_STAGES+=("$2")
shift 2
;;
--subcli)
if [[ $# -lt 2 ]]; then
die "--subcli requires an argument (copilot|codex)"
fi
FORCE_SUBCLI="$2"
shift 2
;;
--cached)
DIFF_ARGS+=(--cached)
shift
;;
*)
DIFF_ARGS+=("$1")
shift
;;
esac
done
# Default diff target if none specified
if [[ ${#DIFF_ARGS[@]} -eq 0 ]]; then
DIFF_ARGS+=("main...HEAD")
fi
debug "Debug mode enabled"
debug "Diff args: ${DIFF_ARGS[*]}"
if [[ ${#SKIP_STAGES[@]} -gt 0 ]]; then
debug "Skipping stages: ${SKIP_STAGES[*]}"
fi
if [[ -n "$FORCE_SUBCLI" ]]; then
debug "Forcing subcli: $FORCE_SUBCLI"
fi
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || die "not a git repo?"
cd "$REPO_ROOT"
debug "Repository root: $REPO_ROOT"
TMP_DIFF="$(mktemp -p /tmp copilot_policy_alloc.XXXXXX.diff)"
cleanup() {
rm -f "$TMP_DIFF"
# distcheck leaves read-only directories - restore write perms before removing
if compgen -G "rsyslog-*.daily" > /dev/null 2>&1; then
chmod -R u+w rsyslog-*.daily 2>/dev/null || true
rm -rf rsyslog-*.daily
fi
rm -f rsyslog-*.tar.gz 2>/dev/null || true
}
trap cleanup EXIT
debug "Temp diff file: $TMP_DIFF"
# Generate minimal diff (unified=0 keeps token usage low)
debug "Generating diff..."
debug "Command: git diff --unified=0 --no-color ${DIFF_ARGS[*]}"
git diff --unified=0 --no-color "${DIFF_ARGS[@]}" >"$TMP_DIFF" || true
# Also include unstaged changes in working directory
if [[ "${DIFF_ARGS[*]}" != *"--cached"* ]]; then
debug "Command: git diff --unified=0 --no-color (unstaged)"
git diff --unified=0 --no-color >>"$TMP_DIFF" || true
fi
# Check for untracked files
debug "Command: git ls-files --others --exclude-standard"
UNTRACKED_FILES=$(git ls-files --others --exclude-standard)
if [[ -n "$UNTRACKED_FILES" ]]; then
debug "Found untracked files:"
$DEBUG && echo "$UNTRACKED_FILES" >&2
# Add untracked file contents to diff as proper unified diffs
# Skip: the script itself, build artifacts, and generated tarballs
while IFS= read -r file; do
if [[ -f "$file" && "$file" != "ai/copilot-policy-alloc.sh" && "$file" != rsyslog-*.daily/* && "$file" != *.tar.gz ]]; then
line_count=$(wc -l < "$file")
# Limit file size to prevent argument list too long
if [[ $line_count -lt 10000 ]]; then
echo "diff --git a/$file b/$file" >>"$TMP_DIFF"
echo "new file mode 100644" >>"$TMP_DIFF"
echo "index 0000000..0000000" >>"$TMP_DIFF"
echo "--- /dev/null" >>"$TMP_DIFF"
echo "+++ b/$file" >>"$TMP_DIFF"
echo "@@ -0,0 +1,$line_count @@" >>"$TMP_DIFF"
# Add all lines as additions with proper + prefix (no space)
sed 's/^/+/' "$file" >>"$TMP_DIFF"
else
debug "Skipping large untracked file: $file ($line_count lines)"
fi
fi
done <<< "$UNTRACKED_FILES"
fi
DIFF_IS_EMPTY=false
if [[ ! -s "$TMP_DIFF" ]]; then
debug "No changes detected: no diff and no untracked files"
echo '{"issues":[]}'
exit 0
fi
# Pipeline: Run checks from least to most costly
debug "Starting pipeline checks..."
# Stage 1: Allocation policy check (only for .c/.h files)
if should_skip "alloc-policy"; then
debug "Stage 1: Skipped via --skip alloc-policy"
RESULT='{"issues":[]}'
elif grep -q '^[+].*\.\(c\|h\)' "$TMP_DIFF" || git diff --name-only "${DIFF_ARGS[@]}" 2>/dev/null | grep -q '\.\(c\|h\)$'; then
debug "Stage 1: Running allocation policy check (C/H files detected)..."
RESULT=$(check_allocation_policy "$TMP_DIFF")
debug "Stage 1 result:"
$DEBUG && echo "$RESULT" | head -20 >&2
if has_issues "$RESULT"; then
debug "Issues found in Stage 1, stopping pipeline"
echo "$RESULT"
exit 1
fi
debug "Stage 1 passed"
else
debug "Stage 1: Skipped (no .c or .h files modified)"
RESULT='{"issues":[]}'
fi
# Stage 2: Add more checks here (in cost order)
# ...
# Stage 2: Mock distcheck
if should_skip "mock-distcheck"; then
debug "Stage 2: Skipped via --skip mock-distcheck"
else
debug "Stage 2: Running mock distcheck..."
if ! run_mock_distcheck; then
debug "Issues found in Stage 2, stopping pipeline"
exit 1
fi
debug "Stage 2 passed"
fi
# Final stage: Run cubic if available
debug "Final stage: Checking for cubic..."
if should_skip "cubic"; then
debug "Cubic: Skipped via --skip cubic"
else
CUBIC_OUTPUT=$(run_cubic_review)
if [[ -n "$CUBIC_OUTPUT" ]]; then
debug "Cubic output received:"
$DEBUG && echo "$CUBIC_OUTPUT" | head -20 >&2
# Check if cubic JSON has non-empty issues array
if has_issues_array "$CUBIC_OUTPUT"; then
debug "Cubic detected issues"
echo "$CUBIC_OUTPUT"
exit 1
fi
debug "Cubic ran but found no issues"
else
debug "Cubic not installed or no output"
fi
fi
# All checks passed
debug "All pipeline checks passed"
echo "$RESULT"
exit 0

View File

@ -22,7 +22,11 @@
set -eu
mode=plan
base_ref="${RSYSLOG_LOCAL_VALIDATION_BASE:-origin/main}"
base_ref="${RSYSLOG_LOCAL_VALIDATION_BASE:-}"
base_ref_source=environment
if [ -z "$base_ref" ]; then
base_ref_source=auto
fi
usage() {
cat <<'EOF'
@ -34,11 +38,17 @@ unstaged tracked changes, and untracked files. Generated build products should
be cleaned before using this helper.
Environment knobs:
RSYSLOG_LOCAL_VALIDATION_BASE Base ref for committed branch changes (default: origin/main)
RSYSLOG_LOCAL_VALIDATION_BASE Base ref for committed branch changes
RSYSLOG_LOCAL_BUILD_JOBS Local build concurrency (default: 10)
RSYSLOG_LOCAL_CHECK_JOBS Local make check concurrency (default: 10)
RSYSLOG_LOCAL_DOC_JOBS Local Sphinx doc build jobs (default: nproc)
Without --base or RSYSLOG_LOCAL_VALIDATION_BASE, the helper uses
rsyslog.localValidationBase from git config when set, then the oldest HEAD
reflog entry for this worktree, then origin/main as a fallback. In normal
dedicated worktrees this makes local review compare against the commit that was
HEAD when the worktree was created, even after origin/main moves.
The --run mode exits on the first validation finding from a tool that actually
runs. Missing local tools produce warnings, not failures; hosted CI or a fuller
local environment must cover the skipped plumbing.
@ -57,6 +67,7 @@ while [ "$#" -gt 0 ]; do
exit 2
fi
base_ref="$2"
base_ref_source=cli
shift 2
;;
-h | --help)
@ -74,12 +85,33 @@ done
repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"
resolve_default_base_ref() {
configured_base="$(git config --get rsyslog.localValidationBase 2>/dev/null || true)"
if [ -n "$configured_base" ]; then
printf '%s\n' "$configured_base"
return
fi
reflog_base="$(git reflog --format=%H HEAD 2>/dev/null | tail -n 1 || true)"
if [ -n "$reflog_base" ] && git rev-parse --verify "$reflog_base^{commit}" >/dev/null 2>&1; then
printf '%s\n' "$reflog_base"
return
fi
printf '%s\n' origin/main
}
if [ -z "$base_ref" ]; then
base_ref="$(resolve_default_base_ref)"
fi
tmp_changed="$(mktemp)"
tmp_status="$(mktemp)"
tmp_shell="$(mktemp)"
tmp_python="$(mktemp)"
tmp_c="$(mktemp)"
cleanup() {
rm -f "$tmp_changed" "$tmp_shell" "$tmp_python" "$tmp_c"
rm -f "$tmp_changed" "$tmp_status" "$tmp_shell" "$tmp_python" "$tmp_c"
}
trap cleanup EXIT
@ -89,8 +121,14 @@ if ! git rev-parse --verify "$base_ref" >/dev/null 2>&1; then
fi
{
git diff --name-only --diff-filter=d "$base_ref"...HEAD
git diff --name-only --diff-filter=d HEAD
git diff --name-status --diff-filter=ACMRD "$base_ref"...HEAD
git diff --name-status --diff-filter=ACMRD HEAD
git ls-files --others --exclude-standard | sed 's/^/A /'
} | sort -u > "$tmp_status"
{
git diff --name-only "$base_ref"...HEAD
git diff --name-only HEAD
git ls-files --others --exclude-standard
} | sort -u > "$tmp_changed"
@ -116,6 +154,7 @@ has_testbench_plumbing=0
has_test_shell_only=0
has_c=0
has_runtime_ci=0
has_dist_risk=0
if matches_any '^(doc/source/|doc/Makefile\.am$|doc/tools/|doc/requirements\.txt$|doc/source/conf\.py$)'; then
has_rendered_docs=1
@ -139,6 +178,22 @@ fi
if matches_any '^((runtime|grammar|tools|plugins|contrib|compat)/|tests/|[^/]+\.(c|h)$|configure\.ac$|Makefile\.am$|m4/|\.github/workflows/run_checks\.yml$)'; then
has_runtime_ci=1
fi
if awk '
BEGIN { FS = "\t" }
/^[ACDMR]/ {
for (i = 2; i <= NF; i++) {
if ($i ~ /^tests\/[^/]+\.sh$/ ||
$i ~ /(^|\/)Makefile\.am$/ ||
$i == "configure.ac" ||
$i ~ /^m4\//) {
found = 1
}
}
}
END { exit found ? 0 : 1 }
' "$tmp_status"; then
has_dist_risk=1
fi
agent_docs_only=0
if matches_only '(^|/)AGENTS(\.local)?\.md$|^\.agent/skills/|^\.codex/skills/'; then
@ -155,9 +210,17 @@ if [ "$has_rendered_docs" -eq 0 ] && matches_only '(^|/)AGENTS(\.local)?\.md$|^\
validation_tooling_only=1
fi
grep -E '\.sh$' "$tmp_changed" > "$tmp_shell" || true
grep -E '\.py$' "$tmp_changed" > "$tmp_python" || true
grep -E '\.(c|h)$' "$tmp_changed" > "$tmp_c" || true
: > "$tmp_shell"
: > "$tmp_python"
: > "$tmp_c"
while IFS= read -r file; do
[ -f "$file" ] || continue
case "$file" in
*.sh) printf '%s\n' "$file" >> "$tmp_shell" ;;
*.py) printf '%s\n' "$file" >> "$tmp_python" ;;
*.c | *.h) printf '%s\n' "$file" >> "$tmp_c" ;;
esac
done < "$tmp_changed"
classification=general
if [ "$agent_docs_only" -eq 1 ]; then
@ -190,7 +253,7 @@ check_jobs="${RSYSLOG_LOCAL_CHECK_JOBS:-10}"
print_plan() {
echo "Local validation classification: $classification"
echo "Base ref: $base_ref"
echo "Base ref: $base_ref ($base_ref_source)"
echo
echo "Changed files:"
sed 's/^/ /' "$tmp_changed"
@ -212,16 +275,23 @@ print_plan() {
;;
test-shell-only)
echo " - shellcheck changed tests if shellcheck is installed."
echo " - devtools/check-test-antipatterns.sh on changed tests."
echo " - Focused container test with CI_MAKE_CHECK_TESTS set to changed tests."
echo " - Add broad Ubuntu 26.04 run if the test changes timing, ports, process lifecycle, or shared behavior."
;;
testbench-plumbing | code-or-ci | general)
echo " - Available cheap diff-scoped linters."
echo " - Cubic where applicable."
echo " - Ubuntu 26.04 static analyzer for C/testbench/code changes."
echo " - Advisory raw allocation scan for changed C/H files."
echo " - devtools/check-test-antipatterns.sh on changed tests."
echo " - Change-gated Ubuntu 26.04 run-ci.sh check."
echo " - Ubuntu 26.04 static analyzer for C/testbench/code changes."
echo " - Late prompt-based audit passes for applicable C/H, concurrency, test, or build changes."
echo " - Cubic where applicable."
;;
esac
if [ "$has_dist_risk" -eq 1 ]; then
echo " - make distcheck TEST_RUN_TYPE=MOCK-OK -j<jobs> for distribution-risk changes."
fi
}
run_shellcheck() {
@ -273,6 +343,82 @@ run_format_code_check() {
fi
}
run_raw_alloc_scan() {
if [ ! -s "$tmp_c" ]; then
return 0
fi
findings="$(
{
git diff --unified=0 --no-color "$base_ref"...HEAD -- '*.c' '*.h'
git diff --unified=0 --no-color HEAD -- '*.c' '*.h'
while IFS= read -r file; do
case "$file" in
*.c | *.h)
if git ls-files --others --exclude-standard -- "$file" | grep -q .; then
printf '+++ b/%s\n' "$file"
awk '{ print "+" $0 }' "$file"
fi
;;
esac
done < "$tmp_c"
} | awk '
/^\+\+\+ / {
file = substr($0, 7)
sub(/^b\//, "", file)
next
}
/^\+[^+]/ {
line = substr($0, 2)
if (line ~ /(^|[^A-Za-z0-9_])(malloc|calloc|realloc|strdup|strndup)[[:space:]]*\(/ &&
line !~ /(^|[^A-Za-z0-9_])CHK(malloc|realloc)[[:space:]]*\(/) {
if (file == "") {
file = "(untracked C/H file)"
}
print file ": " line
}
}
'
)"
if [ -n "$findings" ]; then
echo "warning: raw allocation calls found in changed C/H lines; prefer rsyslog allocation helpers where practical" >&2
printf '%s\n' "$findings" >&2
fi
}
run_test_antipattern_scan() {
found_tests=0
while IFS= read -r file; do
case "$file" in
tests/*/*.sh)
;;
tests/*.sh)
[ -f "$file" ] || continue
found_tests=1
;;
esac
done < "$tmp_changed"
if [ "$found_tests" -eq 0 ]; then
return 0
fi
if [ -x devtools/check-test-antipatterns.sh ]; then
# This helper is advisory and exits successfully even with findings.
while IFS= read -r file; do
case "$file" in
tests/*/*.sh)
;;
tests/*.sh)
[ -f "$file" ] || continue
devtools/check-test-antipatterns.sh "$file"
;;
esac
done < "$tmp_changed"
else
echo "warning: devtools/check-test-antipatterns.sh missing or not executable; skipping test antipattern scan" >&2
fi
}
run_docs_build() {
if [ ! -x ./doc/tools/build-doc-linux.sh ]; then
echo "warning: doc/tools/build-doc-linux.sh missing or not executable; skipping docs build" >&2
@ -324,12 +470,36 @@ run_cubic_if_available() {
;;
esac
if command -v cubic >/dev/null 2>&1; then
cubic review --print-logs --base HEAD
cubic review --print-logs --base "$base_ref"
else
echo "warning: cubic not installed; skipping local Cubic review" >&2
fi
}
run_mock_distcheck_if_needed() {
if [ "$has_dist_risk" -ne 1 ]; then
return 0
fi
if ! command -v make >/dev/null 2>&1; then
echo "warning: make not installed; skipping mock distcheck" >&2
return 0
fi
make distcheck TEST_RUN_TYPE=MOCK-OK -j"$(nproc_jobs)"
}
run_prompt_audit_reminder() {
case "$classification" in
agent-doc-only | internal-doc-only | rendered-docs | local-validation-tooling)
return 0
;;
esac
echo "Prompt audit reminder: after deterministic checks, apply relevant canned prompts manually." >&2
echo " - C/H memory lifecycle: ai/rsyslog_memory_auditor/base_prompt.txt" >&2
echo " - Locks/queues/threads/resources: ai/rsyslog_bug_finder/base_prompt.txt" >&2
echo " - Test/build plumbing: project standards audit from rsyslog_local_container_testing skill" >&2
echo "Do not launch another AI CLI from this helper." >&2
}
run_static_analyzer() {
have_container_tooling || return 0
have_devcontainer_script || return 0
@ -370,7 +540,17 @@ run_change_gated_ubuntu26() {
}
run_focused_test_shell() {
tests="$(grep -E '^tests/[^/]+\.sh$' "$tmp_changed" | sed 's#^tests/##' | tr '\n' ' ')"
tests="$(awk '
BEGIN { FS = "\t" }
$1 !~ /^D/ {
for (i = 2; i <= NF; i++) {
if ($i ~ /^tests\/[^/]+\.sh$/) {
sub(/^tests\//, "", $i)
printf "%s ", $i
}
}
}
' "$tmp_status")"
if [ -z "$tests" ]; then
return 0
fi
@ -400,6 +580,8 @@ echo "Executing local validation plan..."
run_shellcheck
run_python_style
run_format_code_check
run_raw_alloc_scan
run_test_antipattern_scan
git diff --check "$base_ref"...HEAD
git diff --cached --check
git diff --check
@ -415,11 +597,14 @@ rendered-docs)
run_docs_build
;;
test-shell-only)
run_mock_distcheck_if_needed
run_focused_test_shell
;;
testbench-plumbing | code-or-ci | general)
run_cubic_if_available
run_static_analyzer
run_mock_distcheck_if_needed
run_change_gated_ubuntu26
run_static_analyzer
run_prompt_audit_reminder
run_cubic_if_available
;;
esac