miii-cli 0.2.1 → 0.2.3

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.
Files changed (103) hide show
  1. package/README.md +190 -83
  2. package/dist/config.js +0 -1
  3. package/dist/files/ops.js +22 -4
  4. package/dist/index.js +0 -1
  5. package/dist/init.js +0 -1
  6. package/dist/llm/ollama.js +0 -1
  7. package/dist/llm/stream.js +4 -3
  8. package/dist/parser/stream-parser.js +1 -13
  9. package/dist/sessions.js +0 -1
  10. package/dist/skills/loader.js +0 -1
  11. package/dist/tasks/compactor.js +68 -0
  12. package/dist/tasks/executor.js +88 -0
  13. package/dist/tasks/queue.js +72 -0
  14. package/dist/tools/index.js +108 -5
  15. package/dist/tui/App.js +0 -1
  16. package/dist/tui/InputBar.js +379 -32
  17. package/dist/tui/components/AtPicker.js +0 -1
  18. package/dist/tui/components/CommandPalette.js +4 -3
  19. package/dist/tui/components/InputArea.js +25 -13
  20. package/dist/tui/components/MessageList.js +12 -1
  21. package/dist/tui/components/ModelPicker.js +0 -1
  22. package/dist/tui/components/StatusBar.js +0 -1
  23. package/dist/tui/printer.js +0 -1
  24. package/dist/types.js +0 -1
  25. package/dist/workers/context.worker.js +0 -1
  26. package/dist/workers/spawn.js +0 -1
  27. package/package.json +6 -3
  28. package/.claude/settings.local.json +0 -28
  29. package/CONTRIBUTING.md +0 -55
  30. package/Makefile +0 -13
  31. package/dist/config.d.ts +0 -2
  32. package/dist/config.js.map +0 -1
  33. package/dist/files/ops.d.ts +0 -14
  34. package/dist/files/ops.js.map +0 -1
  35. package/dist/index.d.ts +0 -2
  36. package/dist/index.js.map +0 -1
  37. package/dist/init.d.ts +0 -1
  38. package/dist/init.js.map +0 -1
  39. package/dist/llm/ollama.d.ts +0 -10
  40. package/dist/llm/ollama.js.map +0 -1
  41. package/dist/llm/stream.d.ts +0 -12
  42. package/dist/llm/stream.js.map +0 -1
  43. package/dist/parser/stream-parser.d.ts +0 -21
  44. package/dist/parser/stream-parser.js.map +0 -1
  45. package/dist/sessions.d.ts +0 -9
  46. package/dist/sessions.js.map +0 -1
  47. package/dist/skills/loader.d.ts +0 -23
  48. package/dist/skills/loader.js.map +0 -1
  49. package/dist/tools/index.d.ts +0 -8
  50. package/dist/tools/index.js.map +0 -1
  51. package/dist/tui/App.d.ts +0 -9
  52. package/dist/tui/App.js.map +0 -1
  53. package/dist/tui/InputBar.d.ts +0 -10
  54. package/dist/tui/InputBar.js.map +0 -1
  55. package/dist/tui/components/AtPicker.d.ts +0 -8
  56. package/dist/tui/components/AtPicker.js.map +0 -1
  57. package/dist/tui/components/CommandPalette.d.ts +0 -8
  58. package/dist/tui/components/CommandPalette.js.map +0 -1
  59. package/dist/tui/components/InputArea.d.ts +0 -12
  60. package/dist/tui/components/InputArea.js.map +0 -1
  61. package/dist/tui/components/MessageList.d.ts +0 -11
  62. package/dist/tui/components/MessageList.js.map +0 -1
  63. package/dist/tui/components/ModelPicker.d.ts +0 -18
  64. package/dist/tui/components/ModelPicker.js.map +0 -1
  65. package/dist/tui/components/StatusBar.d.ts +0 -12
  66. package/dist/tui/components/StatusBar.js.map +0 -1
  67. package/dist/tui/printer.d.ts +0 -7
  68. package/dist/tui/printer.js.map +0 -1
  69. package/dist/types.d.ts +0 -20
  70. package/dist/types.js.map +0 -1
  71. package/dist/workers/context.worker.js.map +0 -1
  72. package/dist/workers/diff.worker.d.ts +0 -1
  73. package/dist/workers/diff.worker.js +0 -12
  74. package/dist/workers/diff.worker.js.map +0 -1
  75. package/dist/workers/spawn.d.ts +0 -1
  76. package/dist/workers/spawn.js.map +0 -1
  77. package/install.sh +0 -6
  78. package/mii-cli.gif +0 -0
  79. package/src/config.ts +0 -32
  80. package/src/files/ops.ts +0 -89
  81. package/src/index.ts +0 -11
  82. package/src/init.ts +0 -41
  83. package/src/llm/ollama.ts +0 -110
  84. package/src/llm/stream.ts +0 -55
  85. package/src/parser/stream-parser.ts +0 -196
  86. package/src/sessions.ts +0 -54
  87. package/src/skills/loader.ts +0 -144
  88. package/src/tools/index.ts +0 -151
  89. package/src/tui/App.tsx +0 -355
  90. package/src/tui/InputBar.tsx +0 -381
  91. package/src/tui/components/AtPicker.tsx +0 -49
  92. package/src/tui/components/CommandPalette.tsx +0 -50
  93. package/src/tui/components/InputArea.tsx +0 -297
  94. package/src/tui/components/MessageList.tsx +0 -219
  95. package/src/tui/components/ModelPicker.tsx +0 -134
  96. package/src/tui/components/StatusBar.tsx +0 -36
  97. package/src/tui/printer.ts +0 -130
  98. package/src/types.ts +0 -26
  99. package/src/workers/context.worker.ts +0 -66
  100. package/src/workers/diff.worker.ts +0 -20
  101. package/src/workers/spawn.ts +0 -19
  102. package/tsconfig.json +0 -18
  103. /package/dist/{workers/context.worker.d.ts → tasks/types.js} +0 -0
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # miii-cli
1
+ # miii
2
2
 
3
- > Local AI coding assistant for your terminal. No cloud. No API keys. No latency.
3
+ > A local AI coding assistant that actually works. No cloud. No Python. No API keys required.
4
4
 
5
5
  ```
6
- ╭─────────────────────────────────────────────────────────╮
7
- │ miii — Claude Code-level workflows, local models only
8
- ╰─────────────────────────────────────────────────────────╯
6
+ ╭──────────────────────────────────────────────────────────────╮
7
+ │ miii — Claude Code-level workflows, fully offline
8
+ ╰──────────────────────────────────────────────────────────────╯
9
9
  ```
10
10
 
11
11
  [![npm version](https://img.shields.io/npm/v/miii-cli)](https://www.npmjs.com/package/miii-cli)
@@ -16,21 +16,27 @@
16
16
 
17
17
  ---
18
18
 
19
- ## Why I built this
19
+ ## Why miii exists
20
20
 
21
- I couldn't find a local CLI AI tool that actually worked well. The ones that existed were either too clunky to set up, required cloud APIs, or had terminal output that was genuinely painful to read weird formatting, broken renders, text that ran together. I wanted something that felt as clean as Claude Code but ran entirely on local models. So I built miii.
21
+ Every local AI coding tool is either too clunky to set up, requires cloud APIs, or has terminal output that's genuinely painful to read. miii is what happens when you build a local coding assistant that takes UX seriously real Ink-based terminal UI, automatic git context, multi-file refactors with task queues, and a context compactor that keeps local models on-track.
22
22
 
23
- ---
23
+ Your code never leaves your machine.
24
24
 
25
- ## What is miii?
25
+ ---
26
26
 
27
- `miii` is a terminal-native AI coding assistant powered by local models via [Ollama](https://ollama.com) or any OpenAI-compatible API (LM Studio, vLLM, Groq, Together, etc.).
27
+ ## What makes it different
28
28
 
29
- - **Runs 100% locally** your code never leaves your machine
30
- - **File-aware** — type `@filename` to inject any file into context instantly
31
- - **Tool-enabled** reads, writes, edits, and runs shell commands autonomously
32
- - **Session memory** conversations persist across launches
33
- - **Extensible** add custom slash commands via Markdown or TypeScript skill files
29
+ | Feature | miii | aider | shell_gpt | open-interpreter |
30
+ |---|---|---|---|---|
31
+ | Ink terminal UI | | | | ❌ |
32
+ | Zero Python | | | ❌ | ❌ |
33
+ | Auto git context | | | | |
34
+ | Context compaction | ✅ | ✅ | ❌ | ❌ |
35
+ | Multi-file refactor queue | ✅ | partial | ❌ | ❌ |
36
+ | `.miiiignore` | ✅ | ✅ | ❌ | ❌ |
37
+ | Session persistence | ✅ | ❌ | ❌ | ❌ |
38
+ | Planning mode | ✅ | ❌ | ❌ | ❌ |
39
+ | Bundle size | 176K | ~50MB | ~40MB | ~100MB |
34
40
 
35
41
  ---
36
42
 
@@ -47,112 +53,206 @@ npm install -g miii-cli
47
53
  ## Quick start
48
54
 
49
55
  ```bash
50
- # Make sure Ollama is running
56
+ # Start Ollama
51
57
  ollama serve
52
58
 
53
- # Start miii
59
+ # Pull a model
60
+ ollama pull qwen2.5-coder:7b
61
+
62
+ # Launch miii
54
63
  miii
55
64
  ```
56
65
 
57
- On launch, miii opens a model picker. Select a model and start coding.
66
+ A model picker opens on launch. Select a model and start coding.
58
67
 
59
68
  ```bash
60
69
  miii # default session
61
- miii --model codellama # specific model
70
+ miii --model qwen2.5-coder # specific model
62
71
  miii --session myproject # named session
63
- miii -s work -m llama3.2 # short flags
72
+ miii -s work -m codellama # short flags
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Auto git context
78
+
79
+ miii automatically detects changed files via `git status` and injects their contents into the model's context — no `@file` needed for files you're actively working on.
80
+
81
+ ```
82
+ ❯ fix the type error in the auth middleware
83
+
84
+ [auto-loaded 3 changed file(s)]
85
+ ```
86
+
87
+ Only fires for code-related messages. Pure questions ("what is a closure?") skip the git scan entirely. Deduped — same files don't re-inject unless they change.
88
+
89
+ Disable per-project:
90
+ ```json
91
+ { "gitContext": false }
64
92
  ```
65
93
 
66
94
  ---
67
95
 
68
96
  ## File context with `@`
69
97
 
70
- Type `@` anywhere in your message to fuzzy-search and inject project files into the model's context:
98
+ Type `@` to fuzzy-search and inject any project file:
71
99
 
72
100
  ```
73
101
  ❯ review the auth logic in @src/auth/middleware.ts
74
- refactor @src/utils/parser.ts to handle edge cases
102
+ what does @src/utils/parser.ts return when input is empty?
75
103
  ```
76
104
 
77
105
  Automatically excluded: `node_modules`, `dist`, `.git`, lock files, binaries, images.
78
106
 
79
107
  ---
80
108
 
109
+ ## `.miiiignore`
110
+
111
+ Create `.miiiignore` in your project root to exclude files from `@` picker and auto-context:
112
+
113
+ ```
114
+ # .miiiignore
115
+ secrets/
116
+ *.generated.ts
117
+ fixtures/
118
+ *.sql
119
+ ```
120
+
121
+ Supports exact names, relative paths, and `*.ext` glob patterns.
122
+
123
+ ---
124
+
125
+ ## Git integration
126
+
127
+ Full git workflow from the terminal:
128
+
129
+ ```
130
+ /git status show working tree
131
+ /git diff unstaged changes
132
+ /git diff --staged staged changes
133
+ /git log recent commits
134
+ /git review AI reviews current changes for bugs + improvements
135
+ /git branch list branches
136
+ /git commit <msg> stage all and commit
137
+ ```
138
+
139
+ The model also has access to git tools directly — it can check status, read diffs, and commit as part of autonomous workflows.
140
+
141
+ ---
142
+
143
+ ## Multi-file refactor
144
+
145
+ Describe a goal, miii plans and executes across multiple files with isolated context per file:
146
+
147
+ ```
148
+ /refactor extract all database queries into a repository layer
149
+ /refactor rename UserService to AccountService everywhere
150
+ /refactor add input validation to all API route handlers
151
+ ```
152
+
153
+ Uses a priority task queue (P0=blocking, P1=reads parallel, P2=writes sequential, P3=verify). Each file gets its own fresh context — the local model never loses the thread on large codebases.
154
+
155
+ ---
156
+
157
+ ## Planning mode
158
+
159
+ Structured step-by-step planning before writing any code:
160
+
161
+ ```
162
+ /plan add OAuth2 to this Express app
163
+ /plan refactor the frontend to use React Query
164
+ ```
165
+
166
+ In planning mode:
167
+ ```
168
+ /plan:next suggest next concrete steps
169
+ /plan:breakdown break topic into subtasks
170
+ /plan:review critique the plan so far
171
+ /plan:done exit planning mode
172
+ ```
173
+
174
+ ---
175
+
81
176
  ## Built-in commands
82
177
 
83
- Type `/` to open the command palette.
178
+ Type `/` to open the command palette with fuzzy search.
84
179
 
85
180
  | Command | Description |
86
181
  |---|---|
87
- | `/models` | Switch or pull Ollama models |
182
+ | `/model <name>` | Switch model mid-session no restart needed |
183
+ | `/models` | Open model picker, pull new Ollama models |
88
184
  | `/session <name>` | Switch to or create a named session |
89
185
  | `/sessions` | List all sessions with message counts |
186
+ | `/new` | Start a fresh auto-named session |
90
187
  | `/clear` | Clear current session history |
91
- | `/mkdir <path>` | Create a folder (and any missing parents) |
92
- | `/touch <path>` | Create an empty file |
93
- | `/mv <from> <to>` | Move or rename a file or folder |
94
- | `/list` | Show loaded skills |
188
+ | `/plan [topic]` | Enter planning mode |
189
+ | `/refactor <goal>` | Multi-file AI refactor |
190
+ | `/git <subcommand>` | Git commands (see above) |
191
+ | `/list` | Show all loaded skills |
95
192
  | `/exit` | Exit miii |
96
193
 
97
194
  ---
98
195
 
99
196
  ## Built-in tools
100
197
 
101
- The model can call these tools automatically no setup needed.
198
+ The model calls these autonomouslyreads, writes, edits, runs, tests, and commits on its own.
102
199
 
103
200
  | Tool | Description |
104
201
  |---|---|
105
202
  | `read_file` | Read any file |
106
203
  | `list_files` | List directory contents |
107
- | `edit_file` | Create or overwrite a file (auto-creates parent dirs) |
108
- | `create_folder` | Create a directory and any missing parents |
204
+ | `create_file` | Create a new file (fails if exists) |
205
+ | `edit_file` | Create or fully rewrite a file |
206
+ | `patch_file` | Targeted string replacement — throws if match is ambiguous |
207
+ | `delete_file` | Delete a file |
109
208
  | `move_file` | Move or rename a file or directory |
110
- | `delete_file` | Delete a file (requires confirmation) |
111
- | `run_command` | Run a shell command in the current directory |
209
+ | `create_folder` | Create directory with parents |
210
+ | `run_command` | Run shell command in cwd (30s timeout) |
211
+ | `run_tests` | Run test suite, auto-detects jest/vitest/mocha from package.json |
212
+ | `git_status` | Working tree status |
213
+ | `git_diff` | Unstaged or staged diff (truncated at 8K) |
214
+ | `git_log` | Recent commit history |
215
+ | `git_commit` | Stage files and commit |
112
216
 
113
- Tool calls chain up to 6 hops deep the model reads, edits, runs, and verifies on its own.
217
+ Tool calls chain up to 6 hops — model reads, edits, runs tests, and verifies autonomously.
114
218
 
115
219
  ---
116
220
 
117
- ## Thinking indicator
221
+ ## Context compaction
118
222
 
119
- While miii processes your request, a rotating sparkle icon and sarcastic phrase appear so you always know something's happening:
223
+ Local models lose the thread after ~15-20 messages. miii auto-compacts context at threshold: keeps system prompt + original goal + tool result summary + last 6 exchanges. The session history is preserved on disk — only the LLM context window gets trimmed.
120
224
 
121
- ```
122
- miii
123
- ✦ staring into the abyss (it blinked)…
124
- ```
225
+ ---
125
226
 
126
- The phrase rotates every 5 seconds. When a tool call is in flight, the display updates to show exactly which tool is running:
227
+ ## Thinking indicator
127
228
 
128
229
  ```
129
- miii
130
- ⚙ running read_file…
230
+ ✦ staring into the abyss (it blinked)… [0:12]
131
231
 
132
- miii
133
- ⚙ running run_command
232
+ ⚙ running patch_file…
233
+ ⚙ running run_tests
134
234
  ```
135
235
 
136
- Each tool in a chain updates live as execution moves through them.
236
+ Phrase rotates every 5 seconds. Tool name updates live as each call fires. Elapsed time shown throughout.
137
237
 
138
238
  ---
139
239
 
140
240
  ## Sessions
141
241
 
142
- Every conversation is saved and resumed automatically.
242
+ Every conversation persists automatically.
143
243
 
144
244
  ```bash
145
245
  miii # resumes "default" session
146
246
  miii --session feature-auth # resumes or creates "feature-auth"
147
247
  ```
148
248
 
149
- Sessions stored at `~/.config/miii/sessions/`.
249
+ Sessions stored at `~/.config/miii/sessions/`. History capped at 100 messages in memory, full history on disk.
150
250
 
151
251
  ---
152
252
 
153
253
  ## Skills
154
254
 
155
- Skills are custom `/` commands. Create a Markdown file in `~/.config/miii/skills/`:
255
+ Custom `/` commands via Markdown in `~/.config/miii/skills/`:
156
256
 
157
257
  ```markdown
158
258
  ---
@@ -164,32 +264,30 @@ Review the code I'm about to share. Look for bugs, edge cases, and improvements.
164
264
  Be direct and specific. No markdown.
165
265
  ```
166
266
 
167
- Use it:
168
-
169
267
  ```
170
268
  /review
171
269
  ```
172
270
 
173
- Skills can also be TypeScript files with an `execute` function for programmatic behavior.
271
+ TypeScript skills with `execute` functions available for programmatic behavior.
174
272
 
175
273
  ---
176
274
 
177
275
  ## Configuration
178
276
 
179
- Config is loaded from (in order):
180
- 1. `.miii.json` in the current directory
277
+ Config loaded from (in order):
278
+ 1. `.miii.json` in current directory
181
279
  2. `~/.config/miii/config.json`
182
280
 
183
- **Ollama (default):**
281
+ **Ollama:**
184
282
  ```json
185
283
  {
186
- "model": "llama3.2",
284
+ "model": "qwen2.5-coder:7b",
187
285
  "provider": "ollama",
188
286
  "baseUrl": "http://localhost:11434"
189
287
  }
190
288
  ```
191
289
 
192
- **OpenAI-compatible API:**
290
+ **OpenAI-compatible (LM Studio, vLLM, Groq, Together, etc.):**
193
291
  ```json
194
292
  {
195
293
  "model": "gpt-4o",
@@ -199,7 +297,17 @@ Config is loaded from (in order):
199
297
  }
200
298
  ```
201
299
 
202
- Works with LM Studio, vLLM, Groq, Together, and any other OpenAI-compatible server.
300
+ **All options:**
301
+ ```json
302
+ {
303
+ "model": "qwen2.5-coder:7b",
304
+ "provider": "ollama",
305
+ "baseUrl": "http://localhost:11434",
306
+ "apiKey": "",
307
+ "gitContext": true,
308
+ "systemPrompt": "optional override"
309
+ }
310
+ ```
203
311
 
204
312
  ---
205
313
 
@@ -208,42 +316,25 @@ Works with LM Studio, vLLM, Groq, Together, and any other OpenAI-compatible serv
208
316
  | Key | Action |
209
317
  |---|---|
210
318
  | `enter` | Send message |
211
- | `ctrl+c` | Abort current request |
212
- | `ctrl+c` x2 | Exit miii |
213
- | `esc` | Close overlay or cancel |
214
319
  | `↑ / ↓` | Navigate command palette or file picker |
320
+ | `esc` | Close overlay / abort request |
321
+ | `ctrl+c` | Abort current request or exit |
215
322
 
216
323
  ---
217
324
 
218
325
  ## Security
219
326
 
220
- miii **0.1.5+** addresses the following OWASP issues:
221
-
222
327
  | Issue | Fix |
223
328
  |---|---|
224
- | Path traversal (A01) | All file tool operations restricted to cwd via `guardPath()` |
225
- | Path traversal (A01) | `@filename` references validated against `cwd` before reading |
226
- | Path traversal (A01) | `/mv`, `/mkdir`, `/touch` commands restricted to `cwd` |
227
- | Path traversal (A01) | Session names sanitized to alphanumeric + hyphens only |
228
- | Injection (A03) | `run_command` tool enforces a 30-second execution timeout |
229
- | Insecure deserialization (A08) | Config loading whitelists allowed keys; session data validated as array |
230
- | XML injection | File paths in context XML attributes are properly escaped |
231
- | Configurable API key | OpenAI-compatible provider token configurable via `apiKey` in config |
232
-
233
- `delete_file` requires explicit user confirmation (`y/n`) before executing.
329
+ | Path traversal (OWASP A01) | All file operations restricted to cwd via `guardPath()` |
330
+ | Path traversal (OWASP A01) | `@file` refs validated against cwd before reading |
331
+ | Path traversal (OWASP A01) | Session names sanitized to alphanumeric + hyphens |
332
+ | Injection (OWASP A03) | `run_command` enforces 30s execution timeout |
333
+ | Insecure deserialization (OWASP A08) | Config whitelists allowed keys; session data validated as array |
234
334
 
235
335
  ---
236
336
 
237
- ## What's new in 0.2.0
238
-
239
- - **No streaming** — responses delivered in full, no partial renders or flickering
240
- - **Thinking indicator** — rotating sarcastic phrases + sparkle icon while model processes
241
- - **Tool status** — live indicator when a tool call is in flight
242
- - **Status renamed** — internal status `streaming` → `thinking` throughout
243
-
244
- ---
245
-
246
- ## Source
337
+ ## Build from source
247
338
 
248
339
  ```bash
249
340
  git clone https://github.com/maruakshay/miii-cli
@@ -252,3 +343,19 @@ npm install
252
343
  npm run build
253
344
  npm link
254
345
  ```
346
+
347
+ ---
348
+
349
+ ## What's in 0.2.x
350
+
351
+ - **Auto git context** — changed files injected automatically, code-gated heuristic
352
+ - **`.miiiignore`** — per-project file exclusions
353
+ - **Multi-file refactor** — macro/micro task queue, isolated context per file
354
+ - **Planning mode** — structured `/plan` workflow
355
+ - **Git integration** — full git toolkit in commands and model tools
356
+ - **`run_tests` tool** — model runs and retries tests autonomously
357
+ - **`/model` live switch** — change models mid-session
358
+ - **Context compaction** — auto-trim at 18 messages, keep goal + summary + recent
359
+ - **Ambiguous patch detection** — `patch_file` throws on multiple matches instead of silently corrupting
360
+ - **176K bundle** — removed dead workers, sourcemaps, unused deps (was 468K)
361
+ - **Debounced session saves** — writes at most once per 2s, no I/O on every keypress
package/dist/config.js CHANGED
@@ -28,4 +28,3 @@ export function loadConfig() {
28
28
  }
29
29
  return { ...defaults };
30
30
  }
31
- //# sourceMappingURL=config.js.map
package/dist/files/ops.js CHANGED
@@ -27,6 +27,14 @@ const SKIP_NAMES = new Set([
27
27
  'poetry.lock', 'Gemfile.lock', 'composer.lock',
28
28
  '.DS_Store', 'Thumbs.db', '.env.local', 'LICENSE', 'LICENSE.md',
29
29
  ]);
30
+ function loadIgnorePatterns(cwd) {
31
+ const p = join(cwd, '.miiiignore');
32
+ if (!existsSync(p))
33
+ return new Set();
34
+ return new Set(readFileSync(p, 'utf-8').split('\n')
35
+ .map(l => l.trim())
36
+ .filter(l => l && !l.startsWith('#')));
37
+ }
30
38
  export function readFile(p) {
31
39
  if (!existsSync(p))
32
40
  return '';
@@ -46,9 +54,10 @@ export function moveFile(from, to) {
46
54
  mkdirSync(dirname(to), { recursive: true });
47
55
  renameSync(from, to);
48
56
  }
49
- export function listFiles(dir, recursive = false, cwd = process.cwd()) {
57
+ export function listFiles(dir, recursive = false, cwd = process.cwd(), _ignore) {
50
58
  if (!existsSync(dir))
51
59
  return [];
60
+ const ignore = _ignore ?? loadIgnorePatterns(cwd);
52
61
  const entries = [];
53
62
  for (const name of readdirSync(dir)) {
54
63
  if (name.startsWith('.'))
@@ -70,12 +79,21 @@ export function listFiles(dir, recursive = false, cwd = process.cwd()) {
70
79
  continue;
71
80
  }
72
81
  const full = join(dir, name);
82
+ const rel = relative(cwd, full);
73
83
  const type = stat.isDirectory() ? 'dir' : 'file';
74
- entries.push({ name, path: full, rel: relative(cwd, full), type, size: stat.isFile() ? stat.size : undefined });
84
+ // check ignore patterns: match by name, rel path, or name/ for dirs
85
+ if (ignore.size) {
86
+ const dirSuffix = type === 'dir' ? name + '/' : '';
87
+ if (ignore.has(name) || ignore.has(rel) || (dirSuffix && ignore.has(dirSuffix)))
88
+ continue;
89
+ // *.ext pattern
90
+ if ([...ignore].some(p => p.startsWith('*.') && name.endsWith(p.slice(1))))
91
+ continue;
92
+ }
93
+ entries.push({ name, path: full, rel, type, size: stat.isFile() ? stat.size : undefined });
75
94
  if (recursive && type === 'dir') {
76
- entries.push(...listFiles(full, true, cwd));
95
+ entries.push(...listFiles(full, true, cwd, ignore));
77
96
  }
78
97
  }
79
98
  return entries;
80
99
  }
81
- //# sourceMappingURL=ops.js.map
package/dist/index.js CHANGED
@@ -9,4 +9,3 @@ main().catch(err => {
9
9
  process.stderr.write(`fatal: ${err.message}\n`);
10
10
  process.exit(1);
11
11
  });
12
- //# sourceMappingURL=index.js.map
package/dist/init.js CHANGED
@@ -31,4 +31,3 @@ export async function lazyInit() {
31
31
  const { waitUntilExit } = render(React.createElement(InputBar, { config, skills, cwd: process.cwd(), session: sessionName }), { exitOnCtrlC: false });
32
32
  await waitUntilExit();
33
33
  }
34
- //# sourceMappingURL=init.js.map
@@ -97,4 +97,3 @@ export function fmtSize(bytes) {
97
97
  return `${(bytes / 1e6).toFixed(0)}MB`;
98
98
  return `${(bytes / 1e3).toFixed(0)}KB`;
99
99
  }
100
- //# sourceMappingURL=ollama.js.map
@@ -4,7 +4,7 @@ export async function chat(cfg) {
4
4
  return chatOllama(cfg);
5
5
  }
6
6
  async function chatOllama(cfg) {
7
- const { model, messages, baseUrl, signal, onDone, onError } = cfg;
7
+ const { model, messages, baseUrl, signal, onDone, onError, onUsage } = cfg;
8
8
  try {
9
9
  const res = await fetch(`${baseUrl}/api/chat`, {
10
10
  method: 'POST',
@@ -17,6 +17,7 @@ async function chatOllama(cfg) {
17
17
  return;
18
18
  }
19
19
  const obj = await res.json();
20
+ onUsage?.(obj?.prompt_eval_count ?? 0, obj?.eval_count ?? 0);
20
21
  await onDone(obj?.message?.content ?? '');
21
22
  }
22
23
  catch (err) {
@@ -25,7 +26,7 @@ async function chatOllama(cfg) {
25
26
  }
26
27
  }
27
28
  async function chatOpenAI(cfg) {
28
- const { model, messages, baseUrl, apiKey, signal, onDone, onError } = cfg;
29
+ const { model, messages, baseUrl, apiKey, signal, onDone, onError, onUsage } = cfg;
29
30
  try {
30
31
  const res = await fetch(`${baseUrl}/v1/chat/completions`, {
31
32
  method: 'POST',
@@ -38,6 +39,7 @@ async function chatOpenAI(cfg) {
38
39
  return;
39
40
  }
40
41
  const obj = await res.json();
42
+ onUsage?.(obj?.usage?.prompt_tokens ?? 0, obj?.usage?.completion_tokens ?? 0);
41
43
  await onDone(obj?.choices?.[0]?.message?.content ?? '');
42
44
  }
43
45
  catch (err) {
@@ -48,4 +50,3 @@ async function chatOpenAI(cfg) {
48
50
  function toError(e) {
49
51
  return e instanceof Error ? e : new Error(String(e));
50
52
  }
51
- //# sourceMappingURL=stream.js.map
@@ -1,4 +1,3 @@
1
- import { appendFileSync } from 'fs';
2
1
  const OPEN = '<tool_call>';
3
2
  const CLOSE = '</tool_call>';
4
3
  const CTAG_OPEN = '<content>';
@@ -7,13 +6,6 @@ const OLD_OPEN = '<old>';
7
6
  const OLD_CLOSE = '</old>';
8
7
  const NEW_OPEN = '<new>';
9
8
  const NEW_CLOSE = '</new>';
10
- const DEBUG_LOG = '/tmp/miii-debug.log';
11
- function dbg(msg) {
12
- try {
13
- appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] ${msg}\n`);
14
- }
15
- catch { }
16
- }
17
9
  // Fix literal newlines/tabs inside JSON string values — common LLM output mistake
18
10
  function sanitizeJson(s) {
19
11
  let result = '';
@@ -157,7 +149,6 @@ export class StreamParser {
157
149
  this.buf = this.buf.slice(end + CLOSE.length);
158
150
  this.inTool = false;
159
151
  try {
160
- dbg(`raw block (${raw.length} chars): ${raw.slice(0, 300)}`);
161
152
  // Extract named content blocks so file content never needs JSON escaping
162
153
  const extraArgs = {};
163
154
  let jsonPart = raw;
@@ -181,11 +172,9 @@ export class StreamParser {
181
172
  extractBlock(NEW_OPEN, NEW_CLOSE, 'new');
182
173
  const obj = parseToolJson(jsonPart);
183
174
  obj.args = { ...(obj.args ?? {}), ...extraArgs };
184
- dbg(`parsed ok: name=${obj.name} args_keys=${Object.keys(obj.args).join(',')}`);
185
175
  out.push({ type: 'tool_call', content: raw, toolName: obj.name, toolArgs: obj.args });
186
176
  }
187
- catch (e) {
188
- dbg(`parse FAILED: ${e} | raw: ${raw.slice(0, 300)}`);
177
+ catch {
189
178
  out.push({ type: 'text', content: `${OPEN}${raw}${CLOSE}` });
190
179
  }
191
180
  }
@@ -218,4 +207,3 @@ export class StreamParser {
218
207
  return out;
219
208
  }
220
209
  }
221
- //# sourceMappingURL=stream-parser.js.map
package/dist/sessions.js CHANGED
@@ -51,4 +51,3 @@ export function deleteSession(name) {
51
51
  if (existsSync(p))
52
52
  unlinkSync(p);
53
53
  }
54
- //# sourceMappingURL=sessions.js.map
@@ -126,4 +126,3 @@ export class SkillLoader {
126
126
  return [...new Set(this.map.values())];
127
127
  }
128
128
  }
129
- //# sourceMappingURL=loader.js.map
@@ -0,0 +1,68 @@
1
+ const COMPACT_THRESHOLD = 18; // compact when context exceeds this many messages
2
+ const KEEP_RECENT = 6; // always keep last N messages verbatim
3
+ export function shouldCompact(messages) {
4
+ return messages.length > COMPACT_THRESHOLD;
5
+ }
6
+ /**
7
+ * Compact context to keep local models on track during long refactors.
8
+ *
9
+ * Strategy:
10
+ * 1. Keep system prompt (index 0)
11
+ * 2. Keep first user message (original goal)
12
+ * 3. Summarise completed tool results in the middle into one message
13
+ * 4. Keep last KEEP_RECENT messages verbatim (model's working memory)
14
+ */
15
+ export function compactContext(messages, goal) {
16
+ if (messages.length <= COMPACT_THRESHOLD)
17
+ return messages;
18
+ const system = messages[0]?.role === 'system' ? messages[0] : null;
19
+ const userGoal = messages.find(m => m.role === 'user' && !m.content.startsWith('['));
20
+ const anchorCount = (system ? 1 : 0) + (userGoal ? 1 : 0);
21
+ const middle = messages.slice(anchorCount, messages.length - KEEP_RECENT);
22
+ const recent = messages.slice(messages.length - KEEP_RECENT);
23
+ const toolResults = middle
24
+ .filter(m => m.role === 'user' && m.content.startsWith('Tool '))
25
+ .map(m => {
26
+ const lines = m.content.split('\n');
27
+ return `• ${lines[0]}`; // just the "Tool X result:" line
28
+ });
29
+ const assistantSummaries = middle
30
+ .filter(m => m.role === 'assistant' && m.content.trim().length > 0)
31
+ .map(m => m.content.slice(0, 120).replace(/\n/g, ' '));
32
+ const parts = [`[context compacted — ${middle.length} messages summarised]`];
33
+ if (goal)
34
+ parts.push(`Goal: ${goal}`);
35
+ if (toolResults.length)
36
+ parts.push(`Completed:\n${toolResults.join('\n')}`);
37
+ if (assistantSummaries.length)
38
+ parts.push(`Last reasoning: ${assistantSummaries.at(-1)}`);
39
+ const summary = { role: 'user', content: parts.join('\n\n') };
40
+ return [
41
+ ...(system ? [system] : []),
42
+ ...(userGoal ? [userGoal] : []),
43
+ summary,
44
+ ...recent,
45
+ ];
46
+ }
47
+ /**
48
+ * Build a fresh isolated context for a single-file edit step.
49
+ * Keeps context tiny — avoids cross-file noise polluting the model.
50
+ */
51
+ export function fileEditContext(systemPrompt, goal, filePath, fileContent, instruction) {
52
+ return [
53
+ { role: 'system', content: systemPrompt },
54
+ {
55
+ role: 'user',
56
+ content: [
57
+ `Overall goal: ${goal}`,
58
+ ``,
59
+ `File to edit: ${filePath}`,
60
+ `<file>`,
61
+ fileContent,
62
+ `</file>`,
63
+ ``,
64
+ `Instruction: ${instruction}`,
65
+ ].join('\n'),
66
+ },
67
+ ];
68
+ }