@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.
Files changed (71) hide show
  1. package/.claude/commands/relentless.constitution.md +1 -1
  2. package/.claude/commands/relentless.convert.md +25 -0
  3. package/.claude/commands/relentless.specify.md +1 -1
  4. package/.claude/skills/analyze/SKILL.md +113 -40
  5. package/.claude/skills/analyze/templates/analysis-report.md +138 -0
  6. package/.claude/skills/checklist/SKILL.md +143 -51
  7. package/.claude/skills/checklist/templates/checklist.md +43 -11
  8. package/.claude/skills/clarify/SKILL.md +70 -11
  9. package/.claude/skills/constitution/SKILL.md +61 -3
  10. package/.claude/skills/constitution/templates/constitution.md +241 -160
  11. package/.claude/skills/constitution/templates/prompt.md +150 -20
  12. package/.claude/skills/convert/SKILL.md +248 -0
  13. package/.claude/skills/implement/SKILL.md +82 -34
  14. package/.claude/skills/plan/SKILL.md +136 -27
  15. package/.claude/skills/plan/templates/plan.md +92 -9
  16. package/.claude/skills/specify/SKILL.md +110 -19
  17. package/.claude/skills/specify/scripts/bash/create-new-feature.sh +2 -2
  18. package/.claude/skills/specify/scripts/bash/setup-plan.sh +1 -1
  19. package/.claude/skills/specify/templates/spec.md +40 -5
  20. package/.claude/skills/tasks/SKILL.md +75 -1
  21. package/.claude/skills/tasks/templates/tasks.md +5 -4
  22. package/CHANGELOG.md +63 -1
  23. package/MANUAL.md +40 -0
  24. package/README.md +263 -11
  25. package/bin/relentless.ts +292 -5
  26. package/package.json +2 -2
  27. package/relentless/config.json +46 -2
  28. package/relentless/constitution.md +2 -2
  29. package/relentless/prompt.md +97 -18
  30. package/src/agents/amp.ts +53 -13
  31. package/src/agents/claude.ts +70 -15
  32. package/src/agents/codex.ts +73 -14
  33. package/src/agents/droid.ts +68 -14
  34. package/src/agents/exec.ts +96 -0
  35. package/src/agents/gemini.ts +59 -16
  36. package/src/agents/opencode.ts +188 -9
  37. package/src/cli/fallback-order.ts +210 -0
  38. package/src/cli/index.ts +63 -0
  39. package/src/cli/mode-flag.ts +198 -0
  40. package/src/cli/review-flags.ts +192 -0
  41. package/src/config/loader.ts +16 -1
  42. package/src/config/schema.ts +157 -2
  43. package/src/execution/runner.ts +144 -21
  44. package/src/init/scaffolder.ts +285 -25
  45. package/src/prd/parser.ts +92 -1
  46. package/src/prd/types.ts +136 -0
  47. package/src/review/index.ts +92 -0
  48. package/src/review/prompt.ts +293 -0
  49. package/src/review/runner.ts +337 -0
  50. package/src/review/tasks/docs.ts +529 -0
  51. package/src/review/tasks/index.ts +80 -0
  52. package/src/review/tasks/lint.ts +436 -0
  53. package/src/review/tasks/quality.ts +760 -0
  54. package/src/review/tasks/security.ts +452 -0
  55. package/src/review/tasks/test.ts +456 -0
  56. package/src/review/tasks/typecheck.ts +323 -0
  57. package/src/review/types.ts +139 -0
  58. package/src/routing/cascade.ts +310 -0
  59. package/src/routing/classifier.ts +338 -0
  60. package/src/routing/estimate.ts +270 -0
  61. package/src/routing/fallback.ts +512 -0
  62. package/src/routing/index.ts +124 -0
  63. package/src/routing/registry.ts +501 -0
  64. package/src/routing/report.ts +570 -0
  65. package/src/routing/router.ts +287 -0
  66. package/src/tui/App.tsx +2 -0
  67. package/src/tui/TUIRunner.tsx +103 -8
  68. package/src/tui/components/CurrentStory.tsx +23 -1
  69. package/src/tui/hooks/useTUI.ts +1 -0
  70. package/src/tui/types.ts +9 -0
  71. 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";