From 2b0b72109ac6dcfdee768716caba90c3cec38703 Mon Sep 17 00:00:00 2001 From: Beforerr Date: Sun, 26 Jul 2026 20:37:41 +0900 Subject: [PATCH] simplify session routing; make Julia project startup-only --- CHANGELOG.md | 4 ++ README.md | 2 +- docs/architecture.md | 6 +-- go/adapter.go | 3 -- go/daemon.go | 26 ++++------- go/engine_test.go | 39 ++++++++-------- go/julia/adapter.go | 8 ---- go/julia_integration_test.go | 3 +- go/manager.go | 79 +++++++++++--------------------- go/python/adapter.go | 8 ---- go/r/adapter.go | 7 --- go/wolfram/adapter.go | 7 --- skills/repld/SKILL.md | 2 +- skills/repld/references/julia.md | 14 +++--- 14 files changed, 67 insertions(+), 141 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bfb0ab..9b9825b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [Unreleased] + +- Julia `--project` is now startup-only, like other interpreter flags; later calls reuse cwd session without repeating it. + ## [1.0.0] Reworked into a single language-agnostic binary, `repld`, that selects interpreter from a leading exe positional. diff --git a/README.md b/README.md index 47b0020..eb1ba88 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ repld sessions # list active sessions repld stop # shutdown daemon ``` -`` is `julia`, `python3`, `R`, `wolframscript`, an absolute/relative interpreter path, etc. repld's own flags (`--session`/`--lang`/`--trace`/`--fresh`) go before ``; after it, every flag forwards verbatim to the interpreter (e.g. Julia's `--project=DIR`, `+1.11` for juliaup) except native eval/print flags (`-e`/`-c`/`-E`). Each call routes to a persistent session keyed by language + project + `--session`/cwd. +`` is `julia`, `python3`, `R`, `wolframscript`, an absolute/relative interpreter path, etc. repld's own flags (`--session`/`--lang`/`--trace`/`--fresh`) go before ``; after it, every flag forwards verbatim to the interpreter (e.g. Julia's `--project=DIR`, `+1.11` for juliaup) except native eval/print flags (`-e`/`-c`/`-E`). Each call routes to a persistent session keyed by language + interpreter + `--session`/cwd. Forwarded interpreter flags configure a session only when it starts. A session auto-closes once the agent process that created it exits. `repld free ` pins a session against auto-closing; `repld --owner-pid 0 ...` opts out at creation. diff --git a/docs/architecture.md b/docs/architecture.md index d027791..fb6732b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -34,10 +34,8 @@ re-execs itself as `daemon` if none is running. A relative exe path is abs-ified client-side (`absExe`); a bare name is looked up in PATH on the daemon. **Daemon mode** (`daemon` subcommand): one language-agnostic server -(`go/daemon.go`). The per-session `Adapter` is chosen from each request's `lang` (`SessionManager.getOrCreate`). Sessions -are keyed by `lang` + (`--session` label / project / cwd); the lang prefix keeps -same-dir sessions of different languages distinct. Runs until `stop` (or an -optional `daemon --idle-timeout SECS`; default 0 = never). +(`go/daemon.go`). The per-session `Adapter` is chosen from each request's `lang` (`SessionManager.getOrCreate`). +Runs until `stop` (or optional `daemon --idle-timeout SECS`; default 0 = never). **Owner lease**: the daemon closes a session after its owning process exits. Ownerless sessions persist until closed; `repld free` removes an existing lease. diff --git a/go/adapter.go b/go/adapter.go index 225a7fe..545b72a 100644 --- a/go/adapter.go +++ b/go/adapter.go @@ -6,9 +6,6 @@ type Adapter interface { LaunchArgs(forwarded []string) []string - // SessionKey separates environments: project, interpreter, or runtime identity. - SessionKey(exe string, forwarded []string) string - // BootstrapStmt loads the embedded runtime. BootstrapStmt() string WrapEval(hexCode string, printResult bool) string diff --git a/go/daemon.go b/go/daemon.go index cdc13be..faca90a 100644 --- a/go/daemon.go +++ b/go/daemon.go @@ -31,7 +31,7 @@ func handleRequest(state *daemonState, req protocolRequest) response { switch req.Action { case "trace": - key, kerr := state.manager.targetKey(req.ID, req.Lang, req.Session, req.Cwd, discFor(req)) + key, kerr := state.manager.targetKey(req.ID, req.Lang, req.Session, req.Cwd, req.Exe) if kerr != nil { return errResp(kerr.Error()) } @@ -53,7 +53,7 @@ func handleRequest(state *daemonState, req protocolRequest) response { return response{Output: out} case "interrupt": - key, kerr := state.manager.targetKey(req.ID, req.Lang, req.Session, req.Cwd, discFor(req)) + key, kerr := state.manager.targetKey(req.ID, req.Lang, req.Session, req.Cwd, req.Exe) if kerr != nil { return errResp(kerr.Error()) } @@ -64,7 +64,7 @@ func handleRequest(state *daemonState, req protocolRequest) response { return response{Output: msg} case "close": - key, kerr := state.manager.targetKey(req.ID, req.Lang, req.Session, req.Cwd, discFor(req)) + key, kerr := state.manager.targetKey(req.ID, req.Lang, req.Session, req.Cwd, req.Exe) if kerr != nil { return errResp(kerr.Error()) } @@ -75,7 +75,7 @@ func handleRequest(state *daemonState, req protocolRequest) response { return response{Output: msg} case "free": - key, kerr := state.manager.targetKey(req.ID, req.Lang, req.Session, req.Cwd, discFor(req)) + key, kerr := state.manager.targetKey(req.ID, req.Lang, req.Session, req.Cwd, req.Exe) if kerr != nil { return errResp(kerr.Error()) } @@ -119,15 +119,6 @@ func errResp(msg string) response { return response{Error: msg} } -// discFor is the session's environment discriminant, or "" when the language is -// unknown (label-only reuse, where the discriminant isn't part of the key). -func discFor(req protocolRequest) string { - if a := adapterFor(req.Lang); a != nil { - return a.SessionKey(req.Exe, req.Args) - } - return "" -} - func normalizedTraceLevel(level string) string { switch strings.ToLower(level) { case "short", "compact": @@ -199,15 +190,14 @@ func handleStreamingEval(state *daemonState, req protocolRequest, conn net.Conn) enc := json.NewEncoder(conn) emit := func(f streamFrame) { _ = enc.Encode(f) } - disc := discFor(req) if req.RequireExisting { - if req.Fresh || !state.manager.hasLiveSession(req.Lang, req.Session, req.Cwd, disc) { + if req.Fresh || !state.manager.hasLiveSession(req.Lang, req.Session, req.Cwd, req.Exe) { emit(streamFrame{Done: true, Error: "no existing session for label; pass an interpreter or --lang to create one"}) return } } if req.Fresh { - state.manager.restart(req.Lang, req.Session, req.Cwd, disc) + state.manager.restart(req.Lang, req.Session, req.Cwd, req.Exe) } sess, err := state.manager.getOrCreate(req.Lang, req.Cwd, req.Session, req.Exe, req.Args, req.OwnerPID, req.OwnerStart) if err != nil { @@ -251,10 +241,10 @@ func handleStreamingEval(state *daemonState, req protocolRequest, conn net.Conn) err = sess.execute(ctx, code, printResult, onChunk) if err != nil { if !sess.isAlive() { - state.manager.remove(req.Lang, req.Session, req.Cwd, disc) + state.manager.remove(req.Lang, req.Session, req.Cwd, req.Exe) } if evalErr, ok := err.(*evalError); ok { - state.manager.recordError(req.Lang, req.Session, req.Cwd, disc, evalErr) + state.manager.recordError(req.Lang, req.Session, req.Cwd, req.Exe, evalErr) emit(streamFrame{Done: true, Error: formatError(evalErr, req.TraceLevel, sess.id)}) return } diff --git a/go/engine_test.go b/go/engine_test.go index 97a598f..0a3a83c 100644 --- a/go/engine_test.go +++ b/go/engine_test.go @@ -2,10 +2,10 @@ package main import ( "net" - "path/filepath" "testing" "time" + "github.com/Beforerr/repld/go/python" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" ) @@ -95,21 +95,21 @@ func TestHandleRequest_Stop(t *testing.T) { func TestHandleRequest_SessionsList(t *testing.T) { state := newTestState() - // A julia dir session pinned to a project; a global labeled session; a dead + // A Julia cwd session with startup args; a global labeled session; a dead // python dir session. The key is lang-prefixed; --session labels are global. jl := newSession("julia", "s", []string{"--project=/env"}, nil) jl.id = "kqzmkqzm" named := newSession("julia", "s", nil, nil) py := newSession("python", "s", nil, nil) py.dead.Store(true) - state.manager.sessions[sessionKey{lang: "julia", route: "/work", disc: "/env"}] = jl + state.manager.sessions[sessionKey{lang: "julia", cwd: "/work", exe: "julia"}] = jl state.manager.sessions[sessionKey{label: "scratch"}] = named - state.manager.sessions[sessionKey{lang: "python", route: "/work"}] = py + state.manager.sessions[sessionKey{lang: "python", cwd: "/work", exe: "python3"}] = py resp := handleRequest(state, protocolRequest{Action: "sessions"}) require.Empty(t, resp.Error) require.Equal(t, `- {id: kqzmkqzm, lang: julia, dir: /work, args: [--project=/env]} -- {lang: julia, session: scratch, args: [--project=@.]} +- {lang: julia, session: scratch} - {lang: python, dir: /work, status: dead} `, resp.Output) } @@ -119,7 +119,7 @@ func TestHandleRequest_SessionsParseableYAML(t *testing.T) { jl := newSession("julia", "s", []string{"--project=/env"}, nil) jl.id = "kqzmkqzm" named := newSession("julia", "s", nil, nil) - state.manager.sessions[sessionKey{lang: "julia", route: "/work", disc: "/env"}] = jl + state.manager.sessions[sessionKey{lang: "julia", cwd: "/work", exe: "julia"}] = jl state.manager.sessions[sessionKey{label: "scratch"}] = named resp := handleRequest(state, protocolRequest{Action: "sessions"}) @@ -161,7 +161,7 @@ func TestCloseSessionByID(t *testing.T) { state := newTestState() sess := newSession("julia", "s", nil, nil) sess.id = "kqzm" - state.manager.sessions[sessionKey{lang: "julia", route: "/work", disc: "@."}] = sess + state.manager.sessions[sessionKey{lang: "julia", cwd: "/work", exe: "julia"}] = sess // Unique prefix resolves regardless of cwd. resp := handleRequest(state, protocolRequest{Action: "close", ID: "kq", Cwd: t.TempDir()}) @@ -180,13 +180,13 @@ func TestKeyForIDPrefix(t *testing.T) { a.id = "kqzm" b := newSession("julia", "s", nil, nil) b.id = "kxyz" - m.sessions[sessionKey{lang: "julia", route: "/a", disc: "@."}] = a - m.sessions[sessionKey{lang: "julia", route: "/b", disc: "@."}] = b + m.sessions[sessionKey{lang: "julia", cwd: "/a", exe: "julia"}] = a + m.sessions[sessionKey{lang: "julia", cwd: "/b", exe: "julia"}] = b key, ok, err := m.keyForID("kq") require.NoError(t, err) require.True(t, ok) - require.Equal(t, sessionKey{lang: "julia", route: "/a", disc: "@."}, key) + require.Equal(t, sessionKey{lang: "julia", cwd: "/a", exe: "julia"}, key) _, _, err = m.keyForID("k") // shared prefix → ambiguous require.Error(t, err) @@ -201,16 +201,13 @@ func TestSessionManagerKey(t *testing.T) { m := newSessionManager() defer m.shutdown() - // key = lang + cwd + discriminant (project); label keys are global. - require.Equal(t, sessionKey{lang: "julia", route: "/w", disc: "@."}, m.key("julia", "", "/w", "@.")) - require.Equal(t, sessionKey{lang: "python", route: "/w"}, m.key("python", "", "/w", "")) - // same dir, distinct by language or by project → distinct sessions. - require.NotEqual(t, m.key("julia", "", "/w", "@."), m.key("python", "", "/w", "")) - require.NotEqual(t, m.key("julia", "", "/w", "@."), m.key("julia", "", "/w", "/env")) - absProject := filepath.Join(t.TempDir(), "env") - require.Equal(t, m.key("julia", "", "/a", absProject), m.key("julia", "", "/b", absProject)) - require.NotEqual(t, m.key("julia", "", "/a", "@."), m.key("julia", "", "/b", "@.")) - // a --session label is global: same key regardless of language/project. - require.Equal(t, sessionKey{label: "scratch"}, m.key("julia", "scratch", "/w", "@.")) + // key = language + cwd + interpreter; labels are global. + require.Equal(t, sessionKey{lang: "julia", cwd: "/w", exe: "julia"}, m.key("julia", "", "/w", "")) + require.Equal(t, sessionKey{lang: "python", cwd: "/w", exe: python.Adapter{}.DefaultExe()}, m.key("python", "", "/w", "")) + require.NotEqual(t, m.key("julia", "", "/w", "julia"), m.key("python", "", "/w", "python3")) + require.NotEqual(t, m.key("julia", "", "/w", "julia"), m.key("julia", "", "/w", "/opt/julia")) + require.NotEqual(t, m.key("julia", "", "/a", "julia"), m.key("julia", "", "/b", "julia")) + // a --session label is global: same key regardless of language/interpreter. + require.Equal(t, sessionKey{label: "scratch"}, m.key("julia", "scratch", "/w", "julia")) require.Equal(t, sessionKey{label: "scratch"}, m.key("python", "scratch", "/other", "")) } diff --git a/go/julia/adapter.go b/go/julia/adapter.go index 9cf9264..10597ab 100644 --- a/go/julia/adapter.go +++ b/go/julia/adapter.go @@ -35,14 +35,6 @@ func hasProject(args []string) bool { return projectOf(args) != "" } -// each project is its own session. -func (Adapter) SessionKey(_ string, forwarded []string) string { - if p := projectOf(forwarded); p != "" { - return p - } - return "@." -} - func projectOf(args []string) string { for i, a := range args { if v, ok := strings.CutPrefix(a, "--project="); ok { diff --git a/go/julia_integration_test.go b/go/julia_integration_test.go index daf6525..060723d 100644 --- a/go/julia_integration_test.go +++ b/go/julia_integration_test.go @@ -373,8 +373,7 @@ func TestJuliaWorldAgeDisplay(t *testing.T) { // Fresh pkg content forces a cache-miss precompile during this eval (the world bump). res := repldOK(t, socketPath, pkgDir, "julia", "--project="+pkgDir, "-E", "using WAEnum; WAEnum.mkval()") require.Equal(t, "Dark::Shade = 0\n", res.stdout) - require.NotContains(t, res.stderr, "world age") - require.NotContains(t, res.stderr, "too new") + require.Equal(t, "Dark::Shade = 0\n", repldOK(t, socketPath, pkgDir, "julia", "-E", "WAEnum.mkval()").stdout) } // TestKillRunsAtexitHooks: a graceful shutdown must let Julia run its atexit diff --git a/go/manager.go b/go/manager.go index e77d145..679f9a2 100644 --- a/go/manager.go +++ b/go/manager.go @@ -23,29 +23,22 @@ type SessionManager struct { type sessionKey struct { label string lang string - route string - disc string + cwd string + exe string } func (k sessionKey) String() string { if k.label != "" { return "session:" + k.label } - if k.disc == "" { - return k.lang + ":" + k.route - } - return k.lang + ":" + k.route + ":" + k.disc + return k.lang + ":" + k.cwd + ":" + k.exe } func (k sessionKey) logName() string { if k.label != "" { return "session-" + safeLogComponent(k.label) } - parts := []string{safeLogComponent(k.lang), safeLogComponent(k.route)} - if k.disc != "" { - parts = append(parts, safeLogComponent(k.disc)) - } - return strings.Join(parts, "-") + return strings.Join([]string{safeLogComponent(k.lang), safeLogComponent(k.cwd), safeLogComponent(k.exe)}, "-") } func safeLogComponent(s string) string { @@ -65,22 +58,19 @@ func newSessionManager() *SessionManager { } } -// key namespaces a session. A --session label is global (reusable without -// re-specifying interpreter). Otherwise route is cwd or the adapter's -// per-environment location (Julia's --project). -func (m *SessionManager) key(lang, session, cwd, disc string) sessionKey { +// A --session label is global and reusable without re-specifying interpreter. +// Otherwise interpreter identity and cwd select the session; forwarded startup +// args apply only when that session is created. +func (m *SessionManager) key(lang, session, cwd, exe string) sessionKey { if session != "" { return sessionKey{label: session} } - route := cwd - if lang == "julia" && disc != "" && disc != "@." { - route = disc - if !strings.HasPrefix(route, "@") && !filepath.IsAbs(route) { - route = filepath.Join(cwd, route) + if exe == "" { + if lc, ok := langs[lang]; ok { + exe = lc.adapter.DefaultExe() } - route = filepath.Clean(route) } - return sessionKey{lang: lang, route: route, disc: disc} + return sessionKey{lang: lang, cwd: cwd, exe: exe} } // from k-z : IDs never look like exe names, paths @@ -125,7 +115,7 @@ func (m *SessionManager) keyForID(prefix string) (sessionKey, bool, error) { return match, found, nil } -func (m *SessionManager) targetKey(id, lang, session, cwd, disc string) (sessionKey, error) { +func (m *SessionManager) targetKey(id, lang, session, cwd, exe string) (sessionKey, error) { if id != "" { key, ok, err := m.keyForID(id) if err != nil { @@ -136,7 +126,7 @@ func (m *SessionManager) targetKey(id, lang, session, cwd, disc string) (session } return key, nil } - return m.key(lang, session, cwd, disc), nil + return m.key(lang, session, cwd, exe), nil } func (m *SessionManager) openLogFile(key sessionKey) *os.File { @@ -147,12 +137,8 @@ func (m *SessionManager) openLogFile(key sessionKey) *os.File { // forwarded args apply only when a session is first created; // a live session for the key is reused as-is. func (m *SessionManager) getOrCreate(lang, cwd, session, exe string, fwd []string, ownerPID int, ownerStart int64) (*Session, error) { - lc, known := langs[lang] - disc := "" - if known { - disc = lc.adapter.SessionKey(exe, fwd) - } - key := m.key(lang, session, cwd, disc) + _, known := langs[lang] + key := m.key(lang, session, cwd, exe) m.mu.Lock() sess := m.sessions[key] @@ -196,15 +182,15 @@ func (m *SessionManager) getOrCreate(lang, cwd, session, exe string, fwd []strin return v.(*Session), nil } -func (m *SessionManager) remove(lang, session, cwd, disc string) { - key := m.key(lang, session, cwd, disc) +func (m *SessionManager) remove(lang, session, cwd, exe string) { + key := m.key(lang, session, cwd, exe) m.mu.Lock() delete(m.sessions, key) m.mu.Unlock() } -func (m *SessionManager) restart(lang, session, cwd, disc string) { - key := m.key(lang, session, cwd, disc) +func (m *SessionManager) restart(lang, session, cwd, exe string) { + key := m.key(lang, session, cwd, exe) m.mu.Lock() sess := m.sessions[key] delete(m.sessions, key) @@ -268,16 +254,16 @@ func (m *SessionManager) reapLoop(stop <-chan struct{}) { } } -func (m *SessionManager) hasLiveSession(lang, session, cwd, disc string) bool { - key := m.key(lang, session, cwd, disc) +func (m *SessionManager) hasLiveSession(lang, session, cwd, exe string) bool { + key := m.key(lang, session, cwd, exe) m.mu.Lock() defer m.mu.Unlock() sess := m.sessions[key] return sess != nil && sess.isAlive() } -func (m *SessionManager) recordError(lang, session, cwd, disc string, err *evalError) { - key := m.key(lang, session, cwd, disc) +func (m *SessionManager) recordError(lang, session, cwd, exe string, err *evalError) { + key := m.key(lang, session, cwd, exe) m.mu.Lock() m.lastErrors[key] = err m.mu.Unlock() @@ -298,19 +284,10 @@ type sessionInfo struct { Status string `yaml:"status,omitempty"` // "dead" or "busy"; "" when idle and alive Busy float64 `yaml:"busy,omitempty"` // seconds in-flight; set only while busy Owner int `yaml:"owner,omitempty"` // owner pid whose exit auto-closes the session; 0 = pinned - Args []string `yaml:"args,omitempty"` // effective launch args (Julia's implicit --project is made explicit) + Args []string `yaml:"args,omitempty"` // startup args supplied when session was created Log string `yaml:"log,omitempty"` } -func hasProjectFlag(args []string) bool { - for _, a := range args { - if a == "--project" || strings.HasPrefix(a, "--project=") { - return true - } - } - return false -} - func (m *SessionManager) list() []sessionInfo { m.mu.Lock() defer m.mu.Unlock() @@ -326,11 +303,7 @@ func (m *SessionManager) list() []sessionInfo { if key.label != "" { info.Session = key.label } else { - info.Dir = key.route - } - // Surface Julia's implicit project so args reflect the real launch. - if sess.lang == "julia" && !hasProjectFlag(sess.fwd) { - info.Args = append([]string{"--project=" + sess.adapter.SessionKey("", sess.fwd)}, sess.fwd...) + info.Dir = key.cwd } if !sess.isAlive() { info.Status = "dead" diff --git a/go/python/adapter.go b/go/python/adapter.go index 7775040..bdab301 100644 --- a/go/python/adapter.go +++ b/go/python/adapter.go @@ -30,14 +30,6 @@ func bootstrapExec() string { return fmt.Sprintf(`exec(bytes.fromhex("%s").decode())`, hex.EncodeToString([]byte(runtimeSource))) } -// SessionKey: Python has no project notion; the interpreter path is the env. -func (a Adapter) SessionKey(exe string, _ []string) string { - if exe == "" { - return a.DefaultExe() - } - return exe -} - func (Adapter) BootstrapStmt() string { return "" } func (Adapter) WrapEval(hexCode string, _ bool) string { diff --git a/go/r/adapter.go b/go/r/adapter.go index a6b64c4..da2d6a9 100644 --- a/go/r/adapter.go +++ b/go/r/adapter.go @@ -19,13 +19,6 @@ func (Adapter) LaunchArgs(forwarded []string) []string { return append([]string{"--slave", "--no-save", "--no-restore"}, forwarded...) } -func (a Adapter) SessionKey(exe string, _ []string) string { - if exe == "" { - return a.DefaultExe() - } - return exe -} - func (Adapter) BootstrapStmt() string { source := strings.ReplaceAll(strings.ReplaceAll(runtimeSource, "\r\n", "\n"), "\r", "\n") return fmt.Sprintf(`eval(parse(text = %s), envir = .GlobalEnv)`, strconv.Quote(source)) diff --git a/go/wolfram/adapter.go b/go/wolfram/adapter.go index e320273..11f0b17 100644 --- a/go/wolfram/adapter.go +++ b/go/wolfram/adapter.go @@ -28,13 +28,6 @@ func (Adapter) LaunchArgs(forwarded []string) []string { return append(args, "-script", f.Name()) } -func (a Adapter) SessionKey(exe string, _ []string) string { - if exe == "" { - return a.DefaultExe() - } - return exe -} - func (Adapter) BootstrapStmt() string { return "" } func (Adapter) WrapEval(hexCode string, printResult bool) string { diff --git a/skills/repld/SKILL.md b/skills/repld/SKILL.md index 0c39f9a..aa307ef 100644 --- a/skills/repld/SKILL.md +++ b/skills/repld/SKILL.md @@ -31,7 +31,7 @@ repld python3 train.py 50 # 50 → sys.argv[1] - Import packages once per session; later calls should use already-loaded names. - Avoid repeating fixture/setup code in every command. - Repld flags (`--session`, `--fresh`, `--lang`) go before the interpreter. Interpreter flags go after it. -- Session routing: `--session LABEL` has highest priority. Otherwise sessions are keyed by language plus adapter-specific environment (for example project flag for Julia, interpreter path for Python/R/Wolfram) plus cwd. +- Session routing: `--session LABEL` has highest priority. Otherwise sessions are keyed by language, interpreter, and cwd. Interpreter flags configure only a new session. - Avoid using `--fresh` when interpreters can safely pick up changed state. See [julia.md](references/julia.md), [python.md](references/python.md), [r.md](references/r.md), [wolfram.md](references/wolfram.md) for language-specific notes. diff --git a/skills/repld/references/julia.md b/skills/repld/references/julia.md index 362e81e..90d81d5 100644 --- a/skills/repld/references/julia.md +++ b/skills/repld/references/julia.md @@ -1,14 +1,12 @@ # `repld julia` -## Session routing - -Without specifying `--session`, distinct projects correspond to different sessions. +## Examples ```bash -repld julia -e 'include("setup.jl")' -repld julia -e 'using Pkg; Pkg.activate("test"); using ImportTestPackageOnce' -repld julia --project=@temp -e 'using Pkg; Pkg.add("Example")' -repld julia +1.11 -E 'VERSION' # default project env is "@." +repld julia --project=test -e 'using ImportTestPackageOnce' +repld julia -E 'x = ImportTestPackageOnce.result()' +repld --fresh julia --project=@temp -e 'using Pkg; Pkg.add("Example")' +repld --session jl111 julia +1.11 -E 'VERSION' repld --fresh julia -t 4 -E 'Threads.nthreads()' ``` @@ -16,7 +14,7 @@ repld --fresh julia -t 4 -E 'Threads.nthreads()' When `Revise` is available, repld loads it and calls `Revise.revise()` before each eval. Tracking depends on load path: dev'd packages pick up method and `const`/global changes. `includet`'d files only patch method unless files/modules set `__revise_mode__ = :eval`. -Use `--fresh` for untrackable changes, such as: +Untrackable changes include: - Struct/type redefinition. - `using NewPkg` inside modules whose `Project.toml` did not list `NewPkg` when session was created.