Skip to content

Commit 60d2ff9

Browse files
committed
fix(ingest): qualified module names — path-derived module-of, no basename collisions
module-of now derives the @<mod># key from the path RELATIVE to an ingest root (--root, default: common ancestor dir of the ingested files), dot-joined. One dir keeps bare basenames (schema.bclj -> "schema", existing tests/logs unchanged); a tree qualifies (tabs/index.bjs -> "chrome.bjs.tabs.index"), so gjoa's 5 duplicate basenames (index/history/layout/types/window) no longer collide ~10 files into 5 modules. Dots, not slashes: downstream builds flat filenames from the key (fram_mcp's edited-<mod>.bclj / <src>/<mod>.bclj). fram-code-on: --src <subdir> scopes find+ingest+emit-corpus to the source subtree (gjoa: src/gjoa; the gitignored engine/ mirror + tests/tools stay out) while .fram/ + .mcp.json stay at <dir>; ingest passes --root "$SRC". Boot wait raised 20s -> 20min cap (gjoa's 429k-claim fold ≈ 11 min; the loop exits the moment the daemon listens).
1 parent 409f7b9 commit 60d2ff9

2 files changed

Lines changed: 57 additions & 19 deletions

File tree

bin/fram-code-on

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#!/usr/bin/env bash
22
# ============================================================================
33
# fram-code-on — THE FLIP. Turn the graph stack ON for a code directory.
4-
# fram-code-on <dir> [port]
4+
# fram-code-on <dir> [port] [--src <subdir>]
5+
# --src scopes ingest+corpus to the SOURCE subtree (default: <dir>) while .fram/
6+
# and .mcp.json stay at <dir> — repos whose tree also holds build artifacts /
7+
# vendored copies (gjoa: gitignored engine/ mirrors src/) must not ingest those.
58
# Does the whole incantation in one shot:
69
# 1. ingest <dir> -> .fram/code.log (the code becomes claims)
710
# 2. boot the WARM coordinator on that log (sole-writer, in-memory graph)
@@ -15,28 +18,36 @@
1518
set -euo pipefail
1619
HERE="$(cd "$(dirname "$0")/.." && pwd)" # fram repo root
1720
BEAGLE_HOME="${BEAGLE_HOME:-$HOME/code/beagle}"
18-
DIR="${1:?usage: fram-code-on <dir> [port]}"; DIR="$(cd "$DIR" && pwd)"
19-
PORT="${2:-$(( (RANDOM % 20000) + 30000 ))}"
21+
DIR="${1:?usage: fram-code-on <dir> [port] [--src <subdir>]}"; DIR="$(cd "$DIR" && pwd)"; shift
22+
PORT="" SRC="$DIR"
23+
while [ $# -gt 0 ]; do
24+
case "$1" in
25+
--src) SRC="${2:?--src needs a dir}"; shift 2;;
26+
*) PORT="$1"; shift;;
27+
esac
28+
done
29+
SRC="$(cd "$SRC" && pwd)"
30+
PORT="${PORT:-$(( (RANDOM % 20000) + 30000 ))}"
2031
CODE_LOG="$DIR/.fram/code.log"
2132
mkdir -p "$DIR/.fram"
2233

23-
echo "fram-code-on: $DIR (port $PORT)"
34+
echo "fram-code-on: $DIR (port $PORT; src $SRC)"
2435

2536
# 1. ingest the code into claims (deterministic, no LLM) — recurse for ANY Beagle source.
2637
# The extension only picks the emit target (.bclj->clj .bcljs->cljs .bjs->js .bnix->nix
2738
# .bgl->neutral .bodin->odin); the parsed AST is target-independent, so ingest is too.
2839
# fram-ingest-code is non-recursive, so we find + feed it every Beagle file here.
2940
echo " [1/3] ingesting code -> claims …"
30-
mapfile -t SRCS < <(find "$DIR" -regextype posix-extended -regex '.*\.b(clj|cljs|js|nix|gl|sql|py|zig|odin)$' -not -path '*/.fram/*' | sort)
31-
[ "${#SRCS[@]}" -eq 0 ] && { echo " no Beagle source (.bclj/.bcljs/.bjs/.bnix/.bgl/.bodin) under $DIR"; exit 1; }
32-
( cd "$HERE" && BEAGLE_HOME="$BEAGLE_HOME" bin/fram-ingest-code "${SRCS[@]}" --out "$CODE_LOG" ) \
41+
mapfile -t SRCS < <(find "$SRC" -regextype posix-extended -regex '.*\.b(clj|cljs|js|nix|gl|sql|py|zig|odin)$' -not -path '*/.fram/*' | sort)
42+
[ "${#SRCS[@]}" -eq 0 ] && { echo " no Beagle source (.bclj/.bcljs/.bjs/.bnix/.bgl/.bodin) under $SRC"; exit 1; }
43+
( cd "$HERE" && BEAGLE_HOME="$BEAGLE_HOME" bin/fram-ingest-code "${SRCS[@]}" --root "$SRC" --out "$CODE_LOG" ) \
3344
&& echo " ${#SRCS[@]} modules, $(wc -l <"$CODE_LOG") claims -> $CODE_LOG"
3445

3546
# 1b. resolve AST -> reference graph (corpus) for REASONING. code.log is the editing
3647
# AST (tree); the corpus is the resolved who-calls/blast-radius GRAPH (callgraph needs it).
3748
CORPUS="$DIR/.fram/corpus.claims"
3849
echo " [1b] resolving -> reference graph (for who-calls/blast-radius) …"
39-
( cd "$HERE/chartroom" && BEAGLE_HOME="$BEAGLE_HOME" bin/emit-corpus "$DIR" "$CORPUS" >/dev/null 2>&1 ) \
50+
( cd "$HERE/chartroom" && BEAGLE_HOME="$BEAGLE_HOME" bin/emit-corpus "$SRC" "$CORPUS" >/dev/null 2>&1 ) \
4051
&& echo " corpus: $(wc -l <"$CORPUS" 2>/dev/null) claims -> $CORPUS" \
4152
|| echo " (corpus skipped — authoring still works; reasoning needs emit-corpus)"
4253

@@ -47,7 +58,9 @@ else
4758
echo " [2/3] booting warm coordinator on :$PORT"
4859
( cd "$HERE" && setsid nohup clojure -M cnf_coord_daemon.clj serve-flat "$PORT" "$CODE_LOG" \
4960
>"$DIR/.fram/coord-$PORT.log" 2>&1 & )
50-
for i in $(seq 1 40); do sleep 0.5; grep -q "listening on" "$DIR/.fram/coord-$PORT.log" 2>/dev/null && break; done
61+
# boot time scales with the log (gjoa: 429k claims ≈ 11 min — index! dedupe is
62+
# quadratic today). Generous cap; the loop exits the moment the daemon listens.
63+
for i in $(seq 1 2400); do sleep 0.5; grep -q "listening on" "$DIR/.fram/coord-$PORT.log" 2>/dev/null && break; done
5164
grep -q "listening on" "$DIR/.fram/coord-$PORT.log" 2>/dev/null \
5265
&& echo " coordinator up (sole-writer, loopback)" \
5366
|| { echo " FAILED to boot — see $DIR/.fram/coord-$PORT.log"; exit 1; }

bin/fram-ingest-code

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
;; [--module <name>] restrict to ONE module by name substring (the flip
2323
;; experiment ingests ONLY schema; this is how)
2424
;; [--append] append to an existing log (default: TRUNCATE+write)
25+
;; [--root <dir>] source root module names are derived from (default:
26+
;; the common ancestor dir of the ingested files)
2527
;;
2628
;; The default ingests every .bclj it finds (staged adoption uses this); the flip
2729
;; experiment passes `--module schema` so ONLY schema.bclj is folded.
@@ -55,12 +57,13 @@
5557

5658
;; --- args --------------------------------------------------------------------
5759
(defn- parse-args [args]
58-
(loop [a args, opts {:srcs [] :out nil :module nil :append false}]
60+
(loop [a args, opts {:srcs [] :out nil :module nil :append false :root nil}]
5961
(cond
6062
(empty? a) opts
6163
(= "--out" (first a)) (recur (drop 2 a) (assoc opts :out (second a)))
6264
(= "--module" (first a)) (recur (drop 2 a) (assoc opts :module (second a)))
6365
(= "--append" (first a)) (recur (rest a) (assoc opts :append true))
66+
(= "--root" (first a)) (recur (drop 2 a) (assoc opts :root (second a)))
6467
:else (recur (rest a) (update opts :srcs conj (first a))))))
6568

6669
;; Any Beagle source extension — the extension only selects the EMIT TARGET
@@ -79,9 +82,30 @@
7982
sort vec)
8083
:else [])))
8184

82-
;; module name from a Beagle path: basename without the .b* extension. schema.bclj
83-
;; -> "schema". This is the @<module># prefix the resolver groups by (name->module).
84-
(defn- module-of [path] (-> path io/file .getName (str/replace beagle-ext-re "")))
85+
;; module name from a Beagle path: the path RELATIVE to the ingest root (--root,
86+
;; default: common ancestor dir of all ingested files), DOT-joined, .b* stripped.
87+
;; This is the @<module># prefix the resolver groups by (name->module) — its regex
88+
;; (@([^#]+)#) admits any separator, but downstream builds flat filenames from the
89+
;; key (fram_mcp's edited-<mod>.bclj / <src>/<mod>.bclj views), so dots, not slashes.
90+
;; One dir of files keeps bare basenames (src/fram/schema.bclj -> "schema", as
91+
;; before); a TREE qualifies (tabs/index.bjs -> "tabs.index"), so duplicate
92+
;; basenames across dirs never collide into one module.
93+
(defn- path-segs [p] (vec (remove str/blank? (str/split p #"/"))))
94+
(defn- common-prefix [a b]
95+
(loop [i 0]
96+
(if (and (< i (count a)) (< i (count b)) (= (nth a i) (nth b i)))
97+
(recur (inc i))
98+
(subvec a 0 i))))
99+
(defn- infer-root-segs [paths]
100+
(reduce common-prefix (map #(vec (butlast (path-segs %))) paths)))
101+
(defn- module-of [root-segs path]
102+
(let [segs (path-segs path)
103+
n (count root-segs)
104+
rel (if (and (<= n (count segs)) (= root-segs (subvec segs 0 n)))
105+
(subvec segs n)
106+
segs) ; not under root: full path qualifies
107+
rel (if (seq rel) rel [(peek segs)])]
108+
(str/replace (str/join "." rel) beagle-ext-re "")))
85109

86110
;; --- emit-edn one module, parse to [s p o] triples ---------------------------
87111
;; emit-edn prints `@file <path>` then one `[s "pred" o]` per line. o is an INTEGER
@@ -104,8 +128,8 @@
104128
;; object -> :r the string (a LITERAL). Predicate passes through verbatim.
105129
;; PLUS one bookkeeping claim @<mod>#root file "<path>" so render-from-log knows the
106130
;; source path (the @file wrapper analogue; "root" never collides with an int id).
107-
(defn- module->claims [path]
108-
(let [module (module-of path)
131+
(defn- module->claims [root-segs path]
132+
(let [module (module-of root-segs path)
109133
triples (emit-edn-triples path)
110134
node-claims
111135
(mapv (fn [[s p o]]
@@ -118,17 +142,18 @@
118142
node-claims)))
119143

120144
;; --- main --------------------------------------------------------------------
121-
(let [{:keys [srcs out module append]} (parse-args *command-line-args*)
145+
(let [{:keys [srcs out module append root]} (parse-args *command-line-args*)
122146
out-path (or out (str (System/getProperty "user.dir") "/.fram/code.log"))]
123-
(when (empty? srcs) (die "usage: bin/fram-ingest-code <src> [<src>...] [--out <log>] [--module <name>] [--append]"))
147+
(when (empty? srcs) (die "usage: bin/fram-ingest-code <src> [<src>...] [--out <log>] [--module <name>] [--append] [--root <dir>]"))
124148
(when-not (.exists (io/file roundtrip-rkt))
125149
(die "missing claims-roundtrip.rkt at" roundtrip-rkt "(set FRAM_ROUNDTRIP / BEAGLE_HOME)"))
126150
(let [files (->> srcs (mapcat beagle-files) distinct sort
127151
(filter (fn [p] (or (nil? module) (str/includes? p module)))))]
128152
(when (empty? files) (die "no Beagle modules matched" (pr-str srcs) (when module (str "(module filter: " module ")"))))
129153
(log! "fram-ingest-code: folding" (count files) "module(s) ->" out-path
130154
(when module (str "(module filter: " module ")")))
131-
(let [all-claims (mapcat module->claims files)
155+
(let [root-segs (if root (path-segs root) (infer-root-segs files))
156+
all-claims (mapcat (partial module->claims root-segs) files)
132157
;; monotone :tx across the whole log; existing daemon seeds :next-seq to max.
133158
base (if (and append (.exists (io/file out-path)))
134159
(->> (str/split-lines (slurp out-path))
@@ -149,5 +174,5 @@
149174
(spit out-path body))
150175
(log! "fram-ingest-code: wrote" (count all-claims) "AST claims"
151176
(str "(" (count files) " module(s); ids re-keyed @<mod>#<n>)"))
152-
(doseq [f files] (log! " +" (module-of f) "<-" f))
177+
(doseq [f files] (log! " +" (module-of root-segs f) "<-" f))
153178
(log! "DONE — the Beagle source is now downstream of" out-path))))

0 commit comments

Comments
 (0)