codemini-cli 0.5.10 → 0.5.11

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 (59) hide show
  1. package/OPERATIONS.md +242 -242
  2. package/README.md +588 -588
  3. package/codemini-web/dist/assets/{highlighted-body-OFNGDK62-7HL7yft8.js → highlighted-body-OFNGDK62-CANOG7Xg.js} +1 -1
  4. package/codemini-web/dist/assets/{index-BK75hMb2.js → index-B71xykPM.js} +108 -108
  5. package/codemini-web/dist/assets/index-Dkq1DdDX.css +2 -0
  6. package/codemini-web/dist/assets/mermaid-GHXKKRXX-Z_w7M93P.js +1 -0
  7. package/codemini-web/dist/index.html +23 -23
  8. package/codemini-web/lib/approval-manager.js +32 -32
  9. package/codemini-web/lib/runtime-bridge.js +17 -11
  10. package/codemini-web/server.js +534 -205
  11. package/deployment.md +212 -212
  12. package/package.json +1 -1
  13. package/skills/brainstorm/SKILL.md +77 -77
  14. package/skills/codemini.skills.json +40 -40
  15. package/skills/grill-me/SKILL.md +30 -30
  16. package/skills/superpowers-lite/SKILL.md +82 -82
  17. package/src/cli.js +74 -74
  18. package/src/commands/chat.js +210 -210
  19. package/src/commands/run.js +313 -313
  20. package/src/commands/skill.js +438 -304
  21. package/src/commands/web.js +57 -57
  22. package/src/core/agent-loop.js +980 -980
  23. package/src/core/ast.js +309 -307
  24. package/src/core/chat-runtime.js +6261 -6253
  25. package/src/core/command-evaluator.js +72 -72
  26. package/src/core/command-loader.js +311 -311
  27. package/src/core/command-policy.js +301 -301
  28. package/src/core/command-risk.js +156 -156
  29. package/src/core/config-store.js +289 -289
  30. package/src/core/constants.js +18 -1
  31. package/src/core/context-compact.js +365 -365
  32. package/src/core/default-system-prompt.js +114 -107
  33. package/src/core/dream-audit.js +105 -105
  34. package/src/core/dream-consolidate.js +229 -229
  35. package/src/core/dream-evaluator.js +185 -185
  36. package/src/core/fff-adapter.js +383 -383
  37. package/src/core/memory-store.js +543 -543
  38. package/src/core/project-index.js +737 -548
  39. package/src/core/project-instructions.js +98 -98
  40. package/src/core/provider/anthropic.js +514 -514
  41. package/src/core/provider/openai-compatible.js +501 -501
  42. package/src/core/reflect-skill.js +178 -178
  43. package/src/core/reply-language.js +40 -40
  44. package/src/core/session-store.js +474 -474
  45. package/src/core/shell-profile.js +237 -237
  46. package/src/core/shell.js +323 -323
  47. package/src/core/soul.js +69 -69
  48. package/src/core/system-prompt-composer.js +52 -52
  49. package/src/core/tool-args.js +199 -154
  50. package/src/core/tool-output.js +184 -184
  51. package/src/core/tool-result-store.js +206 -206
  52. package/src/core/tools.js +3024 -2893
  53. package/src/core/version.js +11 -11
  54. package/src/tui/chat-app.js +5171 -5171
  55. package/src/tui/tool-activity/presenters/misc.js +30 -30
  56. package/src/tui/tool-activity/presenters/system.js +20 -20
  57. package/templates/project-requirements/report-shell.html +582 -582
  58. package/codemini-web/dist/assets/index-BSdIdn3L.css +0 -2
  59. package/codemini-web/dist/assets/mermaid-GHXKKRXX-Dg9qh8mg.js +0 -1
@@ -1,313 +1,313 @@
1
- import { loadConfig } from '../core/config-store.js';
2
- import { buildDefaultSystemPrompt } from '../core/default-system-prompt.js';
3
- import { runAgentLoop } from '../core/agent-loop.js';
4
- import { createChatCompletion } from '../core/provider/index.js';
5
- import { getBuiltinTools } from '../core/tools.js';
6
- import { getSubAgentRolePrompt } from '../core/chat-runtime.js';
7
- import { composeSystemPrompt } from '../core/system-prompt-composer.js';
8
- import fs from 'node:fs/promises';
9
- import path from 'node:path';
10
-
11
- const ROLE_TOOL_POLICY = {
12
- planner: ['read', 'grep', 'list', 'query_project_index', 'tool_search', 'glob', 'ast_query', 'read_ast_node', 'read_plan', 'update_plan'],
13
- advisor: ['read', 'grep', 'list', 'query_project_index', 'tool_search', 'read_plan'],
14
- coder: ['read', 'grep', 'list', 'edit', 'write', 'run', 'ast_query', 'read_ast_node', 'glob', 'tool_search', 'update_todos', 'read_plan', 'update_plan'],
15
- reviewer: ['read', 'grep', 'list', 'glob', 'tool_search', 'ast_query', 'read_ast_node', 'read_plan'],
16
- tester: ['read', 'grep', 'list', 'run', 'glob', 'tool_search', 'read_plan']
17
- };
18
- const HARNESS_ROLES = Object.keys(ROLE_TOOL_POLICY);
19
-
20
- function parseRunArgs(args) {
21
- const parsed = {
22
- task: '',
23
- model: undefined,
24
- fast: false,
25
- maxSteps: 8,
26
- harness: null,
27
- pipeline: false
28
- };
29
- for (let i = 0; i < args.length; i += 1) {
30
- const arg = args[i];
31
- if (arg === '--model') {
32
- parsed.model = args[i + 1];
33
- i += 1;
34
- continue;
35
- }
36
- if (arg === '--fast' || arg === '--lite') {
37
- parsed.fast = true;
38
- continue;
39
- }
40
- if (arg === '--max-steps') {
41
- parsed.maxSteps = Number(args[i + 1] || 8);
42
- i += 1;
43
- continue;
44
- }
45
- if (arg === '--harness') {
46
- parsed.harness = (args[i + 1] || '').toLowerCase();
47
- i += 1;
48
- continue;
49
- }
50
- if (arg === '--pipeline') {
51
- parsed.pipeline = true;
52
- continue;
53
- }
54
- parsed.task += `${parsed.task ? ' ' : ''}${arg}`;
55
- }
56
- return parsed;
57
- }
58
-
59
- function filterToolsForRole(definitions, handlers, deferredDefinitions, role) {
60
- const allowed = ROLE_TOOL_POLICY[role];
61
- if (!allowed) return { definitions, handlers, deferredDefinitions };
62
- return {
63
- definitions: definitions.filter((t) => allowed.includes(t.function?.name || t.name)),
64
- handlers: Object.fromEntries(Object.entries(handlers).filter(([name]) => allowed.includes(name))),
65
- deferredDefinitions: Object.fromEntries(Object.entries(deferredDefinitions || {}).filter(([name]) => allowed.includes(name)))
66
- };
67
- }
68
-
69
- function makeCompletionFn(config) {
70
- return async ({ messages, tools, model }) =>
71
- createChatCompletion({
72
- sdkProvider: config.sdk?.provider,
73
- baseUrl: config.gateway.base_url,
74
- apiKey: config.gateway.api_key,
75
- model,
76
- messages,
77
- tools,
78
- timeoutMs: config.gateway.timeout_ms || 1800000,
79
- maxRetries: config.gateway.max_retries ?? 2
80
- });
81
- }
82
-
83
- async function buildSystemPrompt(config) {
84
- return composeSystemPrompt({
85
- shellRulesPrompt: buildDefaultSystemPrompt(config),
86
- config,
87
- workspaceRoot: process.cwd()
88
- });
89
- }
90
-
91
- async function runHarness({ role, task, config, systemPrompt, model, maxSteps }) {
92
- if (!HARNESS_ROLES.includes(role)) {
93
- throw new Error(`Unknown harness role: ${role}. Available: ${HARNESS_ROLES.join(', ')}`);
94
- }
95
- const { definitions, handlers, formatters, deferredDefinitions, dispose } = getBuiltinTools({
96
- workspaceRoot: process.cwd(),
97
- config
98
- });
99
- try {
100
- const filtered = filterToolsForRole(definitions, handlers, deferredDefinitions, role);
101
- const rolePrompt = getSubAgentRolePrompt(role);
102
- const harnessSystemPrompt = await composeSystemPrompt({
103
- shellRulesPrompt: systemPrompt,
104
- config,
105
- skillsPrompt: rolePrompt,
106
- includeSoul: false,
107
- includeMemory: false
108
- });
109
-
110
- const result = await runAgentLoop({
111
- systemPrompt: harnessSystemPrompt,
112
- userPrompt: task,
113
- model: model || config.model.name,
114
- toolDefinitions: filtered.definitions,
115
- toolHandlers: filtered.handlers,
116
- toolFormatters: formatters,
117
- deferredDefinitions: filtered.deferredDefinitions,
118
- maxSteps,
119
- requestCompletion: makeCompletionFn(config)
120
- });
121
- return result;
122
- } finally {
123
- await dispose?.();
124
- }
125
- }
126
-
127
- function extractJsonBlock(text) {
128
- const raw = String(text || '').trim();
129
- if (!raw) return null;
130
- try { return JSON.parse(raw); } catch {}
131
- const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
132
- if (fenced?.[1]) { try { return JSON.parse(fenced[1]); } catch {} }
133
- const first = raw.indexOf('{');
134
- const last = raw.lastIndexOf('}');
135
- if (first !== -1 && last !== -1 && last > first) {
136
- try { return JSON.parse(raw.slice(first, last + 1)); } catch {}
137
- }
138
- return null;
139
- }
140
-
141
- function normalizePlan(parsed, goal) {
142
- const steps = Array.isArray(parsed?.steps) ? parsed.steps : [];
143
- const cleaned = steps
144
- .map((s) => ({
145
- title: String(s?.title || '').trim(),
146
- role: String(s?.role || '').trim().toLowerCase(),
147
- task: String(s?.task || '').trim()
148
- }))
149
- .filter((s) => s.title && s.task && HARNESS_ROLES.includes(s.role));
150
- if (cleaned.length === 0) {
151
- return { summary: `Fallback plan for: ${goal}`, steps: [{ title: 'Execute task', role: 'coder', task: goal }] };
152
- }
153
- return { summary: parsed.summary || `Plan for: ${goal}`, steps: cleaned };
154
- }
155
-
156
- async function planPipeline({ goal, config, systemPrompt, model }) {
157
- const plannerPrompt = [
158
- 'Create an execution plan and assign the best sub-agent role for each step.',
159
- 'Return strict JSON only with shape {"summary":"...","steps":[{"title":"...","role":"planner|advisor|coder|reviewer|tester","task":"..."}]}. No markdown.',
160
- `Available roles: ${HARNESS_ROLES.join(', ')}.`,
161
- 'Prefer 3-5 steps total. The first step should usually inspect the target area.',
162
- 'For implementation goals, include a reviewer or tester step near the end.',
163
- 'For advisory/analysis goals, keep it lean with planner/advisor only; do not use coder unless code or files will be modified.'
164
- ].join('\n');
165
- const plannerSystemPrompt = await composeSystemPrompt({
166
- shellRulesPrompt: systemPrompt,
167
- config,
168
- skillsPrompt: plannerPrompt,
169
- includeSoul: false,
170
- includeMemory: false
171
- });
172
-
173
- const planning = await createChatCompletion({
174
- sdkProvider: config.sdk?.provider,
175
- baseUrl: config.gateway.base_url,
176
- apiKey: config.gateway.api_key,
177
- model: model || config.model.name,
178
- messages: [
179
- { role: 'system', content: plannerSystemPrompt },
180
- { role: 'user', content: `Plan the following task:\n${goal}` }
181
- ],
182
- timeoutMs: config.gateway.timeout_ms || 1800000,
183
- maxRetries: config.gateway.max_retries ?? 2
184
- });
185
-
186
- const parsed = extractJsonBlock(planning.text || '');
187
- return normalizePlan(parsed, goal);
188
- }
189
-
190
- function writePipelineState(workspaceRoot, state) {
191
- const dir = path.join(workspaceRoot, '.codemini');
192
- const filePath = path.join(dir, 'pipeline-state.json');
193
- return fs.mkdir(dir, { recursive: true }).then(() =>
194
- fs.writeFile(filePath, JSON.stringify(state, null, 2), 'utf-8')
195
- ).catch(() => {});
196
- }
197
-
198
- async function runPipeline({ task, config, systemPrompt, model }) {
199
- console.log('[pipeline] Planning...');
200
- const plan = await planPipeline({ goal: task, config, systemPrompt, model });
201
-
202
- console.log(`[pipeline] Plan: ${plan.summary}`);
203
- plan.steps.forEach((s, i) => console.log(` ${i + 1}. [${s.role}] ${s.title}`));
204
- console.log('');
205
-
206
- const priorSteps = [];
207
- const pipelineState = {
208
- goal: task,
209
- summary: plan.summary,
210
- steps: plan.steps.map((s) => ({ ...s, status: 'pending' })),
211
- artifacts: [],
212
- startedAt: new Date().toISOString()
213
- };
214
-
215
- for (let i = 0; i < plan.steps.length; i += 1) {
216
- const step = plan.steps[i];
217
- pipelineState.steps[i].status = 'running';
218
- await writePipelineState(process.cwd(), pipelineState);
219
-
220
- console.log(`[pipeline] Step ${i + 1}/${plan.steps.length} -> ${step.role}: ${step.title}`);
221
-
222
- const result = await runHarness({
223
- role: step.role,
224
- task: step.task,
225
- config,
226
- systemPrompt,
227
- model,
228
- maxSteps: Number(config.execution?.max_steps || 12)
229
- });
230
-
231
- const stepResult = {
232
- role: step.role,
233
- title: step.title,
234
- output: (result.text || '').slice(0, 500),
235
- status: 'done'
236
- };
237
- priorSteps.push(stepResult);
238
-
239
- pipelineState.steps[i].status = 'done';
240
- pipelineState.steps[i].output = stepResult.output;
241
- pipelineState.artifacts.push(stepResult);
242
- await writePipelineState(process.cwd(), pipelineState);
243
-
244
- console.log(`[pipeline] Step ${i + 1} complete.\n`);
245
- }
246
-
247
- pipelineState.completedAt = new Date().toISOString();
248
- await writePipelineState(process.cwd(), pipelineState);
249
-
250
- console.log('[pipeline] All steps complete.');
251
- console.log(`[pipeline] State saved to .codemini/pipeline-state.json`);
252
- return pipelineState;
253
- }
254
-
255
- export async function handleRun(args) {
256
- const parsed = parseRunArgs(args);
257
- if (!parsed.task) {
258
- throw new Error('run requires <task>');
259
- }
260
-
261
- const config = await loadConfig();
262
- const selectedModel = parsed.fast ? (config.model?.fast_name || config.model?.name) : parsed.model;
263
- const systemPrompt = await buildSystemPrompt(config);
264
-
265
- if (parsed.pipeline) {
266
- const state = await runPipeline({
267
- task: parsed.task,
268
- config,
269
- systemPrompt,
270
- model: selectedModel
271
- });
272
- for (const step of state.steps) {
273
- console.log(`\n--- [${step.role}] ${step.title} ---`);
274
- console.log(step.output || '(no output)');
275
- }
276
- return;
277
- }
278
-
279
- if (parsed.harness) {
280
- const result = await runHarness({
281
- role: parsed.harness,
282
- task: parsed.task,
283
- config,
284
- systemPrompt,
285
- model: selectedModel,
286
- maxSteps: parsed.maxSteps
287
- });
288
- console.log(result.text);
289
- return;
290
- }
291
-
292
- const { definitions, handlers, formatters, deferredDefinitions, dispose } = getBuiltinTools({
293
- workspaceRoot: process.cwd(),
294
- config
295
- });
296
- try {
297
- const result = await runAgentLoop({
298
- systemPrompt,
299
- userPrompt: parsed.task,
300
- model: selectedModel || config.model.name,
301
- toolDefinitions: definitions,
302
- toolHandlers: handlers,
303
- toolFormatters: formatters,
304
- deferredDefinitions,
305
- maxSteps: parsed.maxSteps,
306
- requestCompletion: makeCompletionFn(config)
307
- });
308
-
309
- console.log(result.text);
310
- } finally {
311
- await dispose?.();
312
- }
313
- }
1
+ import { loadConfig } from '../core/config-store.js';
2
+ import { buildDefaultSystemPrompt } from '../core/default-system-prompt.js';
3
+ import { runAgentLoop } from '../core/agent-loop.js';
4
+ import { createChatCompletion } from '../core/provider/index.js';
5
+ import { getBuiltinTools } from '../core/tools.js';
6
+ import { getSubAgentRolePrompt } from '../core/chat-runtime.js';
7
+ import { composeSystemPrompt } from '../core/system-prompt-composer.js';
8
+ import fs from 'node:fs/promises';
9
+ import path from 'node:path';
10
+
11
+ const ROLE_TOOL_POLICY = {
12
+ planner: ['read', 'grep', 'list', 'query_project_index', 'tool_search', 'glob', 'ast_query', 'read_ast_node', 'read_plan', 'update_plan'],
13
+ advisor: ['read', 'grep', 'list', 'query_project_index', 'tool_search', 'read_plan'],
14
+ coder: ['read', 'grep', 'list', 'edit', 'write', 'run', 'ast_query', 'read_ast_node', 'glob', 'tool_search', 'update_todos', 'read_plan', 'update_plan'],
15
+ reviewer: ['read', 'grep', 'list', 'glob', 'tool_search', 'ast_query', 'read_ast_node', 'read_plan'],
16
+ tester: ['read', 'grep', 'list', 'run', 'glob', 'tool_search', 'read_plan']
17
+ };
18
+ const HARNESS_ROLES = Object.keys(ROLE_TOOL_POLICY);
19
+
20
+ function parseRunArgs(args) {
21
+ const parsed = {
22
+ task: '',
23
+ model: undefined,
24
+ fast: false,
25
+ maxSteps: 8,
26
+ harness: null,
27
+ pipeline: false
28
+ };
29
+ for (let i = 0; i < args.length; i += 1) {
30
+ const arg = args[i];
31
+ if (arg === '--model') {
32
+ parsed.model = args[i + 1];
33
+ i += 1;
34
+ continue;
35
+ }
36
+ if (arg === '--fast' || arg === '--lite') {
37
+ parsed.fast = true;
38
+ continue;
39
+ }
40
+ if (arg === '--max-steps') {
41
+ parsed.maxSteps = Number(args[i + 1] || 8);
42
+ i += 1;
43
+ continue;
44
+ }
45
+ if (arg === '--harness') {
46
+ parsed.harness = (args[i + 1] || '').toLowerCase();
47
+ i += 1;
48
+ continue;
49
+ }
50
+ if (arg === '--pipeline') {
51
+ parsed.pipeline = true;
52
+ continue;
53
+ }
54
+ parsed.task += `${parsed.task ? ' ' : ''}${arg}`;
55
+ }
56
+ return parsed;
57
+ }
58
+
59
+ function filterToolsForRole(definitions, handlers, deferredDefinitions, role) {
60
+ const allowed = ROLE_TOOL_POLICY[role];
61
+ if (!allowed) return { definitions, handlers, deferredDefinitions };
62
+ return {
63
+ definitions: definitions.filter((t) => allowed.includes(t.function?.name || t.name)),
64
+ handlers: Object.fromEntries(Object.entries(handlers).filter(([name]) => allowed.includes(name))),
65
+ deferredDefinitions: Object.fromEntries(Object.entries(deferredDefinitions || {}).filter(([name]) => allowed.includes(name)))
66
+ };
67
+ }
68
+
69
+ function makeCompletionFn(config) {
70
+ return async ({ messages, tools, model }) =>
71
+ createChatCompletion({
72
+ sdkProvider: config.sdk?.provider,
73
+ baseUrl: config.gateway.base_url,
74
+ apiKey: config.gateway.api_key,
75
+ model,
76
+ messages,
77
+ tools,
78
+ timeoutMs: config.gateway.timeout_ms || 1800000,
79
+ maxRetries: config.gateway.max_retries ?? 2
80
+ });
81
+ }
82
+
83
+ async function buildSystemPrompt(config) {
84
+ return composeSystemPrompt({
85
+ shellRulesPrompt: buildDefaultSystemPrompt(config),
86
+ config,
87
+ workspaceRoot: process.cwd()
88
+ });
89
+ }
90
+
91
+ async function runHarness({ role, task, config, systemPrompt, model, maxSteps }) {
92
+ if (!HARNESS_ROLES.includes(role)) {
93
+ throw new Error(`Unknown harness role: ${role}. Available: ${HARNESS_ROLES.join(', ')}`);
94
+ }
95
+ const { definitions, handlers, formatters, deferredDefinitions, dispose } = getBuiltinTools({
96
+ workspaceRoot: process.cwd(),
97
+ config
98
+ });
99
+ try {
100
+ const filtered = filterToolsForRole(definitions, handlers, deferredDefinitions, role);
101
+ const rolePrompt = getSubAgentRolePrompt(role);
102
+ const harnessSystemPrompt = await composeSystemPrompt({
103
+ shellRulesPrompt: systemPrompt,
104
+ config,
105
+ skillsPrompt: rolePrompt,
106
+ includeSoul: false,
107
+ includeMemory: false
108
+ });
109
+
110
+ const result = await runAgentLoop({
111
+ systemPrompt: harnessSystemPrompt,
112
+ userPrompt: task,
113
+ model: model || config.model.name,
114
+ toolDefinitions: filtered.definitions,
115
+ toolHandlers: filtered.handlers,
116
+ toolFormatters: formatters,
117
+ deferredDefinitions: filtered.deferredDefinitions,
118
+ maxSteps,
119
+ requestCompletion: makeCompletionFn(config)
120
+ });
121
+ return result;
122
+ } finally {
123
+ await dispose?.();
124
+ }
125
+ }
126
+
127
+ function extractJsonBlock(text) {
128
+ const raw = String(text || '').trim();
129
+ if (!raw) return null;
130
+ try { return JSON.parse(raw); } catch {}
131
+ const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
132
+ if (fenced?.[1]) { try { return JSON.parse(fenced[1]); } catch {} }
133
+ const first = raw.indexOf('{');
134
+ const last = raw.lastIndexOf('}');
135
+ if (first !== -1 && last !== -1 && last > first) {
136
+ try { return JSON.parse(raw.slice(first, last + 1)); } catch {}
137
+ }
138
+ return null;
139
+ }
140
+
141
+ function normalizePlan(parsed, goal) {
142
+ const steps = Array.isArray(parsed?.steps) ? parsed.steps : [];
143
+ const cleaned = steps
144
+ .map((s) => ({
145
+ title: String(s?.title || '').trim(),
146
+ role: String(s?.role || '').trim().toLowerCase(),
147
+ task: String(s?.task || '').trim()
148
+ }))
149
+ .filter((s) => s.title && s.task && HARNESS_ROLES.includes(s.role));
150
+ if (cleaned.length === 0) {
151
+ return { summary: `Fallback plan for: ${goal}`, steps: [{ title: 'Execute task', role: 'coder', task: goal }] };
152
+ }
153
+ return { summary: parsed.summary || `Plan for: ${goal}`, steps: cleaned };
154
+ }
155
+
156
+ async function planPipeline({ goal, config, systemPrompt, model }) {
157
+ const plannerPrompt = [
158
+ 'Create an execution plan and assign the best sub-agent role for each step.',
159
+ 'Return strict JSON only with shape {"summary":"...","steps":[{"title":"...","role":"planner|advisor|coder|reviewer|tester","task":"..."}]}. No markdown.',
160
+ `Available roles: ${HARNESS_ROLES.join(', ')}.`,
161
+ 'Prefer 3-5 steps total. The first step should usually inspect the target area.',
162
+ 'For implementation goals, include a reviewer or tester step near the end.',
163
+ 'For advisory/analysis goals, keep it lean with planner/advisor only; do not use coder unless code or files will be modified.'
164
+ ].join('\n');
165
+ const plannerSystemPrompt = await composeSystemPrompt({
166
+ shellRulesPrompt: systemPrompt,
167
+ config,
168
+ skillsPrompt: plannerPrompt,
169
+ includeSoul: false,
170
+ includeMemory: false
171
+ });
172
+
173
+ const planning = await createChatCompletion({
174
+ sdkProvider: config.sdk?.provider,
175
+ baseUrl: config.gateway.base_url,
176
+ apiKey: config.gateway.api_key,
177
+ model: model || config.model.name,
178
+ messages: [
179
+ { role: 'system', content: plannerSystemPrompt },
180
+ { role: 'user', content: `Plan the following task:\n${goal}` }
181
+ ],
182
+ timeoutMs: config.gateway.timeout_ms || 1800000,
183
+ maxRetries: config.gateway.max_retries ?? 2
184
+ });
185
+
186
+ const parsed = extractJsonBlock(planning.text || '');
187
+ return normalizePlan(parsed, goal);
188
+ }
189
+
190
+ function writePipelineState(workspaceRoot, state) {
191
+ const dir = path.join(workspaceRoot, '.codemini');
192
+ const filePath = path.join(dir, 'pipeline-state.json');
193
+ return fs.mkdir(dir, { recursive: true }).then(() =>
194
+ fs.writeFile(filePath, JSON.stringify(state, null, 2), 'utf-8')
195
+ ).catch(() => {});
196
+ }
197
+
198
+ async function runPipeline({ task, config, systemPrompt, model }) {
199
+ console.log('[pipeline] Planning...');
200
+ const plan = await planPipeline({ goal: task, config, systemPrompt, model });
201
+
202
+ console.log(`[pipeline] Plan: ${plan.summary}`);
203
+ plan.steps.forEach((s, i) => console.log(` ${i + 1}. [${s.role}] ${s.title}`));
204
+ console.log('');
205
+
206
+ const priorSteps = [];
207
+ const pipelineState = {
208
+ goal: task,
209
+ summary: plan.summary,
210
+ steps: plan.steps.map((s) => ({ ...s, status: 'pending' })),
211
+ artifacts: [],
212
+ startedAt: new Date().toISOString()
213
+ };
214
+
215
+ for (let i = 0; i < plan.steps.length; i += 1) {
216
+ const step = plan.steps[i];
217
+ pipelineState.steps[i].status = 'running';
218
+ await writePipelineState(process.cwd(), pipelineState);
219
+
220
+ console.log(`[pipeline] Step ${i + 1}/${plan.steps.length} -> ${step.role}: ${step.title}`);
221
+
222
+ const result = await runHarness({
223
+ role: step.role,
224
+ task: step.task,
225
+ config,
226
+ systemPrompt,
227
+ model,
228
+ maxSteps: Number(config.execution?.max_steps || 12)
229
+ });
230
+
231
+ const stepResult = {
232
+ role: step.role,
233
+ title: step.title,
234
+ output: (result.text || '').slice(0, 500),
235
+ status: 'done'
236
+ };
237
+ priorSteps.push(stepResult);
238
+
239
+ pipelineState.steps[i].status = 'done';
240
+ pipelineState.steps[i].output = stepResult.output;
241
+ pipelineState.artifacts.push(stepResult);
242
+ await writePipelineState(process.cwd(), pipelineState);
243
+
244
+ console.log(`[pipeline] Step ${i + 1} complete.\n`);
245
+ }
246
+
247
+ pipelineState.completedAt = new Date().toISOString();
248
+ await writePipelineState(process.cwd(), pipelineState);
249
+
250
+ console.log('[pipeline] All steps complete.');
251
+ console.log(`[pipeline] State saved to .codemini/pipeline-state.json`);
252
+ return pipelineState;
253
+ }
254
+
255
+ export async function handleRun(args) {
256
+ const parsed = parseRunArgs(args);
257
+ if (!parsed.task) {
258
+ throw new Error('run requires <task>');
259
+ }
260
+
261
+ const config = await loadConfig();
262
+ const selectedModel = parsed.fast ? (config.model?.fast_name || config.model?.name) : parsed.model;
263
+ const systemPrompt = await buildSystemPrompt(config);
264
+
265
+ if (parsed.pipeline) {
266
+ const state = await runPipeline({
267
+ task: parsed.task,
268
+ config,
269
+ systemPrompt,
270
+ model: selectedModel
271
+ });
272
+ for (const step of state.steps) {
273
+ console.log(`\n--- [${step.role}] ${step.title} ---`);
274
+ console.log(step.output || '(no output)');
275
+ }
276
+ return;
277
+ }
278
+
279
+ if (parsed.harness) {
280
+ const result = await runHarness({
281
+ role: parsed.harness,
282
+ task: parsed.task,
283
+ config,
284
+ systemPrompt,
285
+ model: selectedModel,
286
+ maxSteps: parsed.maxSteps
287
+ });
288
+ console.log(result.text);
289
+ return;
290
+ }
291
+
292
+ const { definitions, handlers, formatters, deferredDefinitions, dispose } = getBuiltinTools({
293
+ workspaceRoot: process.cwd(),
294
+ config
295
+ });
296
+ try {
297
+ const result = await runAgentLoop({
298
+ systemPrompt,
299
+ userPrompt: parsed.task,
300
+ model: selectedModel || config.model.name,
301
+ toolDefinitions: definitions,
302
+ toolHandlers: handlers,
303
+ toolFormatters: formatters,
304
+ deferredDefinitions,
305
+ maxSteps: parsed.maxSteps,
306
+ requestCompletion: makeCompletionFn(config)
307
+ });
308
+
309
+ console.log(result.text);
310
+ } finally {
311
+ await dispose?.();
312
+ }
313
+ }