little-coder 1.8.4 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
2
2
 
3
3
  All notable changes to little-coder are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and little-coder's public interface (CLI, providers, tools, skills) follows semver starting at `v0.0.1` post-rename.
4
4
 
5
+ ## [v1.9.1] — 2026-06-08
6
+
7
+ ### Fixed
8
+ - **Plan Mode shortcut moved to `alt+p` so `shift+tab` stays pi's thinking-level cycle** ([#47](https://github.com/itayinbarr/little-coder/issues/47)). v1.9.0 claimed `shift+tab` for Plan Mode by rebinding pi's built-in `app.thinking.cycle` to `alt+t` in `~/.pi/agent/keybindings.json`. That collided with the muscle memory of every existing pi user — `shift+tab` is the documented thinking cycle — and pi (≥ 0.79) also surfaced an `[Extension issues]` warning whenever the rebind hadn't taken yet. Plan Mode now registers on **`alt+p`** instead (unbound by pi, so the extension claims it cleanly with no shadowing), and `shift+tab` returns to pi's default behavior. The launcher also performs a **one-time cleanup**: on first run after upgrade, if `~/.pi/agent/keybindings.json` still has the v1.9.0 rewrite (`app.thinking.cycle: "alt+t"` exactly), it is removed; any binding you set yourself is preserved untouched. README and the Plan-Mode indicator (`(alt+p to exit)`) updated to match.
9
+
10
+ ### Notes for upgraders
11
+ - No CLI-flag or public-API changes. **Plan Mode is now `alt+p`** (was `shift+tab` in v1.9.0). `shift+tab` is again pi's thinking-level cycle. If you customized `app.thinking.cycle` yourself in `~/.pi/agent/keybindings.json`, your binding is left alone.
12
+
13
+ ---
14
+
15
+ ## [v1.9.0] — 2026-06-15
16
+
17
+ ### Added
18
+ - **Plan Mode (shift+tab).** A Claude-Code-style "research → ask → plan" flow, built as the new `plan-mode` extension. Press **shift+tab** to toggle it (an honey `◆ PLAN MODE` indicator appears below the input). When it's on, submitting a request does *not* run a normal coding turn — instead little-coder: (1) decomposes the request into 1-4 exploration tasks, (2) dispatches read-only explorer sub-coders to gather information (their transcripts never enter the main context — only their concise reports survive), (3) generates 1-3 clarifying questions, each with suggested answers plus a free-text "Other" option, asked via the UI, and (4) synthesizes the findings + your answers into a written plan in the chat. Each reasoning phase ("deciding what to explore…", "preparing clarifying questions…") shows an animated spinner with a running m:ss timer. The planning instructions + research are injected into the synthesis turn's system prompt, so the chat shows only your original request and the plan — never the internal scaffolding. A single continuous m:ss timer runs for the whole process (not just the per-sub-coder timers). When the plan is presented, an **Approve & implement / Keep planning** prompt (arrow keys + enter) gates implementation — only on approval does little-coder start making the changes. **Esc** (or Ctrl+C) cancels a plan in progress.
19
+ - **Up-arrow prompt history** (`prompt-history` extension), **persisted across sessions**. pi's default editor has no prompt recall; from an empty prompt, **↑** now walks back through your recent prompts (most-recent first) and **↓** walks forward. History is saved to `<agentDir>/little-coder-prompt-history.json`, so even a brand-new session can recall prompts from earlier runs. Implemented as a `CustomEditor` subclass (pi copies its keybindings/autocomplete/submit wiring onto it) using `keybindings.matches` for ↑/↓ detection — robust to pi's Kitty keyboard protocol and key-release events — and scoped to recall-from-empty so it never interferes with multi-line cursor movement or the autocomplete dropdown. Edits/writes are blocked during the synthesis turn so plan mode produces a plan, not changes. shift+tab previously cycled the thinking level; pi (≥ 0.79) reserves built-in shortcuts and won't let an extension claim a colliding one, so the launcher rebinds the thinking-level cycle to **alt+t** in `~/.pi/agent/keybindings.json` (non-destructively — only when you haven't set your own binding for it), freeing shift+tab for Plan Mode.
20
+ - **Sub-coders (`dispatch` tool).** little-coder can now spawn isolated child little-coder sessions to research a focused question — single (`{ task }`) or parallel (`{ tasks: [{ label, task }] }`, up to 4, concurrency 2 by default, override with `LITTLE_CODER_SUBCODER_CONCURRENCY`). Children run with the **same local-model provider and extensions** as the parent (spawned through the launcher headless, not bare `pi`) but are constrained to **read + browse-online** tools (read, grep, glob, webfetch, websearch, browser, read-only bash) — no edit/write and no recursive dispatch, enforced via the existing `tool-gating` + `permission-gate` env gates. Each child returns a **concise report**; its full transcript lives in the tool's UI-only `details` and never enters the parent model's context, keeping the main window clean. New `subagent` extension (`spawn.ts` engine, importable by plan mode).
21
+ - **Live sub-coder tracker.** A small animated panel above the input shows each running/finished sub-coder with a spinner, status (✓/✗), elapsed time, and current activity (the latest tool call or report snippet), with a diff-guarded ~120 ms repaint. Hidden on non-interactive (benchmark/RPC) runs.
22
+ - **Session naming + terminal title sync.** The session is auto-named from your first prompt (overridable any time with pi's `/name`), and the terminal tab title now shows the session name (`little-coder · <name>`), updating when you switch sessions with `/resume`. pi's built-in `/resume` already lists past sessions for the current directory.
23
+ - **Read-before-edit guard.** New `read-guard-edit` extension: a file must be **Read** in the current session before it can be **Edited** — an edit to an unread file is blocked with "File must be read first before edit" and a nudge to Read it (so `old_string` matches exactly). Files you just wrote count as read. Mirrors the `write-guard` enforcement pattern.
24
+
25
+ ### Changed
26
+ - **`glob` match cap lowered 500 → 100** (`extra-tools/glob.ts`) to keep results focused for small models. (`grep` was already capped at 100.)
27
+ - **Default thinking level is now `medium`** for interactive sessions (pi's default is `minimal`) — the launcher passes `--thinking medium` unless you set a level yourself (`--thinking`, or a `--model …:<level>` shorthand) or run headless (`--mode`/`-p`).
28
+ - **Auto-named session titles are capped at 4 words**, cut on word boundaries (no more mid-word truncation) with a trailing `…` when the prompt was longer.
29
+
30
+ ### Dependencies
31
+ - **Bumped bundled pi `@earendil-works/pi-coding-agent` 0.75.3 → 0.79.4.** The "Operation aborted" marker patch (`scripts/patch-pi.mjs`) still applies cleanly to the new source (verified by `patch-pi.test.mjs`). pi 0.79 no longer hoists `@earendil-works/pi-tui` to the top level, so the `dispatch` tool's result renderers now build their lines as duck-typed components via the theme (the same pattern `branding` already uses) instead of importing pi-tui primitives — no behavior change.
32
+
33
+ ### Notes for upgraders
34
+ - No breaking CLI-flag or public-API changes. **shift+tab now toggles Plan Mode** instead of cycling the thinking level — use **alt+t** for the thinking-level cycle (the launcher writes this rebinding into `~/.pi/agent/keybindings.json`, preserving any binding you've already set). New env var `LITTLE_CODER_SUBCODER_CONCURRENCY` (default 2) tunes how many sub-coders run at once against your local backend.
35
+
36
+ ---
37
+
5
38
  ## [v1.8.4] — 2026-06-08
6
39
 
7
40
  ### Added
package/README.md CHANGED
@@ -60,6 +60,14 @@ little-coder --list-models # see everything pi knows about
60
60
 
61
61
  The agent uses the directory you launched it from as its working directory — `Read` / `Write` / `Edit` / `Bash` operate on your project, not on little-coder's install path.
62
62
 
63
+ ### Interactive features
64
+
65
+ - **Plan Mode** — press **alt+p** to toggle (a `◆ PLAN MODE` indicator shows below the input). Submit a request and little-coder researches it with sub-coders, asks you 1-3 clarifying questions (each with suggested answers and a free-text option), then writes a plan in the chat instead of editing anything. **Esc** cancels a plan mid-run. (**shift+tab** stays pi's thinking-level cycle.)
66
+ - **Prompt history** — from an empty input, **↑** recalls your recent prompts (most-recent first), **↓** walks forward. History persists across sessions, so a fresh session can recall prompts from earlier runs.
67
+ - **Sub-coders (`dispatch`)** — little-coder can spawn isolated child sessions to research a question (read the repo + browse online, read-only) and report back concisely, without cluttering the main conversation. A live panel above the input tracks them. Tune parallelism with `LITTLE_CODER_SUBCODER_CONCURRENCY` (default 2).
68
+ - **Sessions** — each session is auto-named from your first prompt (rename with `/name`) and shown in the terminal tab title. Use `/resume` to list and reopen past sessions for the current directory.
69
+ - **Read-before-edit** — editing a file requires reading it first, so edits match the file's exact current text.
70
+
63
71
  For local providers (llama.cpp, Ollama, LM Studio) pi expects *some* value in the API-key env even though local servers ignore it:
64
72
 
65
73
  ```bash
@@ -99,12 +107,14 @@ build/bin/llama-server -m ~/models/Qwen3.6-35B-A3B-UD-Q4_K_M.gguf \
99
107
 
100
108
  If you only need text and want to skip the projector download, drop the second `hf download` line and the `--mmproj` flag — little-coder still works text-only, but the TUI's image attachment will be rejected by the server with a 4xx.
101
109
 
110
+ **Context window.** `-c` sets the server's context (`-c 16384` = 16K above — a conservative default for 8 GB VRAM). little-coder **auto-detects the live `n_ctx`** from llama.cpp's `/props` at startup and registers the model with it, so whatever you pass to `-c` is what the TUI shows and budgets against — no `models.json` edit needed. To run larger, relaunch the server with e.g. `-c 131072` (128K) or `-c 262144` (256K); the KV cache grows with it, so size it to your RAM/VRAM. (`--list-models` reflects the detected window.)
111
+
102
112
  **Option B — Ollama** (simpler, but slower on MoE):
103
113
 
104
114
  ```bash
105
115
  curl -fsSL https://ollama.com/install.sh | sh
106
116
  ollama pull qwen3.5 # 9.7B — the paper's model
107
- # or: ollama pull qwen3.6-35b-a3b
117
+ # or: ollama pull qwen3.6:35b-a3b
108
118
  ```
109
119
 
110
120
  **Option C — LM Studio** (GUI; OpenAI-compatible server on port 1234):
@@ -330,11 +340,15 @@ The benchmarks harness (`benchmarks/`) is dev-only and not shipped with the npm
330
340
  little-coder/
331
341
  ├── .pi/
332
342
  │ ├── settings.json # per-model profiles + benchmark_overrides (terminal_bench, gaia)
333
- │ └── extensions/ # 23 TypeScript extensions, auto-discovered by pi
334
- │ ├── branding/ # little-coder startup header + terminal title (replaces pi's built-in)
343
+ │ └── extensions/ # 27 TypeScript extensions, auto-discovered by pi
344
+ │ ├── branding/ # little-coder startup header + terminal title + session auto-naming
345
+ │ ├── plan-mode/ # alt+p "research → ask → plan" flow (sub-coders + clarifying questions → written plan)
346
+ │ ├── subagent/ # `dispatch` tool: isolated read/browse-only sub-coders + live tracker (spawn.ts engine)
347
+ │ ├── prompt-history/ # up-arrow recall of recent prompts (from an empty input)
335
348
  │ ├── llama-cpp-provider/ # data-driven provider registration from models.json — ships llamacpp, ollama, lmstudio (+ user override file)
336
349
  │ ├── write-guard/ # Write refuses on existing files; rewrites root-bare /foo.md paths to cwd
337
350
  │ ├── read-guard/ # trims a Read that would overflow the context window to its first 30 lines + a search-instead directive
351
+ │ ├── read-guard-edit/ # Edit refuses until the file has been Read this session
338
352
  │ ├── extra-tools/ # glob, webfetch, websearch (pi ships grep/find)
339
353
  │ ├── skill-inject/ # per-turn tool-skill selection (error > recency > intent)
340
354
  │ ├── knowledge-inject/ # algorithm cheat-sheet scoring (word=1.0, bigram=2.0, threshold=2.0)
@@ -9,6 +9,7 @@ import {
9
9
  mkdirSync,
10
10
  readdirSync,
11
11
  readFileSync,
12
+ rmSync,
12
13
  statSync,
13
14
  writeFileSync,
14
15
  } from "node:fs";
@@ -36,6 +37,13 @@ if (tooOld) {
36
37
  const here = dirname(fileURLToPath(import.meta.url));
37
38
  const pkgRoot = resolve(here, "..");
38
39
 
40
+ // Headless sub-coder fast-path. When the subagent extension re-invokes this
41
+ // launcher to spawn a child little-coder (--mode json -p), the update check and
42
+ // the global-settings merge below are pointless per-child overhead (network +
43
+ // disk) — and we want children to start fast. The env flag is set by
44
+ // .pi/extensions/subagent/spawn.ts::buildChildEnv.
45
+ const isSubagent = process.env.LITTLE_CODER_SUBAGENT === "1";
46
+
39
47
  // ---- 3. Resolve the bundled pi CLI entry point ----
40
48
  // We invoke pi's JS entry directly under the current Node binary instead of
41
49
  // the `node_modules/.bin/pi` shim. Two reasons:
@@ -110,10 +118,12 @@ try {
110
118
  } catch {
111
119
  // ignore — update-check just won't fire if we can't read the version
112
120
  }
113
- const exitAfterCheck = await checkForUpdate(currentVersion);
114
- if (exitAfterCheck) {
115
- // Successful update happened; user needs to re-run the new binary.
116
- process.exit(0);
121
+ if (!isSubagent) {
122
+ const exitAfterCheck = await checkForUpdate(currentVersion);
123
+ if (exitAfterCheck) {
124
+ // Successful update happened; user needs to re-run the new binary.
125
+ process.exit(0);
126
+ }
117
127
  }
118
128
 
119
129
  // ---- 6. Compose pi argv ----
@@ -124,10 +134,22 @@ if (exitAfterCheck) {
124
134
  // Strip our own flags before forwarding to pi so it doesn't reject them.
125
135
  const userArgs = process.argv.slice(2).filter((a) => a !== "--no-update-check");
126
136
  const agentsMd = join(pkgRoot, "AGENTS.md");
137
+
138
+ // Default the thinking level to "medium" for interactive sessions (pi's own
139
+ // default is "minimal"). Only when the user hasn't asked for a level themselves
140
+ // (--thinking, or the --model "provider/id:level" shorthand) and this isn't a
141
+ // headless/sub-coder run (--mode rpc/json) where the caller controls thinking.
142
+ const userPickedThinking =
143
+ userArgs.includes("--thinking") ||
144
+ userArgs.some((a, i) => a === "--model" && /:/.test(userArgs[i + 1] || ""));
145
+ const headless = isSubagent || userArgs.includes("--mode") || userArgs.includes("-p");
146
+ const thinkingArgs = !userPickedThinking && !headless ? ["--thinking", "medium"] : [];
147
+
127
148
  const piArgs = [
128
149
  "--no-context-files",
129
150
  "--no-extensions",
130
151
  ...(existsSync(agentsMd) ? ["--system-prompt", agentsMd] : []),
152
+ ...thinkingArgs,
131
153
  ...extArgs,
132
154
  ...userArgs,
133
155
  ];
@@ -167,7 +189,10 @@ if (process.env.PI_SKIP_VERSION_CHECK === undefined) {
167
189
  //
168
190
  // Existing keys are preserved. We only write when the desired value differs
169
191
  // from what's already on disk, so this is a no-op on warm launches.
170
- try {
192
+ //
193
+ // Skipped for headless sub-coders: they share the user's settings (already
194
+ // written by the interactive parent) and shouldn't each re-do the merge.
195
+ if (!isSubagent) try {
171
196
  const agentDirEnv = process.env.PI_CODING_AGENT_DIR;
172
197
  let agentDir;
173
198
  if (agentDirEnv && agentDirEnv.trim().length > 0) {
@@ -219,6 +244,32 @@ try {
219
244
  if (mutated) {
220
245
  writeFileSync(globalSettingsPath, JSON.stringify(globalSettings, null, 2));
221
246
  }
247
+
248
+ // ---- 8b. One-time cleanup of the v1.9.0 keybinding rewrite ----
249
+ // v1.9.0 wrote `app.thinking.cycle: "alt+t"` into ~/.pi/agent/keybindings.json
250
+ // so the plan-mode extension could claim shift+tab (issue #47). Plan mode now
251
+ // lives on alt+p, so shift+tab should go back to pi's default thinking-cycle
252
+ // binding — but only if the value is *exactly* the one we wrote. A user who
253
+ // chose their own binding (anything ≠ "alt+t") wins.
254
+ const keybindingsPath = join(agentDir, "keybindings.json");
255
+ if (existsSync(keybindingsPath)) {
256
+ try {
257
+ const parsed = JSON.parse(readFileSync(keybindingsPath, "utf-8"));
258
+ if (parsed && typeof parsed === "object" && parsed["app.thinking.cycle"] === "alt+t") {
259
+ delete parsed["app.thinking.cycle"];
260
+ if (Object.keys(parsed).length === 0) {
261
+ // Don't leave an empty {} sitting around — remove the file so pi
262
+ // reads its defaults cleanly.
263
+ rmSync(keybindingsPath);
264
+ } else {
265
+ writeFileSync(keybindingsPath, JSON.stringify(parsed, null, 2));
266
+ }
267
+ }
268
+ } catch {
269
+ // Corrupted JSON or unreadable — leave it alone; pi will surface its own error.
270
+ }
271
+ }
272
+
222
273
  } catch {
223
274
  // Best-effort. If we can't write the settings (read-only HOME, etc.) pi
224
275
  // falls back to its built-in defaults — the [Extensions] block will show
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "little-coder",
3
- "version": "1.8.4",
3
+ "version": "1.9.1",
4
4
  "description": "A pi-based coding agent optimized for small local language models. Reproduces the whitepaper's scaffold-model-fit adaptations as pi extensions.",
5
5
  "homepage": "https://github.com/itayinbarr/little-coder",
6
6
  "repository": {
@@ -38,7 +38,7 @@
38
38
  "postinstall": "node scripts/patch-pi.mjs"
39
39
  },
40
40
  "dependencies": {
41
- "@earendil-works/pi-coding-agent": "^0.75.3",
41
+ "@earendil-works/pi-coding-agent": "^0.79.4",
42
42
  "@sinclair/typebox": "^0.34.49",
43
43
  "playwright": "^1.59.1"
44
44
  },
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: dispatch-guidance
3
+ type: tool-guidance
4
+ target_tool: dispatch
5
+ priority: 6
6
+ token_cost: 140
7
+ user-invocable: false
8
+ ---
9
+ ## Dispatch Tool (sub-coders)
10
+ Delegate focused research to isolated child little-coder sessions ("sub-coders").
11
+ Each runs in its own context window, can READ the repo and BROWSE online
12
+ (read, grep, glob, webfetch, websearch, browser, read-only bash) but CANNOT
13
+ edit or write files. Each returns a concise report. Use this to gather
14
+ information without filling your own context with raw file dumps or web pages.
15
+
16
+ SINGLE: `task` (string) — one research question.
17
+ PARALLEL: `tasks` — array of `{label, task}`, up to 4, run concurrently.
18
+ OPTIONAL: `cwd` (defaults to the current working directory).
19
+
20
+ RULES:
21
+ - Give each parallel task a short, distinct `label` — it shows in the live tracker.
22
+ - Ask narrow, answerable questions ("how does auth work in this repo?", not "do everything").
23
+ - You receive only each sub-coder's short report; the full transcript stays in the
24
+ tool's details (not in your context). Act on the reports.
25
+ - Sub-coders can't change files — do the editing yourself after they report back.
26
+
27
+ EXAMPLE (single):
28
+ ```tool
29
+ {"name": "dispatch", "input": {"task": "Find where sessions are persisted in this repo and summarize the format."}}
30
+ ```
31
+
32
+ EXAMPLE (parallel):
33
+ ```tool
34
+ {"name": "dispatch", "input": {"tasks": [
35
+ {"label": "repo auth", "task": "How does authentication work in this codebase? Cite files."},
36
+ {"label": "lib docs", "task": "Look up the current recommended API for the jose JWT library online."}
37
+ ]}}
38
+ ```