;; ported-from: src/ingest.ts @ v1.0.0 (extracted-from: bot/src/rl/train.ts @ fa686ee,
;; expandDirs + the ingest-state load/save that framed the trainer script)
;;
;; Trainer-side plumbing for live-episode ingestion: expand per-bot episode
;; dir patterns and persist the byte offsets already consumed, so every
;; transition is trained on exactly once across runs.
(ns ardegazu.train.ingest
(:require ["node:fs" :as fs]
["node:path" :as path]))
(defn expand-dirs
"Expand one \"*\" path segment against the filesystem (per-bot state dirs)."
[patterns]
(let [out #js []]
(dotimes [pi (.-length ^js patterns)]
(let [p (aget patterns pi)
star (.indexOf ^js p "*")]
(if (< star 0)
(.push out p)
(let [base (.replace ^js (.slice ^js p 0 star) #"/[^/]*$" "")
tail (.slice ^js p (inc (.indexOf ^js p "/" star)))]
(try
(let [entries (fs/readdirSync base #js {:withFileTypes true})]
(dotimes [di (.-length entries)]
(let [d (aget entries di)]
(when ^boolean (.isDirectory ^js d)
(.push out (path/join base (.-name ^js d) tail))))))
(catch :default _
;; base absent — nothing to ingest
nil))))))
out))
(defn ingest-state-path [model-dir]
(path/join model-dir "ingest-state.json"))
(defn load-ingest-state
"The persisted offsets, or a fresh state on first run / corrupt file."
[model-dir]
(try
(js/JSON.parse (fs/readFileSync (ingest-state-path model-dir) "utf8"))
(catch :default _
;; first run (or corrupt — re-ingest is idempotent enough)
#js {:offsets #js {}})))
(defn save-ingest-state [model-dir state]
(fs/mkdirSync model-dir #js {:recursive true})
(fs/writeFileSync (ingest-state-path model-dir) (js/JSON.stringify state))
js/undefined)