Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions hooks/bestwork-requirement-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/bin/bash
# bestwork requirement check — PostToolUse on Write|Edit
# Checks clarify/validate state files for unmet requirements
# Outputs warning if requirements are not yet covered

INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""')

# Defensive: hooks.json matcher already filters, but guard here too
case "$TOOL" in
Edit|Write) ;;
*) echo '{}'; exit 0 ;;
esac

PROJECT_ROOT=$(pwd)
BESTWORK_STATE="${PROJECT_ROOT}/.bestwork/state"
STATS_FILE="${BESTWORK_STATE}/harness-stats.json"

# Initialize stats file if missing or malformed
if [ ! -f "$STATS_FILE" ] || ! jq empty "$STATS_FILE" 2>/dev/null; then
mkdir -p "$BESTWORK_STATE"
echo '{"checksRun":0,"warningsIssued":0,"requirementsMet":0,"requirementsMissed":0,"lastUpdated":""}' > "$STATS_FILE"
fi

# NOTE: increment_stat uses read-modify-write without locking.
# Stats are informational (not gating), so occasional lost increments
# under concurrent Write/Edit are accepted. flock is not portable to macOS.
increment_stat() {
local key="$1"
local val
val=$(jq -r ".$key" "$STATS_FILE" 2>/dev/null)
[ -z "$val" ] || [ "$val" = "null" ] && val=0
val=$((val + 1))
local tmp
tmp=$(mktemp)
jq --arg k "$key" --argjson v "$val" --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'.[$k] = $v | .lastUpdated = $ts' "$STATS_FILE" > "$tmp" && mv "$tmp" "$STATS_FILE"
}

# Track that we ran a check
increment_stat "checksRun"

WARNINGS=""

# === Check clarify state ===
CLARIFY_FILE="${BESTWORK_STATE}/clarify.json"
if [ -f "$CLARIFY_FILE" ]; then
STATUS=$(jq -r '.status // ""' "$CLARIFY_FILE" 2>/dev/null)
if [ "$STATUS" = "complete" ]; then
OPEN_GAPS=$(jq -r '(.openGaps // []) | length' "$CLARIFY_FILE" 2>/dev/null)
if [ "$OPEN_GAPS" -gt 0 ] 2>/dev/null; then
GAP_LIST=$(jq -r '(.openGaps // []) | join(", ")' "$CLARIFY_FILE" 2>/dev/null)
WARNINGS="${WARNINGS}[BW gate] clarify: ${OPEN_GAPS} open gap(s) remaining — ${GAP_LIST}\n"
increment_stat "requirementsMissed"
else
increment_stat "requirementsMet"
fi
fi
fi

# === Check validate state ===
VALIDATE_FILE="${BESTWORK_STATE}/validate.json"
if [ -f "$VALIDATE_FILE" ]; then
STATUS=$(jq -r '.status // ""' "$VALIDATE_FILE" 2>/dev/null)
if [ "$STATUS" = "complete" ]; then
VERDICT=$(jq -r '.verdict // ""' "$VALIDATE_FILE" 2>/dev/null)
OVERALL=$(jq -r '(.scores.overall // 0)' "$VALIDATE_FILE" 2>/dev/null)
case "$VERDICT" in
REJECTED)
WARNINGS="${WARNINGS}[BW gate] validate: REJECTED (${OVERALL}%) — building a feature that failed validation\n"
increment_stat "requirementsMissed"
;;
WEAK)
WARNINGS="${WARNINGS}[BW gate] validate: WEAK (${OVERALL}%) — thin evidence for this feature\n"
increment_stat "requirementsMissed"
;;
CONDITIONAL)
# Info only, not a hard warning
increment_stat "requirementsMet"
;;
VALIDATED)
increment_stat "requirementsMet"
;;
esac
fi
fi

# === Output ===
if [ -n "$WARNINGS" ]; then
increment_stat "warningsIssued"
CONTEXT=$(printf '%s' "$WARNINGS" | sed '/^$/d')
jq -n --arg ctx "$CONTEXT" \
'{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":$ctx}}'
else
echo '{}'
fi
5 changes: 5 additions & 0 deletions hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
"type": "agent",
"prompt": "You are bestwork's validation agent. A file was just modified via $ARGUMENTS.\nCheck for:\n1. TypeScript errors: run `npx tsc --noEmit` and report any errors in the changed file\n2. If the file imports modules, verify the imports actually exist (grep for them)\nDo NOT fix anything. Only report issues concisely (under 3 lines). If no issues, say nothing.",
"timeout": 30
},
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/bestwork-requirement-check.sh\"",
"timeout": 5
}
]
}
Expand Down
Loading