@arvorco/relentless 0.3.1 → 0.4.3

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 (66) hide show
  1. package/.claude/commands/relentless.convert.md +25 -0
  2. package/.claude/skills/analyze/SKILL.md +113 -40
  3. package/.claude/skills/analyze/templates/analysis-report.md +138 -0
  4. package/.claude/skills/checklist/SKILL.md +144 -51
  5. package/.claude/skills/checklist/templates/checklist.md +43 -11
  6. package/.claude/skills/clarify/SKILL.md +70 -11
  7. package/.claude/skills/constitution/SKILL.md +61 -3
  8. package/.claude/skills/constitution/templates/constitution.md +241 -160
  9. package/.claude/skills/constitution/templates/prompt.md +150 -20
  10. package/.claude/skills/convert/SKILL.md +248 -0
  11. package/.claude/skills/implement/SKILL.md +82 -34
  12. package/.claude/skills/plan/SKILL.md +139 -27
  13. package/.claude/skills/plan/templates/plan.md +92 -9
  14. package/.claude/skills/specify/SKILL.md +112 -20
  15. package/.claude/skills/specify/templates/spec.md +40 -5
  16. package/.claude/skills/tasks/SKILL.md +76 -1
  17. package/.claude/skills/tasks/templates/tasks.md +5 -4
  18. package/CHANGELOG.md +84 -1
  19. package/MANUAL.md +40 -0
  20. package/README.md +268 -13
  21. package/bin/relentless.ts +292 -5
  22. package/package.json +2 -2
  23. package/relentless/config.json +45 -1
  24. package/relentless/constitution.md +41 -19
  25. package/relentless/prompt.md +142 -72
  26. package/src/agents/amp.ts +53 -13
  27. package/src/agents/claude.ts +70 -15
  28. package/src/agents/codex.ts +73 -14
  29. package/src/agents/droid.ts +68 -14
  30. package/src/agents/exec.ts +96 -0
  31. package/src/agents/gemini.ts +59 -16
  32. package/src/agents/opencode.ts +188 -9
  33. package/src/cli/fallback-order.ts +210 -0
  34. package/src/cli/index.ts +63 -0
  35. package/src/cli/mode-flag.ts +198 -0
  36. package/src/cli/review-flags.ts +192 -0
  37. package/src/config/loader.ts +16 -1
  38. package/src/config/schema.ts +157 -2
  39. package/src/execution/runner.ts +144 -21
  40. package/src/init/scaffolder.ts +285 -25
  41. package/src/prd/parser.ts +111 -6
  42. package/src/prd/types.ts +136 -0
  43. package/src/review/index.ts +92 -0
  44. package/src/review/prompt.ts +293 -0
  45. package/src/review/runner.ts +337 -0
  46. package/src/review/tasks/docs.ts +529 -0
  47. package/src/review/tasks/index.ts +80 -0
  48. package/src/review/tasks/lint.ts +436 -0
  49. package/src/review/tasks/quality.ts +760 -0
  50. package/src/review/tasks/security.ts +452 -0
  51. package/src/review/tasks/test.ts +456 -0
  52. package/src/review/tasks/typecheck.ts +323 -0
  53. package/src/review/types.ts +139 -0
  54. package/src/routing/cascade.ts +310 -0
  55. package/src/routing/classifier.ts +338 -0
  56. package/src/routing/estimate.ts +270 -0
  57. package/src/routing/fallback.ts +512 -0
  58. package/src/routing/index.ts +124 -0
  59. package/src/routing/registry.ts +501 -0
  60. package/src/routing/report.ts +570 -0
  61. package/src/routing/router.ts +287 -0
  62. package/src/tui/App.tsx +2 -0
  63. package/src/tui/TUIRunner.tsx +103 -8
  64. package/src/tui/components/CurrentStory.tsx +23 -1
  65. package/src/tui/hooks/useTUI.ts +1 -0
  66. package/src/tui/types.ts +9 -0
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Typecheck Micro-Task
3
+ *
4
+ * Runs `bun run typecheck` in the project's working directory,
5
+ * parses TypeScript errors, and generates fix tasks.
6
+ *
7
+ * Features:
8
+ * - Parses TypeScript compiler errors with file, line, column, code, message
9
+ * - Groups errors by file for efficient fixing
10
+ * - Generates fix tasks with priority "high"
11
+ * - Strips ANSI color codes from output
12
+ * - Tracks warnings separately (doesn't generate fix tasks)
13
+ * - Provides summary for 100+ errors
14
+ * - Reports tsconfig.json configuration errors
15
+ *
16
+ * @module src/review/tasks/typecheck
17
+ */
18
+
19
+ import type { ReviewTaskResult, FixTask } from "../types";
20
+
21
+ /**
22
+ * A parsed TypeScript error
23
+ */
24
+ export interface TypecheckError {
25
+ /** File path relative to project root */
26
+ file: string;
27
+ /** Line number (1-based) */
28
+ line: number;
29
+ /** Column number (1-based, optional) */
30
+ column?: number;
31
+ /** TypeScript error code (e.g., "TS2339") */
32
+ code: string;
33
+ /** Error message text */
34
+ message: string;
35
+ }
36
+
37
+ /**
38
+ * Extended result type for typecheck micro-task
39
+ */
40
+ export interface TypecheckResult extends ReviewTaskResult {
41
+ /** The command that was executed */
42
+ command: string;
43
+ /** Warning count (not included in fix tasks) */
44
+ warningCount?: number;
45
+ /** Summary for paginated results (when 100+ errors) */
46
+ summary?: string;
47
+ }
48
+
49
+ /**
50
+ * Options for running typecheck
51
+ */
52
+ export interface TypecheckOptions {
53
+ /** Working directory for the command */
54
+ cwd?: string;
55
+ }
56
+
57
+ /**
58
+ * Strip ANSI color codes from a string
59
+ *
60
+ * @param str - Input string potentially containing ANSI codes
61
+ * @returns Clean string without ANSI codes
62
+ */
63
+ export function stripAnsiCodes(str: string): string {
64
+ // eslint-disable-next-line no-control-regex
65
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
66
+ }
67
+
68
+ /**
69
+ * Parse TypeScript compiler output into structured errors
70
+ *
71
+ * Handles multiple output formats:
72
+ * - Standard tsc format: `file(line,col): error TSxxxx: message`
73
+ * - Colon-separated format: `file:line:col - error TSxxxx: message`
74
+ * - Bun typecheck format (arrow style)
75
+ *
76
+ * @param output - Raw compiler output (stdout)
77
+ * @returns Array of parsed errors (excludes warnings)
78
+ */
79
+ export function parseTypecheckOutput(output: string): TypecheckError[] {
80
+ const cleanOutput = stripAnsiCodes(output);
81
+ const errors: TypecheckError[] = [];
82
+
83
+ // Split into lines and process each
84
+ const lines = cleanOutput.split("\n");
85
+
86
+ for (const line of lines) {
87
+ // Skip warning lines
88
+ if (line.includes("warning TS") || line.includes("warning:")) {
89
+ continue;
90
+ }
91
+
92
+ // Pattern 1: Standard tsc format - file(line,col): error TSxxxx: message
93
+ const stdMatch = line.match(
94
+ /^(.+?)\((\d+),(\d+)\):\s*error\s+(TS\d+):\s*(.+)$/
95
+ );
96
+ if (stdMatch) {
97
+ errors.push({
98
+ file: stdMatch[1],
99
+ line: parseInt(stdMatch[2], 10),
100
+ column: parseInt(stdMatch[3], 10),
101
+ code: stdMatch[4],
102
+ message: stdMatch[5],
103
+ });
104
+ continue;
105
+ }
106
+
107
+ // Pattern 1b: Without column - file(line): error TSxxxx: message
108
+ const noColMatch = line.match(
109
+ /^(.+?)\((\d+)\):\s*error\s+(TS\d+):\s*(.+)$/
110
+ );
111
+ if (noColMatch) {
112
+ errors.push({
113
+ file: noColMatch[1],
114
+ line: parseInt(noColMatch[2], 10),
115
+ code: noColMatch[3],
116
+ message: noColMatch[4],
117
+ });
118
+ continue;
119
+ }
120
+
121
+ // Pattern 2: Colon-separated format - file:line:col - error TSxxxx: message
122
+ const colonMatch = line.match(
123
+ /^(.+?):(\d+):(\d+)\s+-\s*error\s+(TS\d+):\s*(.+)$/
124
+ );
125
+ if (colonMatch) {
126
+ errors.push({
127
+ file: colonMatch[1],
128
+ line: parseInt(colonMatch[2], 10),
129
+ column: parseInt(colonMatch[3], 10),
130
+ code: colonMatch[4],
131
+ message: colonMatch[5],
132
+ });
133
+ continue;
134
+ }
135
+ }
136
+
137
+ return errors;
138
+ }
139
+
140
+ /**
141
+ * Parse warnings from TypeScript compiler output
142
+ *
143
+ * @param output - Raw compiler output
144
+ * @returns Number of warnings found
145
+ */
146
+ function parseWarningCount(output: string): number {
147
+ const cleanOutput = stripAnsiCodes(output);
148
+ const lines = cleanOutput.split("\n");
149
+ let count = 0;
150
+
151
+ for (const line of lines) {
152
+ if (line.includes("warning TS") || line.includes("warning:")) {
153
+ count++;
154
+ }
155
+ }
156
+
157
+ return count;
158
+ }
159
+
160
+ /**
161
+ * Group errors by file path
162
+ *
163
+ * @param errors - Array of parsed errors
164
+ * @returns Record mapping file paths to their errors
165
+ */
166
+ export function groupErrorsByFile(
167
+ errors: TypecheckError[]
168
+ ): Record<string, TypecheckError[]> {
169
+ const grouped: Record<string, TypecheckError[]> = {};
170
+
171
+ for (const error of errors) {
172
+ if (!grouped[error.file]) {
173
+ grouped[error.file] = [];
174
+ }
175
+ grouped[error.file].push(error);
176
+ }
177
+
178
+ return grouped;
179
+ }
180
+
181
+ /**
182
+ * Create a fix task from a TypeScript error
183
+ *
184
+ * @param error - The parsed TypeScript error
185
+ * @returns A fix task for the review system
186
+ */
187
+ function createFixTask(error: TypecheckError): FixTask {
188
+ const lineInfo = error.column
189
+ ? `at line ${error.line}, column ${error.column}`
190
+ : `at line ${error.line}`;
191
+
192
+ return {
193
+ type: "typecheck_fix",
194
+ file: error.file,
195
+ line: error.line,
196
+ column: error.column,
197
+ description: `Fix TypeScript error ${error.code} in ${error.file} ${lineInfo}: ${error.message}`,
198
+ priority: "high",
199
+ code: error.code,
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Run the typecheck micro-task
205
+ *
206
+ * Executes `bun run typecheck` in the specified working directory,
207
+ * parses the output, and generates fix tasks for any errors found.
208
+ *
209
+ * @param options - Options including working directory
210
+ * @returns TypecheckResult with success status, errors, and fix tasks
211
+ *
212
+ * @example
213
+ * ```typescript
214
+ * const result = await runTypecheck({ cwd: "/path/to/project" });
215
+ * if (!result.success) {
216
+ * console.log(`${result.errorCount} errors found`);
217
+ * result.fixTasks.forEach(task => console.log(task.description));
218
+ * }
219
+ * ```
220
+ */
221
+ export async function runTypecheck(
222
+ options: TypecheckOptions = {}
223
+ ): Promise<TypecheckResult> {
224
+ const cwd = options.cwd || process.cwd();
225
+ const command = "bun run typecheck";
226
+ const startTime = Date.now();
227
+
228
+ try {
229
+ // Spawn the typecheck process
230
+ const proc = Bun.spawn(["bun", "run", "typecheck"], {
231
+ cwd,
232
+ stdout: "pipe",
233
+ stderr: "pipe",
234
+ });
235
+
236
+ // Wait for completion
237
+ const exitCode = await proc.exited;
238
+ const stdout = await proc.stdout.text();
239
+ const stderr = await proc.stderr.text();
240
+ const duration = Date.now() - startTime;
241
+
242
+ // Check for tsconfig.json errors
243
+ if (
244
+ stderr.includes("tsconfig.json") ||
245
+ stderr.includes("Could not find") ||
246
+ stderr.includes("Cannot find")
247
+ ) {
248
+ return {
249
+ taskType: "typecheck",
250
+ success: false,
251
+ errorCount: 1,
252
+ warningCount: 0,
253
+ fixTasks: [],
254
+ duration,
255
+ command,
256
+ error: `Configuration error: ${stderr.includes("tsconfig") ? "tsconfig.json not found or invalid" : stderr}`,
257
+ };
258
+ }
259
+
260
+ // Parse errors from output (check both stdout and stderr)
261
+ const combinedOutput = stdout + "\n" + stderr;
262
+ const errors = parseTypecheckOutput(combinedOutput);
263
+ const warningCount = parseWarningCount(combinedOutput);
264
+
265
+ // Generate fix tasks for each error
266
+ const fixTasks = errors.map(createFixTask);
267
+
268
+ // Create summary for large error counts
269
+ let summary: string | undefined;
270
+ if (errors.length >= 100) {
271
+ const grouped = groupErrorsByFile(errors);
272
+ const fileCount = Object.keys(grouped).length;
273
+ summary = `Found ${errors.length} TypeScript errors across ${fileCount} files. Top files with errors: ${Object.entries(grouped)
274
+ .sort((a, b) => b[1].length - a[1].length)
275
+ .slice(0, 5)
276
+ .map(([file, errs]) => `${file} (${errs.length})`)
277
+ .join(", ")}`;
278
+ }
279
+
280
+ // Check for general command failure (stderr has content but no parsed errors)
281
+ if (exitCode !== 0 && errors.length === 0 && stderr.trim()) {
282
+ return {
283
+ taskType: "typecheck",
284
+ success: false,
285
+ errorCount: 1,
286
+ warningCount: 0,
287
+ fixTasks: [],
288
+ duration,
289
+ command,
290
+ error: stderr.trim(),
291
+ };
292
+ }
293
+
294
+ // Success if no errors (warnings are OK)
295
+ const success = errors.length === 0 && exitCode === 0;
296
+
297
+ return {
298
+ taskType: "typecheck",
299
+ success,
300
+ errorCount: errors.length,
301
+ warningCount,
302
+ fixTasks,
303
+ duration,
304
+ command,
305
+ summary,
306
+ };
307
+ } catch (error) {
308
+ const duration = Date.now() - startTime;
309
+ const errorMessage =
310
+ error instanceof Error ? error.message : String(error);
311
+
312
+ return {
313
+ taskType: "typecheck",
314
+ success: false,
315
+ errorCount: 1,
316
+ warningCount: 0,
317
+ fixTasks: [],
318
+ duration,
319
+ command,
320
+ error: `Command execution failed: ${errorMessage}`,
321
+ };
322
+ }
323
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Shared types and interfaces for the Review module
3
+ *
4
+ * This module defines the core types used by the review runner
5
+ * and all micro-task implementations.
6
+ *
7
+ * @module src/review/types
8
+ */
9
+
10
+ import { z } from "zod";
11
+
12
+ /**
13
+ * Priority levels for fix tasks
14
+ * - critical: Security vulnerabilities, data corruption risks
15
+ * - high: TypeScript errors, failing tests
16
+ * - medium: Lint warnings, code quality issues
17
+ * - low: Documentation, style suggestions
18
+ */
19
+ export const FixTaskPrioritySchema = z.enum(["critical", "high", "medium", "low"]);
20
+ export type FixTaskPriority = z.infer<typeof FixTaskPrioritySchema>;
21
+
22
+ /**
23
+ * Fix task types corresponding to review micro-tasks
24
+ */
25
+ export const FixTaskTypeSchema = z.enum([
26
+ "typecheck_fix",
27
+ "lint_fix",
28
+ "test_fix",
29
+ "security_fix",
30
+ "quality_fix",
31
+ "docs_fix",
32
+ ]);
33
+ export type FixTaskType = z.infer<typeof FixTaskTypeSchema>;
34
+
35
+ /**
36
+ * A fix task queued from a review micro-task
37
+ *
38
+ * Fix tasks are generated when micro-tasks find issues
39
+ * and are queued to progress.txt for subsequent processing.
40
+ */
41
+ export const FixTaskSchema = z.object({
42
+ /** Type of fix task, corresponds to the micro-task that generated it */
43
+ type: FixTaskTypeSchema,
44
+ /** File path relative to project root */
45
+ file: z.string(),
46
+ /** Line number where the issue was found (optional) */
47
+ line: z.number().int().positive().optional(),
48
+ /** Column number (optional) */
49
+ column: z.number().int().positive().optional(),
50
+ /** Human-readable description of the issue and fix needed */
51
+ description: z.string(),
52
+ /** Priority level for ordering fix tasks */
53
+ priority: FixTaskPrioritySchema,
54
+ /** Specific rule or error code (optional, e.g., ESLint rule) */
55
+ rule: z.string().optional(),
56
+ /** Error code (optional, e.g., TS2339) */
57
+ code: z.string().optional(),
58
+ });
59
+ export type FixTask = z.infer<typeof FixTaskSchema>;
60
+
61
+ /**
62
+ * Result from running a single micro-task
63
+ */
64
+ export const ReviewTaskResultSchema = z.object({
65
+ /** Type of micro-task that was run */
66
+ taskType: z.enum(["typecheck", "lint", "test", "security", "quality", "docs"]),
67
+ /** Whether the task passed (no blocking issues) */
68
+ success: z.boolean(),
69
+ /** Number of errors/issues found */
70
+ errorCount: z.number().int().min(0),
71
+ /** Warning count (non-blocking) */
72
+ warningCount: z.number().int().min(0).optional(),
73
+ /** Fix tasks generated for issues found */
74
+ fixTasks: z.array(FixTaskSchema),
75
+ /** Duration in milliseconds */
76
+ duration: z.number().min(0),
77
+ /** Error message if task threw an exception */
78
+ error: z.string().optional(),
79
+ /** Number of retry attempts used */
80
+ retryAttempts: z.number().int().min(0).optional(),
81
+ });
82
+ export type ReviewTaskResult = z.infer<typeof ReviewTaskResultSchema>;
83
+
84
+ /**
85
+ * Summary of the complete review phase
86
+ */
87
+ export const ReviewSummarySchema = z.object({
88
+ /** Total number of micro-tasks run */
89
+ tasksRun: z.number().int().min(0),
90
+ /** Number of tasks that passed */
91
+ tasksPassed: z.number().int().min(0),
92
+ /** Number of tasks that failed */
93
+ tasksFailed: z.number().int().min(0),
94
+ /** Total fix tasks generated across all micro-tasks */
95
+ fixTasksGenerated: z.number().int().min(0),
96
+ /** Total duration of review phase in milliseconds */
97
+ totalDuration: z.number().min(0),
98
+ /** Estimated cost before running (based on mode and task count) */
99
+ estimatedCost: z.number().min(0),
100
+ /** Actual cost after running (based on model usage) */
101
+ actualCost: z.number().min(0),
102
+ /** Individual results for each micro-task */
103
+ results: z.array(ReviewTaskResultSchema),
104
+ /** Whether review was stopped early due to stopOnFailure */
105
+ stoppedEarly: z.boolean().optional(),
106
+ });
107
+ export type ReviewSummary = z.infer<typeof ReviewSummarySchema>;
108
+
109
+ /**
110
+ * Options for controlling review execution
111
+ */
112
+ export const ReviewOptionsSchema = z.object({
113
+ /** Stop on first failure instead of continuing */
114
+ stopOnFailure: z.boolean().default(false),
115
+ /** Mode override (defaults to config.defaultMode) */
116
+ mode: z.enum(["free", "cheap", "good", "genius"]).default("good"),
117
+ /** Micro-tasks to skip */
118
+ skipTasks: z.array(z.enum(["typecheck", "lint", "test", "security", "quality", "docs"])).optional(),
119
+ /** Custom handlers for testing (overrides default implementations) */
120
+ handlers: z.record(z.string(), z.any()).optional(),
121
+ /** Custom logger function */
122
+ logger: z.function().args(z.string()).returns(z.unknown()).optional(),
123
+ /** Callback when model is selected for review */
124
+ onModelSelected: z.function().args(z.string()).returns(z.unknown()).optional(),
125
+ });
126
+ export type ReviewOptions = z.infer<typeof ReviewOptionsSchema>;
127
+
128
+ /**
129
+ * Handler function for a micro-task
130
+ *
131
+ * Each micro-task (typecheck, lint, test, etc.) implements this interface.
132
+ * The handler runs in a fresh process/context with no shared state.
133
+ */
134
+ export type MicroTaskHandler = () => Promise<ReviewTaskResult>;
135
+
136
+ /**
137
+ * Registry of micro-task handlers
138
+ */
139
+ export type MicroTaskHandlerRegistry = Record<string, MicroTaskHandler>;