From bce49e0f63e9d68a1515b9cc1bcd63e76eeeb217 Mon Sep 17 00:00:00 2001 From: John-Luke Peck Date: Sat, 21 Feb 2026 16:36:24 -0800 Subject: [PATCH 1/2] fix(way-match): rebuild binary for arm64 macOS and fix bash 3.2 compat The checked-in binary was a Linux x86-64 ELF which fails with "exec format error" on arm64 macOS. Rebuilt with `make local` to produce a native Mach-O binary. Both test scripts used bash 4+ features (declare -A, mapfile) that fail on macOS default bash 3.2. Replaced with parallel indexed arrays and while-read loops. --- bin/way-match | Bin 61688 -> 74344 bytes tools/way-match/test-harness.sh | 289 ++++++++++++++-------------- tools/way-match/test-integration.sh | 65 ++++--- 3 files changed, 187 insertions(+), 167 deletions(-) diff --git a/bin/way-match b/bin/way-match index 970d7b9fcceed807ee17db2aa8755424c8981f66..bcd02c14de68cf5441047353d36078fcb5ebe821 100755 GIT binary patch literal 74344 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/dev/null; then @@ -147,9 +190,10 @@ ncd_matches_way() { # --- BM25 scorer --- bm25_matches_way() { local prompt="$1" way_id="$2" - local desc="${WAY_DESC[$way_id]}" - local vocab="${WAY_VOCAB[$way_id]}" - local thresh="${WAY_THRESH[$way_id]}" + way_index "$way_id" || return 1 + local desc="${WAY_DESCS[$__idx]}" + local vocab="${WAY_VOCABS[$__idx]}" + local thresh="${WAY_THRESHS[$__idx]}" if "$BM25_BINARY" pair \ --description "$desc" \ @@ -162,52 +206,6 @@ bm25_matches_way() { fi } -# --- Score a prompt against all ways, return best match --- -# For BM25: scores all ways, returns highest-scoring match. -# For NCD: binary scorer (no score output), returns first match. -find_best_match() { - local scorer="$1" prompt="$2" - - if [[ "$scorer" == "bm25" ]]; then - local best_way="none" best_score="0" - for way_id in "${WAY_IDS[@]}"; do - local stderr_out - stderr_out=$("$BM25_BINARY" pair \ - --description "${WAY_DESC[$way_id]}" \ - --vocabulary "${WAY_VOCAB[$way_id]}" \ - --query "$prompt" \ - --threshold "0" 2>&1 >/dev/null) - local score - score=$(echo "$stderr_out" | sed -n 's/match: score=\([0-9.]*\).*/\1/p') - if [[ -n "$score" ]] && command -v bc >/dev/null 2>&1; then - if (( $(echo "$score > $best_score" | bc -l) )); then - best_score="$score" - best_way="$way_id" - fi - fi - done - # Verify best actually meets its threshold - if [[ "$best_way" != "none" ]]; then - local thresh="${WAY_THRESH[$best_way]}" - if command -v bc >/dev/null 2>&1 && (( $(echo "$best_score < $thresh" | bc -l) )); then - best_way="none" - fi - fi - echo "$best_way" - return 0 - fi - - # NCD fallback: binary match, return first - for way_id in "${WAY_IDS[@]}"; do - if "${scorer}_matches_way" "$prompt" "$way_id"; then - echo "$way_id" - return 0 - fi - done - echo "none" - return 0 -} - # --- Colors --- RED='\033[0;31m' GREEN='\033[0;32m' @@ -226,7 +224,7 @@ while IFS= read -r line; do category=$(echo "$line" | jq -r '.category') note=$(echo "$line" | jq -r '.note // ""') - # Parse expected: null → negative, string → single, array → co-activation + # Parse expected: null -> negative, string -> single, array -> co-activation expected_type=$(echo "$line" | jq -r '.expected | type') expected_list=() is_negative=false @@ -235,8 +233,12 @@ while IFS= read -r line; do case "$expected_type" in null) is_negative=true ;; string) expected_list=("$(echo "$line" | jq -r '.expected')") ;; - array) mapfile -t expected_list < <(echo "$line" | jq -r '.expected[]') - [[ ${#expected_list[@]} -gt 1 ]] && is_coact=true ;; + array) + while IFS= read -r item; do + expected_list+=("$item") + done < <(echo "$line" | jq -r '.expected[]') + [[ ${#expected_list[@]} -gt 1 ]] && is_coact=true + ;; esac total=$((total + 1)) @@ -246,8 +248,6 @@ while IFS= read -r line; do bm25_result="skip" # --- Scorer evaluation function --- - # Usage: eval_scorer - # Sets: ${scorer}_result variable eval_scorer() { local scorer="$1" prompt="$2" shift 2 @@ -255,12 +255,12 @@ while IFS= read -r line; do local result="" if $is_negative; then - # Negative test: check no way matches local any_match=false - for way_id in "${WAY_IDS[@]}"; do - if "${scorer}_matches_way" "$prompt" "$way_id"; then + local i + for (( i=0; i<${#WAY_IDS[@]}; i++ )); do + if "${scorer}_matches_way" "$prompt" "${WAY_IDS[$i]}"; then any_match=true - result="FP:$way_id" + result="FP:${WAY_IDS[$i]}" break fi done @@ -268,9 +268,9 @@ while IFS= read -r line; do result="TN" fi elif $is_coact; then - # Co-activation: check ALL expected ways match local matched=0 local missed="" + local exp for exp in "${exp_list[@]}"; do if "${scorer}_matches_way" "$prompt" "$exp"; then matched=$((matched + 1)) @@ -286,7 +286,6 @@ while IFS= read -r line; do result="MISS" fi else - # Single-expected: check the one expected way matches if "${scorer}_matches_way" "$prompt" "${exp_list[0]}"; then result="TP" else diff --git a/tools/way-match/test-integration.sh b/tools/way-match/test-integration.sh index 23f2f85..6c9e1f1 100755 --- a/tools/way-match/test-integration.sh +++ b/tools/way-match/test-integration.sh @@ -2,7 +2,9 @@ # Integration test: run way-match against actual way.md files # Reads frontmatter from real semantic ways and scores test prompts # -# This tests the real pipeline: way files → frontmatter extraction → BM25 scoring +# This tests the real pipeline: way files -> frontmatter extraction -> BM25 scoring +# +# Compatible with bash 3.2+ (macOS default) set -euo pipefail @@ -24,8 +26,25 @@ CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' -# --- Extract frontmatter from actual way files --- -declare -A WAY_DESC WAY_VOCAB WAY_THRESH WAY_PATH +# --- Parallel arrays for way data (bash 3.2 compatible) --- +WAY_IDS=() +WAY_DESCS=() +WAY_VOCABS=() +WAY_THRESHS=() +WAY_PATHS=() + +# Lookup index by way ID; returns via global __idx (-1 if not found) +__idx=-1 +way_index() { + local target="$1" + local i + for (( i=0; i<${#WAY_IDS[@]}; i++ )); do + if [[ "${WAY_IDS[$i]}" == "$target" ]]; then + __idx=$i; return 0 + fi + done + __idx=-1; return 1 +} echo -e "${BOLD}=== Integration Test: Real Way Files ===${NC}" echo "" @@ -46,23 +65,24 @@ while IFS= read -r wayfile; do # Skip ways without semantic matching fields [[ -z "$desc" || -z "$vocab" ]] && continue - WAY_DESC[$way_id]="$desc" - WAY_VOCAB[$way_id]="$vocab" - WAY_THRESH[$way_id]="${thresh:-2.0}" - WAY_PATH[$way_id]="$wayfile" + WAY_IDS+=("$way_id") + WAY_DESCS+=("$desc") + WAY_VOCABS+=("$vocab") + WAY_THRESHS+=("${thresh:-2.0}") + WAY_PATHS+=("$wayfile") printf " %-30s thresh=%-5s %s\n" "$way_id" "${thresh:-2.0}" "$(echo "$desc" | cut -c1-60)" done < <(find "$WAYS_DIR" -name "way.md" -type f | sort) echo "" -echo "Found ${#WAY_DESC[@]} semantic ways" +echo "Found ${#WAY_IDS[@]} semantic ways" echo "" # --- Test prompts with expected matches --- # Format: "expected_way_id|prompt" # Use "NONE" for prompts that shouldn't match anything TEST_CASES=( - # Direct matches — vocabulary terms present + # Direct matches -- vocabulary terms present "softwaredev-code-testing|write some unit tests for this module" "softwaredev-code-testing|run pytest with coverage" "softwaredev-code-testing|mock the database connection in tests" @@ -84,7 +104,7 @@ TEST_CASES=( "softwaredev-architecture-adr-context|plan how to build the notification system" "softwaredev-architecture-adr-context|why was this feature designed this way" "softwaredev-architecture-adr-context|pick up work on the auth implementation" - # Negative cases — should not trigger any semantic way + # Negative cases -- should not trigger any semantic way "NONE|what is the capital of France" "NONE|tell me about photosynthesis" "NONE|how tall is Mount Everest" @@ -96,7 +116,7 @@ TEST_CASES=( "softwaredev-code-security|are our API keys exposed anywhere" "softwaredev-architecture-design|should we use a monolith or microservices architecture" "softwaredev-environment-config|the database connection string needs updating" - # Co-activation cases — comma-separated expected ways + # Co-activation cases -- comma-separated expected ways "softwaredev-environment-debugging,softwaredev-code-errors|debug the unhandled exception and add proper error handling" "softwaredev-environment-deps,softwaredev-code-security|audit our dependencies for security vulnerabilities" "softwaredev-architecture-design,softwaredev-delivery-migrations|design the database schema for the new microservice" @@ -118,16 +138,16 @@ for test_case in "${TEST_CASES[@]}"; do # Score against all ways with BM25 bm25_matches=() bm25_scores="" - for way_id in "${!WAY_DESC[@]}"; do + for (( i=0; i<${#WAY_IDS[@]}; i++ )); do + way_id="${WAY_IDS[$i]}" score=$("$BM25_BINARY" pair \ - --description "${WAY_DESC[$way_id]}" \ - --vocabulary "${WAY_VOCAB[$way_id]}" \ + --description "${WAY_DESCS[$i]}" \ + --vocabulary "${WAY_VOCABS[$i]}" \ --query "$prompt" \ - --threshold 0.0 2>&1 | grep -oP 'score=\K[0-9.]+') - if (( $(echo "$score > 0" | bc -l 2>/dev/null || echo 0) )); then + --threshold 0.0 2>&1 | sed -n 's/.*score=\([0-9.]*\).*/\1/p') + if [[ -n "$score" ]] && (( $(echo "$score > 0" | bc -l 2>/dev/null || echo 0) )); then bm25_scores="$bm25_scores $way_id=$score" - # Check against per-way threshold from way.md - thresh="${WAY_THRESH[$way_id]}" + thresh="${WAY_THRESHS[$i]}" if (( $(echo "$score >= $thresh" | bc -l 2>/dev/null || echo 0) )); then bm25_matches+=("$way_id") fi @@ -136,8 +156,9 @@ for test_case in "${TEST_CASES[@]}"; do # Score against all ways with NCD (uses fixed NCD threshold, not BM25 threshold) ncd_matches=() - for way_id in "${!WAY_DESC[@]}"; do - if bash "$NCD_SCRIPT" "$prompt" "${WAY_DESC[$way_id]}" "${WAY_VOCAB[$way_id]}" "0.55" 2>/dev/null; then + for (( i=0; i<${#WAY_IDS[@]}; i++ )); do + way_id="${WAY_IDS[$i]}" + if bash "$NCD_SCRIPT" "$prompt" "${WAY_DESCS[$i]}" "${WAY_VOCABS[$i]}" "0.55" 2>/dev/null; then ncd_matches+=("$way_id") fi done @@ -159,7 +180,7 @@ for test_case in "${TEST_CASES[@]}"; do all_found=true for exp in "${expected_list[@]}"; do found=false - for m in "${bm25_matches[@]}"; do + for m in "${bm25_matches[@]+"${bm25_matches[@]}"}"; do [[ "$m" == "$exp" ]] && found=true && break done [[ "$found" == false ]] && all_found=false @@ -183,7 +204,7 @@ for test_case in "${TEST_CASES[@]}"; do all_found=true for exp in "${expected_list[@]}"; do found=false - for m in "${ncd_matches[@]}"; do + for m in "${ncd_matches[@]+"${ncd_matches[@]}"}"; do [[ "$m" == "$exp" ]] && found=true && break done [[ "$found" == false ]] && all_found=false From 7970569f25817c342455cb9279fb23346b70674b Mon Sep 17 00:00:00 2001 From: John-Luke Peck Date: Sat, 21 Feb 2026 16:39:26 -0800 Subject: [PATCH 2/2] feat(skills): add /sync-upstream skill for fork integration workflow This fork diverges from upstream (aaronsb/claude-code-config) with platform-specific fixes (arm64 binary, bash 3.2 compat). The skill automates fetching, diffing, and merging upstream changes while preserving our local fixes through documented conflict resolution. --- skills/sync-upstream/SKILL.md | 108 ++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 skills/sync-upstream/SKILL.md diff --git a/skills/sync-upstream/SKILL.md b/skills/sync-upstream/SKILL.md new file mode 100644 index 0000000..4f78ee8 --- /dev/null +++ b/skills/sync-upstream/SKILL.md @@ -0,0 +1,108 @@ +--- +name: sync-upstream +description: Sync fork with upstream (aaronsb/claude-code-config). Fetches upstream/main, shows what's new, and merges into your current branch or main. Handles conflicts by preserving local fixes. Use when the user says "sync upstream", "pull upstream", "update from upstream", or invokes /sync-upstream. +allowed-tools: Bash, Read, Grep, Glob +--- + +# Sync Upstream + +Integrate changes from the upstream repo (aaronsb/claude-code-config) into this fork. + +## Assess First + +Run these in parallel to understand current state: + +```bash +git remote -v # Verify upstream is configured +git branch --show-current # What branch are we on? +git status --short # Any uncommitted work? +git log --oneline upstream/main..main # Local-only commits (our fixes) +``` + +### If upstream remote is missing + +```bash +git remote add upstream https://github.com/aaronsb/claude-code-config.git +``` + +## Flow + +### 1. Stash uncommitted work (if any) + +```bash +git stash push -m "sync-upstream: stash before merge" +``` + +### 2. Fetch upstream + +```bash +git fetch upstream +``` + +### 3. Show what's incoming + +```bash +git log --oneline main..upstream/main +``` + +If nothing new, report "already up to date" and stop. + +### 4. Show divergence + +```bash +git log --oneline upstream/main..main # Our local-only commits +git log --oneline main..upstream/main # Incoming from upstream +``` + +Report both sides so the user sees the full picture. + +### 5. Merge upstream into main + +If on a feature branch, switch to main first: + +```bash +git checkout main +git merge upstream/main +``` + +If conflicts arise: + +- **bin/way-match**: Keep ours (`git checkout --ours bin/way-match`). Upstream ships a Linux ELF; we need the arm64 macOS binary. After resolving, rebuild with `make -f tools/way-match/Makefile local` to ensure we have the latest source compiled natively. +- **tools/way-match/test-harness.sh** or **test-integration.sh**: Inspect carefully. If upstream added new test cases, incorporate them into our bash 3.2 compatible version. Don't accept upstream's `declare -A` or `mapfile` patterns. +- **Other files**: Accept upstream's version unless we have intentional local changes. + +After resolving all conflicts: + +```bash +git add +git commit # Accept or adjust the merge commit message +``` + +### 6. Push to origin + +```bash +git push origin main +``` + +### 7. Rebase feature branch (if we were on one) + +If the user was on a feature branch before sync: + +```bash +git checkout +git rebase main +``` + +### 8. Restore stashed work (if any) + +```bash +git stash pop +``` + +## Key Principles + +- **Show the diff summary before merging** — let the user see what's incoming +- **Preserve local platform fixes** — our arm64 binary and bash 3.2 compat are intentional divergences +- **If upstream changed way-match.c source**, rebuild locally after merge: `make -f tools/way-match/Makefile local` +- **Don't force-push main** — always fast-forward or merge +- **Report what happened** — summarize commits integrated, conflicts resolved, and current state