You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
buffer_platform/.trae/skills/comet/scripts/comet-yaml-validate.sh

226 lines
6.7 KiB

#!/bin/bash
# Comet YAML Schema Validator — validates .comet.yaml structure
# Usage: comet-yaml-validate.sh <change-name>
# Exit 0 = valid, exit 1 = errors found (printed to stderr)
set -euo pipefail
red() { echo -e "\033[31m$1\033[0m" >&2; }
green() { echo -e "\033[32m$1\033[0m" >&2; }
warn() { echo -e "\033[33m$1\033[0m" >&2; }
# Input validation - prevent path traversal
validate_change_name() {
local name="$1"
# Reject empty names
if [ -z "$name" ]; then
red "ERROR: Change name cannot be empty" >&2
exit 1
fi
# Only allow alphanumeric, hyphens, and underscores
if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
red "ERROR: Invalid change name: '$name'" >&2
red "Valid characters: a-z, A-Z, 0-9, -, _" >&2
exit 1
fi
# Reject path traversal attempts
if [[ "$name" =~ \.\. ]]; then
red "ERROR: Change name cannot contain '..' (path traversal not allowed)" >&2
exit 1
fi
}
validate_change_name "$1"
CHANGE="$1"
CHANGE_DIR="openspec/changes/$CHANGE"
if [ ! -d "$CHANGE_DIR" ] && [ -d "openspec/changes/archive/$CHANGE" ]; then
CHANGE_DIR="openspec/changes/archive/$CHANGE"
fi
YAML="$CHANGE_DIR/.comet.yaml"
ERRORS=0
WARNINGS=0
# Helper: get value of a top-level field (handles null, empty, quoted)
field_value() {
local value
value=$(grep "^${1}:" "$YAML" 2>/dev/null | sed "s/^${1}: *//" || true)
value=$(strip_inline_comment "$value")
strip_wrapping_quotes "$value"
}
strip_inline_comment() {
local value="$1"
printf '%s\n' "$value" | awk -v squote="'" '
{
out = ""
quote = ""
for (i = 1; i <= length($0); i++) {
c = substr($0, i, 1)
if (quote == "") {
if (c == "\"" || c == squote) {
quote = c
} else if (c == "#" && (i == 1 || substr($0, i - 1, 1) ~ /[[:space:]]/)) {
sub(/[[:space:]]+$/, "", out)
print out
next
}
} else if (c == quote) {
quote = ""
}
out = out c
}
print out
}
'
}
strip_wrapping_quotes() {
local value="$1"
case "$value" in
\"*\")
printf '%s\n' "${value:1:${#value}-2}"
;;
\'*\')
printf '%s\n' "${value:1:${#value}-2}"
;;
*)
printf '%s\n' "$value"
;;
esac
}
fail() { red " FAIL: $1"; ERRORS=$((ERRORS + 1)); }
warn_msg() { warn " WARN: $1"; WARNINGS=$((WARNINGS + 1)); }
echo "[VALIDATE] $YAML" >&2
# --- Required fields ---
REQUIRED_FIELDS="workflow phase design_doc plan build_mode isolation verify_mode verify_result verified_at archived"
for field in $REQUIRED_FIELDS; do
if ! grep -q "^${field}:" "$YAML" 2>/dev/null; then
fail "missing required field '$field'"
fi
done
# --- Enum validation ---
validate_enum() {
local field="$1" value="$2"
shift 2
local valid_values="$*"
# null or empty is always acceptable
if [ -z "$value" ] || [ "$value" = "null" ]; then
return 0
fi
for v in $valid_values; do
if [ "$value" = "$v" ]; then
return 0
fi
done
fail "$field='$value' is not valid. Expected: $valid_values"
}
validate_required_enum() {
local field="$1" value="$2"
shift 2
local valid_values="$*"
if [ -z "$value" ] || [ "$value" = "null" ]; then
fail "$field='${value:-}' is not valid. Expected: $valid_values"
return 0
fi
validate_enum "$field" "$value" "$@"
}
workflow=$(field_value "workflow")
phase=$(field_value "phase")
context_compression=$(field_value "context_compression")
build_mode=$(field_value "build_mode")
build_pause=$(field_value "build_pause")
subagent_dispatch=$(field_value "subagent_dispatch")
tdd_mode=$(field_value "tdd_mode")
review_mode=$(field_value "review_mode")
isolation=$(field_value "isolation")
verify_mode=$(field_value "verify_mode")
auto_transition=$(field_value "auto_transition")
verify_result=$(field_value "verify_result")
branch_status=$(field_value "branch_status")
archived=$(field_value "archived")
direct_override=$(field_value "direct_override")
design_doc=$(field_value "design_doc")
plan=$(field_value "plan")
handoff_context=$(field_value "handoff_context")
handoff_hash=$(field_value "handoff_hash")
validate_enum "workflow" "$workflow" "full hotfix tweak"
validate_enum "phase" "$phase" "open design build verify archive"
validate_enum "context_compression" "$context_compression" "off beta"
validate_enum "build_mode" "$build_mode" "subagent-driven-development executing-plans direct"
validate_enum "build_pause" "$build_pause" "null plan-ready"
validate_enum "subagent_dispatch" "$subagent_dispatch" "null confirmed"
validate_enum "tdd_mode" "$tdd_mode" "tdd direct null"
validate_enum "review_mode" "$review_mode" "off standard thorough"
validate_enum "isolation" "$isolation" "branch worktree"
validate_enum "verify_mode" "$verify_mode" "light full"
if grep -q "^auto_transition:" "$YAML" 2>/dev/null; then
validate_required_enum "auto_transition" "$auto_transition" "true false"
fi
validate_enum "verify_result" "$verify_result" "pending pass fail"
validate_enum "branch_status" "$branch_status" "pending handled"
validate_enum "archived" "$archived" "true false"
validate_enum "direct_override" "$direct_override" "true false"
# --- Path validation ---
if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then
if [ ! -f "$design_doc" ]; then
fail "design_doc='$design_doc' does not exist on disk"
fi
fi
if [ -n "$plan" ] && [ "$plan" != "null" ]; then
if [ ! -f "$plan" ]; then
fail "plan='$plan' does not exist on disk"
fi
fi
if [ -n "$handoff_context" ] && [ "$handoff_context" != "null" ]; then
if [ ! -f "$handoff_context" ]; then
fail "handoff_context='$handoff_context' does not exist on disk"
fi
fi
if [ -n "$handoff_hash" ] && [ "$handoff_hash" != "null" ]; then
if [[ ! "$handoff_hash" =~ ^[a-f0-9]{64}$ ]]; then
fail "handoff_hash='$handoff_hash' is not a sha256 hex digest"
fi
fi
# --- Unknown keys check ---
KNOWN_KEYS="workflow phase context_compression design_doc plan build_mode build_pause subagent_dispatch tdd_mode review_mode isolation verify_mode auto_transition verify_result verification_report branch_status verified_at created_at archived direct_override build_command verify_command handoff_context handoff_hash base_ref"
while IFS=: read -r key _; do
key="${key// /}"
[ -z "$key" ] && continue
found=0
for known in $KNOWN_KEYS; do
[ "$key" = "$known" ] && found=1 && break
done
if [ "$found" -eq 0 ]; then
warn_msg "unknown field '$key' found"
fi
done < "$YAML"
# --- Summary ---
echo "" >&2
if [ "$ERRORS" -gt 0 ]; then
red "$ERRORS error(s), $WARNINGS warning(s) — validation FAILED"
exit 1
else
green "0 errors, $WARNINGS warning(s) — validation PASSED"
exit 0
fi