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
@@ -0,0 +1,88 @@
1
+ import { MicroQueue } from './queue.js';
2
+ export class TaskExecutor {
3
+ toolMap;
4
+ constructor(tools) {
5
+ this.toolMap = new Map(tools.map(t => [t.name, t]));
6
+ }
7
+ async runMacro(macro, onProgress) {
8
+ const queue = new MicroQueue();
9
+ for (const t of macro.microtasks) {
10
+ t.status = 'pending';
11
+ queue.push(t);
12
+ }
13
+ return this.drain(queue, onProgress);
14
+ }
15
+ async drain(queue, onProgress) {
16
+ const results = new Map();
17
+ const allTasks = queue.toArray();
18
+ while (queue.size > 0) {
19
+ const ready = this._ready(allTasks, results);
20
+ if (!ready.length)
21
+ break;
22
+ // Remove ready tasks from queue — rebuild without them
23
+ const readyIds = new Set(ready.map(t => t.id));
24
+ const remaining = queue.toArray().filter(t => !readyIds.has(t.id));
25
+ // Clear and re-push remaining
26
+ while (queue.pop()) { }
27
+ for (const t of remaining)
28
+ queue.push(t);
29
+ // Execute by priority group
30
+ const byPri = groupBy(ready, t => t.priority);
31
+ for (const pri of [0, 1, 2, 3]) {
32
+ const group = byPri.get(pri) ?? [];
33
+ if (!group.length)
34
+ continue;
35
+ if (pri === 1) {
36
+ // Reads run in parallel
37
+ await Promise.all(group.map(t => this._run(t, results, onProgress)));
38
+ }
39
+ else {
40
+ // Blocking (0), writes (2), verify (3) — sequential
41
+ for (const t of group) {
42
+ await this._run(t, results, onProgress);
43
+ }
44
+ }
45
+ }
46
+ }
47
+ return results;
48
+ }
49
+ _ready(all, results) {
50
+ return all.filter(t => t.status === 'pending' &&
51
+ t.deps.every(dep => {
52
+ const dt = all.find(x => x.id === dep);
53
+ return !dt || dt.status === 'done' || dt.status === 'skipped';
54
+ }));
55
+ }
56
+ async _run(task, results, onProgress) {
57
+ task.status = 'running';
58
+ const tool = this.toolMap.get(task.tool);
59
+ if (!tool) {
60
+ task.status = 'failed';
61
+ task.error = `unknown tool: ${task.tool}`;
62
+ onProgress({ task, error: task.error });
63
+ return;
64
+ }
65
+ try {
66
+ const result = await tool.execute(task.args);
67
+ task.status = 'done';
68
+ task.result = result;
69
+ results.set(task.id, result);
70
+ onProgress({ task, result });
71
+ }
72
+ catch (e) {
73
+ task.status = 'failed';
74
+ task.error = String(e);
75
+ onProgress({ task, error: task.error });
76
+ }
77
+ }
78
+ }
79
+ function groupBy(arr, key) {
80
+ const m = new Map();
81
+ for (const v of arr) {
82
+ const k = key(v);
83
+ const g = m.get(k) ?? [];
84
+ g.push(v);
85
+ m.set(k, g);
86
+ }
87
+ return m;
88
+ }
@@ -0,0 +1,72 @@
1
+ // ─── MicroQueue — min-heap by priority, FIFO within same priority ────────────
2
+ export class MicroQueue {
3
+ heap = [];
4
+ seq = 0;
5
+ order = new Map();
6
+ push(task) {
7
+ this.order.set(task.id, this.seq++);
8
+ this.heap.push(task);
9
+ this._up(this.heap.length - 1);
10
+ }
11
+ pop() {
12
+ if (!this.heap.length)
13
+ return undefined;
14
+ const top = this.heap[0];
15
+ const last = this.heap.pop();
16
+ if (this.heap.length) {
17
+ this.heap[0] = last;
18
+ this._down(0);
19
+ }
20
+ return top;
21
+ }
22
+ peek() { return this.heap[0]; }
23
+ get size() { return this.heap.length; }
24
+ toArray() { return [...this.heap]; }
25
+ _cmp(a, b) {
26
+ if (a.priority !== b.priority)
27
+ return a.priority < b.priority;
28
+ return (this.order.get(a.id) ?? 0) < (this.order.get(b.id) ?? 0);
29
+ }
30
+ _up(i) {
31
+ while (i > 0) {
32
+ const p = (i - 1) >> 1;
33
+ if (this._cmp(this.heap[i], this.heap[p])) {
34
+ ;
35
+ [this.heap[i], this.heap[p]] = [this.heap[p], this.heap[i]];
36
+ i = p;
37
+ }
38
+ else
39
+ break;
40
+ }
41
+ }
42
+ _down(i) {
43
+ const n = this.heap.length;
44
+ while (true) {
45
+ let min = i;
46
+ const l = 2 * i + 1, r = 2 * i + 2;
47
+ if (l < n && this._cmp(this.heap[l], this.heap[min]))
48
+ min = l;
49
+ if (r < n && this._cmp(this.heap[r], this.heap[min]))
50
+ min = r;
51
+ if (min === i)
52
+ break;
53
+ [this.heap[i], this.heap[min]] = [this.heap[min], this.heap[i]];
54
+ i = min;
55
+ }
56
+ }
57
+ }
58
+ // ─── MacroQueue — priority-sorted list of refactor goals ─────────────────────
59
+ export class MacroQueue {
60
+ tasks = [];
61
+ enqueue(task) {
62
+ const i = this.tasks.findIndex(t => t.priority > task.priority);
63
+ if (i === -1)
64
+ this.tasks.push(task);
65
+ else
66
+ this.tasks.splice(i, 0, task);
67
+ }
68
+ dequeue() { return this.tasks.shift(); }
69
+ peek() { return this.tasks[0]; }
70
+ get size() { return this.tasks.length; }
71
+ list() { return [...this.tasks]; }
72
+ }
@@ -1,5 +1,6 @@
1
1
  import { readFile, writeFile, deleteFile, listFiles, createDir, moveFile, guardPath } from '../files/ops.js';
2
2
  import { existsSync } from 'fs';
3
+ import { join } from 'path';
3
4
  import { exec } from 'child_process';
4
5
  import { promisify } from 'util';
5
6
  const run = promisify(exec);
@@ -59,10 +60,13 @@ export const tools = [
59
60
  const current = readFile(safe);
60
61
  if (!current)
61
62
  throw new Error(`file not found or empty: ${path}`);
62
- if (!current.includes(oldStr))
63
+ const old = oldStr;
64
+ const count = current.split(old).length - 1;
65
+ if (count === 0)
63
66
  throw new Error(`old text not found in ${path}`);
64
- const updated = current.replace(oldStr, newStr);
65
- writeFile(safe, updated);
67
+ if (count > 1)
68
+ throw new Error(`ambiguous: ${count} matches found in ${path} — add more surrounding context to make unique`);
69
+ writeFile(safe, current.replace(old, newStr));
66
70
  return `patched: ${path}`;
67
71
  },
68
72
  },
@@ -102,6 +106,101 @@ export const tools = [
102
106
  return `moved: ${from} → ${to}`;
103
107
  },
104
108
  },
109
+ {
110
+ name: 'git_status',
111
+ description: 'Show git working tree status',
112
+ params: '{}',
113
+ execute: async () => {
114
+ try {
115
+ const { stdout } = await run('git status --short', { timeout: EXEC_TIMEOUT_MS });
116
+ return stdout.trim() || '(clean — no changes)';
117
+ }
118
+ catch (e) {
119
+ throw new Error(`git_status: ${e}`);
120
+ }
121
+ },
122
+ },
123
+ {
124
+ name: 'git_diff',
125
+ description: 'Show git diff. staged=true for staged changes, path for specific file',
126
+ params: '{"staged": "boolean (optional)", "path": "string (optional)"}',
127
+ execute: async ({ staged = false, path = '' }) => {
128
+ const args = staged ? '--staged' : '';
129
+ const target = path ? `-- "${path}"` : '';
130
+ try {
131
+ const { stdout } = await run(`git diff ${args} ${target}`.trim(), { timeout: EXEC_TIMEOUT_MS });
132
+ const out = stdout.trim();
133
+ if (!out)
134
+ return '(no diff)';
135
+ return out.length > 8000 ? out.slice(0, 8000) + '\n…[diff truncated at 8k chars]' : out;
136
+ }
137
+ catch (e) {
138
+ throw new Error(`git_diff: ${e}`);
139
+ }
140
+ },
141
+ },
142
+ {
143
+ name: 'git_log',
144
+ description: 'Show recent git commits',
145
+ params: '{"n": "number (optional, default 10)"}',
146
+ execute: async ({ n = 10 }) => {
147
+ try {
148
+ const { stdout } = await run(`git log --oneline -${Math.min(Number(n), 50)}`, { timeout: EXEC_TIMEOUT_MS });
149
+ return stdout.trim() || '(no commits)';
150
+ }
151
+ catch (e) {
152
+ throw new Error(`git_log: ${e}`);
153
+ }
154
+ },
155
+ },
156
+ {
157
+ name: 'git_commit',
158
+ description: 'Stage files and create a git commit. Use files="-A" to stage all.',
159
+ params: '{"message": "string", "files": "string (optional, default -A)"}',
160
+ execute: async ({ message, files = '-A' }) => {
161
+ if (!message)
162
+ throw new Error('git_commit: message required');
163
+ try {
164
+ await run(`git add ${files}`, { timeout: EXEC_TIMEOUT_MS });
165
+ const { stdout } = await run(`git commit -m ${JSON.stringify(String(message))}`, { timeout: EXEC_TIMEOUT_MS });
166
+ return stdout.trim();
167
+ }
168
+ catch (e) {
169
+ throw new Error(`git_commit: ${e}`);
170
+ }
171
+ },
172
+ },
173
+ {
174
+ name: 'run_tests',
175
+ description: 'Run the test suite. Detects jest/vitest/mocha from package.json scripts.test. Pass path to run a specific file.',
176
+ params: '{"path": "string (optional)"}',
177
+ execute: async ({ path = '' }) => {
178
+ const pkgPath = join(process.cwd(), 'package.json');
179
+ if (!existsSync(pkgPath))
180
+ return '(no package.json found)';
181
+ let testScript = '';
182
+ try {
183
+ const pkg = JSON.parse(readFile(pkgPath));
184
+ testScript = pkg?.scripts?.test ?? '';
185
+ }
186
+ catch {
187
+ return '(could not parse package.json)';
188
+ }
189
+ if (!testScript || testScript === 'echo "Error: no test specified" && exit 1') {
190
+ return '(no test script configured in package.json)';
191
+ }
192
+ const cmd = path ? `npm test -- ${path}` : 'npm test';
193
+ try {
194
+ const { stdout, stderr } = await run(cmd, { cwd: process.cwd(), timeout: 60_000 });
195
+ const out = (stdout + (stderr ? '\nstderr: ' + stderr : '')).trim();
196
+ return out.length > 4000 ? '…[truncated]\n' + out.slice(-4000) : out;
197
+ }
198
+ catch (e) {
199
+ const out = ((e.stdout ?? '') + (e.stderr ? '\n' + e.stderr : '') || String(e)).trim();
200
+ return out.length > 4000 ? '…[truncated]\n' + out.slice(-4000) : out;
201
+ }
202
+ },
203
+ },
105
204
  ];
106
205
  export function getSystemPrompt(extra = '') {
107
206
  const toolDocs = tools.map(t => `- ${t.name}(${t.params}): ${t.description}`).join('\n');
@@ -141,10 +240,14 @@ Rules:
141
240
  - To create a new file: use edit_file with full content in the <content> block
142
241
  - read_file before patch_file so you know the exact text to match
143
242
  - Never delete without confirming
243
+ - Use git_status and git_diff before any refactor to understand what has already changed
244
+ - Use git_log to understand recent history before suggesting changes
245
+ - Always call git_status before git_commit to verify what will be staged
144
246
  - Be concise
145
247
  - Output plain text only — never use markdown formatting in your responses
146
248
  - No headers (no #, ##), no bold (**text**), no italic (*text*), no bullet points with *, no horizontal rules (---)
147
249
  - No fenced code blocks with backticks in prose
148
- - Use plain indentation and labels for structure. This is a terminal, not a chat UI${extra}`;
250
+ - Use plain indentation and labels for structure. This is a terminal, not a chat UI
251
+ - After editing files that have tests, call run_tests to verify nothing broke
252
+ - If run_tests fails, read the failing test output and fix the code, then run_tests again (max 3 retries)${extra}`;
149
253
  }
150
- //# sourceMappingURL=index.js.map
package/dist/tui/App.js CHANGED
@@ -283,4 +283,3 @@ export function App({ config, skills, cwd }) {
283
283
  const skillList = skills.list();
284
284
  return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsx(StatusBar, { model: currentModel, provider: config.provider, status: status, tick: tick }), _jsx(Divider, { cols: cols }), pickerOpen ? (_jsx(ModelPicker, { models: pickerModels, current: currentModel, loading: pickerLoading, error: pickerError, pull: pullState, onSelect: handleModelSelect, onPull: handleModelPull, onClose: () => { setPickerOpen(false); setPullState(undefined); } })) : (_jsx(MessageList, { messages: messages, rows: rows - 8, cols: cols, scrollOffset: scrollOffset, streaming: false, thinkingTick: status === 'thinking' ? tick : undefined })), _jsx(Divider, { cols: cols }), pendingApproval && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginBottom: 1, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["Allow ", pendingApproval.toolName, "?"] }), _jsxs(Text, { children: [" path: ", _jsx(Text, { color: "cyan", children: pendingApproval.path })] }), pendingApproval.content && (_jsx(Text, { color: "gray", dimColor: true, children: pendingApproval.content.split('\n').slice(0, 12).join('\n') })), _jsxs(Text, { color: "green", children: ["[y] approve ", _jsx(Text, { color: "red", children: "[n] cancel" })] })] })), _jsx(InputArea, { status: status, skills: skillList, cwd: cwd, onSubmit: handleSubmit, onAbort: handleAbort })] }));
285
285
  }
286
- //# sourceMappingURL=App.js.map