Skip to content

Commit b7da1d7

Browse files
Jonathan D.A. Jewellclaude
andcommitted
perf: add hash-cache, git-ls-files traversal, parallel dispatch, and Elixir sync-all
- scripts/lib/scan-cache.sh: SHA-256 hash cache so fix scripts skip unchanged files between runs (panic-attacker-style incremental scanning) - scripts/lib/smart-files.sh: git ls-files helper with vendored dir exclusions (winget-pkgs, compiler-source, deps, node_modules) - dispatch-runner.sh: --parallel N for concurrent fix execution, --dedup-repo to skip duplicate repo+category entries - fix-http-to-https.sh: use git ls-files + scan cache instead of find - fix-todo-markers.sh: use git ls-files + scan cache instead of find - sync-all-parallel.exs: Elixir rewrite of sync-all.sh using BEAM Task.async_stream for 32-worker concurrent fetch/pull/push Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e20da6f commit b7da1d7

6 files changed

Lines changed: 832 additions & 25 deletions

File tree

scripts/dispatch-runner.sh

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
# --repo REPO Only process entries for a specific repo
1717
# --limit N Process at most N entries
1818
# --manifest PATH Path to manifest (default: verisimdb-data/dispatch/pending.jsonl)
19+
# --parallel N Run up to N fix scripts concurrently (default: 1)
20+
# --dedup-repo Group entries by repo, run only first per repo+category
1921

2022
set -euo pipefail
2123

@@ -69,6 +71,8 @@ FILTER_TIER=""
6971
FILTER_STRATEGY=""
7072
FILTER_REPO=""
7173
LIMIT=0
74+
PARALLEL=1
75+
DEDUP_REPO=false
7276

7377
# Counters
7478
TOTAL=0
@@ -86,6 +90,8 @@ while [[ $# -gt 0 ]]; do
8690
--repo) FILTER_REPO="$2"; shift 2 ;;
8791
--limit) LIMIT="$2"; shift 2 ;;
8892
--manifest) MANIFEST_PATH="$2"; shift 2 ;;
93+
--parallel) PARALLEL="$2"; shift 2 ;;
94+
--dedup-repo) DEDUP_REPO=true; shift ;;
8995
-h|--help)
9096
head -18 "$0" | tail -16
9197
exit 0
@@ -303,8 +309,47 @@ execute_entry() {
303309
esac
304310
}
305311

312+
# --- Deduplication ---
313+
# When --dedup-repo is set, only process the first entry per repo+category combo.
314+
# This avoids running the same fix script on the same repo multiple times.
315+
declare -A SEEN_REPO_CAT=()
316+
317+
should_skip_dedup() {
318+
local line="$1"
319+
if [[ "$DEDUP_REPO" != "true" ]]; then
320+
return 1
321+
fi
322+
local repo category key
323+
repo=$(echo "$line" | jq -r '.repo')
324+
category=$(echo "$line" | jq -r '.category // "unknown"')
325+
key="${repo}::${category}"
326+
if [[ -n "${SEEN_REPO_CAT[$key]+x}" ]]; then
327+
return 0
328+
fi
329+
SEEN_REPO_CAT["$key"]=1
330+
return 1
331+
}
332+
306333
# --- Main loop ---
307334
LINE_NUM=0
335+
ACTIVE_JOBS=0
336+
PARALLEL_TMPDIR=$(mktemp -d /tmp/dispatch-parallel-XXXXXX)
337+
338+
# Parallel wrapper: run execute_entry in background, track jobs
339+
run_entry() {
340+
local line="$1"
341+
if [[ "$PARALLEL" -le 1 ]]; then
342+
execute_entry "$line"
343+
else
344+
# Wait if we've hit the parallel limit
345+
while [[ "$ACTIVE_JOBS" -ge "$PARALLEL" ]]; do
346+
wait -n 2>/dev/null || true
347+
((ACTIVE_JOBS--)) || true
348+
done
349+
execute_entry "$line" &
350+
((ACTIVE_JOBS++)) || true
351+
fi
352+
}
308353

309354
while IFS= read -r line; do
310355
# Skip empty lines
@@ -333,6 +378,11 @@ while IFS= read -r line; do
333378
[[ "$entry_repo" != "$FILTER_REPO" ]] && continue
334379
fi
335380

381+
# Deduplicate by repo+category
382+
if should_skip_dedup "$line"; then
383+
continue
384+
fi
385+
336386
((TOTAL++)) || true
337387

338388
# Check limit
@@ -341,10 +391,16 @@ while IFS= read -r line; do
341391
break
342392
fi
343393

344-
execute_entry "$line"
394+
run_entry "$line"
345395

346396
done < "$MANIFEST_PATH"
347397

398+
# Wait for all background jobs to finish
399+
if [[ "$PARALLEL" -gt 1 ]]; then
400+
wait
401+
fi
402+
rm -rf "$PARALLEL_TMPDIR" 2>/dev/null || true
403+
348404
# --- Summary ---
349405
echo ""
350406
echo "=== Summary ==="

scripts/fix-http-to-https.sh

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,93 @@
66
# Fixes PA014 InsecureProtocol findings where http:// is used.
77
# Upgrades to https:// where safe to do so.
88
#
9+
# Uses git ls-files for fast traversal and hash-based caching to skip
10+
# files unchanged since last scan.
11+
#
912
# Usage: fix-http-to-https.sh <repo-path> <finding-json>
1013

1114
set -euo pipefail
1215

1316
REPO_PATH="${1:?Usage: $0 <repo-path> <finding-json>}"
1417
FINDING_JSON="${2:?Missing finding JSON file}"
1518

19+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
20+
1621
echo "=== HTTP to HTTPS Fix ==="
1722
echo " Repo: $REPO_PATH"
1823

1924
FIXED_COUNT=0
2025
SKIPPED_COUNT=0
2126

27+
# Source scan cache if available
28+
if [[ -f "$SCRIPT_DIR/lib/scan-cache.sh" ]]; then
29+
source "$SCRIPT_DIR/lib/scan-cache.sh"
30+
cache_init "fix-http-to-https" "$REPO_PATH"
31+
USE_CACHE=true
32+
else
33+
USE_CACHE=false
34+
fi
35+
2236
# Domains known to support HTTPS
2337
SAFE_DOMAINS="github.com|gitlab.com|bitbucket.org|crates.io|npmjs.com|pypi.org|rubygems.org|hex.pm|opam.ocaml.org|hackage.haskell.org|pkg.go.dev|docs.rs|wikipedia.org|stackoverflow.com|mozilla.org|w3.org|schema.org|creativecommons.org|opensource.org|spdx.org"
2438

25-
# Find all text files (not binary, not .git)
39+
# File extensions to check
40+
EXTENSIONS=("*.sh" "*.yml" "*.yaml" "*.md" "*.adoc" "*.txt" "*.json" "*.toml" "*.cfg" "*.ini" "*.rs" "*.ex" "*.exs" "*.res" "*.js")
41+
42+
# Use git ls-files if in a git repo, otherwise find
43+
list_files() {
44+
if [[ -d "$REPO_PATH/.git" ]]; then
45+
(cd "$REPO_PATH" && git ls-files -z "${EXTENSIONS[@]}" 2>/dev/null) | \
46+
while IFS= read -r -d '' f; do
47+
# Skip vendored/generated directories
48+
case "$f" in
49+
*/node_modules/*|*/target/*|*/.git/*|*/deps/*|*/vendor/*|*/_build/*) continue ;;
50+
*/winget-pkgs/*|*/compiler-source/*|*/.lake/*|*/__pycache__/*) continue ;;
51+
esac
52+
[[ -f "$REPO_PATH/$f" ]] && printf '%s\0' "$REPO_PATH/$f"
53+
done
54+
else
55+
find "$REPO_PATH" -type f \( -name "*.sh" -o -name "*.yml" -o -name "*.yaml" -o -name "*.md" -o -name "*.adoc" -o -name "*.txt" -o -name "*.json" -o -name "*.toml" -o -name "*.cfg" -o -name "*.ini" -o -name "*.rs" -o -name "*.ex" -o -name "*.exs" -o -name "*.res" -o -name "*.js" \) \
56+
-not -path "*/.git/*" -not -path "*/target/*" -not -path "*/node_modules/*" \
57+
-not -path "*/winget-pkgs/*" -not -path "*/compiler-source/*" \
58+
-print0 2>/dev/null
59+
fi
60+
}
61+
2662
while IFS= read -r -d '' file; do
27-
# Skip binary files
28-
if file "$file" | grep -q "binary"; then
63+
# Skip via hash cache
64+
if [[ "$USE_CACHE" == "true" ]] && cache_unchanged "$file"; then
2965
continue
3066
fi
3167

68+
# Skip binary files (quick check via file extension — avoid slow `file` command)
69+
case "$file" in
70+
*.png|*.jpg|*.gif|*.ico|*.woff|*.woff2|*.ttf|*.eot|*.svg|*.pdf|*.zip|*.tar|*.gz) continue ;;
71+
esac
72+
3273
if grep -qP 'http://(?!localhost|127\.|0\.0\.0\.|192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.)' "$file" 2>/dev/null; then
3374
rel_path="${file#$REPO_PATH/}"
3475

3576
# Only fix URLs for known HTTPS-capable domains
3677
if grep -qP "http://($SAFE_DOMAINS)" "$file" 2>/dev/null; then
3778
sed -i -E "s#http://($SAFE_DOMAINS)#https://\1#g" "$file" 2>/dev/null || true
38-
count=$(grep -cP "http://($SAFE_DOMAINS)" "$file" 2>/dev/null || echo 0)
3979
echo " FIXED $rel_path"
4080
((FIXED_COUNT++)) || true
4181
else
4282
echo " SKIP $rel_path — unknown domain (manual review needed)"
4383
((SKIPPED_COUNT++)) || true
4484
fi
4585
fi
46-
done < <(find "$REPO_PATH" -type f \( -name "*.sh" -o -name "*.yml" -o -name "*.yaml" -o -name "*.md" -o -name "*.adoc" -o -name "*.txt" -o -name "*.json" -o -name "*.toml" -o -name "*.cfg" -o -name "*.ini" -o -name "*.rs" -o -name "*.ex" -o -name "*.exs" -o -name "*.res" -o -name "*.js" \) -not -path "*/\.git/*" -not -path "*/target/*" -not -path "*/node_modules/*" -print0 2>/dev/null)
86+
87+
# Update cache regardless of whether we fixed anything
88+
[[ "$USE_CACHE" == "true" ]] && cache_update "$file"
89+
done < <(list_files)
90+
91+
# Save cache
92+
if [[ "$USE_CACHE" == "true" ]]; then
93+
cache_save
94+
cache_stats
95+
fi
4796

4897
echo ""
4998
echo "Fixed: $FIXED_COUNT file(s), Skipped: $SKIPPED_COUNT file(s)"

scripts/fix-todo-markers.sh

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,66 @@
66
# Fixes PA018-unchecked-todo findings.
77
# Converts bare TODOs to structured FILL markers or resolves them.
88
#
9+
# Uses git ls-files for fast traversal and hash-based caching to skip
10+
# files unchanged since last scan.
11+
#
912
# Usage: fix-todo-markers.sh <repo-path> <finding-json>
1013

1114
set -euo pipefail
1215

1316
REPO_PATH="${1:?Usage: $0 <repo-path> <finding-json>}"
1417
FINDING_JSON="${2:?Missing finding JSON file}"
1518

19+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
20+
1621
echo "=== TODO/FIXME Marker Fix ==="
1722
echo " Repo: $REPO_PATH"
1823

1924
FIXED_COUNT=0
2025
TODO_COUNT=0
2126

22-
# Find files with TODO/FIXME (excluding .git, target, node_modules, vendor)
27+
# Source scan cache if available
28+
if [[ -f "$SCRIPT_DIR/lib/scan-cache.sh" ]]; then
29+
source "$SCRIPT_DIR/lib/scan-cache.sh"
30+
cache_init "fix-todo-markers" "$REPO_PATH"
31+
USE_CACHE=true
32+
else
33+
USE_CACHE=false
34+
fi
35+
36+
# File extensions to check
37+
EXTENSIONS=("*.rs" "*.ex" "*.exs" "*.res" "*.js" "*.sh" "*.bash" "*.idr" "*.lean" "*.hs" "*.ml" "*.mli" "*.zig" "*.ada" "*.adb" "*.ads" "justfile" "Makefile" "*.toml")
38+
39+
# Use git ls-files if in a git repo
40+
list_files() {
41+
if [[ -d "$REPO_PATH/.git" ]]; then
42+
(cd "$REPO_PATH" && git ls-files -z "${EXTENSIONS[@]}" 2>/dev/null) | \
43+
while IFS= read -r -d '' f; do
44+
case "$f" in
45+
*/node_modules/*|*/target/*|*/.git/*|*/deps/*|*/vendor/*|*/_build/*) continue ;;
46+
*/winget-pkgs/*|*/compiler-source/*|*/.lake/*|*/.pack/*) continue ;;
47+
*.min.js|*.min.css) continue ;;
48+
esac
49+
[[ -f "$REPO_PATH/$f" ]] && printf '%s\0' "$REPO_PATH/$f"
50+
done
51+
else
52+
find "$REPO_PATH" -type f \
53+
-not -path "*/.git/*" -not -path "*/target/*" -not -path "*/node_modules/*" \
54+
-not -path "*/_build/*" -not -path "*/vendor/*" -not -path "*/.pack/*" \
55+
-not -path "*/.lake/*" -not -path "*/winget-pkgs/*" -not -path "*/compiler-source/*" \
56+
-not -name "*.min.js" -not -name "*.min.css" \
57+
\( -name "*.rs" -o -name "*.ex" -o -name "*.exs" -o -name "*.res" -o -name "*.js" \
58+
-o -name "*.sh" -o -name "*.bash" -o -name "*.idr" -o -name "*.lean" \
59+
-o -name "*.hs" -o -name "*.ml" -o -name "*.mli" -o -name "*.zig" \
60+
-o -name "*.ada" -o -name "*.adb" -o -name "*.ads" \
61+
-o -name "justfile" -o -name "Makefile" -o -name "*.toml" \) \
62+
-print0 2>/dev/null
63+
fi
64+
}
65+
2366
while IFS= read -r -d '' file; do
24-
# Skip binary files
25-
if file "$file" | grep -q "binary"; then
67+
# Skip via hash cache
68+
if [[ "$USE_CACHE" == "true" ]] && cache_unchanged "$file"; then
2669
continue
2770
fi
2871

@@ -35,22 +78,16 @@ while IFS= read -r -d '' file; do
3578
TODO_COUNT=$((TODO_COUNT + count))
3679
((FIXED_COUNT++)) || true
3780
fi
38-
done < <(find "$REPO_PATH" -type f \
39-
-not -path "*/\.git/*" \
40-
-not -path "*/target/*" \
41-
-not -path "*/node_modules/*" \
42-
-not -path "*/_build/*" \
43-
-not -path "*/vendor/*" \
44-
-not -path "*/.pack/*" \
45-
-not -path "*/.lake/*" \
46-
-not -name "*.min.js" \
47-
-not -name "*.min.css" \
48-
\( -name "*.rs" -o -name "*.ex" -o -name "*.exs" -o -name "*.res" -o -name "*.js" \
49-
-o -name "*.sh" -o -name "*.bash" -o -name "*.idr" -o -name "*.lean" \
50-
-o -name "*.hs" -o -name "*.ml" -o -name "*.mli" -o -name "*.zig" \
51-
-o -name "*.ada" -o -name "*.adb" -o -name "*.ads" \
52-
-o -name "justfile" -o -name "Makefile" -o -name "*.toml" \) \
53-
-print0 2>/dev/null)
81+
82+
# Update cache
83+
[[ "$USE_CACHE" == "true" ]] && cache_update "$file"
84+
done < <(list_files)
85+
86+
# Save cache
87+
if [[ "$USE_CACHE" == "true" ]]; then
88+
cache_save
89+
cache_stats
90+
fi
5491

5592
echo ""
5693
if [[ "$FIXED_COUNT" -gt 0 ]]; then

0 commit comments

Comments
 (0)