@bamptee/aia-code 0.2.0 → 0.4.0
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/package.json +1 -1
- package/src/cli.js +2 -0
- package/src/commands/next.js +28 -0
- package/src/commands/run.js +5 -3
- package/src/prompt-builder.js +22 -4
- package/src/providers/anthropic.js +8 -2
- package/src/providers/cli-runner.js +34 -14
- package/src/providers/gemini.js +5 -2
- package/src/providers/openai.js +5 -2
- package/src/services/model-call.js +4 -3
- package/src/services/runner.js +3 -3
- package/src/services/status.js +2 -5
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { registerRunCommand } from './commands/run.js';
|
|
|
5
5
|
import { registerRepoCommand } from './commands/repo.js';
|
|
6
6
|
import { registerStatusCommand } from './commands/status.js';
|
|
7
7
|
import { registerResetCommand } from './commands/reset.js';
|
|
8
|
+
import { registerNextCommand } from './commands/next.js';
|
|
8
9
|
|
|
9
10
|
export function createCli() {
|
|
10
11
|
const program = new Command();
|
|
@@ -17,6 +18,7 @@ export function createCli() {
|
|
|
17
18
|
registerInitCommand(program);
|
|
18
19
|
registerFeatureCommand(program);
|
|
19
20
|
registerRunCommand(program);
|
|
21
|
+
registerNextCommand(program);
|
|
20
22
|
registerRepoCommand(program);
|
|
21
23
|
registerStatusCommand(program);
|
|
22
24
|
registerResetCommand(program);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { loadStatus } from '../services/status.js';
|
|
3
|
+
import { runStep } from '../services/runner.js';
|
|
4
|
+
|
|
5
|
+
export function registerNextCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('next <feature> [description]')
|
|
8
|
+
.description('Run the next pending step for a feature')
|
|
9
|
+
.option('-v, --verbose', 'Show CLI logs (thinking, tool use, etc.)')
|
|
10
|
+
.option('-a, --apply', 'Let the AI edit and create files in the project')
|
|
11
|
+
.action(async (feature, description, opts) => {
|
|
12
|
+
try {
|
|
13
|
+
const status = await loadStatus(feature);
|
|
14
|
+
const nextStep = status.current_step;
|
|
15
|
+
|
|
16
|
+
if (!nextStep) {
|
|
17
|
+
console.log(chalk.green(`All steps completed for feature "${feature}".`));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(chalk.cyan(`[next] Running step: ${nextStep}`));
|
|
22
|
+
await runStep(nextStep, feature, { description, verbose: opts.verbose, apply: opts.apply });
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.error(chalk.red(err.message));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
package/src/commands/run.js
CHANGED
|
@@ -3,11 +3,13 @@ import { runStep } from '../services/runner.js';
|
|
|
3
3
|
|
|
4
4
|
export function registerRunCommand(program) {
|
|
5
5
|
program
|
|
6
|
-
.command('run <step> <feature>')
|
|
6
|
+
.command('run <step> <feature> [description]')
|
|
7
7
|
.description('Execute a step for a feature using the configured AI model')
|
|
8
|
-
.
|
|
8
|
+
.option('-v, --verbose', 'Show CLI logs (thinking, tool use, etc.)')
|
|
9
|
+
.option('-a, --apply', 'Let the AI edit and create files in the project')
|
|
10
|
+
.action(async (step, feature, description, opts) => {
|
|
9
11
|
try {
|
|
10
|
-
await runStep(step, feature);
|
|
12
|
+
await runStep(step, feature, { description, verbose: opts.verbose, apply: opts.apply });
|
|
11
13
|
} catch (err) {
|
|
12
14
|
console.error(chalk.red(err.message));
|
|
13
15
|
process.exit(1);
|
package/src/prompt-builder.js
CHANGED
|
@@ -44,9 +44,14 @@ async function loadFeatureFiles(feature, step, root) {
|
|
|
44
44
|
return sections.join('\n\n');
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
async function loadPreviousOutput(feature, step, root) {
|
|
48
|
+
const filePath = path.join(root, AIA_DIR, 'features', feature, `${step}.md`);
|
|
49
|
+
return readIfExists(filePath);
|
|
50
|
+
}
|
|
51
|
+
|
|
47
52
|
async function resolveKnowledgeCategories(feature, config, root) {
|
|
48
|
-
const
|
|
49
|
-
const raw = await readIfExists(
|
|
53
|
+
const statusFile = path.join(root, AIA_DIR, 'features', feature, 'status.yaml');
|
|
54
|
+
const raw = await readIfExists(statusFile);
|
|
50
55
|
|
|
51
56
|
if (raw) {
|
|
52
57
|
const status = yaml.parse(raw);
|
|
@@ -67,13 +72,14 @@ async function loadPromptTemplate(step, root) {
|
|
|
67
72
|
return content;
|
|
68
73
|
}
|
|
69
74
|
|
|
70
|
-
export async function buildPrompt(feature, step, root = process.cwd()) {
|
|
75
|
+
export async function buildPrompt(feature, step, { description, root = process.cwd() } = {}) {
|
|
71
76
|
const config = await loadConfig(root);
|
|
72
77
|
|
|
73
|
-
const [context, knowledgeCategories, featureContent, task] = await Promise.all([
|
|
78
|
+
const [context, knowledgeCategories, featureContent, previousOutput, task] = await Promise.all([
|
|
74
79
|
loadContextFiles(config, root),
|
|
75
80
|
resolveKnowledgeCategories(feature, config, root),
|
|
76
81
|
loadFeatureFiles(feature, step, root),
|
|
82
|
+
loadPreviousOutput(feature, step, root),
|
|
77
83
|
loadPromptTemplate(step, root),
|
|
78
84
|
]);
|
|
79
85
|
|
|
@@ -81,6 +87,12 @@ export async function buildPrompt(feature, step, root = process.cwd()) {
|
|
|
81
87
|
|
|
82
88
|
const parts = [];
|
|
83
89
|
|
|
90
|
+
if (description) {
|
|
91
|
+
parts.push('=== DESCRIPTION ===\n');
|
|
92
|
+
parts.push(description);
|
|
93
|
+
parts.push('');
|
|
94
|
+
}
|
|
95
|
+
|
|
84
96
|
parts.push('=== CONTEXT ===\n');
|
|
85
97
|
parts.push(context || '(no context files)');
|
|
86
98
|
|
|
@@ -90,6 +102,12 @@ export async function buildPrompt(feature, step, root = process.cwd()) {
|
|
|
90
102
|
parts.push('\n\n=== FEATURE ===\n');
|
|
91
103
|
parts.push(featureContent || '(no prior steps)');
|
|
92
104
|
|
|
105
|
+
if (previousOutput) {
|
|
106
|
+
parts.push('\n\n=== PREVIOUS OUTPUT ===\n');
|
|
107
|
+
parts.push(previousOutput);
|
|
108
|
+
parts.push('\n\nThe above is a previous version of this step. Rewrite it incorporating any new information, answers to questions, and improvements.');
|
|
109
|
+
}
|
|
110
|
+
|
|
93
111
|
parts.push('\n\n=== TASK ===\n');
|
|
94
112
|
parts.push(task);
|
|
95
113
|
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { runCli } from './cli-runner.js';
|
|
2
2
|
|
|
3
|
-
export async function generate(prompt, model) {
|
|
3
|
+
export async function generate(prompt, model, { verbose = false, apply = false } = {}) {
|
|
4
4
|
const args = ['-p'];
|
|
5
5
|
if (model) {
|
|
6
6
|
args.push('--model', model);
|
|
7
7
|
}
|
|
8
|
+
if (apply) {
|
|
9
|
+
args.push('--allowedTools', 'Edit', 'Write', 'Bash', 'Read', 'Glob', 'Grep');
|
|
10
|
+
}
|
|
11
|
+
if (verbose) {
|
|
12
|
+
args.push('--verbose');
|
|
13
|
+
}
|
|
8
14
|
args.push('-');
|
|
9
15
|
|
|
10
|
-
return runCli('claude', args, { stdin: prompt });
|
|
16
|
+
return runCli('claude', args, { stdin: prompt, verbose });
|
|
11
17
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import chalk from 'chalk';
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 180_000;
|
|
4
5
|
|
|
5
|
-
export function runCli(command, args, { stdin: stdinData,
|
|
6
|
+
export function runCli(command, args, { stdin: stdinData, verbose = false, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS } = {}) {
|
|
6
7
|
return new Promise((resolve, reject) => {
|
|
7
8
|
const child = spawn(command, args, {
|
|
8
9
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -11,37 +12,56 @@ export function runCli(command, args, { stdin: stdinData, timeoutMs = DEFAULT_TI
|
|
|
11
12
|
|
|
12
13
|
const chunks = [];
|
|
13
14
|
let stderr = '';
|
|
15
|
+
let settled = false;
|
|
16
|
+
|
|
17
|
+
function resetTimer() {
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
timer = setTimeout(() => {
|
|
20
|
+
child.kill('SIGTERM');
|
|
21
|
+
finish(new Error(`CLI idle timeout (no output for ${idleTimeoutMs / 1000}s): ${command} ${args.join(' ')}`));
|
|
22
|
+
}, idleTimeoutMs);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function finish(err, result) {
|
|
26
|
+
if (settled) return;
|
|
27
|
+
settled = true;
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
if (err) reject(err);
|
|
30
|
+
else resolve(result);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let timer;
|
|
34
|
+
resetTimer();
|
|
14
35
|
|
|
15
36
|
child.stdout.on('data', (data) => {
|
|
16
37
|
const text = data.toString();
|
|
17
38
|
process.stdout.write(text);
|
|
18
39
|
chunks.push(text);
|
|
40
|
+
resetTimer();
|
|
19
41
|
});
|
|
20
42
|
|
|
21
43
|
child.stderr.on('data', (data) => {
|
|
22
|
-
|
|
44
|
+
const text = data.toString();
|
|
45
|
+
stderr += text;
|
|
46
|
+
if (verbose) {
|
|
47
|
+
process.stderr.write(chalk.gray(text));
|
|
48
|
+
}
|
|
49
|
+
resetTimer();
|
|
23
50
|
});
|
|
24
51
|
|
|
25
|
-
const timer = setTimeout(() => {
|
|
26
|
-
child.kill('SIGTERM');
|
|
27
|
-
reject(new Error(`CLI timed out after ${timeoutMs / 1000}s: ${command} ${args.join(' ')}`));
|
|
28
|
-
}, timeoutMs);
|
|
29
|
-
|
|
30
52
|
child.on('error', (err) => {
|
|
31
|
-
clearTimeout(timer);
|
|
32
53
|
if (err.code === 'ENOENT') {
|
|
33
|
-
|
|
54
|
+
finish(new Error(`CLI not found: "${command}". Make sure it is installed and in your PATH.`));
|
|
34
55
|
} else {
|
|
35
|
-
|
|
56
|
+
finish(err);
|
|
36
57
|
}
|
|
37
58
|
});
|
|
38
59
|
|
|
39
60
|
child.on('close', (code) => {
|
|
40
|
-
clearTimeout(timer);
|
|
41
61
|
if (code !== 0) {
|
|
42
|
-
|
|
62
|
+
finish(new Error(`${command} exited with code ${code}:\n${stderr.trim()}`));
|
|
43
63
|
} else {
|
|
44
|
-
|
|
64
|
+
finish(null, chunks.join(''));
|
|
45
65
|
}
|
|
46
66
|
});
|
|
47
67
|
|
package/src/providers/gemini.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { runCli } from './cli-runner.js';
|
|
2
2
|
|
|
3
|
-
export async function generate(prompt, model) {
|
|
3
|
+
export async function generate(prompt, model, { verbose = false, apply = false } = {}) {
|
|
4
4
|
const args = [];
|
|
5
5
|
if (model) {
|
|
6
6
|
args.push('-m', model);
|
|
7
7
|
}
|
|
8
|
+
if (apply) {
|
|
9
|
+
args.push('--sandbox', 'false');
|
|
10
|
+
}
|
|
8
11
|
args.push('-');
|
|
9
12
|
|
|
10
|
-
return runCli('gemini', args, { stdin: prompt });
|
|
13
|
+
return runCli('gemini', args, { stdin: prompt, verbose });
|
|
11
14
|
}
|
package/src/providers/openai.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { runCli } from './cli-runner.js';
|
|
2
2
|
|
|
3
|
-
export async function generate(prompt, model) {
|
|
3
|
+
export async function generate(prompt, model, { verbose = false, apply = false } = {}) {
|
|
4
4
|
const args = ['exec'];
|
|
5
5
|
if (model) {
|
|
6
6
|
args.push('-c', `model="${model}"`);
|
|
7
7
|
}
|
|
8
|
+
if (apply) {
|
|
9
|
+
args.push('-c', 'approval_policy="auto-edit"');
|
|
10
|
+
}
|
|
8
11
|
args.push('-');
|
|
9
12
|
|
|
10
|
-
return runCli('codex', args, { stdin: prompt });
|
|
13
|
+
return runCli('codex', args, { stdin: prompt, verbose });
|
|
11
14
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { resolveModelAlias } from '../providers/registry.js';
|
|
3
3
|
|
|
4
|
-
export async function callModel(model, prompt) {
|
|
4
|
+
export async function callModel(model, prompt, { verbose = false, apply = false } = {}) {
|
|
5
5
|
const resolved = resolveModelAlias(model);
|
|
6
6
|
const displayName = resolved.model ?? `${model} (CLI default)`;
|
|
7
|
+
const mode = apply ? 'agent' : 'print';
|
|
7
8
|
|
|
8
|
-
console.log(chalk.yellow(`[AI] Calling ${displayName}...`));
|
|
9
|
+
console.log(chalk.yellow(`[AI] Calling ${displayName} (${mode} mode)...`));
|
|
9
10
|
|
|
10
|
-
return resolved.provider.generate(prompt, resolved.model);
|
|
11
|
+
return resolved.provider.generate(prompt, resolved.model, { verbose, apply });
|
|
11
12
|
}
|
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, root = process.cwd()) {
|
|
11
|
+
export async function runStep(step, feature, { description, verbose = false, apply = false, root = process.cwd() } = {}) {
|
|
12
12
|
if (!FEATURE_STEPS.includes(step)) {
|
|
13
13
|
throw new Error(`Unknown step "${step}". Valid steps: ${FEATURE_STEPS.join(', ')}`);
|
|
14
14
|
}
|
|
@@ -25,10 +25,10 @@ export async function runStep(step, feature, root = process.cwd()) {
|
|
|
25
25
|
|
|
26
26
|
try {
|
|
27
27
|
const model = await resolveModel(step, root);
|
|
28
|
-
const prompt = await buildPrompt(feature, step, root);
|
|
28
|
+
const prompt = await buildPrompt(feature, step, { description, root });
|
|
29
29
|
|
|
30
30
|
const start = performance.now();
|
|
31
|
-
const output = await callModel(model, prompt);
|
|
31
|
+
const output = await callModel(model, prompt, { verbose, apply });
|
|
32
32
|
const duration = performance.now() - start;
|
|
33
33
|
|
|
34
34
|
const outputPath = path.join(root, AIA_DIR, 'features', feature, `${step}.md`);
|
package/src/services/status.js
CHANGED
|
@@ -38,7 +38,7 @@ export async function updateStepStatus(feature, step, value, root = process.cwd(
|
|
|
38
38
|
|
|
39
39
|
const stepIndex = FEATURE_STEPS.indexOf(step);
|
|
40
40
|
const nextStep = FEATURE_STEPS[stepIndex + 1] ?? null;
|
|
41
|
-
if (value === STEP_STATUS.DONE
|
|
41
|
+
if (value === STEP_STATUS.DONE) {
|
|
42
42
|
status.current_step = nextStep;
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -63,8 +63,5 @@ export async function resetStep(feature, step, root = process.cwd()) {
|
|
|
63
63
|
const content = yaml.stringify(status);
|
|
64
64
|
await fs.writeFile(statusPath(feature, root), content, 'utf-8');
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
if (await fs.pathExists(outputPath)) {
|
|
68
|
-
await fs.writeFile(outputPath, '', 'utf-8');
|
|
69
|
-
}
|
|
66
|
+
// Keep the existing output — it will be fed back as context on re-run
|
|
70
67
|
}
|