Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
52abe49
Adding tests to repo
SanjaySoniLV Apr 25, 2026
4efb5de
Add batch processing for speciesnet classifier for improved analysis …
SanjaySoniLV Apr 27, 2026
479439c
implemented batch processing of SAM_HQ model with updated weights.
SanjaySoniLV Apr 27, 2026
0c1bfc8
Add batch processing for speciesnet classifier for improved analysis …
SanjaySoniLV Apr 27, 2026
33ec483
Performance improvement: Apply bird crop cap before sam_hq is run.
SanjaySoniLV Apr 27, 2026
c6e6dbf
feat: Implement resilience for ONNX execution providers
SanjaySoniLV May 4, 2026
e5c9691
feat: Integrate ResilientOnnxSession with ProviderCoordinator for imp…
SanjaySoniLV May 4, 2026
0b66f7a
Updated version
SanjaySoniLV May 5, 2026
2501522
feat: Enhance species detection functionality with taxonomy mapping a…
SanjaySoniLV May 5, 2026
fd84732
feat: Distinguish OS shutdowns from real crashes to suppress false re…
claude May 10, 2026
16e91d8
Merge pull request #43 from SanjaySoniLV/claude/improve-shutdown-hand…
SanjaySoniLV May 11, 2026
615a100
feat: Add resave_quality_model script for improved macOS/XLA compatib…
SanjaySoniLV May 11, 2026
f0d6d1d
Add unit tests for ratings, reject move, settings, and speciesnet fil…
SanjaySoniLV May 11, 2026
938f4bf
feat: Add backward compatibility tests for legacy database and settin…
SanjaySoniLV May 11, 2026
1dd6bea
test fixtures: legacy settings.json captured from release tags
claude May 11, 2026
a3c7dea
test fixtures: add captured legacy database schemas and notes
Copilot May 11, 2026
921ef8b
Merge pull request #45 from SanjaySoniLV/copilot/legacy-database-fixt…
SanjaySoniLV May 11, 2026
51432eb
Code Cleanup: Removed dead code paths across 10 files and rewrote DEV…
SanjaySoniLV May 11, 2026
f1e37da
Merge branch 'dev' of https://github.com/SanjaySoniLV/ProjectKestrel …
SanjaySoniLV May 11, 2026
0f31993
Merge branch 'dev' into claude/backfill-test-fixtures-hAHdT
SanjaySoniLV May 11, 2026
942bc2c
Merge pull request #44 from SanjaySoniLV/claude/backfill-test-fixture…
SanjaySoniLV May 11, 2026
a66e8b6
testing: Added machine learning model tests to the test suite
SanjaySoniLV May 11, 2026
e85fe3b
Refactor: Consolidate RAW and JPEG extensions imports and improve cap…
SanjaySoniLV May 11, 2026
483e4bb
Extended test suite to include a UI test + extended dev environments …
SanjaySoniLV May 11, 2026
14e7f42
Refactor: Add leveled logging API (DEBUG/INFO/WARN/ERROR); demote per…
SanjaySoniLV May 11, 2026
8c3d065
Refactor: Split visualizer.css into 18 feature-scoped files under ana…
SanjaySoniLV May 11, 2026
abea704
feature: migrate fast model to megadetector v1000-cedar
SanjaySoniLV May 12, 2026
fad6a93
Feature: Implement build attestation for official builds and update a…
SanjaySoniLV May 12, 2026
e088437
chore(deps): bump rawpy/pyinstaller/pywebview/requests/PyQt6; drop ms…
claude May 15, 2026
c96d02d
feat(cli): expose advanced analysis settings as CLI flags
claude May 15, 2026
cf0c056
fix(speciesnet): remove duplicate batch classifier block
claude May 15, 2026
b19b536
fix(culling preview): avoid double raw.postprocess() on non-zero expo…
claude May 15, 2026
18cbed0
fix(shutdown): preserve os_shutdown/crash exit reason on clean-exit path
claude May 15, 2026
19c733a
fix(attestation): don't give up after first malformed candidate
claude May 15, 2026
a818ef7
feat(quality): replace quality normalization curve (v3) and bump VERS…
claude May 15, 2026
63066fa
chore(deps): bump numpy 2.1.3 -> 2.4.4 across all requirements files
claude May 15, 2026
317626f
test(ui): probe real visualizer.html on the frozen binary in CI
claude May 15, 2026
31d463a
chore(deps): drop unused PyQt6 dependency
claude May 15, 2026
f85298b
Merge pull request #47 from SanjaySoniLV/claude/audit-package-version…
SanjaySoniLV May 15, 2026
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
112 changes: 86 additions & 26 deletions .github/workflows/build-macos-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,20 @@ jobs:
source .venv2/bin/activate
python -m pip install --upgrade pip
pip install -r requirements-macos.txt
pip install pyinstaller
pip install pyinstaller pytest

- name: Run pytest from source (skip ui marker)
id: pretest
continue-on-error: true
shell: bash
run: |
source .venv2/bin/activate
python -m pytest analyzer/tests/ -m "not ui" -q --tb=short

- name: Warn if pre-build pytest failed
if: ${{ always() && steps.pretest.outcome == 'failure' }}
shell: bash
run: echo "::warning::Pre-build pytest failed on macOS dev build; continuing to PyInstaller."

- name: Smoke test CLI from source (repo root, first 2 test images)
shell: bash
Expand Down Expand Up @@ -67,47 +80,76 @@ jobs:
chmod +x packaging/build_app_headless.sh
./packaging/build_app_headless.sh

- name: Smoke test built executable (first 2 test images)
id: smoke_test
- name: Run --validate against frozen binary
id: validate
continue-on-error: true
shell: bash
run: |
set -euo pipefail
SMOKE_DIR="${RUNNER_TEMP}/kestrel-smoke-input"
rm -rf "${SMOKE_DIR}"
mkdir -p "${SMOKE_DIR}"
VALIDATE_OUTPUT="${RUNNER_TEMP}/validate-result.json"
rm -f "${VALIDATE_OUTPUT}"

# mapfile is unavailable in macOS runner bash (3.2), so use while-read.
smoke_imgs=()
while IFS= read -r img; do
smoke_imgs+=("${img}")
done < <(find test_imgs -maxdepth 1 -type f | sort | head -n 2)
if [ "${#smoke_imgs[@]}" -lt 2 ]; then
echo "Expected at least 2 images in test_imgs/ for smoke test"
analyzer/dist/ProjectKestrel/ProjectKestrel \
--cli --validate \
--validate-images test_imgs \
--validate-output "${VALIDATE_OUTPUT}"

if [ ! -f "${VALIDATE_OUTPUT}" ]; then
echo "Frozen --validate did not produce a result JSON"
exit 1
fi
echo "=== validate-result.json ==="
cat "${VALIDATE_OUTPUT}"

for img in "${smoke_imgs[@]}"; do
cp "${img}" "${SMOKE_DIR}/"
done
# Copy to repo root so upload-artifact finds it.
cp "${VALIDATE_OUTPUT}" validate-result.json

ok=$(python3 -c "import json,sys; print(str(json.load(open('${VALIDATE_OUTPUT}')).get('ok')).lower())")
if [ "${ok}" != "true" ]; then
echo "Validation reported failure (ok=${ok})"
exit 1
fi

analyzer/dist/ProjectKestrel/ProjectKestrel --cli "${SMOKE_DIR}" --no-gpu --parallel-prefetch 1
- name: Warn if frozen --validate failed
if: ${{ always() && steps.validate.outcome == 'failure' }}
shell: bash
run: echo "::warning::Frozen-binary --validate failed on macOS dev build; continuing to publish available artifacts."

META_PATH="${SMOKE_DIR}/.kestrel/kestrel_database.csv"
if [ ! -f "${META_PATH}" ]; then
echo "Smoke test did not produce kestrel_database.csv"
- name: UI probe (real visualizer.html) against frozen binary
id: ui_probe
continue-on-error: true
shell: bash
run: |
set -euo pipefail
UI_PROBE_OUTPUT="${RUNNER_TEMP}/ui-probe-result.json"
rm -f "${UI_PROBE_OUTPUT}"

analyzer/dist/ProjectKestrel/ProjectKestrel \
--api-probe \
--probe-target visualizer \
--probe-output "${UI_PROBE_OUTPUT}" \
--probe-timeout 60 \
--port 8798

if [ ! -f "${UI_PROBE_OUTPUT}" ]; then
echo "UI probe did not produce a result JSON"
exit 1
fi
PROCESSED_ROWS=$(( $(wc -l < "${META_PATH}") - 1 ))
if [ "${PROCESSED_ROWS}" -lt 2 ]; then
echo "Smoke test processed fewer than 2 images (rows=${PROCESSED_ROWS})"
echo "=== ui-probe-result.json ==="
cat "${UI_PROBE_OUTPUT}"

cp "${UI_PROBE_OUTPUT}" ui-probe-result.json

ok=$(python3 -c "import json,sys; print(str(json.load(open('${UI_PROBE_OUTPUT}')).get('ok')).lower())")
if [ "${ok}" != "true" ]; then
echo "UI probe reported failure (ok=${ok}); visualizer.html did not wire up window.pywebview.api"
exit 1
fi

- name: Warn if smoke test failed
if: ${{ always() && steps.smoke_test.outcome == 'failure' }}
- name: Warn if UI probe failed
if: ${{ always() && steps.ui_probe.outcome == 'failure' }}
shell: bash
run: echo "::warning::Smoke test failed on macOS dev build; continuing to publish available artifacts."
run: echo "::warning::Frozen-binary UI probe failed on macOS dev build; visualizer.html did not see window.pywebview.api."

- name: Collect macOS .app
shell: bash
Expand All @@ -131,3 +173,21 @@ jobs:
with:
name: macos-dev-app
path: artifact/

- name: Upload validate-result.json
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: macos-dev-validate-result
path: validate-result.json
if-no-files-found: warn
retention-days: 14

- name: Upload ui-probe-result.json
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: macos-dev-ui-probe-result
path: ui-probe-result.json
if-no-files-found: warn
retention-days: 14
109 changes: 109 additions & 0 deletions .github/workflows/build-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,69 @@ jobs:
exit 1
fi

- name: Generate build attestation
id: attestation
shell: bash
env:
SIGNING_SECRET: ${{ secrets.KESTREL_BUILD_SIGNING_SECRET }}
run: |
set -euo pipefail
if [ -z "${SIGNING_SECRET:-}" ]; then
echo "::warning::KESTREL_BUILD_SIGNING_SECRET is not set; skipping attestation. This build will be tagged 'legacy' by the API worker."
exit 0
fi
if [ ! -d analyzer ]; then
echo "::error::analyzer/ directory is missing — cannot place build_attestation.json"
exit 1
fi

# Use python (already installed by setup-python@v5) to keep version-parsing
# logic in sync with kestrel_telemetry._read_version. Doing all work in a
# single python invocation avoids whitespace-splitting bugs in bash `read`
# when the version string contains spaces (e.g. "Nelson's Sparrow").
python3 - <<'PY' | tee -a "$GITHUB_OUTPUT"
import hashlib, hmac, json, os, time

def read_version():
for cand in ("VERSION.txt", os.path.join("analyzer", "VERSION.txt")):
if not os.path.isfile(cand):
continue
with open(cand, "r", encoding="utf-8") as f:
lines = [l.strip() for l in f if l.strip()]
for line in lines:
if line.lower().startswith("version:"):
return line.split(":", 1)[1].strip()
if len(lines) == 1:
return lines[0]
return "unknown"

version = read_version()
sha = os.environ["GITHUB_SHA"]
ts = str(int(time.time()))
meta = f"{version}|{sha}|{ts}"
sig = hmac.new(
os.environ["SIGNING_SECRET"].encode("utf-8"),
meta.encode("utf-8"),
hashlib.sha256,
).hexdigest()

with open("analyzer/build_attestation.json", "w", encoding="utf-8") as f:
json.dump({"meta": meta, "sig": sig}, f, separators=(",", ":"))

# GITHUB_OUTPUT lines (tee'd by the surrounding bash so they're visible too):
print(f"version={version}")
print(f"sha={sha}")
print(f"ts={ts}")
# Diagnostics on stderr so they don't pollute GITHUB_OUTPUT:
import sys
print(f"Build attestation generated:", file=sys.stderr)
print(f" version : {version}", file=sys.stderr)
print(f" sha : {sha}", file=sys.stderr)
print(f" ts : {ts}", file=sys.stderr)
print(f" meta : {meta}", file=sys.stderr)
print(f" sig : {sig[:16]}... (truncated)", file=sys.stderr)
PY

- name: Run macOS headless build
shell: bash
run: |
Expand Down Expand Up @@ -263,3 +326,49 @@ jobs:
with:
name: macos-bundle
path: analyzer/dist/ProjectKestrel/

- name: Register build with worker
if: ${{ always() && steps.attestation.outputs.sha != '' }}
shell: bash
continue-on-error: true
env:
SIGNING_SECRET: ${{ secrets.KESTREL_BUILD_SIGNING_SECRET }}
BUILD_VERSION: ${{ steps.attestation.outputs.version }}
BUILD_SHA: ${{ steps.attestation.outputs.sha }}
BUILD_TS: ${{ steps.attestation.outputs.ts }}
run: |
set -euo pipefail
if [ -z "${SIGNING_SECRET:-}" ]; then
echo "::warning::No signing secret available; skipping build registration."
exit 0
fi

# Build JSON via python to avoid quoting headaches when version contains apostrophes.
BODY=$(BUILD_VERSION="$BUILD_VERSION" BUILD_SHA="$BUILD_SHA" BUILD_TS="$BUILD_TS" \
RUN_ID="$GITHUB_RUN_ID" ACTOR="$GITHUB_ACTOR" python3 -c '
import json, os
print(json.dumps({
"version": os.environ["BUILD_VERSION"],
"sha": os.environ["BUILD_SHA"],
"ts": os.environ["BUILD_TS"],
"run_id": os.environ["RUN_ID"],
"actor": os.environ["ACTOR"],
}))
')

# --fail makes curl exit non-zero on HTTP errors; --max-time 15 bounds the call.
# Capture both stdout and exit code so failure is logged but non-blocking.
set +e
RESP=$(curl --silent --show-error --fail --max-time 15 \
-H "X-Kestrel-Signing-Token: ${SIGNING_SECRET}" \
-H "Content-Type: application/json" \
--data-raw "${BODY}" \
"https://api.projectkestrel.org/api/builds/register" 2>&1)
RC=$?
set -e

if [ "$RC" -eq 0 ]; then
echo "Build registered with worker: ${RESP}"
else
echo "::warning::Build registration failed (non-blocking, rc=${RC}): ${RESP}"
fi
Loading
Loading