Skip to content

Commit b6100ea

Browse files
committed
feat: add support for agentic plugin documentation
1 parent 9916149 commit b6100ea

File tree

3 files changed

+130
-52
lines changed

3 files changed

+130
-52
lines changed

scripts/jsdoc-automation/src/AIService.ts

+111-48
Original file line numberDiff line numberDiff line change
@@ -46,26 +46,42 @@ export class AIService {
4646
*/
4747
public async generateComment(prompt: string): Promise<string> {
4848
try {
49-
// Truncate the prompt if it contains code blocks
50-
let finalPrompt = this.truncateCodeBlock(prompt);
49+
// First try with generous limit
50+
let finalPrompt = this.truncateCodeBlock(prompt, 8000);
5151

5252
// Only append language instruction if not English
53-
if (this.configuration.language.toLowerCase() !== 'english') {
53+
const normalizedLanguage = this.configuration.language.toLowerCase().trim();
54+
if (normalizedLanguage !== 'english') {
5455
finalPrompt += `\n\nEverything except the JSDoc conventions and code should be in ${this.configuration.language}`;
5556
}
5657

5758
console.log(`Generating comment for prompt of length: ${finalPrompt.length}`);
5859

59-
const response = await this.chatModel.invoke(finalPrompt);
60-
return response.content as string;
61-
} catch (error) {
62-
if (error instanceof Error && error.message.includes('maximum context length')) {
63-
console.warn('Token limit exceeded, attempting with further truncation...');
64-
// Try again with more aggressive truncation
65-
const truncatedPrompt = this.truncateCodeBlock(prompt, 4000);
66-
const response = await this.chatModel.invoke(truncatedPrompt);
60+
try {
61+
const response = await this.chatModel.invoke(finalPrompt);
6762
return response.content as string;
63+
} catch (error) {
64+
if (error instanceof Error && error.message.includes('maximum context length')) {
65+
console.warn('Token limit exceeded, attempting with further truncation...');
66+
// Try with more aggressive truncation
67+
finalPrompt = this.truncateCodeBlock(prompt, 4000);
68+
try {
69+
const response = await this.chatModel.invoke(finalPrompt);
70+
return response.content as string;
71+
} catch (retryError) {
72+
if (retryError instanceof Error && retryError.message.includes('maximum context length')) {
73+
console.warn('Still exceeding token limit, using minimal context...');
74+
// Final attempt with minimal context
75+
finalPrompt = this.truncateCodeBlock(prompt, 2000);
76+
const response = await this.chatModel.invoke(finalPrompt);
77+
return response.content as string;
78+
}
79+
throw retryError;
80+
}
81+
}
82+
throw error;
6883
}
84+
} catch (error) {
6985
this.handleAPIError(error as Error);
7086
return '';
7187
}
@@ -250,16 +266,15 @@ export class AIService {
250266
const fileGroups = this.groupDocsByFile(docs);
251267
const sections: string[] = [];
252268

253-
// Generate documentation for each file without individual intros
254269
for (const fileGroup of fileGroups) {
255-
const fileDoc = await this.generateFileApiDoc(fileGroup);
256-
if (fileDoc.trim()) {
257-
sections.push(fileDoc);
258-
}
270+
const fileDoc = await this.generateFileApiDoc(fileGroup);
271+
if (fileDoc.trim()) {
272+
sections.push(fileDoc);
273+
}
259274
}
260275

261-
return sections.join('\n\n');
262-
}
276+
return sections.join('\n');
277+
}
263278

264279
/**
265280
* Generates troubleshooting guide based on documentation and common patterns
@@ -462,68 +477,86 @@ export class AIService {
462477
private async generateFileApiDoc(fileGroup: FileDocsGroup): Promise<string> {
463478
const filePath = this.formatFilePath(fileGroup.filePath);
464479
const formattedDocs = this.formatApiComponents(fileGroup);
465-
return formattedDocs ? `### ${filePath}\n\n${formattedDocs}` : '';
480+
// Add TypeScript code block for the file path to indicate it's a TypeScript module
481+
return formattedDocs ? `### File: \`${filePath}\`\n${formattedDocs}` : '';
466482
}
467483

468484
private formatApiComponents(fileGroup: FileDocsGroup): string {
469485
const sections: string[] = [];
470486

471487
// Classes
472488
if (fileGroup.classes.length > 0) {
473-
sections.push('#### Classes\n');
489+
sections.push('#### Classes');
474490
fileGroup.classes.forEach(c => {
475-
sections.push(`##### ${c.name}\n`);
476-
if (c.jsDoc) sections.push(`\`\`\`\n${c.jsDoc}\n\`\`\`\n`);
491+
sections.push(`##### \`${c.name}\``);
492+
if (c.jsDoc) sections.push(this.formatJSDoc(c.jsDoc, c.code));
477493

478494
// Add any methods belonging to this class
479495
const classMethods = fileGroup.methods.filter(m => m.className === c.name);
480496
if (classMethods.length > 0) {
481-
sections.push('Methods:\n');
497+
sections.push('**Methods:**');
482498
classMethods.forEach(m => {
483-
sections.push(`* \`${m.name}\`\n \`\`\`\n ${m.jsDoc || ''}\n \`\`\`\n`);
499+
sections.push(`###### \`${m.name}\`${m.jsDoc ? `\n${this.formatJSDoc(m.jsDoc, m.code)}` : ''}`);
484500
});
485501
}
486502
});
487503
}
488504

489505
// Interfaces
490506
if (fileGroup.interfaces.length > 0) {
491-
sections.push('#### Interfaces\n');
507+
sections.push('#### Interfaces');
492508
fileGroup.interfaces.forEach(i => {
493-
sections.push(`##### ${i.name}\n`);
494-
if (i.jsDoc) sections.push(`\`\`\`\n${i.jsDoc}\n\`\`\`\n`);
509+
sections.push(`##### \`${i.name}\``);
510+
if (i.jsDoc) sections.push(this.formatJSDoc(i.jsDoc, i.code));
495511
});
496512
}
497513

498514
// Types
499515
if (fileGroup.types.length > 0) {
500-
sections.push('#### Types\n');
516+
sections.push('#### Types');
501517
fileGroup.types.forEach(t => {
502-
sections.push(`##### ${t.name}\n`);
503-
if (t.jsDoc) sections.push(`\`\`\`\n${t.jsDoc}\n\`\`\`\n`);
518+
sections.push(`##### \`${t.name}\``);
519+
if (t.jsDoc) sections.push(this.formatJSDoc(t.jsDoc, t.code));
504520
});
505521
}
506522

507-
// Standalone Functions (not class methods)
523+
// Standalone Functions
508524
if (fileGroup.functions.length > 0) {
509-
sections.push('#### Functions\n');
525+
sections.push('#### Functions');
510526
fileGroup.functions.forEach(f => {
511-
sections.push(`##### ${f.name}\n`);
512-
if (f.jsDoc) sections.push(`\`\`\`\n${f.jsDoc}\n\`\`\`\n`);
527+
sections.push(`##### \`${f.name}\``);
528+
if (f.jsDoc) sections.push(this.formatJSDoc(f.jsDoc, f.code));
513529
});
514530
}
515531

516-
// Standalone Methods (not belonging to any class)
532+
// Standalone Methods
517533
const standaloneMethods = fileGroup.methods.filter(m => !m.className);
518534
if (standaloneMethods.length > 0) {
519-
sections.push('#### Methods\n');
535+
sections.push('#### Methods');
520536
standaloneMethods.forEach(m => {
521-
sections.push(`##### ${m.name}\n`);
522-
if (m.jsDoc) sections.push(`\`\`\`\n${m.jsDoc}\n\`\`\`\n`);
537+
sections.push(`##### \`${m.name}\``);
538+
if (m.jsDoc) sections.push(this.formatJSDoc(m.jsDoc, m.code));
523539
});
524540
}
525541

526-
return sections.join('\n');
542+
return sections.join('\n\n');
543+
}
544+
545+
private formatJSDoc(jsDoc: string, code?: string): string {
546+
// Clean up the JSDoc
547+
let cleanDoc = jsDoc.replace(/^```\s*\n?/gm, '').replace(/\n?```\s*$/gm, '');
548+
cleanDoc = cleanDoc.trim().replace(/\n{3,}/g, '\n\n');
549+
550+
// Format JSDoc with typescript declaration
551+
const docSection = '```typescript\n' + cleanDoc + '\n```';
552+
553+
// If we have the actual code, include it after the JSDoc
554+
// if (code) {
555+
// const cleanCode = code.trim().replace(/^```\s*\n?/gm, '').replace(/\n?```\s*$/gm, '');
556+
// return `${docSection}\n\n**Implementation:**\n\n\`\`\`typescript\n${cleanCode}\n\`\`\``;
557+
// }
558+
559+
return docSection;
527560
}
528561

529562
private formatComponents(fileGroup: FileDocsGroup): string {
@@ -576,27 +609,57 @@ export class AIService {
576609
const codeBlockRegex = /```[\s\S]*?```/g;
577610
const codeBlocks = code.match(codeBlockRegex) || [];
578611

612+
// If no code blocks found, truncate the text directly
613+
if (codeBlocks.length === 0) {
614+
return code.slice(0, maxLength) + '... (truncated)';
615+
}
616+
617+
// Calculate maximum length per block to stay under total limit
618+
const nonCodeLength = code.replace(codeBlockRegex, '').length;
619+
const maxLengthPerBlock = Math.floor((maxLength - nonCodeLength) / codeBlocks.length);
620+
579621
for (let i = 0; i < codeBlocks.length; i++) {
580622
const block = codeBlocks[i];
581-
if (block.length > maxLength) {
582-
// Keep the opening and closing parts of the code block
623+
if (block.length > maxLengthPerBlock) {
583624
const lines = block.split('\n');
584625
const header = lines[0]; // Keep the ```typescript or similar
585626
const footer = lines[lines.length - 1]; // Keep the closing ```
586627

587-
// Take the first few lines of actual code
588-
const codeStart = lines.slice(1, Math.floor(maxLength/60)).join('\n');
589-
// Take some lines from the middle
628+
// Calculate how many lines we can keep
629+
const maxLinesPerSection = Math.floor((maxLengthPerBlock - header.length - footer.length) / 3);
630+
631+
// Take fewer lines but ensure we get the most important parts
632+
const codeStart = lines.slice(1, maxLinesPerSection).join('\n');
633+
634+
// For the middle section, focus on the important parts
590635
const middleIndex = Math.floor(lines.length / 2);
591-
const codeMiddle = lines.slice(middleIndex - 2, middleIndex + 2).join('\n');
592-
// Take the last few lines
593-
const codeEnd = lines.slice(lines.length - Math.floor(maxLength/60), -1).join('\n');
636+
const middleStart = Math.max(maxLinesPerSection, middleIndex - Math.floor(maxLinesPerSection / 2));
637+
const middleEnd = Math.min(lines.length - maxLinesPerSection, middleIndex + Math.floor(maxLinesPerSection / 2));
638+
const codeMiddle = lines.slice(middleStart, middleEnd).join('\n');
594639

595-
const truncatedBlock = `${header}\n${codeStart}\n\n// ... truncated ...\n\n${codeMiddle}\n\n// ... truncated ...\n\n${codeEnd}\n${footer}`;
640+
// Take the end section
641+
const codeEnd = lines.slice(lines.length - maxLinesPerSection, -1).join('\n');
642+
643+
const truncatedBlock = `${header}\n${codeStart}\n// ... truncated [${lines.length - (maxLinesPerSection * 2)} lines] ...\n${codeMiddle}\n// ... truncated ...\n${codeEnd}\n${footer}`;
596644
code = code.replace(block, truncatedBlock);
597645
}
598646
}
599647

648+
// Final safety check - if still too long, do a hard truncate
649+
if (code.length > maxLength) {
650+
const blocks = code.split('```');
651+
const truncatedBlocks = blocks.map((block, index) => {
652+
// Every odd index is a code block
653+
if (index % 2 === 1) {
654+
const lines = block.split('\n');
655+
const maxLines = 10; // Keep only first few lines of each block
656+
return lines.slice(0, maxLines).join('\n') + '\n// ... remaining code truncated ...\n';
657+
}
658+
return block.slice(0, 500); // Limit non-code text
659+
});
660+
code = truncatedBlocks.join('```');
661+
}
662+
600663
return code;
601664
}
602665
}

scripts/jsdoc-automation/src/TypeScriptParser.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,10 @@ export class TypeScriptParser {
7474
return exports;
7575
}
7676

77-
public findActionBounds(ast: any): ActionBounds | null {
77+
public findActionBounds(ast: any): ActionBounds | null {
7878
let startLine: number | null = null;
7979
let endLine: number | null = null;
80+
let actionNameStartLine: number | null = null;
8081

8182
const findActionTypeAnnotation = (node: any) => {
8283
// Look for Action type annotation
@@ -89,6 +90,14 @@ export class TypeScriptParser {
8990
endLine = node.loc.end.line;
9091
}
9192

93+
// Backup: Look for action name property
94+
if (node?.type === 'Property' &&
95+
node?.key?.type === 'Identifier' &&
96+
node?.key?.name === 'name' &&
97+
node?.value?.type === 'Literal') {
98+
actionNameStartLine = node.loc.start.line;
99+
}
100+
92101
// Recursively search in child nodes
93102
for (const key in node) {
94103
if (node[key] && typeof node[key] === 'object') {
@@ -103,6 +112,12 @@ export class TypeScriptParser {
103112

104113
findActionTypeAnnotation(ast);
105114

115+
// If we found a valid end line but no start line, use the action name line as fallback
116+
if (!startLine && actionNameStartLine && endLine) {
117+
console.log('Using action name line as fallback');
118+
startLine = actionNameStartLine;
119+
}
120+
106121
if (startLine && endLine) {
107122
return { startLine, endLine };
108123
}

scripts/jsdoc-automation/src/utils/prompts.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,14 @@ Create a comprehensive API reference including:
145145
146146
Format the response in markdown with proper headings and code blocks.`,
147147

148-
todos: `Generate TODO documentation with the following structure, do not return the context (code) rather a description of the code and how the todo is related to the code:
148+
todos: `Generate TODO documentation with the following structure, DO NOT return the context/code rather a description of the code and how the todo is related to the code, if no todos are provided return "No todos found in the code":
149149
150150
### Items
151151
1. [First TODO item]
152-
- Context: [describe the TODO]
152+
- Context: [describe the code associated with the todo]
153153
- Type: [bug/feature/enhancement]
154154
2. [Second TODO item]
155-
- Context: [describe the TODO]
155+
- Context: [describe the code associated with the todo]
156156
- Type: [bug/feature/enhancement]
157157
158158
Format in markdown without adding any additional headers.`,

0 commit comments

Comments
 (0)