mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 01:35:36 +00:00
From the full-repo Codex audit: - The publisher read discovery fields one per line, so a report title with newlines shifted every later field and published a manifest without source.publish_date, which every client rejects (blocking all syncs). Read them NUL-separated, and refuse to write a manifest missing a field clients require. - The snapshot builder copied the SQLite file with cp; committed data still in the -wal file of an open database would be missing. Checkpoint first (a no-op for the publisher's closed database, so the bytes and hash are unchanged) and refuse to copy when that cannot complete. - install.sh took the tag from a greedy match, which on a compact single-line releases response picks the last tag, not the newest (it installed v0.2.3 over v0.2.4 in the new test). Split fields first. - scripts/audit-msn-fundamentals.sh read the symbol map from a path that no longer exists and silently checked no stocks. - release.yml granted contents: write to every job; only publish gets it now. Actions in the release and snapshot workflows are pinned to commit SHAs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
188 lines
4.6 KiB
Bash
Executable file
188 lines
4.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
set -u
|
|
set -o pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
BIN_PATH=""
|
|
WORKDIR=""
|
|
TICKERS=""
|
|
BUILD=1
|
|
|
|
CONFIG_HOME=""
|
|
CACHE_HOME=""
|
|
DATA_HOME=""
|
|
LOG_DIR=""
|
|
SUMMARY_FILE=""
|
|
|
|
declare -a BASE_ENV=()
|
|
declare -a TICKER_LIST=()
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: scripts/audit-msn-fundamentals.sh [options]
|
|
|
|
Run `idx -o json stocks valuation <ticker>` across the IDX MSN symbol map.
|
|
This is a heavier provider audit, not a default smoke check.
|
|
|
|
Options:
|
|
--bin <path> Use an existing idx binary instead of target/debug/idx
|
|
--no-build Skip cargo build
|
|
--workdir <path> Artifact root. Default: tmp/msn-fundamentals-audit/<timestamp>
|
|
--tickers <list> Comma-separated ticker override instead of the full symbol map
|
|
--help Show this help
|
|
|
|
Examples:
|
|
scripts/audit-msn-fundamentals.sh
|
|
scripts/audit-msn-fundamentals.sh --tickers BUMI,ADRO,AIMS
|
|
scripts/audit-msn-fundamentals.sh --bin ./target/debug/idx --no-build
|
|
EOF
|
|
}
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--bin)
|
|
BIN_PATH="${2:-}"
|
|
BUILD=0
|
|
shift 2
|
|
;;
|
|
--no-build)
|
|
BUILD=0
|
|
shift
|
|
;;
|
|
--workdir)
|
|
WORKDIR="${2:-}"
|
|
shift 2
|
|
;;
|
|
--tickers)
|
|
TICKERS="${2:-}"
|
|
shift 2
|
|
;;
|
|
--help|-h)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "unknown argument: $1" >&2
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
prepare_paths() {
|
|
if [[ -z "$WORKDIR" ]]; then
|
|
WORKDIR="$ROOT_DIR/tmp/msn-fundamentals-audit/$(date +%Y%m%d-%H%M%S)"
|
|
fi
|
|
|
|
CONFIG_HOME="$WORKDIR/config"
|
|
CACHE_HOME="$WORKDIR/cache"
|
|
DATA_HOME="$WORKDIR/data"
|
|
LOG_DIR="$WORKDIR/logs"
|
|
SUMMARY_FILE="$WORKDIR/summary.tsv"
|
|
|
|
mkdir -p "$CONFIG_HOME" "$CACHE_HOME" "$DATA_HOME" "$LOG_DIR"
|
|
|
|
BASE_ENV=(
|
|
"XDG_CONFIG_HOME=$CONFIG_HOME"
|
|
"XDG_CACHE_HOME=$CACHE_HOME"
|
|
"XDG_DATA_HOME=$DATA_HOME"
|
|
"IDX_PROVIDER=msn"
|
|
"IDX_HISTORY_PROVIDER=auto"
|
|
)
|
|
}
|
|
|
|
build_binary() {
|
|
if [[ -z "$BIN_PATH" ]]; then
|
|
BIN_PATH="$ROOT_DIR/target/debug/idx"
|
|
fi
|
|
|
|
if (( BUILD )); then
|
|
(
|
|
cd "$ROOT_DIR" || exit 1
|
|
cargo build --quiet --bin idx
|
|
) || {
|
|
echo "build failed" >&2
|
|
exit 1
|
|
}
|
|
fi
|
|
|
|
if [[ ! -x "$BIN_PATH" ]]; then
|
|
echo "idx binary not found or not executable: $BIN_PATH" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
resolve_tickers() {
|
|
local raw_ticker
|
|
local old_ifs
|
|
|
|
if [[ -n "$TICKERS" ]]; then
|
|
old_ifs="$IFS"
|
|
IFS=','
|
|
for raw_ticker in $TICKERS; do
|
|
raw_ticker="${raw_ticker//[[:space:]]/}"
|
|
if [[ -n "$raw_ticker" ]]; then
|
|
TICKER_LIST+=("$raw_ticker")
|
|
fi
|
|
done
|
|
IFS="$old_ifs"
|
|
else
|
|
symbol_map="$ROOT_DIR/crates/idx-core/src/api/msn/symbol_ids.tsv"
|
|
[[ -f "$symbol_map" ]] || { echo "symbol map not found: $symbol_map" >&2; exit 1; }
|
|
mapfile -t TICKER_LIST < <(cut -f1 "$symbol_map")
|
|
fi
|
|
|
|
if [[ ${#TICKER_LIST[@]} -eq 0 ]]; then
|
|
echo "no tickers selected" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
run_audit() {
|
|
local total="${#TICKER_LIST[@]}"
|
|
local index=0
|
|
local passed=0
|
|
local failed=0
|
|
local ticker
|
|
local log_file
|
|
local message
|
|
|
|
printf 'ticker\tstatus\tmessage\tlog\n' >"$SUMMARY_FILE"
|
|
|
|
for ticker in "${TICKER_LIST[@]}"; do
|
|
index=$((index + 1))
|
|
log_file="$LOG_DIR/${ticker}.log"
|
|
|
|
printf '[%04d/%04d] %s\n' "$index" "$total" "$ticker"
|
|
|
|
if env "${BASE_ENV[@]}" "$BIN_PATH" -q -o json stocks valuation "$ticker" >"$log_file" 2>&1 \
|
|
&& grep -q '"overall_signal"' "$log_file"; then
|
|
passed=$((passed + 1))
|
|
printf '%s\tok\t-\t%s\n' "$ticker" "$log_file" >>"$SUMMARY_FILE"
|
|
else
|
|
failed=$((failed + 1))
|
|
message="$(tr '\n' ' ' <"$log_file" | tr '\t' ' ' | cut -c1-240)"
|
|
printf '%s\tfailed\t%s\t%s\n' "$ticker" "$message" "$log_file" >>"$SUMMARY_FILE"
|
|
fi
|
|
done
|
|
|
|
printf '\nresults: passed=%d failed=%d total=%d\n' "$passed" "$failed" "$total"
|
|
printf 'summary: %s\n' "$SUMMARY_FILE"
|
|
|
|
if (( failed > 0 )); then
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
prepare_paths
|
|
build_binary
|
|
resolve_tickers
|
|
run_audit
|
|
}
|
|
|
|
main "$@"
|