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.
- package/README.md +190 -83
- package/dist/config.js +0 -1
- package/dist/files/ops.js +22 -4
- package/dist/index.js +0 -1
- package/dist/init.js +0 -1
- package/dist/llm/ollama.js +0 -1
- package/dist/llm/stream.js +4 -3
- package/dist/parser/stream-parser.js +1 -13
- package/dist/sessions.js +0 -1
- package/dist/skills/loader.js +0 -1
- package/dist/tasks/compactor.js +68 -0
- package/dist/tasks/executor.js +88 -0
- package/dist/tasks/queue.js +72 -0
- package/dist/tools/index.js +108 -5
- package/dist/tui/App.js +0 -1
- package/dist/tui/InputBar.js +379 -32
- package/dist/tui/components/AtPicker.js +0 -1
- package/dist/tui/components/CommandPalette.js +4 -3
- package/dist/tui/components/InputArea.js +25 -13
- package/dist/tui/components/MessageList.js +12 -1
- package/dist/tui/components/ModelPicker.js +0 -1
- package/dist/tui/components/StatusBar.js +0 -1
- package/dist/tui/printer.js +0 -1
- package/dist/types.js +0 -1
- package/dist/workers/context.worker.js +0 -1
- package/dist/workers/spawn.js +0 -1
- package/package.json +6 -3
- package/.claude/settings.local.json +0 -28
- package/CONTRIBUTING.md +0 -55
- package/Makefile +0 -13
- package/dist/config.d.ts +0 -2
- package/dist/config.js.map +0 -1
- package/dist/files/ops.d.ts +0 -14
- package/dist/files/ops.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js.map +0 -1
- package/dist/init.d.ts +0 -1
- package/dist/init.js.map +0 -1
- package/dist/llm/ollama.d.ts +0 -10
- package/dist/llm/ollama.js.map +0 -1
- package/dist/llm/stream.d.ts +0 -12
- package/dist/llm/stream.js.map +0 -1
- package/dist/parser/stream-parser.d.ts +0 -21
- package/dist/parser/stream-parser.js.map +0 -1
- package/dist/sessions.d.ts +0 -9
- package/dist/sessions.js.map +0 -1
- package/dist/skills/loader.d.ts +0 -23
- package/dist/skills/loader.js.map +0 -1
- package/dist/tools/index.d.ts +0 -8
- package/dist/tools/index.js.map +0 -1
- package/dist/tui/App.d.ts +0 -9
- package/dist/tui/App.js.map +0 -1
- package/dist/tui/InputBar.d.ts +0 -10
- package/dist/tui/InputBar.js.map +0 -1
- package/dist/tui/components/AtPicker.d.ts +0 -8
- package/dist/tui/components/AtPicker.js.map +0 -1
- package/dist/tui/components/CommandPalette.d.ts +0 -8
- package/dist/tui/components/CommandPalette.js.map +0 -1
- package/dist/tui/components/InputArea.d.ts +0 -12
- package/dist/tui/components/InputArea.js.map +0 -1
- package/dist/tui/components/MessageList.d.ts +0 -11
- package/dist/tui/components/MessageList.js.map +0 -1
- package/dist/tui/components/ModelPicker.d.ts +0 -18
- package/dist/tui/components/ModelPicker.js.map +0 -1
- package/dist/tui/components/StatusBar.d.ts +0 -12
- package/dist/tui/components/StatusBar.js.map +0 -1
- package/dist/tui/printer.d.ts +0 -7
- package/dist/tui/printer.js.map +0 -1
- package/dist/types.d.ts +0 -20
- package/dist/types.js.map +0 -1
- package/dist/workers/context.worker.js.map +0 -1
- package/dist/workers/diff.worker.d.ts +0 -1
- package/dist/workers/diff.worker.js +0 -12
- package/dist/workers/diff.worker.js.map +0 -1
- package/dist/workers/spawn.d.ts +0 -1
- package/dist/workers/spawn.js.map +0 -1
- package/install.sh +0 -6
- package/mii-cli.gif +0 -0
- package/src/config.ts +0 -32
- package/src/files/ops.ts +0 -89
- package/src/index.ts +0 -11
- package/src/init.ts +0 -41
- package/src/llm/ollama.ts +0 -110
- package/src/llm/stream.ts +0 -55
- package/src/parser/stream-parser.ts +0 -196
- package/src/sessions.ts +0 -54
- package/src/skills/loader.ts +0 -144
- package/src/tools/index.ts +0 -151
- package/src/tui/App.tsx +0 -355
- package/src/tui/InputBar.tsx +0 -381
- package/src/tui/components/AtPicker.tsx +0 -49
- package/src/tui/components/CommandPalette.tsx +0 -50
- package/src/tui/components/InputArea.tsx +0 -297
- package/src/tui/components/MessageList.tsx +0 -219
- package/src/tui/components/ModelPicker.tsx +0 -134
- package/src/tui/components/StatusBar.tsx +0 -36
- package/src/tui/printer.ts +0 -130
- package/src/types.ts +0 -26
- package/src/workers/context.worker.ts +0 -66
- package/src/workers/diff.worker.ts +0 -20
- package/src/workers/spawn.ts +0 -19
- package/tsconfig.json +0 -18
- /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
|
+
}
|
package/dist/tools/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
|
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
|