@arvorco/relentless 0.3.0 → 0.4.2
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/.claude/commands/relentless.constitution.md +1 -1
- package/.claude/commands/relentless.convert.md +25 -0
- package/.claude/commands/relentless.specify.md +1 -1
- package/.claude/skills/analyze/SKILL.md +113 -40
- package/.claude/skills/analyze/templates/analysis-report.md +138 -0
- package/.claude/skills/checklist/SKILL.md +143 -51
- package/.claude/skills/checklist/templates/checklist.md +43 -11
- package/.claude/skills/clarify/SKILL.md +70 -11
- package/.claude/skills/constitution/SKILL.md +61 -3
- package/.claude/skills/constitution/templates/constitution.md +241 -160
- package/.claude/skills/constitution/templates/prompt.md +150 -20
- package/.claude/skills/convert/SKILL.md +248 -0
- package/.claude/skills/implement/SKILL.md +82 -34
- package/.claude/skills/plan/SKILL.md +136 -27
- package/.claude/skills/plan/templates/plan.md +92 -9
- package/.claude/skills/specify/SKILL.md +110 -19
- package/.claude/skills/specify/scripts/bash/create-new-feature.sh +2 -2
- package/.claude/skills/specify/scripts/bash/setup-plan.sh +1 -1
- package/.claude/skills/specify/templates/spec.md +40 -5
- package/.claude/skills/tasks/SKILL.md +75 -1
- package/.claude/skills/tasks/templates/tasks.md +5 -4
- package/CHANGELOG.md +63 -1
- package/MANUAL.md +40 -0
- package/README.md +263 -11
- package/bin/relentless.ts +292 -5
- package/package.json +2 -2
- package/relentless/config.json +46 -2
- package/relentless/constitution.md +2 -2
- package/relentless/prompt.md +97 -18
- package/src/agents/amp.ts +53 -13
- package/src/agents/claude.ts +70 -15
- package/src/agents/codex.ts +73 -14
- package/src/agents/droid.ts +68 -14
- package/src/agents/exec.ts +96 -0
- package/src/agents/gemini.ts +59 -16
- package/src/agents/opencode.ts +188 -9
- package/src/cli/fallback-order.ts +210 -0
- package/src/cli/index.ts +63 -0
- package/src/cli/mode-flag.ts +198 -0
- package/src/cli/review-flags.ts +192 -0
- package/src/config/loader.ts +16 -1
- package/src/config/schema.ts +157 -2
- package/src/execution/runner.ts +144 -21
- package/src/init/scaffolder.ts +285 -25
- package/src/prd/parser.ts +92 -1
- package/src/prd/types.ts +136 -0
- package/src/review/index.ts +92 -0
- package/src/review/prompt.ts +293 -0
- package/src/review/runner.ts +337 -0
- package/src/review/tasks/docs.ts +529 -0
- package/src/review/tasks/index.ts +80 -0
- package/src/review/tasks/lint.ts +436 -0
- package/src/review/tasks/quality.ts +760 -0
- package/src/review/tasks/security.ts +452 -0
- package/src/review/tasks/test.ts +456 -0
- package/src/review/tasks/typecheck.ts +323 -0
- package/src/review/types.ts +139 -0
- package/src/routing/cascade.ts +310 -0
- package/src/routing/classifier.ts +338 -0
- package/src/routing/estimate.ts +270 -0
- package/src/routing/fallback.ts +512 -0
- package/src/routing/index.ts +124 -0
- package/src/routing/registry.ts +501 -0
- package/src/routing/report.ts +570 -0
- package/src/routing/router.ts +287 -0
- package/src/tui/App.tsx +2 -0
- package/src/tui/TUIRunner.tsx +103 -8
- package/src/tui/components/CurrentStory.tsx +23 -1
- package/src/tui/hooks/useTUI.ts +1 -0
- package/src/tui/types.ts +9 -0
- package/.claude/skills/specify/scripts/bash/update-agent-context.sh +0 -799
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docs Micro-Task
|
|
3
|
+
*
|
|
4
|
+
* Checks if README and JSDoc need updates based on changed files.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Retrieves changed files from git diff
|
|
8
|
+
* - Detects new exports in index.ts without README update
|
|
9
|
+
* - Detects new CLI commands in bin/ without README update
|
|
10
|
+
* - Detects missing JSDoc on exported functions
|
|
11
|
+
* - JSDoc issues are advisory (success: true)
|
|
12
|
+
* - README issues block (success: false)
|
|
13
|
+
* - Excludes functions with @internal tag
|
|
14
|
+
* - Skips test files for JSDoc checks
|
|
15
|
+
*
|
|
16
|
+
* @module src/review/tasks/docs
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { ReviewTaskResult, FixTask } from "../types";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Types of documentation issues
|
|
23
|
+
*/
|
|
24
|
+
export type DocsIssueType = "missing_readme_update" | "missing_jsdoc";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A detected documentation issue
|
|
28
|
+
*/
|
|
29
|
+
export interface DocsIssue {
|
|
30
|
+
/** Type of documentation issue */
|
|
31
|
+
type: DocsIssueType;
|
|
32
|
+
/** File path where found */
|
|
33
|
+
file: string;
|
|
34
|
+
/** Line number (1-based, optional) */
|
|
35
|
+
line?: number;
|
|
36
|
+
/** Description of the issue */
|
|
37
|
+
message: string;
|
|
38
|
+
/** Function name (for missing_jsdoc) */
|
|
39
|
+
functionName?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Extended result type for docs micro-task
|
|
44
|
+
*/
|
|
45
|
+
export interface DocsResult extends ReviewTaskResult {
|
|
46
|
+
/** The command that was executed */
|
|
47
|
+
command: string;
|
|
48
|
+
/** Number of files scanned */
|
|
49
|
+
scannedFiles: number;
|
|
50
|
+
/** Detected documentation issues */
|
|
51
|
+
issues?: DocsIssue[];
|
|
52
|
+
/** Whether README needs update */
|
|
53
|
+
readmeNeedsUpdate: boolean;
|
|
54
|
+
/** Number of missing JSDoc issues */
|
|
55
|
+
missingJSDocCount: number;
|
|
56
|
+
/** Number of exported functions analyzed */
|
|
57
|
+
exportedFunctionsCount: number;
|
|
58
|
+
/** Human-readable summary */
|
|
59
|
+
summary?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Options for running docs scan
|
|
64
|
+
*/
|
|
65
|
+
export interface DocsOptions {
|
|
66
|
+
/** Working directory for the command */
|
|
67
|
+
cwd?: string;
|
|
68
|
+
/** Custom file reader for testing */
|
|
69
|
+
readFile?: (path: string) => Promise<string>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Code file extensions to scan for JSDoc
|
|
74
|
+
*/
|
|
75
|
+
const CODE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if a file is a test file
|
|
79
|
+
*/
|
|
80
|
+
function isTestFile(path: string): boolean {
|
|
81
|
+
return (
|
|
82
|
+
path.includes(".test.") ||
|
|
83
|
+
path.includes(".spec.") ||
|
|
84
|
+
path.includes("/tests/") ||
|
|
85
|
+
path.includes("/test/") ||
|
|
86
|
+
path.includes("__tests__")
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if a file should be scanned for JSDoc
|
|
92
|
+
*/
|
|
93
|
+
function shouldScanFile(path: string): boolean {
|
|
94
|
+
return CODE_EXTENSIONS.some((ext) => path.endsWith(ext)) && !isTestFile(path);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if a file is an index file
|
|
99
|
+
*/
|
|
100
|
+
function isIndexFile(path: string): boolean {
|
|
101
|
+
return path.endsWith("/index.ts") || path.endsWith("/index.js") || path === "index.ts" || path === "index.js";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if a file is in the bin directory
|
|
106
|
+
*/
|
|
107
|
+
function isBinFile(path: string): boolean {
|
|
108
|
+
return path.startsWith("bin/") || path.startsWith("./bin/");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if a file is README.md
|
|
113
|
+
*/
|
|
114
|
+
function isReadmeFile(path: string): boolean {
|
|
115
|
+
return path === "README.md" || path.endsWith("/README.md");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Check if a file is a markdown file that doesn't require README update
|
|
120
|
+
*/
|
|
121
|
+
function isExemptMarkdownFile(path: string): boolean {
|
|
122
|
+
const exemptFiles = ["CLAUDE.md", "AGENTS.md", "CHANGELOG.md", "LICENSE.md"];
|
|
123
|
+
const fileName = path.split("/").pop() || "";
|
|
124
|
+
return exemptFiles.includes(fileName);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Pattern to match exported functions
|
|
129
|
+
*/
|
|
130
|
+
const EXPORTED_FUNCTION_PATTERNS = [
|
|
131
|
+
// export function name()
|
|
132
|
+
/export\s+function\s+(\w+)\s*\(/g,
|
|
133
|
+
// export const name = () =>
|
|
134
|
+
/export\s+const\s+(\w+)\s*=\s*(?:\([^)]*\)|[^=])\s*=>/g,
|
|
135
|
+
// export class Name
|
|
136
|
+
/export\s+class\s+(\w+)/g,
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Pattern to match JSDoc comment
|
|
141
|
+
*/
|
|
142
|
+
const JSDOC_PATTERN = /\/\*\*[\s\S]*?\*\//g;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Pattern to match @internal tag
|
|
146
|
+
*/
|
|
147
|
+
const INTERNAL_TAG_PATTERN = /@internal/;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Detect missing JSDoc on exported functions
|
|
151
|
+
*
|
|
152
|
+
* @param content - File content to analyze
|
|
153
|
+
* @param filePath - Path to the file (for context)
|
|
154
|
+
* @returns Array of missing JSDoc issues
|
|
155
|
+
*/
|
|
156
|
+
export function detectMissingJSDoc(
|
|
157
|
+
content: string,
|
|
158
|
+
filePath: string
|
|
159
|
+
): DocsIssue[] {
|
|
160
|
+
// Skip test files
|
|
161
|
+
if (isTestFile(filePath)) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const issues: DocsIssue[] = [];
|
|
166
|
+
|
|
167
|
+
// Find all JSDoc comments and their positions
|
|
168
|
+
const jsdocComments: Array<{ start: number; end: number; content: string }> = [];
|
|
169
|
+
JSDOC_PATTERN.lastIndex = 0;
|
|
170
|
+
let match;
|
|
171
|
+
while ((match = JSDOC_PATTERN.exec(content)) !== null) {
|
|
172
|
+
const start = content.substring(0, match.index).split("\n").length;
|
|
173
|
+
const commentLines = match[0].split("\n").length;
|
|
174
|
+
jsdocComments.push({
|
|
175
|
+
start,
|
|
176
|
+
end: start + commentLines - 1,
|
|
177
|
+
content: match[0],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Find all exported functions
|
|
182
|
+
for (const pattern of EXPORTED_FUNCTION_PATTERNS) {
|
|
183
|
+
pattern.lastIndex = 0;
|
|
184
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
185
|
+
const functionName = match[1];
|
|
186
|
+
const matchPos = match.index;
|
|
187
|
+
const lineNumber = content.substring(0, matchPos).split("\n").length;
|
|
188
|
+
|
|
189
|
+
// Check if there's a JSDoc comment immediately before this line
|
|
190
|
+
const hasJSDoc = jsdocComments.some((jsdoc) => {
|
|
191
|
+
// JSDoc should end on the line before the function
|
|
192
|
+
return jsdoc.end === lineNumber - 1 || jsdoc.end === lineNumber;
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (!hasJSDoc) {
|
|
196
|
+
issues.push({
|
|
197
|
+
type: "missing_jsdoc",
|
|
198
|
+
file: filePath,
|
|
199
|
+
line: lineNumber,
|
|
200
|
+
message: `Missing JSDoc documentation for exported ${match[0].includes("class") ? "class" : "function"} '${functionName}'`,
|
|
201
|
+
functionName,
|
|
202
|
+
});
|
|
203
|
+
} else {
|
|
204
|
+
// Check if JSDoc has @internal tag
|
|
205
|
+
const jsdoc = jsdocComments.find(
|
|
206
|
+
(j) => j.end === lineNumber - 1 || j.end === lineNumber
|
|
207
|
+
);
|
|
208
|
+
if (jsdoc && INTERNAL_TAG_PATTERN.test(jsdoc.content)) {
|
|
209
|
+
// Skip internal functions (remove from issues if added)
|
|
210
|
+
const existingIndex = issues.findIndex(
|
|
211
|
+
(i) => i.functionName === functionName && i.file === filePath
|
|
212
|
+
);
|
|
213
|
+
if (existingIndex !== -1) {
|
|
214
|
+
issues.splice(existingIndex, 1);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return issues;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Detect new exports in index.ts without README update
|
|
226
|
+
*
|
|
227
|
+
* @param changedFiles - Array of changed file paths
|
|
228
|
+
* @param readmeUpdated - Whether README.md was updated
|
|
229
|
+
* @param fileContents - Map of file paths to their content
|
|
230
|
+
* @returns Array of missing README update issues
|
|
231
|
+
*/
|
|
232
|
+
export function detectNewExportsWithoutReadme(
|
|
233
|
+
changedFiles: string[],
|
|
234
|
+
readmeUpdated: boolean,
|
|
235
|
+
fileContents: Map<string, string>
|
|
236
|
+
): DocsIssue[] {
|
|
237
|
+
if (readmeUpdated) {
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const issues: DocsIssue[] = [];
|
|
242
|
+
const indexFiles = changedFiles.filter(isIndexFile);
|
|
243
|
+
|
|
244
|
+
for (const indexFile of indexFiles) {
|
|
245
|
+
const content = fileContents.get(indexFile);
|
|
246
|
+
if (!content) continue;
|
|
247
|
+
|
|
248
|
+
// Check if file has export statements
|
|
249
|
+
const hasExports = /export\s+\{/.test(content) || /export\s+\w+/.test(content);
|
|
250
|
+
if (hasExports) {
|
|
251
|
+
issues.push({
|
|
252
|
+
type: "missing_readme_update",
|
|
253
|
+
file: indexFile,
|
|
254
|
+
message: `New exports added to ${indexFile} but README.md not updated`,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return issues;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Detect new CLI commands in bin/ without README update
|
|
264
|
+
*
|
|
265
|
+
* @param changedFiles - Array of changed file paths
|
|
266
|
+
* @param readmeUpdated - Whether README.md was updated
|
|
267
|
+
* @returns Array of missing README update issues
|
|
268
|
+
*/
|
|
269
|
+
export function detectNewCliCommandsWithoutReadme(
|
|
270
|
+
changedFiles: string[],
|
|
271
|
+
readmeUpdated: boolean
|
|
272
|
+
): DocsIssue[] {
|
|
273
|
+
if (readmeUpdated) {
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const issues: DocsIssue[] = [];
|
|
278
|
+
const binFiles = changedFiles.filter(isBinFile);
|
|
279
|
+
|
|
280
|
+
for (const binFile of binFiles) {
|
|
281
|
+
issues.push({
|
|
282
|
+
type: "missing_readme_update",
|
|
283
|
+
file: binFile,
|
|
284
|
+
message: `New CLI command ${binFile} added but README.md not updated`,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return issues;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Count exported functions in content
|
|
293
|
+
*/
|
|
294
|
+
function countExportedFunctions(content: string): number {
|
|
295
|
+
let count = 0;
|
|
296
|
+
for (const pattern of EXPORTED_FUNCTION_PATTERNS) {
|
|
297
|
+
pattern.lastIndex = 0;
|
|
298
|
+
const matches = content.match(new RegExp(pattern.source, "g"));
|
|
299
|
+
if (matches) {
|
|
300
|
+
count += matches.length;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return count;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Generate human-readable summary
|
|
308
|
+
*/
|
|
309
|
+
function generateSummary(
|
|
310
|
+
issues: DocsIssue[],
|
|
311
|
+
scannedFiles: number,
|
|
312
|
+
readmeNeedsUpdate: boolean
|
|
313
|
+
): string {
|
|
314
|
+
const parts: string[] = [];
|
|
315
|
+
parts.push(`${scannedFiles} file${scannedFiles !== 1 ? "s" : ""} scanned`);
|
|
316
|
+
|
|
317
|
+
if (readmeNeedsUpdate) {
|
|
318
|
+
parts.push("README needs update");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const jsdocIssues = issues.filter((i) => i.type === "missing_jsdoc").length;
|
|
322
|
+
if (jsdocIssues > 0) {
|
|
323
|
+
parts.push(`${jsdocIssues} missing JSDoc`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (issues.length === 0) {
|
|
327
|
+
return `${scannedFiles} file${scannedFiles !== 1 ? "s" : ""} scanned, documentation up to date`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return parts.join(", ");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Create a fix task from a docs issue
|
|
335
|
+
*/
|
|
336
|
+
function createFixTask(issue: DocsIssue): FixTask {
|
|
337
|
+
let description: string;
|
|
338
|
+
|
|
339
|
+
switch (issue.type) {
|
|
340
|
+
case "missing_readme_update":
|
|
341
|
+
description = `Update README to document new ${issue.file.includes("bin/") ? "commands" : "exports"}`;
|
|
342
|
+
break;
|
|
343
|
+
case "missing_jsdoc":
|
|
344
|
+
description = `Add JSDoc for ${issue.functionName}`;
|
|
345
|
+
break;
|
|
346
|
+
default:
|
|
347
|
+
description = issue.message;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
type: "docs_fix",
|
|
352
|
+
file: issue.file,
|
|
353
|
+
line: issue.line,
|
|
354
|
+
description,
|
|
355
|
+
priority: "low",
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Run the docs micro-task
|
|
361
|
+
*
|
|
362
|
+
* Retrieves changed files from git diff, analyzes them for documentation issues,
|
|
363
|
+
* and generates fix tasks for missing documentation.
|
|
364
|
+
*
|
|
365
|
+
* @param options - Options including working directory and custom file reader
|
|
366
|
+
* @returns DocsResult with success status, issues, and fix tasks
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* ```typescript
|
|
370
|
+
* const result = await runDocs({ cwd: "/path/to/project" });
|
|
371
|
+
* if (!result.success) {
|
|
372
|
+
* console.log(`README needs update: ${result.readmeNeedsUpdate}`);
|
|
373
|
+
* result.fixTasks.forEach(task => console.log(task.description));
|
|
374
|
+
* }
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
377
|
+
export async function runDocs(
|
|
378
|
+
options: DocsOptions = {}
|
|
379
|
+
): Promise<DocsResult> {
|
|
380
|
+
const cwd = options.cwd || process.cwd();
|
|
381
|
+
const command = "git diff --name-only HEAD~1";
|
|
382
|
+
const startTime = Date.now();
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
// Get list of changed files
|
|
386
|
+
const proc = Bun.spawn(["git", "diff", "--name-only", "HEAD~1"], {
|
|
387
|
+
cwd,
|
|
388
|
+
stdout: "pipe",
|
|
389
|
+
stderr: "pipe",
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
await proc.exited;
|
|
393
|
+
const stdout = await proc.stdout.text();
|
|
394
|
+
// stderr is captured but not used since git diff errors are rare
|
|
395
|
+
await proc.stderr.text();
|
|
396
|
+
|
|
397
|
+
// Parse changed files
|
|
398
|
+
const changedFiles = stdout
|
|
399
|
+
.split("\n")
|
|
400
|
+
.map((f) => f.trim())
|
|
401
|
+
.filter((f) => f.length > 0);
|
|
402
|
+
|
|
403
|
+
const duration = Date.now() - startTime;
|
|
404
|
+
|
|
405
|
+
// If no files changed, return success
|
|
406
|
+
if (changedFiles.length === 0) {
|
|
407
|
+
return {
|
|
408
|
+
taskType: "docs",
|
|
409
|
+
success: true,
|
|
410
|
+
errorCount: 0,
|
|
411
|
+
warningCount: 0,
|
|
412
|
+
fixTasks: [],
|
|
413
|
+
duration,
|
|
414
|
+
command,
|
|
415
|
+
scannedFiles: 0,
|
|
416
|
+
issues: [],
|
|
417
|
+
readmeNeedsUpdate: false,
|
|
418
|
+
missingJSDocCount: 0,
|
|
419
|
+
exportedFunctionsCount: 0,
|
|
420
|
+
summary: "0 files scanned, no changed files",
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Check if README was updated
|
|
425
|
+
const readmeUpdated = changedFiles.some(isReadmeFile);
|
|
426
|
+
|
|
427
|
+
// Filter out exempt markdown files for README checks
|
|
428
|
+
const relevantChangedFiles = changedFiles.filter(
|
|
429
|
+
(f) => !isExemptMarkdownFile(f)
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
// Read file contents for analysis
|
|
433
|
+
const fileContents = new Map<string, string>();
|
|
434
|
+
for (const filePath of changedFiles) {
|
|
435
|
+
if (shouldScanFile(filePath) || isIndexFile(filePath)) {
|
|
436
|
+
try {
|
|
437
|
+
let content: string;
|
|
438
|
+
if (options.readFile) {
|
|
439
|
+
content = await options.readFile(filePath);
|
|
440
|
+
} else {
|
|
441
|
+
const file = Bun.file(`${cwd}/${filePath}`);
|
|
442
|
+
content = await file.text();
|
|
443
|
+
}
|
|
444
|
+
fileContents.set(filePath, content);
|
|
445
|
+
} catch {
|
|
446
|
+
// Skip files that can't be read
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const allIssues: DocsIssue[] = [];
|
|
453
|
+
let totalExportedFunctions = 0;
|
|
454
|
+
|
|
455
|
+
// Check for JSDoc issues on code files
|
|
456
|
+
for (const [filePath, content] of fileContents) {
|
|
457
|
+
if (shouldScanFile(filePath)) {
|
|
458
|
+
const jsdocIssues = detectMissingJSDoc(content, filePath);
|
|
459
|
+
allIssues.push(...jsdocIssues);
|
|
460
|
+
totalExportedFunctions += countExportedFunctions(content);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Check for README update issues
|
|
465
|
+
const exportIssues = detectNewExportsWithoutReadme(
|
|
466
|
+
relevantChangedFiles,
|
|
467
|
+
readmeUpdated,
|
|
468
|
+
fileContents
|
|
469
|
+
);
|
|
470
|
+
allIssues.push(...exportIssues);
|
|
471
|
+
|
|
472
|
+
const cliIssues = detectNewCliCommandsWithoutReadme(
|
|
473
|
+
relevantChangedFiles,
|
|
474
|
+
readmeUpdated
|
|
475
|
+
);
|
|
476
|
+
allIssues.push(...cliIssues);
|
|
477
|
+
|
|
478
|
+
// Count issues
|
|
479
|
+
const missingJSDocCount = allIssues.filter(
|
|
480
|
+
(i) => i.type === "missing_jsdoc"
|
|
481
|
+
).length;
|
|
482
|
+
const readmeNeedsUpdate =
|
|
483
|
+
exportIssues.length > 0 || cliIssues.length > 0;
|
|
484
|
+
|
|
485
|
+
// Generate fix tasks for all issues
|
|
486
|
+
const fixTasks = allIssues.map(createFixTask);
|
|
487
|
+
|
|
488
|
+
// JSDoc issues are advisory (success: true), README issues block (success: false)
|
|
489
|
+
const success = !readmeNeedsUpdate;
|
|
490
|
+
|
|
491
|
+
// Code files scanned for JSDoc
|
|
492
|
+
const scannedFiles = Array.from(fileContents.keys()).filter(shouldScanFile).length;
|
|
493
|
+
|
|
494
|
+
return {
|
|
495
|
+
taskType: "docs",
|
|
496
|
+
success,
|
|
497
|
+
errorCount: readmeNeedsUpdate ? 1 : 0,
|
|
498
|
+
warningCount: missingJSDocCount,
|
|
499
|
+
fixTasks,
|
|
500
|
+
duration: Date.now() - startTime,
|
|
501
|
+
command,
|
|
502
|
+
scannedFiles,
|
|
503
|
+
issues: allIssues,
|
|
504
|
+
readmeNeedsUpdate,
|
|
505
|
+
missingJSDocCount,
|
|
506
|
+
exportedFunctionsCount: totalExportedFunctions,
|
|
507
|
+
summary: generateSummary(allIssues, scannedFiles, readmeNeedsUpdate),
|
|
508
|
+
};
|
|
509
|
+
} catch (error) {
|
|
510
|
+
const duration = Date.now() - startTime;
|
|
511
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
512
|
+
|
|
513
|
+
return {
|
|
514
|
+
taskType: "docs",
|
|
515
|
+
success: false,
|
|
516
|
+
errorCount: 1,
|
|
517
|
+
warningCount: 0,
|
|
518
|
+
fixTasks: [],
|
|
519
|
+
duration,
|
|
520
|
+
command,
|
|
521
|
+
scannedFiles: 0,
|
|
522
|
+
issues: [],
|
|
523
|
+
readmeNeedsUpdate: false,
|
|
524
|
+
missingJSDocCount: 0,
|
|
525
|
+
exportedFunctionsCount: 0,
|
|
526
|
+
error: `Docs scan failed: ${errorMessage}`,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Micro-Tasks
|
|
3
|
+
*
|
|
4
|
+
* Individual task implementations for the review runner.
|
|
5
|
+
* Each task runs in isolation and produces fix tasks.
|
|
6
|
+
*
|
|
7
|
+
* @module src/review/tasks
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Typecheck micro-task
|
|
11
|
+
export {
|
|
12
|
+
runTypecheck,
|
|
13
|
+
parseTypecheckOutput,
|
|
14
|
+
stripAnsiCodes,
|
|
15
|
+
groupErrorsByFile,
|
|
16
|
+
type TypecheckError,
|
|
17
|
+
type TypecheckResult,
|
|
18
|
+
type TypecheckOptions,
|
|
19
|
+
} from "./typecheck";
|
|
20
|
+
|
|
21
|
+
// Lint micro-task
|
|
22
|
+
export {
|
|
23
|
+
runLint,
|
|
24
|
+
parseLintOutput,
|
|
25
|
+
parseFallbackLintOutput,
|
|
26
|
+
groupIssuesByFile,
|
|
27
|
+
type LintIssue,
|
|
28
|
+
type LintResult,
|
|
29
|
+
type LintOptions,
|
|
30
|
+
type LintSummary,
|
|
31
|
+
type LintParseResult,
|
|
32
|
+
type LintSeverity,
|
|
33
|
+
} from "./lint";
|
|
34
|
+
|
|
35
|
+
// Test micro-task
|
|
36
|
+
export {
|
|
37
|
+
runTest,
|
|
38
|
+
parseTestOutput,
|
|
39
|
+
parseFallbackTestOutput,
|
|
40
|
+
type TestFailure,
|
|
41
|
+
type TestResult,
|
|
42
|
+
type TestOptions,
|
|
43
|
+
type TestParseResult,
|
|
44
|
+
} from "./test";
|
|
45
|
+
|
|
46
|
+
// Security micro-task
|
|
47
|
+
export {
|
|
48
|
+
runSecurity,
|
|
49
|
+
scanFileForVulnerabilities,
|
|
50
|
+
type Vulnerability,
|
|
51
|
+
type SecurityResult,
|
|
52
|
+
type SecurityOptions,
|
|
53
|
+
type VulnerabilityType,
|
|
54
|
+
type VulnerabilitySeverity,
|
|
55
|
+
type OwaspCategory,
|
|
56
|
+
} from "./security";
|
|
57
|
+
|
|
58
|
+
// Quality micro-task
|
|
59
|
+
export {
|
|
60
|
+
runQuality,
|
|
61
|
+
analyzeComplexity,
|
|
62
|
+
detectUnusedExports,
|
|
63
|
+
detectDuplication,
|
|
64
|
+
type QualityIssue,
|
|
65
|
+
type QualityResult,
|
|
66
|
+
type QualityOptions,
|
|
67
|
+
type QualityIssueType,
|
|
68
|
+
} from "./quality";
|
|
69
|
+
|
|
70
|
+
// Docs micro-task
|
|
71
|
+
export {
|
|
72
|
+
runDocs,
|
|
73
|
+
detectMissingJSDoc,
|
|
74
|
+
detectNewExportsWithoutReadme,
|
|
75
|
+
detectNewCliCommandsWithoutReadme,
|
|
76
|
+
type DocsIssue,
|
|
77
|
+
type DocsResult,
|
|
78
|
+
type DocsOptions,
|
|
79
|
+
type DocsIssueType,
|
|
80
|
+
} from "./docs";
|