265 lines
5.9 KiB
Bash
Executable File
265 lines
5.9 KiB
Bash
Executable File
#!/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 "$@"
|