@thynameisjayvee/alpha-cli 0.1.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/LICENSE +21 -0
- package/README.md +143 -0
- package/bin/alpha.js +15 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +125 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/alpha.d.ts +64 -0
- package/dist/core/alpha.d.ts.map +1 -0
- package/dist/core/alpha.js +94 -0
- package/dist/core/alpha.js.map +1 -0
- package/dist/core/generator/index.d.ts +16 -0
- package/dist/core/generator/index.d.ts.map +1 -0
- package/dist/core/generator/index.js +231 -0
- package/dist/core/generator/index.js.map +1 -0
- package/dist/core/implementation/index.d.ts +22 -0
- package/dist/core/implementation/index.d.ts.map +1 -0
- package/dist/core/implementation/index.js +398 -0
- package/dist/core/implementation/index.js.map +1 -0
- package/dist/core/init/catalog.d.ts +23 -0
- package/dist/core/init/catalog.d.ts.map +1 -0
- package/dist/core/init/catalog.js +87 -0
- package/dist/core/init/catalog.js.map +1 -0
- package/dist/core/init/index.d.ts +10 -0
- package/dist/core/init/index.d.ts.map +1 -0
- package/dist/core/init/index.js +214 -0
- package/dist/core/init/index.js.map +1 -0
- package/dist/core/requirements/index.d.ts +10 -0
- package/dist/core/requirements/index.d.ts.map +1 -0
- package/dist/core/requirements/index.js +232 -0
- package/dist/core/requirements/index.js.map +1 -0
- package/dist/core/reviewer/index.d.ts +29 -0
- package/dist/core/reviewer/index.d.ts.map +1 -0
- package/dist/core/reviewer/index.js +419 -0
- package/dist/core/reviewer/index.js.map +1 -0
- package/dist/core/stack/index.d.ts +10 -0
- package/dist/core/stack/index.d.ts.map +1 -0
- package/dist/core/stack/index.js +243 -0
- package/dist/core/stack/index.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +128 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/config.d.ts +105 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +99 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/file-system.d.ts +17 -0
- package/dist/utils/file-system.d.ts.map +1 -0
- package/dist/utils/file-system.js +48 -0
- package/dist/utils/file-system.js.map +1 -0
- package/dist/utils/input.d.ts +18 -0
- package/dist/utils/input.d.ts.map +1 -0
- package/dist/utils/input.js +50 -0
- package/dist/utils/input.js.map +1 -0
- package/dist/utils/logger.d.ts +37 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +109 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/paths.d.ts +26 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +51 -0
- package/dist/utils/paths.js.map +1 -0
- package/package.json +79 -0
- package/scripts/postinstall.js +110 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { logger, emit } from '../../utils/logger.js';
|
|
3
|
+
import { writeFile, ensureDir, readJsonFile } from '../../utils/file-system.js';
|
|
4
|
+
import { getAlphaDir, getSpecsDir } from '../../utils/paths.js';
|
|
5
|
+
import { readInputJson } from '../../utils/input.js';
|
|
6
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
7
|
+
const SPEC_TYPES = [
|
|
8
|
+
'feature',
|
|
9
|
+
'bugfix',
|
|
10
|
+
'refactor',
|
|
11
|
+
'enhancement',
|
|
12
|
+
];
|
|
13
|
+
async function loadContext() {
|
|
14
|
+
const alphaDir = getAlphaDir();
|
|
15
|
+
let project = null;
|
|
16
|
+
let stack = null;
|
|
17
|
+
try {
|
|
18
|
+
project = await readJsonFile(`${alphaDir}/project-context.json`);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
project = null;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
stack = await readJsonFile(`${alphaDir}/tech-stack.json`);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
stack = null;
|
|
28
|
+
}
|
|
29
|
+
return { project, stack };
|
|
30
|
+
}
|
|
31
|
+
function slugify(title) {
|
|
32
|
+
return title
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.replace(/\s+/g, '-')
|
|
35
|
+
.replace(/[^a-z0-9-]/g, '');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build a Specification from flexible input. The caller (LLM) supplies the
|
|
39
|
+
* acceptance criteria, dependencies and implementation options; Alpha only
|
|
40
|
+
* fills structural defaults.
|
|
41
|
+
*/
|
|
42
|
+
function buildSpec(raw) {
|
|
43
|
+
const now = new Date().toISOString();
|
|
44
|
+
const type = SPEC_TYPES.includes(raw.type)
|
|
45
|
+
? raw.type
|
|
46
|
+
: 'feature';
|
|
47
|
+
return {
|
|
48
|
+
id: raw.id || uuidv4(),
|
|
49
|
+
title: raw.title || 'Untitled Specification',
|
|
50
|
+
type,
|
|
51
|
+
description: raw.description || '',
|
|
52
|
+
acceptanceCriteria: Array.isArray(raw.acceptanceCriteria)
|
|
53
|
+
? raw.acceptanceCriteria
|
|
54
|
+
: [],
|
|
55
|
+
dependencies: Array.isArray(raw.dependencies)
|
|
56
|
+
? raw.dependencies
|
|
57
|
+
: [],
|
|
58
|
+
implementationOptions: Array.isArray(raw.implementationOptions)
|
|
59
|
+
? raw.implementationOptions
|
|
60
|
+
: [],
|
|
61
|
+
metadata: {
|
|
62
|
+
createdAt: raw?.metadata?.createdAt || now,
|
|
63
|
+
updatedAt: now,
|
|
64
|
+
version: raw?.metadata?.version || '1.0.0',
|
|
65
|
+
status: raw?.metadata?.status || 'finalized',
|
|
66
|
+
codebasePath: process.cwd(),
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function renderSpecMarkdown(spec) {
|
|
71
|
+
const criteria = spec.acceptanceCriteria.length > 0
|
|
72
|
+
? spec.acceptanceCriteria
|
|
73
|
+
.map((c) => `- [${c.priority.toUpperCase()}] [${c.layer.toUpperCase()}] ${c.description}`)
|
|
74
|
+
.join('\n')
|
|
75
|
+
: '- _TODO: add Given/When/Then acceptance criteria across unit / integration / e2e layers_';
|
|
76
|
+
const dependencies = spec.dependencies.length > 0
|
|
77
|
+
? spec.dependencies
|
|
78
|
+
.map((d) => `- **${d.name}** (${d.type}) - ${d.status === 'exists' ? '✓' : '⚠'} ${d.description}`)
|
|
79
|
+
.join('\n')
|
|
80
|
+
: '- _TODO: list files/modules/services this spec depends on_';
|
|
81
|
+
const options = spec.implementationOptions.length > 0
|
|
82
|
+
? spec.implementationOptions
|
|
83
|
+
.map((opt, idx) => `### Option ${idx + 1}: ${opt.title}${opt.recommended ? ' ⭐ RECOMMENDED' : ''}\n\n${opt.description}\n\n**Approach:** ${opt.approach}\n\n**Pros:**\n${opt.pros
|
|
84
|
+
.map((p) => `- ${p}`)
|
|
85
|
+
.join('\n')}\n\n**Cons:**\n${opt.cons
|
|
86
|
+
.map((c) => `- ${c}`)
|
|
87
|
+
.join('\n')}\n\n**Estimated Effort:** ${opt.estimatedEffort}\n\n**Risks:**\n${opt.risks
|
|
88
|
+
.map((r) => `- ${r}`)
|
|
89
|
+
.join('\n')}`)
|
|
90
|
+
.join('\n\n---\n')
|
|
91
|
+
: '_TODO: propose 2-3 implementation options with pros/cons and a recommendation_';
|
|
92
|
+
return `# ${spec.title}
|
|
93
|
+
|
|
94
|
+
**Type:** ${spec.type}
|
|
95
|
+
**Status:** ${spec.metadata.status}
|
|
96
|
+
**Version:** ${spec.metadata.version}
|
|
97
|
+
**Created:** ${spec.metadata.createdAt}
|
|
98
|
+
|
|
99
|
+
## Description
|
|
100
|
+
|
|
101
|
+
${spec.description || '_TODO: describe the change_'}
|
|
102
|
+
|
|
103
|
+
## Dependencies
|
|
104
|
+
|
|
105
|
+
${dependencies}
|
|
106
|
+
|
|
107
|
+
## Acceptance Criteria
|
|
108
|
+
|
|
109
|
+
${criteria}
|
|
110
|
+
|
|
111
|
+
## Implementation Options
|
|
112
|
+
|
|
113
|
+
${options}
|
|
114
|
+
|
|
115
|
+
## Notes
|
|
116
|
+
|
|
117
|
+
This specification is intentionally flexible. Implementers should review the
|
|
118
|
+
options and choose the best approach based on current context and constraints.
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
async function finalizeSpec(spec) {
|
|
122
|
+
const specsDir = getSpecsDir();
|
|
123
|
+
await ensureDir(specsDir);
|
|
124
|
+
const specPath = `${specsDir}/${spec.id}-${slugify(spec.title)}.md`;
|
|
125
|
+
await writeFile(specPath, renderSpecMarkdown(spec));
|
|
126
|
+
logger.success(`Specification saved to ${specPath}`);
|
|
127
|
+
return specPath;
|
|
128
|
+
}
|
|
129
|
+
function renderAuthoringPrompt() {
|
|
130
|
+
return [
|
|
131
|
+
`# Specification Authoring`,
|
|
132
|
+
``,
|
|
133
|
+
`Author a specification, then submit it to Alpha:`,
|
|
134
|
+
``,
|
|
135
|
+
'```bash',
|
|
136
|
+
`alpha spec --input spec.json`,
|
|
137
|
+
`# or: echo '<json>' | alpha spec --input -`,
|
|
138
|
+
'```',
|
|
139
|
+
``,
|
|
140
|
+
`Expected JSON shape:`,
|
|
141
|
+
'```json',
|
|
142
|
+
`{`,
|
|
143
|
+
` "title": "...",`,
|
|
144
|
+
` "type": "feature | bugfix | refactor | enhancement",`,
|
|
145
|
+
` "description": "...",`,
|
|
146
|
+
` "acceptanceCriteria": [`,
|
|
147
|
+
` { "description": "Given..., When..., Then...", "layer": "unit|integration|e2e|manual", "priority": "must|should|could" }`,
|
|
148
|
+
` ],`,
|
|
149
|
+
` "dependencies": [`,
|
|
150
|
+
` { "name": "...", "type": "file|module|service|external|skill", "description": "...", "status": "exists|needs-creation|needs-modification" }`,
|
|
151
|
+
` ],`,
|
|
152
|
+
` "implementationOptions": [`,
|
|
153
|
+
` { "title": "...", "description": "...", "approach": "...", "pros": [], "cons": [], "estimatedEffort": "low|medium|high", "risks": [], "recommended": true }`,
|
|
154
|
+
` ]`,
|
|
155
|
+
`}`,
|
|
156
|
+
'```',
|
|
157
|
+
``,
|
|
158
|
+
`Tip: pass \`--title "..."\` to have Alpha write an empty skeleton file you`,
|
|
159
|
+
`can fill in directly under \`.alpha/specs/\`.`,
|
|
160
|
+
].join('\n');
|
|
161
|
+
}
|
|
162
|
+
async function specInteractive() {
|
|
163
|
+
const inquirer = (await import('inquirer')).default;
|
|
164
|
+
const chalk = (await import('chalk')).default;
|
|
165
|
+
logger.section('Specification Details');
|
|
166
|
+
const { specType } = await inquirer.prompt([
|
|
167
|
+
{
|
|
168
|
+
type: 'list',
|
|
169
|
+
name: 'specType',
|
|
170
|
+
message: chalk.cyan('What type of specification is this?'),
|
|
171
|
+
choices: SPEC_TYPES.map((t) => ({ name: t, value: t })),
|
|
172
|
+
},
|
|
173
|
+
]);
|
|
174
|
+
const { title } = await inquirer.prompt([
|
|
175
|
+
{
|
|
176
|
+
type: 'input',
|
|
177
|
+
name: 'title',
|
|
178
|
+
message: chalk.cyan('Specification title:'),
|
|
179
|
+
validate: (input) => (input.trim() ? true : 'Title is required'),
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
182
|
+
const { description } = await inquirer.prompt([
|
|
183
|
+
{
|
|
184
|
+
type: 'input',
|
|
185
|
+
name: 'description',
|
|
186
|
+
message: chalk.cyan('Brief description (1-2 sentences):'),
|
|
187
|
+
validate: (input) => input.trim() ? true : 'Description is required',
|
|
188
|
+
},
|
|
189
|
+
]);
|
|
190
|
+
return buildSpec({ type: specType, title, description });
|
|
191
|
+
}
|
|
192
|
+
export async function generateSpec(options = {}) {
|
|
193
|
+
try {
|
|
194
|
+
const inputData = await readInputJson(options.input);
|
|
195
|
+
if (options.interactive) {
|
|
196
|
+
const spec = await specInteractive();
|
|
197
|
+
const specPath = await finalizeSpec(spec);
|
|
198
|
+
emit('spec', { ...spec, path: specPath });
|
|
199
|
+
return spec;
|
|
200
|
+
}
|
|
201
|
+
if (inputData) {
|
|
202
|
+
const spec = buildSpec(inputData);
|
|
203
|
+
const specPath = await finalizeSpec(spec);
|
|
204
|
+
emit('spec', { ...spec, path: specPath });
|
|
205
|
+
return spec;
|
|
206
|
+
}
|
|
207
|
+
// Default emit mode.
|
|
208
|
+
if (options.title) {
|
|
209
|
+
const spec = buildSpec({
|
|
210
|
+
title: options.title,
|
|
211
|
+
type: options.type,
|
|
212
|
+
description: options.description,
|
|
213
|
+
});
|
|
214
|
+
const specPath = await finalizeSpec(spec);
|
|
215
|
+
emit('spec', { mode: 'skeleton', path: specPath, spec }, `${renderSpecMarkdown(spec)}\n\nSkeleton written to ${specPath}`);
|
|
216
|
+
return spec;
|
|
217
|
+
}
|
|
218
|
+
emit('spec', { mode: 'authoring' }, renderAuthoringPrompt());
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
logger.error(`Spec generation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
223
|
+
process.exitCode = 1;
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// CLI entry point
|
|
228
|
+
if (process.argv[1]?.endsWith('generate.ts')) {
|
|
229
|
+
generateSpec();
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/generator/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Options for the `implement` command.
|
|
4
|
+
*
|
|
5
|
+
* Alpha never edits code or spawns agents. `implement` parses the latest review
|
|
6
|
+
* report and emits a structured plan (per-finding prompts) for the calling LLM
|
|
7
|
+
* to execute itself. `--verify` re-runs the deterministic checks afterwards.
|
|
8
|
+
*/
|
|
9
|
+
export interface ImplementOptions {
|
|
10
|
+
/** Run the human-facing interactive selection (inquirer). */
|
|
11
|
+
interactive?: boolean;
|
|
12
|
+
/** Comma-separated severities to include, e.g. "critical,high". */
|
|
13
|
+
severity?: string;
|
|
14
|
+
/** Comma-separated finding ids (or 1-based indexes) to include. */
|
|
15
|
+
findings?: string;
|
|
16
|
+
/** Re-run deterministic checks to see if findings are resolved. */
|
|
17
|
+
verify?: boolean;
|
|
18
|
+
/** Delete the report when verification passes for every selected finding. */
|
|
19
|
+
cleanup?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function runImplementation(options?: ImplementOptions): Promise<void>;
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/implementation/index.ts"],"names":[],"mappings":";AAaA;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AA4bD,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAqFf"}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
import { logger, emit, isJsonMode } from '../../utils/logger.js';
|
|
5
|
+
import { readFile, deleteFile, fileExists } from '../../utils/file-system.js';
|
|
6
|
+
import { getReportsDir } from '../../utils/paths.js';
|
|
7
|
+
async function findLatestReport() {
|
|
8
|
+
const reportsDir = getReportsDir();
|
|
9
|
+
try {
|
|
10
|
+
try {
|
|
11
|
+
await fs.access(reportsDir);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const files = await fs.readdir(reportsDir);
|
|
17
|
+
const mdFiles = files.filter((f) => f.endsWith('.md'));
|
|
18
|
+
if (mdFiles.length === 0) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const filesWithStats = await Promise.all(mdFiles.map(async (file) => {
|
|
22
|
+
const filePath = path.join(reportsDir, file);
|
|
23
|
+
const stats = await fs.stat(filePath);
|
|
24
|
+
return { name: file, path: filePath, mtime: stats.mtime };
|
|
25
|
+
}));
|
|
26
|
+
filesWithStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
27
|
+
return filesWithStats[0]?.path || null;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
logger.warn(`Failed to find latest report: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function parseReport(reportPath) {
|
|
35
|
+
const content = await readFile(reportPath);
|
|
36
|
+
const filename = path.basename(reportPath);
|
|
37
|
+
const idMatch = filename.match(/^([a-f0-9-]+)/i);
|
|
38
|
+
const reportId = idMatch ? idMatch[1] : 'unknown';
|
|
39
|
+
const { marked } = await import('marked');
|
|
40
|
+
const tokens = marked.lexer(content);
|
|
41
|
+
const findings = [];
|
|
42
|
+
let currentFinding = null;
|
|
43
|
+
let currentSection = '';
|
|
44
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
45
|
+
const token = tokens[i];
|
|
46
|
+
if (token.type === 'heading' && token.depth === 3) {
|
|
47
|
+
const headingText = 'text' in token ? token.text : '';
|
|
48
|
+
if (headingText.startsWith('Finding')) {
|
|
49
|
+
if (currentFinding && currentFinding.title) {
|
|
50
|
+
findings.push(currentFinding);
|
|
51
|
+
}
|
|
52
|
+
currentFinding = {
|
|
53
|
+
id: `finding-${findings.length}`,
|
|
54
|
+
title: headingText.replace(/^Finding\s+F?-?\d+:?\s*/i, '').trim(),
|
|
55
|
+
description: '',
|
|
56
|
+
severity: 'medium',
|
|
57
|
+
location: undefined,
|
|
58
|
+
dependencies: [],
|
|
59
|
+
recommendationOptions: [],
|
|
60
|
+
};
|
|
61
|
+
currentSection = '';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (currentFinding && token.type === 'paragraph') {
|
|
65
|
+
const paragraphText = 'text' in token ? token.text : '';
|
|
66
|
+
if (paragraphText.includes('**Severity:**')) {
|
|
67
|
+
const severityMatch = paragraphText.match(/\*\*Severity:\*\*\s*(🔴\s*)?(\w+)/i);
|
|
68
|
+
if (severityMatch && severityMatch[2]) {
|
|
69
|
+
currentFinding.severity =
|
|
70
|
+
severityMatch[2].toLowerCase();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (paragraphText.includes('**Location:**')) {
|
|
74
|
+
const locationMatch = paragraphText.match(/\*\*Location:\*\*\s*`([^`]+)`/);
|
|
75
|
+
if (locationMatch) {
|
|
76
|
+
const locationParts = locationMatch[1].split(':');
|
|
77
|
+
currentFinding.location = {
|
|
78
|
+
file: locationParts[0],
|
|
79
|
+
line: parseInt(locationParts[1] || '0'),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (currentFinding &&
|
|
85
|
+
currentSection !== 'options' &&
|
|
86
|
+
token.type === 'paragraph') {
|
|
87
|
+
const paragraphText = 'text' in token ? token.text : '';
|
|
88
|
+
if (!paragraphText.includes('**Severity:**') &&
|
|
89
|
+
!paragraphText.includes('**Location:**') &&
|
|
90
|
+
!paragraphText.includes('Option')) {
|
|
91
|
+
currentFinding.description += paragraphText + ' ';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (token.type === 'heading' && token.depth === 4) {
|
|
95
|
+
const headingText = 'text' in token ? token.text : '';
|
|
96
|
+
if (headingText.toLowerCase().includes('option')) {
|
|
97
|
+
currentSection = 'options';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (currentFinding &&
|
|
101
|
+
currentSection === 'options' &&
|
|
102
|
+
token.type === 'list') {
|
|
103
|
+
const listToken = token;
|
|
104
|
+
for (const item of listToken.items) {
|
|
105
|
+
const itemText = 'text' in item ? item.text : '';
|
|
106
|
+
if (itemText.match(/^\d+\.\s+\*\*(.*?)\*\*/)) {
|
|
107
|
+
const titleMatch = itemText.match(/^\d+\.\s+\*\*(.*?)\*\*/);
|
|
108
|
+
const title = titleMatch ? titleMatch[1].trim() : 'Unknown Option';
|
|
109
|
+
const isRecommended = itemText.includes('⭐') ||
|
|
110
|
+
itemText.toLowerCase().includes('recommended');
|
|
111
|
+
const effortMatch = itemText.match(/\*\*Estimated Effort:\*\*\s*(\w+)/i);
|
|
112
|
+
const effort = effortMatch
|
|
113
|
+
? effortMatch[1].toLowerCase()
|
|
114
|
+
: 'medium';
|
|
115
|
+
const steps = [];
|
|
116
|
+
if ('tokens' in item && item.tokens) {
|
|
117
|
+
for (const subToken of item.tokens) {
|
|
118
|
+
if (subToken.type === 'list') {
|
|
119
|
+
const subList = subToken;
|
|
120
|
+
for (const subItem of subList.items) {
|
|
121
|
+
if ('text' in subItem) {
|
|
122
|
+
steps.push(subItem.text.trim());
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
currentFinding.recommendationOptions.push({
|
|
129
|
+
id: `opt-${currentFinding.recommendationOptions.length}`,
|
|
130
|
+
title,
|
|
131
|
+
description: '',
|
|
132
|
+
implementationSteps: steps,
|
|
133
|
+
estimatedEffort: effort,
|
|
134
|
+
risks: [],
|
|
135
|
+
recommended: isRecommended,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (currentFinding && currentFinding.title) {
|
|
142
|
+
findings.push(currentFinding);
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
id: reportId,
|
|
146
|
+
title: `Review Report: ${reportId}`,
|
|
147
|
+
reviewTarget: 'codebase',
|
|
148
|
+
findings,
|
|
149
|
+
summary: {
|
|
150
|
+
totalFindings: findings.length,
|
|
151
|
+
critical: findings.filter((f) => f.severity === 'critical').length,
|
|
152
|
+
high: findings.filter((f) => f.severity === 'high').length,
|
|
153
|
+
medium: findings.filter((f) => f.severity === 'medium').length,
|
|
154
|
+
low: findings.filter((f) => f.severity === 'low').length,
|
|
155
|
+
info: findings.filter((f) => f.severity === 'info').length,
|
|
156
|
+
},
|
|
157
|
+
createdAt: new Date().toISOString(),
|
|
158
|
+
status: 'ready-for-implementation',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Non-interactive finding selection driven by flags.
|
|
163
|
+
* `--findings` takes precedence over `--severity`; with neither, all are kept.
|
|
164
|
+
*/
|
|
165
|
+
function selectFindingsByFlags(findings, options) {
|
|
166
|
+
if (options.findings) {
|
|
167
|
+
const wanted = options.findings
|
|
168
|
+
.split(',')
|
|
169
|
+
.map((s) => s.trim())
|
|
170
|
+
.filter(Boolean);
|
|
171
|
+
return findings.filter((f, idx) => {
|
|
172
|
+
const oneBasedIndex = String(idx + 1);
|
|
173
|
+
return wanted.includes(f.id) || wanted.includes(oneBasedIndex);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (options.severity) {
|
|
177
|
+
const wanted = new Set(options.severity
|
|
178
|
+
.split(',')
|
|
179
|
+
.map((s) => s.trim().toLowerCase())
|
|
180
|
+
.filter(Boolean));
|
|
181
|
+
return findings.filter((f) => wanted.has(f.severity));
|
|
182
|
+
}
|
|
183
|
+
return findings;
|
|
184
|
+
}
|
|
185
|
+
async function selectFindingsInteractive(findings) {
|
|
186
|
+
logger.subSection('Select Findings');
|
|
187
|
+
if (findings.length === 0) {
|
|
188
|
+
logger.warn('No findings to implement');
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
const inquirer = (await import('inquirer')).default;
|
|
192
|
+
const chalk = (await import('chalk')).default;
|
|
193
|
+
const { selectionType } = await inquirer.prompt([
|
|
194
|
+
{
|
|
195
|
+
type: 'list',
|
|
196
|
+
name: 'selectionType',
|
|
197
|
+
message: chalk.cyan('Which findings would you like to implement?'),
|
|
198
|
+
choices: [
|
|
199
|
+
{ name: 'All findings', value: 'all' },
|
|
200
|
+
{ name: 'Critical and High only', value: 'critical-high' },
|
|
201
|
+
{ name: 'Custom selection', value: 'custom' },
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
]);
|
|
205
|
+
if (selectionType === 'all') {
|
|
206
|
+
return findings;
|
|
207
|
+
}
|
|
208
|
+
if (selectionType === 'critical-high') {
|
|
209
|
+
return findings.filter((f) => f.severity === 'critical' || f.severity === 'high');
|
|
210
|
+
}
|
|
211
|
+
const { selectedIndices } = await inquirer.prompt([
|
|
212
|
+
{
|
|
213
|
+
type: 'checkbox',
|
|
214
|
+
name: 'selectedIndices',
|
|
215
|
+
message: chalk.cyan('Select findings to implement:'),
|
|
216
|
+
choices: findings.map((f, idx) => ({
|
|
217
|
+
name: `[${f.severity.toUpperCase()}] ${f.title}`,
|
|
218
|
+
value: idx,
|
|
219
|
+
checked: f.severity === 'critical' || f.severity === 'high',
|
|
220
|
+
})),
|
|
221
|
+
},
|
|
222
|
+
]);
|
|
223
|
+
return selectedIndices.map((idx) => findings[idx]);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Pick a recommendation option for a finding without a human:
|
|
227
|
+
* prefer the recommended option, otherwise the first available.
|
|
228
|
+
*/
|
|
229
|
+
function pickOption(finding) {
|
|
230
|
+
if (finding.recommendationOptions.length === 0) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
return (finding.recommendationOptions.find((opt) => opt.recommended) ||
|
|
234
|
+
finding.recommendationOptions[0]);
|
|
235
|
+
}
|
|
236
|
+
async function pickOptionInteractive(finding) {
|
|
237
|
+
if (finding.recommendationOptions.length === 0) {
|
|
238
|
+
logger.warn(`No recommendation options for finding: ${finding.title}`);
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const recommended = finding.recommendationOptions.find((opt) => opt.recommended);
|
|
242
|
+
if (recommended) {
|
|
243
|
+
logger.info(`Using recommended option for "${finding.title}": ${recommended.title}`);
|
|
244
|
+
return recommended;
|
|
245
|
+
}
|
|
246
|
+
const inquirer = (await import('inquirer')).default;
|
|
247
|
+
const chalk = (await import('chalk')).default;
|
|
248
|
+
const { selectedOption } = await inquirer.prompt([
|
|
249
|
+
{
|
|
250
|
+
type: 'list',
|
|
251
|
+
name: 'selectedOption',
|
|
252
|
+
message: chalk.cyan(`Select implementation option for "${finding.title}":`),
|
|
253
|
+
choices: finding.recommendationOptions.map((opt, idx) => ({
|
|
254
|
+
name: `${opt.title} (${opt.estimatedEffort} effort)`,
|
|
255
|
+
value: idx,
|
|
256
|
+
})),
|
|
257
|
+
},
|
|
258
|
+
]);
|
|
259
|
+
return finding.recommendationOptions[selectedOption];
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Build the prompt the calling LLM should act on to fix a single finding.
|
|
263
|
+
*/
|
|
264
|
+
function generateImplementationPrompt(finding, option) {
|
|
265
|
+
const promptParts = [
|
|
266
|
+
`## Task: Fix Code Issue`,
|
|
267
|
+
``,
|
|
268
|
+
`**Issue:** ${finding.title}`,
|
|
269
|
+
`**Severity:** ${finding.severity}`,
|
|
270
|
+
`**Description:** ${finding.description}`,
|
|
271
|
+
];
|
|
272
|
+
if (finding.location) {
|
|
273
|
+
promptParts.push(``, `**Location:**`, `- File: ${finding.location.file || 'Unknown'}`, finding.location.line ? `- Line: ${finding.location.line}` : '');
|
|
274
|
+
}
|
|
275
|
+
if (option) {
|
|
276
|
+
promptParts.push(``, `**Implementation Approach:** ${option.title}`, option.description ? `\n${option.description}` : '', ``, `**Steps:**`, ...option.implementationSteps.map((step, i) => `${i + 1}. ${step}`));
|
|
277
|
+
}
|
|
278
|
+
promptParts.push(``, `**Instructions:**`, `- Make the minimal changes needed to fix this issue`, `- Preserve existing code style and formatting`, `- Do not modify unrelated code`, `- After making changes, verify the fix works correctly`, `- Report what changes you made`);
|
|
279
|
+
return promptParts.filter((line) => line !== '').join('\n');
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Re-run lightweight deterministic checks to see whether a finding still
|
|
283
|
+
* reproduces. This is a best-effort signal, not a guarantee.
|
|
284
|
+
*/
|
|
285
|
+
async function verifyFindingResolved(finding) {
|
|
286
|
+
if (finding.title.includes('Console')) {
|
|
287
|
+
const methodMatch = finding.title.match(/Console\.(\w+)/);
|
|
288
|
+
if (methodMatch && finding.location?.file) {
|
|
289
|
+
try {
|
|
290
|
+
const content = await readFile(finding.location.file);
|
|
291
|
+
const pattern = new RegExp(`console\\.${methodMatch[1]}\\s*\\(`);
|
|
292
|
+
return !pattern.test(content);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// For findings we cannot deterministically re-check, report "unknown" as
|
|
300
|
+
// unresolved so the caller does not assume success.
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
function renderPlanMarkdown(report, plan) {
|
|
304
|
+
const lines = [
|
|
305
|
+
`# Implementation Plan`,
|
|
306
|
+
``,
|
|
307
|
+
`Report: ${report.id}`,
|
|
308
|
+
`Selected findings: ${plan.length} of ${report.summary.totalFindings}`,
|
|
309
|
+
``,
|
|
310
|
+
`Alpha does not edit code. Work through each task below and apply the`,
|
|
311
|
+
`changes yourself, then run \`alpha implement --verify\` to check progress.`,
|
|
312
|
+
``,
|
|
313
|
+
];
|
|
314
|
+
plan.forEach((item, idx) => {
|
|
315
|
+
lines.push(`---`, ``, `## ${idx + 1}. ${item.finding.title}`, ``);
|
|
316
|
+
if (item.verify !== undefined && item.resolved !== undefined) {
|
|
317
|
+
lines.push(`Verification: ${item.resolved ? 'resolved ✓' : 'still present ⚠'}`, ``);
|
|
318
|
+
}
|
|
319
|
+
lines.push(item.prompt, ``);
|
|
320
|
+
});
|
|
321
|
+
return lines.join('\n');
|
|
322
|
+
}
|
|
323
|
+
export async function runImplementation(options = {}) {
|
|
324
|
+
const reportPath = await findLatestReport();
|
|
325
|
+
if (!reportPath || !(await fileExists(reportPath))) {
|
|
326
|
+
logger.error('No review report found. Run `alpha review` first.');
|
|
327
|
+
process.exitCode = 1;
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
logger.section('Implementation');
|
|
331
|
+
logger.info(`Found report: ${reportPath}`);
|
|
332
|
+
const report = await parseReport(reportPath);
|
|
333
|
+
logger.info(`Report contains ${report.summary.totalFindings} finding(s)`);
|
|
334
|
+
const selectedFindings = options.interactive
|
|
335
|
+
? await selectFindingsInteractive(report.findings)
|
|
336
|
+
: selectFindingsByFlags(report.findings, options);
|
|
337
|
+
if (selectedFindings.length === 0) {
|
|
338
|
+
logger.warn('No findings selected for implementation');
|
|
339
|
+
emit('implement', { report: report.id, plan: [] });
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const plan = [];
|
|
343
|
+
for (const finding of selectedFindings) {
|
|
344
|
+
const option = options.interactive
|
|
345
|
+
? await pickOptionInteractive(finding)
|
|
346
|
+
: pickOption(finding);
|
|
347
|
+
plan.push({
|
|
348
|
+
finding,
|
|
349
|
+
option,
|
|
350
|
+
prompt: generateImplementationPrompt(finding, option),
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
let allResolved = false;
|
|
354
|
+
if (options.verify) {
|
|
355
|
+
logger.subSection('Verification');
|
|
356
|
+
allResolved = true;
|
|
357
|
+
for (const item of plan) {
|
|
358
|
+
const resolved = await verifyFindingResolved(item.finding);
|
|
359
|
+
item.verify = true;
|
|
360
|
+
item.resolved = resolved;
|
|
361
|
+
if (resolved) {
|
|
362
|
+
logger.success(` ✓ ${item.finding.title}`);
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
logger.warn(` ⚠ ${item.finding.title} - still present or unverifiable`);
|
|
366
|
+
allResolved = false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (options.verify && options.cleanup && allResolved) {
|
|
371
|
+
await deleteFile(reportPath);
|
|
372
|
+
logger.success(`Report deleted: ${reportPath}`);
|
|
373
|
+
}
|
|
374
|
+
emit('implement', {
|
|
375
|
+
report: report.id,
|
|
376
|
+
reportPath,
|
|
377
|
+
selected: plan.length,
|
|
378
|
+
verified: options.verify || false,
|
|
379
|
+
allResolved: options.verify ? allResolved : undefined,
|
|
380
|
+
plan: plan.map((item) => ({
|
|
381
|
+
findingId: item.finding.id,
|
|
382
|
+
title: item.finding.title,
|
|
383
|
+
severity: item.finding.severity,
|
|
384
|
+
location: item.finding.location,
|
|
385
|
+
option: item.option?.title || null,
|
|
386
|
+
resolved: item.resolved,
|
|
387
|
+
prompt: item.prompt,
|
|
388
|
+
})),
|
|
389
|
+
}, renderPlanMarkdown(report, plan));
|
|
390
|
+
if (!isJsonMode()) {
|
|
391
|
+
logger.info('Apply the changes above, then re-run with --verify to confirm.');
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// CLI entry point
|
|
395
|
+
if (process.argv[1]?.endsWith('index.ts')) {
|
|
396
|
+
runImplementation();
|
|
397
|
+
}
|
|
398
|
+
//# sourceMappingURL=index.js.map
|