Diff
1diff --git a/.gitignore b/.gitignore
2index 8fc497423bd20060b1ff9f0995e4cc0724be0e63..ad5a59025f74622229708ff3fc03dcadaa758e8f 100644
3--- a/.gitignore
4+++ b/.gitignore
5@@ -1,17 +1,13 @@
6 # Go build artifacts
7-/jp
8+/kaiwari
9 /bin/
10 *.test
11
12-# Local temporary output
13-/tmp/
14-jp_raw.log
15-
16 # Agent files
17 .pi/
18 /plan.md
19 /PLAN.md
20 /todo.md
21
22-# Model weights (fetched by download-qwen3.sh)
23+# Model weights
24 /models/
25diff --git a/AGENTS.md b/AGENTS.md
26index 20b6fffba42db0b9dc55437fd343b5c61454b0ae..2cda111e89a163f78359f9c1ea1450df4a6295ca 100644
27--- a/AGENTS.md
28+++ b/AGENTS.md
29@@ -1,4 +1,4 @@
30-# jp — Japanese learning RPG (TUI + HTTP server)
31+# kaiwari — Japanese learning RPG (TUI + HTTP server)
32
33 ## Overview
34
35@@ -13,10 +13,10 @@ consistent across visits. Only romaji is ever shown to the player; kana and kanj
36 Two entry points share one game core (`internal/game`), the LLM layer
37 (`internal/llm`), config, and bootstrap:
38
39-- **`cmd/jp`** — a Bubble Tea TUI. Runs the game in-process: it builds its own
40+- **`cmd/kaiwari`** — a Bubble Tea TUI. Runs the game in-process: it builds its own
41 state + orchestrator with local adapters (mic capture via `arecord`, local
42 audio playback) and talks straight to the model services.
43-- **`cmd/jp-server`** — an HTTP server exposing in-memory game sessions for a
44+- **`cmd/kaiwari-server`** — an HTTP server exposing in-memory game sessions for a
45 thin external client (e.g. iOS). The client records and plays audio; the
46 server does ASR transcription, the game loop, the judge, and TTS synthesis
47 (returns the NPC line as WAV bytes; replay is client-side). Turns serialize
48@@ -38,7 +38,7 @@ transcription, and the judge; scratch questions only touch the scratch model
49 and never enter world state. Per-location history is compacted into summaries
50 when it grows past budget (`internal/game/state.go`,
51 `internal/llm/history.go`). Raw model I/O for debugging is appended to
52-`jp_raw.log` in the working directory.
53+`kaiwari_raw.log` in the working directory.
54
55 ## How things are done here
56
57@@ -52,7 +52,7 @@ almost fully allocated to the chat model.
58
59 ### Product invariants
60
61-- Render romaji only; never expose kana or kanji to the player — not in the TUI and not across the jp-server wire.
62+- Render romaji only; never expose kana or kanji to the player — not in the TUI and not across the kaiwari-server wire.
63 - NPCs behave as normal people, not language teachers. Grading stays separate
64 from NPC dialogue; judge output never enters the NPC prompt.
65 - Persona data contains no game, player, NPC, quest, or scenario context.
66@@ -64,7 +64,7 @@ almost fully allocated to the chat model.
67 interfaces defined at their point of use in `internal/game/orchestrator.go`.
68 - Speech has two adapter pairs over the same interface: local (mic via
69 `arecord` + player) in `internal/adapters` for the TUI, and remote (uploaded
70- WAV in, returned WAV out) in `internal/server/speech.go` for jp-server.
71+ WAV in, returned WAV out) in `internal/server/speech.go` for kaiwari-server.
72 - LLM replies are parsed from strict `FIELD|value` contracts, never freeform
73 JSON.
74 - Errors wrap with `%w`. Startup failures print one stderr line naming the
75diff --git a/Makefile b/Makefile
76index 6c24344555e10a32e18fd097d9101ae2827e1965..bc240eca5af3158c1de0e80a350551e9a5893ff4 100644
77--- a/Makefile
78+++ b/Makefile
79@@ -8,10 +8,10 @@ fmt:
80 golangci-lint fmt ./...
81
82 build:
83- go build -o jp ./cmd/jp
84+ go build -o kaiwari ./cmd/kaiwari
85
86 run:
87- go run ./cmd/jp
88+ go run ./cmd/kaiwari
89
90 llama:
91 llama-server \
92diff --git a/README.md b/README.md
93index 6f0a5ff147c215047a5a4f9db47263bf3c3d10c7..cd5b691b319da6461cf9fcd3b87f36bb84389bc3 100644
94--- a/README.md
95+++ b/README.md
96@@ -1,4 +1,4 @@
97-# jp — Japanese learning RPG (TUI)
98+# kaiwari — Japanese learning RPG (TUI)
99
100 A terminal game for practicing spoken Japanese. You type English actions to
101 explore an LLM-narrated world and push-to-talk lines of Japanese; your words
102@@ -31,7 +31,7 @@ downloads a model for any of them. You run those services yourself.
103
104 ```bash
105 go build ./... # or: make build
106-jp # or: make run (go run ./cmd/jp)
107+kaiwari # or: make run (go run ./cmd/kaiwari)
108 ```
109
110 On startup the game waits up to 30s for the LLM model to finish loading
111@@ -55,7 +55,7 @@ which win over defaults. Defaults match a local setup.
112 | Audio base URL | `--audio.url` | `JP_AUDIO_BASE_URL` | `http://127.0.0.1:8080` | audio.cpp server (TTS + ASR) |
113 | Scenario brief | `--scenario` | `JP_SCENARIO_PATH` | `assets/scenarios/small_city.md` | plain-text scenario brief file |
114
115-Run `jp -help` for the full flag list.
116+Run `kaiwari -help` for the full flag list.
117
118 ## Controls
119
120diff --git a/cmd/jp-server/main.go b/cmd/kaiwari-server/main.go
121rename from cmd/jp-server/main.go
122rename to cmd/kaiwari-server/main.go
123index e488c5fa7bb4477d2a70441e6011091863f410ee..b1bdaea2e4be69982e8472b7934920f75af1890b 100644
124--- a/cmd/jp-server/main.go
125+++ b/cmd/kaiwari-server/main.go
126@@ -6,9 +6,9 @@ import (
127 "net/http"
128 "os"
129
130- "japanese/internal/bootstrap"
131- "japanese/internal/config"
132- "japanese/internal/server"
133+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/bootstrap"
134+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/config"
135+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/server"
136 )
137
138 func main() {
139@@ -24,13 +24,13 @@ func main() {
140 }
141
142 srv := server.New(svc, cfg.Token)
143- fmt.Printf("jp-server: listening on %s\n", cfg.Listen)
144+ fmt.Printf("kaiwari-server: listening on %s\n", cfg.Listen)
145 if err := http.ListenAndServe(cfg.Listen, srv.Handler()); err != nil {
146 fatalf("%v", err)
147 }
148 }
149
150 func fatalf(format string, args ...any) {
151- fmt.Fprintf(os.Stderr, "jp-server: "+format+"\n", args...)
152+ fmt.Fprintf(os.Stderr, "kaiwari-server: "+format+"\n", args...)
153 os.Exit(1)
154 }
155diff --git a/cmd/jp/main.go b/cmd/kaiwari/main.go
156rename from cmd/jp/main.go
157rename to cmd/kaiwari/main.go
158index 02057132812065acb68a653de824856b4c73934a..68f1d699a43147774ce2733578f41f5eb06d27cd 100644
159--- a/cmd/jp/main.go
160+++ b/cmd/kaiwari/main.go
161@@ -7,13 +7,13 @@ import (
162
163 "github.com/charmbracelet/bubbletea"
164
165- "japanese/internal/adapters"
166- "japanese/internal/bootstrap"
167- "japanese/internal/config"
168- "japanese/internal/game"
169- "japanese/internal/stt"
170- "japanese/internal/tts"
171- "japanese/internal/ui"
172+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/adapters"
173+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/bootstrap"
174+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/config"
175+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/game"
176+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/stt"
177+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/tts"
178+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/ui"
179 )
180
181 // recordCap bounds a single push-to-talk capture. It is passed to the recorder
182@@ -61,6 +61,6 @@ func buildOrchestrator(svc *bootstrap.Services, state *game.State) *game.Orchest
183 }
184
185 func fatalf(format string, args ...any) {
186- fmt.Fprintf(os.Stderr, "jp: "+format+"\n", args...)
187+ fmt.Fprintf(os.Stderr, "kaiwari: "+format+"\n", args...)
188 os.Exit(1)
189 }
190diff --git a/go.mod b/go.mod
191index 9eb7802f39776007ca032c68ceed94919cecde05..26c6f3ac629abc0fc01fd15fee04c413bebfbd21 100644
192--- a/go.mod
193+++ b/go.mod
194@@ -1,4 +1,4 @@
195-module japanese
196+module git.theedgeofrage.com/TheEdgeOfRage/kaiwari
197
198 go 1.27.0
199
200diff --git a/internal/adapters/flashcards.go b/internal/adapters/flashcards.go
201index 5a44d55ec92f01290fa54c50e827f8a121408b8c..9b473c1422369c0473e2a14ab7ad7af3d36b79f3 100644
202--- a/internal/adapters/flashcards.go
203+++ b/internal/adapters/flashcards.go
204@@ -7,7 +7,7 @@ import (
205 "os"
206 "sync"
207
208- "japanese/internal/llm"
209+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/llm"
210 )
211
212 // flashcardsPath is where generated vocabulary cards are persisted, next to the
213diff --git a/internal/adapters/llm.go b/internal/adapters/llm.go
214index 2d8132af494f6d807ac36c0f338d5a7eb004adf5..131e6327202aacfcf01dd2b4859e80f792866815 100644
215--- a/internal/adapters/llm.go
216+++ b/internal/adapters/llm.go
217@@ -12,12 +12,12 @@ import (
218 "strings"
219 "time"
220
221- "japanese/internal/llm"
222+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/llm"
223 )
224
225 // rawLogPath is where raw model outputs are dumped for debugging, since the TUI
226 // owns the terminal and direct stderr writes get mangled by redraws.
227-const rawLogPath = "jp_raw.log"
228+const rawLogPath = "kaiwari_raw.log"
229
230 func dumpRaw(label, transcript, raw string) {
231 f, err := os.OpenFile(rawLogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
232diff --git a/internal/adapters/speech.go b/internal/adapters/speech.go
233index 03508667b35a6778cfe5c0af3f5665db12f4f4c4..10031c1260706364e3c3c88c02bf35e9e2049c7b 100644
234--- a/internal/adapters/speech.go
235+++ b/internal/adapters/speech.go
236@@ -7,8 +7,8 @@ import (
237 "os"
238 "sync"
239
240- "japanese/internal/stt"
241- "japanese/internal/tts"
242+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/stt"
243+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/tts"
244 )
245
246 // SpeechInput adapts the stt recorder and ASR client to game.SpeechInput.
247diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go
248index 608e3dc9bb7662b581fd4e32bc0aa1f0e2e42aae..14edc838b5c780bf7136e2dbdc2567670abb6fa6 100644
249--- a/internal/bootstrap/bootstrap.go
250+++ b/internal/bootstrap/bootstrap.go
251@@ -1,4 +1,4 @@
252-// Package bootstrap builds the service clients shared by the jp entry points:
253+// Package bootstrap builds the service clients shared by the kaiwari entry points:
254 // scenario load, one LLM client per prompt slot, the audio availability check,
255 // the LLM readiness wait, and the prompt warmup. Warnings (unavailable audio,
256 // not-ready LLM, failed warmup) print to stderr; only a bad scenario file is
257@@ -12,12 +12,12 @@ import (
258 "os"
259 "time"
260
261- "japanese/internal/availability"
262- "japanese/internal/config"
263- "japanese/internal/llm"
264- "japanese/internal/scenario"
265- "japanese/internal/stt"
266- "japanese/internal/tts"
267+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/availability"
268+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/config"
269+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/llm"
270+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/scenario"
271+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/stt"
272+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/tts"
273 )
274
275 const (
276@@ -36,7 +36,7 @@ const (
277 scratchSlot = 4
278 )
279
280-// Services is the set of shared clients one jp entry point builds at startup.
281+// Services is the set of shared clients one kaiwari entry point builds at startup.
282 type Services struct {
283 Cfg *config.Config
284 Brief string
285@@ -64,12 +64,12 @@ func New(cfg *config.Config) (*Services, error) {
286 scratchClient := newLLMClient(cfg, hc, scratchSlot)
287
288 if err := availability.CheckAudio(hc, cfg.AudioConfig.AudioBaseURL, []string{tts.ModelName, stt.ASRModelName}); err != nil {
289- fmt.Fprintf(os.Stderr, "jp: audio service unavailable: %v\n", err)
290+ fmt.Fprintf(os.Stderr, "kaiwari: audio service unavailable: %v\n", err)
291 }
292
293 readyCtx, cancelReady := context.WithTimeout(context.Background(), llmReadyTimeout)
294 if err := gameClient.WaitReady(readyCtx); err != nil {
295- fmt.Fprintf(os.Stderr, "jp: LLM service not ready: %v\n", err)
296+ fmt.Fprintf(os.Stderr, "kaiwari: LLM service not ready: %v\n", err)
297 }
298 cancelReady()
299
300@@ -91,7 +91,7 @@ func New(cfg *config.Config) (*Services, error) {
301 }
302 }
303 if warmErr != nil {
304- fmt.Fprintf(os.Stderr, "jp: LLM warmup failed: %v\n", warmErr)
305+ fmt.Fprintf(os.Stderr, "kaiwari: LLM warmup failed: %v\n", warmErr)
306 }
307
308 return &Services{
309diff --git a/internal/config/config.go b/internal/config/config.go
310index d09e76bc5d4e3ffb4fcf3230097a725556ab25a2..b8f367f99ba4dfaea5798a129000394cd02404d4 100644
311--- a/internal/config/config.go
312+++ b/internal/config/config.go
313@@ -1,5 +1,5 @@
314 // Package config holds the service endpoint and recorder configuration shared
315-// by all jp subcommands. Values come from command-line flags, environment
316+// by all kaiwari subcommands. Values come from command-line flags, environment
317 // variables, or built-in defaults; flags win over env vars, which win over
318 // defaults.
319 package config
320@@ -30,7 +30,7 @@ type Config struct {
321 Scenario string `long:"scenario" env:"JP_SCENARIO_PATH" default:"assets/scenarios/small_city.md" description:"path to the scenario brief file"`
322 }
323
324-// ServerConfig is the jp-server option group: the shared Config plus the HTTP
325+// ServerConfig is the kaiwari-server option group: the shared Config plus the HTTP
326 // listen address and bearer token.
327 type ServerConfig struct {
328 Config
329@@ -41,14 +41,14 @@ type ServerConfig struct {
330
331 func ParseConfig() *Config {
332 var cfg Config
333- parse("jp", &cfg)
334+ parse("kaiwari", &cfg)
335 return &cfg
336 }
337
338-// ParseServerConfig parses the jp-server command line into a ServerConfig.
339+// ParseServerConfig parses the kaiwari-server command line into a ServerConfig.
340 func ParseServerConfig() *ServerConfig {
341 var cfg ServerConfig
342- parse("jp-server", &cfg)
343+ parse("kaiwari-server", &cfg)
344 return &cfg
345 }
346
347diff --git a/internal/game/orchestrator.go b/internal/game/orchestrator.go
348index ed2ae60b2f1805a3844ed46a4debd6f17da3ae54..5cedf5964db1c78d6d8d722628e5fbfeb33decbd 100644
349--- a/internal/game/orchestrator.go
350+++ b/internal/game/orchestrator.go
351@@ -7,7 +7,7 @@ import (
352 "sync"
353 "unicode/utf8"
354
355- "japanese/internal/llm"
356+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/llm"
357 )
358
359 // SpeechInput captures push-to-talk audio and returns the raw transcript. Begin
360diff --git a/internal/game/state.go b/internal/game/state.go
361index b843cc44027e88994ea4b408d44b0fa532dda320..716cf54093a19ff6da867e35e80217c8be2e0efb 100644
362--- a/internal/game/state.go
363+++ b/internal/game/state.go
364@@ -5,7 +5,7 @@ import (
365 "strings"
366 "unicode/utf8"
367
368- "japanese/internal/llm"
369+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/llm"
370 )
371
372 // Message is a role/content pair owned by the game layer so session and display
373diff --git a/internal/llm/client.go b/internal/llm/client.go
374index 9c1ff27e52eb35f0997190e5852eeb02c76cae18..ba313bb46727018727ac51a87534bd29ca8ef945 100644
375--- a/internal/llm/client.go
376+++ b/internal/llm/client.go
377@@ -11,7 +11,7 @@ import (
378 "encoding/json"
379 "fmt"
380 "io"
381- "japanese/internal/config"
382+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/config"
383 "net/http"
384 "strings"
385 "time"
386diff --git a/internal/server/handlers.go b/internal/server/handlers.go
387index c92f3f8ba783ee4e1b21b00b083267391e2f2bfe..16002a2e56d090505997043a6a8b70cf9c5a3195 100644
388--- a/internal/server/handlers.go
389+++ b/internal/server/handlers.go
390@@ -11,7 +11,7 @@ import (
391 "net/http"
392 "strings"
393
394- "japanese/internal/game"
395+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/game"
396 )
397
398 type judgeWire struct {
399diff --git a/internal/server/server.go b/internal/server/server.go
400index 67bc2feb49b077c481dfbaadbddb5bcfba4cb174..ed31fd963ddab1dcfda10bfcbc2fb5a3b408c2a2 100644
401--- a/internal/server/server.go
402+++ b/internal/server/server.go
403@@ -1,4 +1,4 @@
404-// Package server exposes jp game sessions over HTTP for a thin client.
405+// Package server exposes kaiwari game sessions over HTTP for a thin client.
406 // Sessions are in-memory and die with the process; turns serialize per
407 // session. Kana never crosses the wire.
408 package server
409@@ -13,11 +13,11 @@ import (
410 "sync"
411 "time"
412
413- "japanese/internal/adapters"
414- "japanese/internal/bootstrap"
415- "japanese/internal/game"
416- "japanese/internal/stt"
417- "japanese/internal/tts"
418+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/adapters"
419+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/bootstrap"
420+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/game"
421+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/stt"
422+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/tts"
423 )
424
425 const (
426@@ -25,7 +25,7 @@ const (
427 maxUpload = int64(2 << 20)
428 )
429
430-// Server serves jp game sessions over HTTP.
431+// Server serves kaiwari game sessions over HTTP.
432 type Server struct {
433 svc *bootstrap.Services
434 token []byte
435diff --git a/internal/server/speech.go b/internal/server/speech.go
436index da6d2b66889dd43d342e7618d64425f069d84c98..dd169ab3ce89d5b235d260d9346478874e98dc64 100644
437--- a/internal/server/speech.go
438+++ b/internal/server/speech.go
439@@ -5,8 +5,8 @@ import (
440 "errors"
441 "sync"
442
443- "japanese/internal/stt"
444- "japanese/internal/tts"
445+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/stt"
446+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/tts"
447 )
448
449 // remoteInput adapts the uploaded WAV of one spoken turn to game.SpeechInput.
450diff --git a/internal/stt/recorder.go b/internal/stt/recorder.go
451index 40eab3aa4a1e5772c6cbefa4c5de1b85691e4452..b02e8545ad94cad23a0503de91f764947dc5b1bf 100644
452--- a/internal/stt/recorder.go
453+++ b/internal/stt/recorder.go
454@@ -56,7 +56,7 @@ func (r *Recorder) Start(ctx context.Context) (*Recording, error) {
455 return nil, errors.New("stt: empty recorder command")
456 }
457
458- f, err := os.CreateTemp("", "jp-record-*.wav")
459+ f, err := os.CreateTemp("", "kaiwari-record-*.wav")
460 if err != nil {
461 return nil, fmt.Errorf("stt: create temp wav: %w", err)
462 }
463diff --git a/internal/ui/app.go b/internal/ui/app.go
464index c4e54340ecd56b8283db27df8728de59f1e8758e..47a85e879235243f5cb6f31584ee3389326be829 100644
465--- a/internal/ui/app.go
466+++ b/internal/ui/app.go
467@@ -12,7 +12,7 @@ import (
468 "github.com/charmbracelet/bubbletea"
469 "github.com/charmbracelet/lipgloss"
470
471- "japanese/internal/game"
472+ "git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/game"
473 )
474
475 // tickInterval refreshes the recording elapsed indicator while a capture is open.