#!/usr/bin/env sh # scan-logs.sh — 離線回填:解析各 CLI 的原生日誌,把可辨識的技能使用與工作階段界線 # 補進 $JSC_HOME/usage/skills.jsonl(與 skill-usage.sh 同一種紀錄格式)與 $JSC_HOME/sessions/。 # 用法: scan-logs.sh --cli {name} [--session {sid}] # 支援: copilot(~/.config/copilot/)、antigravity(全域日誌目錄)、codex(~/.codex/sessions/、~/.codex/log/)。 # 去重: 以 $JSC_HOME/usage/scan-state/ 記住每個日誌檔已掃描的位元組數,重掃只讀新增段落。 HERE=$(cd "$(dirname "$0")" && pwd); . "$HERE/../hooks/lib.sh" cli=""; sid="" while [ $# -gt 0 ]; do case "$1" in --cli) cli="${2:-}"; shift; shift ;; --session) sid="${2:-}"; shift; shift ;; *) shift ;; esac done if [ -z "$cli" ]; then echo "用法:scan-logs.sh --cli {name} [--session {sid}]" >&2 exit 2 fi STATE="$JSC_HOME/usage/scan-state" mkdir -p "$STATE" 2>/dev/null || true # 檔案修改時間(epoch),取不到就用現在 mtime_epoch() { stat -c %Y "$1" 2>/dev/null || date -r "$1" +%s 2>/dev/null || now_epoch } # 回填工作階段界線:start 只補缺,end 取較新值(不覆蓋較新的即時紀錄) record_session() { # $1=sid $2=epoch [ -f "$JSC_HOME/sessions/$1.start" ] || echo "$2" > "$JSC_HOME/sessions/$1.start" old=$(cat "$JSC_HOME/sessions/$1.end" 2>/dev/null || echo 0) if [ "$2" -gt "$old" ] 2>/dev/null; then echo "$2" > "$JSC_HOME/sessions/$1.end"; fi } # 印出日誌檔自上次掃描後的新增內容,並更新位移(位移即去重機制) new_content() { # $1=file key=$(printf '%s' "$1" | cksum | tr ' \t' '--') off_f="$STATE/$cli-$key.offset" off=$(cat "$off_f" 2>/dev/null || echo 0) size=$(wc -c < "$1" 2>/dev/null || echo 0) [ "$size" -gt "$off" ] 2>/dev/null || return 0 tail -c +"$((off + 1))" "$1" 2>/dev/null echo "$size" > "$off_f" } # 從新增內容萃取 jsc 技能代號,逐筆追加(同一批先去重;格式與 skill-usage.sh 完全相同) append_skills() { # stdin=內容 $1=sid grep -oE 'jsc-[a-z]+:[a-z0-9][a-z0-9-]*' 2>/dev/null | sort -u | while IFS= read -r sk; do printf '{"ts":"%s","cli":"%s","session":"%s","skill":"%s"}\n' \ "$(now_iso)" "$cli" "$1" "$sk" >> "$JSC_HOME/usage/skills.jsonl" done } # 掃描一個日誌目錄;$2=預設 sid(空字串時用檔名當 sid);$3=sessions 時回填階段界線 scan_dir() { find "$1" -maxdepth 3 -type f \( -name '*.json' -o -name '*.jsonl' -o -name '*.log' \) 2>/dev/null \ | while IFS= read -r f; do s="$2" [ -n "$s" ] || s="$cli-$(basename "$f" | sed 's/\.[^.]*$//')" [ "${3:-}" = "sessions" ] && record_session "$s" "$(mtime_epoch "$f")" new_content "$f" | append_skills "$s" done return 0 } case "$cli" in copilot) d="$HOME/.config/copilot" if [ ! -d "$d" ]; then echo "[jsc] 找不到 copilot 日誌目錄($d),略過回填。"; exit 0; fi [ -d "$d/history-session-state" ] && scan_dir "$d/history-session-state" "$sid" sessions [ -d "$d/history" ] && scan_dir "$d/history" "$sid" sessions [ -d "$d/logs" ] && scan_dir "$d/logs" "$sid" logs ;; antigravity) found="" for d in "$HOME/.antigravity/logs" "$HOME/.config/antigravity/logs" "$HOME/.antigravity"; do if [ -d "$d" ]; then found=1; scan_dir "$d" "$sid" sessions; break; fi done if [ -z "$found" ]; then echo "[jsc] 找不到 antigravity 日誌目錄,略過回填。"; exit 0; fi ;; codex) d1="$HOME/.codex/sessions"; d2="$HOME/.codex/log" if [ ! -d "$d1" ] && [ ! -d "$d2" ]; then echo "[jsc] 找不到 codex 日誌目錄($d1、$d2),略過回填。"; exit 0 fi [ -d "$d1" ] && scan_dir "$d1" "$sid" sessions [ -d "$d2" ] && scan_dir "$d2" "$sid" logs ;; *) echo "[jsc] 尚未支援 $cli 的日誌回填,略過。" ;; esac exit 0