@bamptee/aia-code 1.0.5 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/package.json +1 -1
- package/src/cli.js +2 -0
- package/src/commands/iterate.js +26 -0
- package/src/prompt-builder.js +7 -1
- package/src/services/runner.js +3 -3
- package/src/ui/api/constants.js +17 -0
- package/src/ui/api/features.js +29 -0
- package/src/ui/public/components/feature-detail.js +99 -53
package/README.md
CHANGED
|
@@ -35,10 +35,12 @@ Each CLI manages its own authentication. Run `claude`, `codex`, or `gemini` once
|
|
|
35
35
|
| `aia next <feature> [description]` | Run the next pending step automatically |
|
|
36
36
|
| `aia status <feature>` | Show the current status of a feature |
|
|
37
37
|
| `aia reset <step> <feature>` | Reset a step to pending so it can be re-run |
|
|
38
|
+
| `aia iterate <step> <feature> <instructions>` | Re-run a step with additional instructions to refine the output |
|
|
38
39
|
| `aia quick <name> [description]` | Quick story/ticket: dev-plan → implement → review only |
|
|
39
40
|
| `aia repo scan` | Scan codebase and generate `repo-map.json` |
|
|
41
|
+
| `aia ui` | Launch the local web UI to manage features and config |
|
|
40
42
|
|
|
41
|
-
### Options for `run`, `next`, and `
|
|
43
|
+
### Options for `run`, `next`, `quick`, and `iterate`
|
|
42
44
|
|
|
43
45
|
| Flag | Description |
|
|
44
46
|
|------|-------------|
|
|
@@ -281,6 +283,18 @@ aia reset tech-spec session-replay
|
|
|
281
283
|
aia run tech-spec session-replay "Add WebSocket support and rate limiting"
|
|
282
284
|
```
|
|
283
285
|
|
|
286
|
+
#### Iterating on a step
|
|
287
|
+
|
|
288
|
+
Use `aia iterate` to refine a completed step with specific instructions. It resets the step, feeds back the previous output, and applies your instructions in a single command:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
aia iterate tech-spec session-replay "Add error handling for WebSocket disconnections"
|
|
292
|
+
aia iterate brief session-replay "Focus more on mobile use cases"
|
|
293
|
+
aia iterate dev-plan session-replay "Split the implementation into smaller PRs" -v
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
You can iterate multiple times — each run builds on the previous output.
|
|
297
|
+
|
|
284
298
|
#### Quick mode (stories & tickets)
|
|
285
299
|
|
|
286
300
|
For small stories or tickets that don't need the full 8-step pipeline, use `aia quick`. It skips brief, ba-spec, questions, tech-spec, and challenge, and runs only **dev-plan → implement → review**:
|
|
@@ -419,6 +433,7 @@ src/
|
|
|
419
433
|
feature.js # aia feature <name>
|
|
420
434
|
run.js # aia run <step> <feature>
|
|
421
435
|
next.js # aia next <feature>
|
|
436
|
+
iterate.js # aia iterate <step> <feature> <instructions>
|
|
422
437
|
quick.js # aia quick <name> [description]
|
|
423
438
|
status.js # aia status <feature>
|
|
424
439
|
reset.js # aia reset <step> <feature>
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -13,6 +13,7 @@ import { registerStatusCommand } from './commands/status.js';
|
|
|
13
13
|
import { registerResetCommand } from './commands/reset.js';
|
|
14
14
|
import { registerNextCommand } from './commands/next.js';
|
|
15
15
|
import { registerQuickCommand } from './commands/quick.js';
|
|
16
|
+
import { registerIterateCommand } from './commands/iterate.js';
|
|
16
17
|
import { registerUiCommand } from './commands/ui.js';
|
|
17
18
|
|
|
18
19
|
export function createCli() {
|
|
@@ -31,6 +32,7 @@ export function createCli() {
|
|
|
31
32
|
registerRepoCommand(program);
|
|
32
33
|
registerStatusCommand(program);
|
|
33
34
|
registerResetCommand(program);
|
|
35
|
+
registerIterateCommand(program);
|
|
34
36
|
registerUiCommand(program);
|
|
35
37
|
|
|
36
38
|
return program;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { resetStep } from '../services/status.js';
|
|
3
|
+
import { runStep } from '../services/runner.js';
|
|
4
|
+
|
|
5
|
+
export function registerIterateCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('iterate <step> <feature> <instructions>')
|
|
8
|
+
.description('Re-run a step with additional instructions to refine the output')
|
|
9
|
+
.option('-v, --verbose', 'Show AI thinking/tool usage')
|
|
10
|
+
.option('-a, --apply', 'Force agent mode (file editing)')
|
|
11
|
+
.action(async (step, feature, instructions, opts) => {
|
|
12
|
+
try {
|
|
13
|
+
await resetStep(feature, step);
|
|
14
|
+
console.log(chalk.gray(`Reset "${step}" — iterating with new instructions...`));
|
|
15
|
+
|
|
16
|
+
await runStep(step, feature, {
|
|
17
|
+
instructions,
|
|
18
|
+
verbose: opts.verbose,
|
|
19
|
+
apply: opts.apply,
|
|
20
|
+
});
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error(chalk.red(err.message));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
package/src/prompt-builder.js
CHANGED
|
@@ -77,7 +77,7 @@ async function loadPromptTemplate(step, root) {
|
|
|
77
77
|
return content;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
export async function buildPrompt(feature, step, { description, root = process.cwd() } = {}) {
|
|
80
|
+
export async function buildPrompt(feature, step, { description, instructions, root = process.cwd() } = {}) {
|
|
81
81
|
const config = await loadConfig(root);
|
|
82
82
|
|
|
83
83
|
const [context, knowledgeCategories, initSpecs, featureContent, previousOutput, task] = await Promise.all([
|
|
@@ -127,6 +127,12 @@ export async function buildPrompt(feature, step, { description, root = process.c
|
|
|
127
127
|
parts.push('\n\nThe above is a previous version of this step. Rewrite it incorporating any new information, answers to questions, and improvements.');
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
if (instructions) {
|
|
131
|
+
parts.push('\n\n=== ITERATION INSTRUCTIONS ===\n');
|
|
132
|
+
parts.push('Apply the following changes/feedback to the previous output:\n');
|
|
133
|
+
parts.push(instructions);
|
|
134
|
+
}
|
|
135
|
+
|
|
130
136
|
parts.push('\n\n=== TASK ===\n');
|
|
131
137
|
parts.push(task);
|
|
132
138
|
|
package/src/services/runner.js
CHANGED
|
@@ -8,7 +8,7 @@ import { callModel } from './model-call.js';
|
|
|
8
8
|
import { loadStatus, updateStepStatus } from './status.js';
|
|
9
9
|
import { logExecution } from '../logger.js';
|
|
10
10
|
|
|
11
|
-
export async function runStep(step, feature, { description, verbose = false, apply = false, root = process.cwd(), onData } = {}) {
|
|
11
|
+
export async function runStep(step, feature, { description, instructions, model: modelOverride, verbose = false, apply = false, root = process.cwd(), onData } = {}) {
|
|
12
12
|
if (!FEATURE_STEPS.includes(step)) {
|
|
13
13
|
throw new Error(`Unknown step "${step}". Valid steps: ${FEATURE_STEPS.join(', ')}`);
|
|
14
14
|
}
|
|
@@ -26,8 +26,8 @@ export async function runStep(step, feature, { description, verbose = false, app
|
|
|
26
26
|
const shouldApply = apply || APPLY_STEPS.has(step);
|
|
27
27
|
|
|
28
28
|
try {
|
|
29
|
-
const model = await resolveModel(step, root);
|
|
30
|
-
const prompt = await buildPrompt(feature, step, { description, root });
|
|
29
|
+
const model = modelOverride || await resolveModel(step, root);
|
|
30
|
+
const prompt = await buildPrompt(feature, step, { description, instructions, root });
|
|
31
31
|
|
|
32
32
|
const start = performance.now();
|
|
33
33
|
const output = await callModel(model, prompt, { verbose, apply: shouldApply, onData });
|
package/src/ui/api/constants.js
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import { FEATURE_STEPS, STEP_STATUS, QUICK_STEPS } from '../../constants.js';
|
|
2
|
+
import { loadConfig } from '../../models.js';
|
|
2
3
|
import { json } from '../router.js';
|
|
3
4
|
|
|
4
5
|
export function registerConstantsRoutes(router) {
|
|
5
6
|
router.get('/api/constants', (req, res) => {
|
|
6
7
|
json(res, { FEATURE_STEPS, STEP_STATUS, QUICK_STEPS });
|
|
7
8
|
});
|
|
9
|
+
|
|
10
|
+
// List all unique models from config
|
|
11
|
+
router.get('/api/models', async (req, res, { root }) => {
|
|
12
|
+
try {
|
|
13
|
+
const config = await loadConfig(root);
|
|
14
|
+
const all = new Set();
|
|
15
|
+
for (const models of Object.values(config.models || {})) {
|
|
16
|
+
for (const entry of models) {
|
|
17
|
+
all.add(entry.model);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
json(res, [...all]);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
json(res, []);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
8
25
|
}
|
package/src/ui/api/features.js
CHANGED
|
@@ -97,6 +97,7 @@ export function registerFeatureRoutes(router) {
|
|
|
97
97
|
try {
|
|
98
98
|
const output = await runStep(params.step, params.name, {
|
|
99
99
|
description: body.description,
|
|
100
|
+
model: body.model || undefined,
|
|
100
101
|
verbose: true,
|
|
101
102
|
apply: body.apply || false,
|
|
102
103
|
root,
|
|
@@ -157,6 +158,34 @@ export function registerFeatureRoutes(router) {
|
|
|
157
158
|
res.end();
|
|
158
159
|
});
|
|
159
160
|
|
|
161
|
+
// Iterate a step with SSE streaming (reset + re-run with instructions)
|
|
162
|
+
router.post('/api/features/:name/iterate/:step', async (req, res, { params, root, parseBody }) => {
|
|
163
|
+
const body = await parseBody();
|
|
164
|
+
sseHeaders(res);
|
|
165
|
+
sseSend(res, 'status', { step: params.step, status: 'iterating' });
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
await resetStep(params.name, params.step, root);
|
|
169
|
+
|
|
170
|
+
const onData = ({ type, text }) => {
|
|
171
|
+
try { sseSend(res, 'log', { type, text }); } catch {}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const output = await runStep(params.step, params.name, {
|
|
175
|
+
instructions: body.instructions,
|
|
176
|
+
model: body.model || undefined,
|
|
177
|
+
verbose: true,
|
|
178
|
+
apply: body.apply || false,
|
|
179
|
+
root,
|
|
180
|
+
onData,
|
|
181
|
+
});
|
|
182
|
+
sseSend(res, 'done', { step: params.step });
|
|
183
|
+
} catch (err) {
|
|
184
|
+
sseSend(res, 'error', { message: err.message });
|
|
185
|
+
}
|
|
186
|
+
res.end();
|
|
187
|
+
});
|
|
188
|
+
|
|
160
189
|
// Reset a step
|
|
161
190
|
router.post('/api/features/:name/reset/:step', async (req, res, { params, root }) => {
|
|
162
191
|
try {
|
|
@@ -138,75 +138,121 @@ function LogViewer({ logs }) {
|
|
|
138
138
|
}, logs.join(''));
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
function
|
|
141
|
+
function ModelSelect({ model, onChange, disabled }) {
|
|
142
|
+
const [models, setModels] = React.useState([]);
|
|
143
|
+
|
|
144
|
+
React.useEffect(() => {
|
|
145
|
+
api.get('/models').then(setModels).catch(() => {});
|
|
146
|
+
}, []);
|
|
147
|
+
|
|
148
|
+
return React.createElement('select', {
|
|
149
|
+
value: model,
|
|
150
|
+
onChange: e => onChange(e.target.value),
|
|
151
|
+
disabled,
|
|
152
|
+
className: 'bg-aia-card border border-aia-border rounded px-2 py-1 text-xs text-slate-300 focus:border-aia-accent focus:outline-none',
|
|
153
|
+
},
|
|
154
|
+
React.createElement('option', { value: '' }, 'Model: auto (weighted)'),
|
|
155
|
+
...models.map(m => React.createElement('option', { key: m, value: m }, m)),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function RunPanel({ name, step, stepStatus, onDone }) {
|
|
160
|
+
const isDone = stepStatus === 'done';
|
|
142
161
|
const [description, setDescription] = React.useState('');
|
|
162
|
+
const [instructions, setInstructions] = React.useState('');
|
|
163
|
+
const [model, setModel] = React.useState('');
|
|
143
164
|
const [apply, setApply] = React.useState(false);
|
|
144
165
|
const [running, setRunning] = React.useState(false);
|
|
145
166
|
const [result, setResult] = React.useState(null);
|
|
146
167
|
const [err, setErr] = React.useState(null);
|
|
147
168
|
const [logs, setLogs] = React.useState([]);
|
|
148
169
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
setLogs([]);
|
|
170
|
+
const sseCallbacks = {
|
|
171
|
+
onLog: (text) => setLogs(prev => [...prev, text]),
|
|
172
|
+
onStatus: (data) => setLogs(prev => [...prev, `[${data.status}] ${data.step || ''}\n`]),
|
|
173
|
+
};
|
|
154
174
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
175
|
+
async function run() {
|
|
176
|
+
setRunning(true); setResult(null); setErr(null); setLogs([]);
|
|
177
|
+
const res = await streamPost(`/features/${name}/run/${step}`, { description, apply, model: model || undefined }, sseCallbacks);
|
|
178
|
+
if (res.ok) { setResult('Step completed.'); if (onDone) onDone(); }
|
|
179
|
+
else setErr(res.error);
|
|
180
|
+
setRunning(false);
|
|
181
|
+
}
|
|
159
182
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
183
|
+
async function iterate() {
|
|
184
|
+
setRunning(true); setResult(null); setErr(null); setLogs([]);
|
|
185
|
+
const res = await streamPost(`/features/${name}/iterate/${step}`, { instructions, apply, model: model || undefined }, sseCallbacks);
|
|
186
|
+
if (res.ok) { setResult('Iteration completed.'); setInstructions(''); if (onDone) onDone(); }
|
|
187
|
+
else setErr(res.error);
|
|
166
188
|
setRunning(false);
|
|
167
189
|
}
|
|
168
190
|
|
|
169
191
|
async function reset() {
|
|
170
|
-
try {
|
|
171
|
-
|
|
172
|
-
if (onDone) onDone();
|
|
173
|
-
} catch (e) {
|
|
174
|
-
setErr(e.message);
|
|
175
|
-
}
|
|
192
|
+
try { await api.post(`/features/${name}/reset/${step}`); if (onDone) onDone(); }
|
|
193
|
+
catch (e) { setErr(e.message); }
|
|
176
194
|
}
|
|
177
195
|
|
|
178
|
-
return React.createElement('div', { className: '
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
React.createElement('div', { className: 'flex items-center gap-4' },
|
|
189
|
-
React.createElement('label', { className: 'flex items-center gap-2 text-xs text-slate-400 cursor-pointer' },
|
|
190
|
-
React.createElement('input', {
|
|
191
|
-
type: 'checkbox',
|
|
192
|
-
checked: apply,
|
|
193
|
-
onChange: e => setApply(e.target.checked),
|
|
194
|
-
disabled: running,
|
|
195
|
-
className: 'rounded',
|
|
196
|
-
}),
|
|
197
|
-
'Agent mode (--apply)'
|
|
198
|
-
),
|
|
199
|
-
React.createElement('button', {
|
|
200
|
-
onClick: run,
|
|
196
|
+
return React.createElement('div', { className: 'space-y-3' },
|
|
197
|
+
|
|
198
|
+
// --- Run block (when step is not done) ---
|
|
199
|
+
!isDone && React.createElement('div', { className: 'bg-slate-900 border border-aia-border rounded p-4 space-y-3' },
|
|
200
|
+
React.createElement('h4', { className: 'text-sm font-semibold text-emerald-400' }, `Run: ${step}`),
|
|
201
|
+
React.createElement('input', {
|
|
202
|
+
type: 'text',
|
|
203
|
+
value: description,
|
|
204
|
+
onChange: e => setDescription(e.target.value),
|
|
205
|
+
placeholder: 'Optional description...',
|
|
201
206
|
disabled: running,
|
|
202
|
-
className: '
|
|
203
|
-
}
|
|
204
|
-
React.createElement('
|
|
205
|
-
|
|
207
|
+
className: 'w-full bg-aia-card border border-aia-border rounded px-3 py-1.5 text-sm text-slate-200 placeholder-slate-500 focus:border-emerald-400 focus:outline-none',
|
|
208
|
+
}),
|
|
209
|
+
React.createElement('div', { className: 'flex items-center gap-4 flex-wrap' },
|
|
210
|
+
React.createElement(ModelSelect, { model, onChange: setModel, disabled: running }),
|
|
211
|
+
React.createElement('label', { className: 'flex items-center gap-2 text-xs text-slate-400 cursor-pointer' },
|
|
212
|
+
React.createElement('input', { type: 'checkbox', checked: apply, onChange: e => setApply(e.target.checked), disabled: running, className: 'rounded' }),
|
|
213
|
+
'Agent mode (--apply)'
|
|
214
|
+
),
|
|
215
|
+
React.createElement('button', {
|
|
216
|
+
onClick: run, disabled: running,
|
|
217
|
+
className: 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30 rounded px-4 py-1.5 text-sm hover:bg-emerald-500/30 disabled:opacity-40',
|
|
218
|
+
}, running ? 'Running...' : 'Run Step'),
|
|
219
|
+
),
|
|
220
|
+
),
|
|
221
|
+
|
|
222
|
+
// --- Iterate block (when step is done) ---
|
|
223
|
+
isDone && React.createElement('div', { className: 'bg-slate-900 border border-violet-500/30 rounded p-4 space-y-3' },
|
|
224
|
+
React.createElement('div', { className: 'flex items-center justify-between' },
|
|
225
|
+
React.createElement('h4', { className: 'text-sm font-semibold text-violet-400' }, `Iterate: ${step}`),
|
|
226
|
+
React.createElement('span', { className: 'text-xs bg-emerald-500/20 text-emerald-400 px-2 py-0.5 rounded' }, 'done'),
|
|
227
|
+
),
|
|
228
|
+
React.createElement('p', { className: 'text-xs text-slate-500' }, 'The previous output will be used as base. Describe what to change below.'),
|
|
229
|
+
React.createElement('label', { className: 'text-xs font-medium text-slate-400 block' }, 'Iteration instructions'),
|
|
230
|
+
React.createElement('textarea', {
|
|
231
|
+
value: instructions,
|
|
232
|
+
onChange: e => setInstructions(e.target.value),
|
|
233
|
+
placeholder: 'e.g. "Add error handling for edge cases", "Focus more on mobile", "Split into smaller functions"...',
|
|
206
234
|
disabled: running,
|
|
207
|
-
|
|
208
|
-
|
|
235
|
+
rows: 3,
|
|
236
|
+
className: 'w-full bg-aia-card border border-aia-border rounded px-3 py-2 text-sm text-slate-200 placeholder-slate-500 focus:border-violet-400 focus:outline-none resize-y',
|
|
237
|
+
}),
|
|
238
|
+
React.createElement('div', { className: 'flex items-center gap-4 flex-wrap' },
|
|
239
|
+
React.createElement(ModelSelect, { model, onChange: setModel, disabled: running }),
|
|
240
|
+
React.createElement('label', { className: 'flex items-center gap-2 text-xs text-slate-400 cursor-pointer' },
|
|
241
|
+
React.createElement('input', { type: 'checkbox', checked: apply, onChange: e => setApply(e.target.checked), disabled: running, className: 'rounded' }),
|
|
242
|
+
'Agent mode (--apply)'
|
|
243
|
+
),
|
|
244
|
+
React.createElement('button', {
|
|
245
|
+
onClick: iterate, disabled: running || !instructions.trim(),
|
|
246
|
+
className: 'bg-violet-500/20 text-violet-400 border border-violet-500/30 rounded px-4 py-1.5 text-sm hover:bg-violet-500/30 disabled:opacity-40',
|
|
247
|
+
}, running ? 'Iterating...' : 'Iterate'),
|
|
248
|
+
React.createElement('button', {
|
|
249
|
+
onClick: reset, disabled: running,
|
|
250
|
+
className: 'text-slate-500 hover:text-slate-300 text-xs',
|
|
251
|
+
}, 'Reset to pending'),
|
|
252
|
+
),
|
|
209
253
|
),
|
|
254
|
+
|
|
255
|
+
// --- Shared log viewer + results ---
|
|
210
256
|
React.createElement(LogViewer, { logs }),
|
|
211
257
|
result && React.createElement('p', { className: 'text-emerald-400 text-xs' }, result),
|
|
212
258
|
err && React.createElement('p', { className: 'text-red-400 text-xs' }, err),
|
|
@@ -258,8 +304,8 @@ export function FeatureDetail({ name }) {
|
|
|
258
304
|
)
|
|
259
305
|
),
|
|
260
306
|
|
|
261
|
-
// Run panel
|
|
262
|
-
activeStep && React.createElement(RunPanel, { name, step: activeStep, onDone: load }),
|
|
307
|
+
// Run / Iterate panel
|
|
308
|
+
activeStep && React.createElement(RunPanel, { name, step: activeStep, stepStatus: steps[activeStep], onDone: load }),
|
|
263
309
|
|
|
264
310
|
// File tabs
|
|
265
311
|
React.createElement('div', { className: 'flex gap-1 border-b border-aia-border' },
|