@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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -0
  3. package/bin/alpha.js +15 -0
  4. package/dist/cli.d.ts +12 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +125 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/core/alpha.d.ts +64 -0
  9. package/dist/core/alpha.d.ts.map +1 -0
  10. package/dist/core/alpha.js +94 -0
  11. package/dist/core/alpha.js.map +1 -0
  12. package/dist/core/generator/index.d.ts +16 -0
  13. package/dist/core/generator/index.d.ts.map +1 -0
  14. package/dist/core/generator/index.js +231 -0
  15. package/dist/core/generator/index.js.map +1 -0
  16. package/dist/core/implementation/index.d.ts +22 -0
  17. package/dist/core/implementation/index.d.ts.map +1 -0
  18. package/dist/core/implementation/index.js +398 -0
  19. package/dist/core/implementation/index.js.map +1 -0
  20. package/dist/core/init/catalog.d.ts +23 -0
  21. package/dist/core/init/catalog.d.ts.map +1 -0
  22. package/dist/core/init/catalog.js +87 -0
  23. package/dist/core/init/catalog.js.map +1 -0
  24. package/dist/core/init/index.d.ts +10 -0
  25. package/dist/core/init/index.d.ts.map +1 -0
  26. package/dist/core/init/index.js +214 -0
  27. package/dist/core/init/index.js.map +1 -0
  28. package/dist/core/requirements/index.d.ts +10 -0
  29. package/dist/core/requirements/index.d.ts.map +1 -0
  30. package/dist/core/requirements/index.js +232 -0
  31. package/dist/core/requirements/index.js.map +1 -0
  32. package/dist/core/reviewer/index.d.ts +29 -0
  33. package/dist/core/reviewer/index.d.ts.map +1 -0
  34. package/dist/core/reviewer/index.js +419 -0
  35. package/dist/core/reviewer/index.js.map +1 -0
  36. package/dist/core/stack/index.d.ts +10 -0
  37. package/dist/core/stack/index.d.ts.map +1 -0
  38. package/dist/core/stack/index.js +243 -0
  39. package/dist/core/stack/index.js.map +1 -0
  40. package/dist/index.d.ts +29 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +32 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/types/index.d.ts +128 -0
  45. package/dist/types/index.d.ts.map +1 -0
  46. package/dist/types/index.js +5 -0
  47. package/dist/types/index.js.map +1 -0
  48. package/dist/utils/config.d.ts +105 -0
  49. package/dist/utils/config.d.ts.map +1 -0
  50. package/dist/utils/config.js +99 -0
  51. package/dist/utils/config.js.map +1 -0
  52. package/dist/utils/file-system.d.ts +17 -0
  53. package/dist/utils/file-system.d.ts.map +1 -0
  54. package/dist/utils/file-system.js +48 -0
  55. package/dist/utils/file-system.js.map +1 -0
  56. package/dist/utils/input.d.ts +18 -0
  57. package/dist/utils/input.d.ts.map +1 -0
  58. package/dist/utils/input.js +50 -0
  59. package/dist/utils/input.js.map +1 -0
  60. package/dist/utils/logger.d.ts +37 -0
  61. package/dist/utils/logger.d.ts.map +1 -0
  62. package/dist/utils/logger.js +109 -0
  63. package/dist/utils/logger.js.map +1 -0
  64. package/dist/utils/paths.d.ts +26 -0
  65. package/dist/utils/paths.d.ts.map +1 -0
  66. package/dist/utils/paths.js +51 -0
  67. package/dist/utils/paths.js.map +1 -0
  68. package/package.json +79 -0
  69. 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