Files
gitea-pr-review/skills/organized-feedback/scripts/validate_organized_feedback_skill.sh
T

265 lines
5.9 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
SKILL_FILE="$SKILL_ROOT/SKILL.md"
INPUT_EXAMPLE="$SKILL_ROOT/examples/input-pr-review.md"
OUTPUT_EXAMPLE="$SKILL_ROOT/examples/output-organized-feedback.md"
print_usage() {
cat <<'USAGE'
Usage:
validate_organized_feedback_skill.sh --output <output.md> [--self-check]
validate_organized_feedback_skill.sh --help
Modes:
Default: validate an arbitrary Organized Feedback markdown output file.
--self-check: additionally validate bundled skill contract and examples.
USAGE
}
require_cmds() {
if ! command -v rg >/dev/null 2>&1; then
echo "FAIL: rg is required but was not found in PATH"
exit 1
fi
if ! command -v awk >/dev/null 2>&1; then
echo "FAIL: awk is required but was not found in PATH"
exit 1
fi
}
validate_output_file() {
local file="$1"
if [[ ! -f "$file" ]]; then
echo "FAIL: missing output file -> $file"
exit 1
fi
if ! rg -n -q --pcre2 '^## Organized Feedback$' "$file"; then
echo "FAIL: missing required section -> ## Organized Feedback"
exit 1
fi
if rg -n -q -i --pcre2 'coverage[ -]?audit' "$file"; then
echo "FAIL: output must not include Coverage Audit"
exit 1
fi
awk '
function trim(s) {
gsub(/^[ \t]+/, "", s)
gsub(/[ \t]+$/, "", s)
return s
}
function validate_refs(line) {
sub(/^- Source-Refs:[ \t]*/, "", line)
n = split(line, parts, /,/)
if (n < 1) return 0
for (i = 1; i <= n; i++) {
ref = trim(parts[i])
if (ref !~ /^R[0-9]+(\.[0-9]+)+$/) {
return 0
}
}
return 1
}
function reset_item() {
item_seen = 0
has_type = 0
has_refs = 0
is_rfc = 0
has_scope = 0
has_necessity = 0
}
function finalize_item() {
if (!item_seen) return 1
if (!has_type) {
print "FAIL: item missing - Type: line" > "/dev/stderr"
return 0
}
if (!has_refs) {
print "FAIL: item missing valid - Source-Refs: line" > "/dev/stderr"
return 0
}
if (is_rfc && (!has_scope || !has_necessity)) {
print "FAIL: request-for-change item must include Change-Scope and Necessity" > "/dev/stderr"
return 0
}
return 1
}
BEGIN {
in_of = 0
item_count = 0
reset_item()
}
/^## Organized Feedback$/ {
in_of = 1
next
}
in_of && /^## / {
if (!finalize_item()) exit 1
in_of = 0
next
}
in_of {
if ($0 ~ /^### Item [0-9]+$/) {
if (!finalize_item()) exit 1
reset_item()
item_seen = 1
item_count++
next
}
if (!item_seen) next
if ($0 ~ /^- Type:[ \t]*/) {
has_type = 1
if ($0 ~ /^- Type:[ \t]*request-for-change([ \t]|$)/) {
is_rfc = 1
}
}
if ($0 ~ /^- Change-Scope:[ \t]*/) has_scope = 1
if ($0 ~ /^- Necessity:[ \t]*/) has_necessity = 1
if ($0 ~ /^- Source-Refs:[ \t]*/) {
if (validate_refs($0)) {
has_refs = 1
} else {
print "FAIL: invalid Source-Refs format -> " $0 > "/dev/stderr"
exit 1
}
}
}
END {
if (in_of && !finalize_item()) exit 1
if (item_count == 0) {
print "FAIL: ## Organized Feedback has no ### Item entries" > "/dev/stderr"
exit 1
}
}
' "$file"
echo "PASS: output format validated -> $file"
}
validate_skill_contract() {
if [[ ! -f "$SKILL_FILE" ]]; then
echo "FAIL: missing $SKILL_FILE"
exit 1
fi
for example_file in "$INPUT_EXAMPLE" "$OUTPUT_EXAMPLE"; do
if [[ ! -f "$example_file" ]]; then
echo "FAIL: missing $example_file"
exit 1
fi
done
local -a anchored_patterns=(
'^## Inputs$'
'^## Hard Gates$'
'^## Classification Taxonomy$'
'^## RFC Subfields$'
'^## Processing Flow$'
'^## Interaction-Only Coverage Audit$'
'^## Unknown Handling$'
'^## Final File Format$'
'^## Output Discipline$'
'^ - `local`: .+$'
'^ - `implement`: .+$'
'^ - `api-change`: .+$'
'^ - `requirement-change`: .+$'
'^ - `nice-to-have`: .+$'
'^ - `should-fix`: .+$'
'^ - `must-fix`: .+$'
'^- `All-Source-Refs`: .+$'
'^- `Covered-Source-Refs`: .+$'
'^- `Missing-Source-Refs`: .+$'
'^Source reference format rule:$'
)
for pattern in "${anchored_patterns[@]}"; do
if ! rg -n -q --pcre2 "$pattern" "$SKILL_FILE"; then
echo "FAIL: missing required skill rule -> $pattern"
exit 1
fi
done
for legacy in "All refs" "Covered refs" "Missing refs"; do
if rg -Fq "$legacy" "$SKILL_FILE"; then
echo "FAIL: legacy alias found in SKILL.md -> $legacy"
exit 1
fi
done
if ! rg -Fq -- "unknown>=1" "$SKILL_FILE" || ! rg -Fq -- "反思复判" "$SKILL_FILE"; then
echo "FAIL: missing unknown reflection rule semantics in SKILL.md"
exit 1
fi
echo "PASS: skill contract validated -> $SKILL_FILE"
}
main() {
require_cmds
local output_file=""
local self_check=0
while [[ $# -gt 0 ]]; do
case "$1" in
--output)
if [[ $# -lt 2 ]]; then
echo "FAIL: --output requires a file path"
print_usage
exit 1
fi
output_file="$2"
shift 2
;;
--self-check)
self_check=1
shift
;;
--help|-h)
print_usage
exit 0
;;
*)
echo "FAIL: unknown argument -> $1"
print_usage
exit 1
;;
esac
done
if [[ -z "$output_file" ]]; then
echo "FAIL: --output is required"
print_usage
exit 1
fi
validate_output_file "$output_file"
if [[ "$self_check" -eq 1 ]]; then
validate_skill_contract
validate_output_file "$OUTPUT_EXAMPLE"
fi
echo "PASS: organized-feedback validation completed"
}
main "$@"