2025-01-07 00:55:17 +08:00
/ *
2025-01-07 07:18:22 +08:00
* Copyright ( C ) 2024 - present Puter Technologies Inc .
2025-01-07 00:55:17 +08:00
*
* This file is part of Puter .
*
* Puter is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
* /
2024-12-02 23:56:57 +08:00
// METADATA // {"ai-params":{"service":"claude"},"comment-verbosity": "high","ai-commented":{"service":"claude"}}
2024-11-30 04:22:16 +08:00
const enq = require ( 'enquirer' ) ;
const wrap = require ( 'word-wrap' ) ;
const dedent = require ( 'dedent' ) ;
const axios = require ( 'axios' ) ;
const { walk , EXCLUDE _LISTS } = require ( '../file-walker/test' ) ;
const https = require ( 'https' ) ;
const fs = require ( 'fs' ) ;
const path _ = require ( 'path' ) ;
const FILE _EXCLUDES = [
/^\.git/ ,
/^node_modules\// ,
/\/node_modules$/ ,
/^node_modules$/ ,
/package-lock\.json/ ,
/^src\/dev-center\/js/ ,
/src\/backend\/src\/public\/assets/ ,
/^src\/gui\/src\/lib/ ,
/^eslint\.config\.js$/ ,
] ;
2024-12-02 23:22:58 +08:00
const models _to _try = [
{
service : 'openai-completion' ,
model : 'gpt-4o' ,
} ,
{
service : 'claude' ,
} ,
{
service : 'xai' ,
} ,
2024-12-03 01:18:46 +08:00
// === Models that didn't work for this ===
// Sometimes outputs source lines despite "no surrounding text" instruction
// {
// service: 'openai-completion',
// model: 'gpt-4o-mini',
// },
// Was the first to break a source file
2024-12-02 23:22:58 +08:00
// {
// service: 'together-ai',
// model: 'meta-llama/Meta-Llama-3-70B-Instruct-Turbo',
// },
2024-12-03 01:18:46 +08:00
// Occasionally fails spectacularly
// {
// service: 'mistral',
// model: 'mistral-large-latest',
// }
2024-12-02 23:22:58 +08:00
] ;
2024-11-30 04:22:16 +08:00
const axi = axios . create ( {
httpsAgent : new https . Agent ( {
rejectUnauthorized : false
} )
} ) ;
const cwd = process . cwd ( ) ;
const context = { } ;
context . config = JSON . parse (
fs . readFileSync ( 'config.json' )
) ;
2024-12-03 00:01:23 +08:00
/ * *
* @ class AI
* @ description A class that handles interactions with the Puter API for AI - powered chat completions .
* This class provides an interface to make requests to the Puter chat completion service ,
* handling authentication and message formatting . It supports various AI models through
* the puter - chat - completion driver interface .
* /
2024-11-30 04:22:16 +08:00
class AI {
constructor ( context ) {
//
}
2024-12-03 00:01:23 +08:00
/ * *
* Sends a chat completion request to the Puter API and returns the response message .
*
* @ param { Object } params - The parameters for the completion request
* @ param { Array } params . messages - Array of message objects to send to the API
* @ param { Object } params . driver _params - Additional parameters for the driver interface
* @ returns { Promise < Object > } The response message from the API
*
* Makes a POST request to the configured API endpoint with the provided messages and
* driver parameters . Authenticates using the configured auth token and returns the
* message content from the response .
* /
2024-12-02 23:22:58 +08:00
async complete ( { messages , driver _params } ) {
2024-11-30 04:22:16 +08:00
const response = await axi . post ( ` ${ context . config . api _url } /drivers/call ` , {
interface : 'puter-chat-completion' ,
method : 'complete' ,
2024-12-02 23:22:58 +08:00
... driver _params ,
2024-11-30 04:22:16 +08:00
args : {
messages ,
} ,
} , {
headers : {
"Content-Type" : "application/json" ,
Origin : 'https://puter.local' ,
Authorization : ` Bearer ${ context . config . auth _token } ` ,
} ,
} ) ;
return response . data . result . message ;
}
}
2024-12-02 23:22:58 +08:00
const ai _message _to _lines = text => {
2024-12-03 00:01:23 +08:00
// Extract text content from message object, handling various formats
2024-12-02 23:22:58 +08:00
while ( typeof text === 'object' ) {
if ( Array . isArray ( text ) ) text = text [ 0 ] ;
else if ( text . content ) text = text . content ;
else if ( text . text ) text = text . text ;
else {
console . log ( 'Invalid message object' , text ) ;
throw new Error ( 'Invalid message object' ) ;
}
}
return text . split ( '\n' ) ;
}
2024-12-03 00:01:23 +08:00
/ * *
* @ class JavascriptFileProcessor
* @ description A class responsible for processing JavaScript source files to identify and extract
* various code definitions and structures . It analyzes the file content line by line using
* configurable pattern matchers to detect classes , methods , functions , control structures ,
* and constants . The processor maintains context and parameters for consistent processing
* across multiple files .
* /
2024-11-30 04:22:16 +08:00
class JavascriptFileProcessor {
constructor ( context , parameters ) {
this . context = context ;
this . parameters = parameters ;
}
process ( lines ) {
const definitions = [ ] ;
2024-12-03 00:01:23 +08:00
// Collect definitions by iterating through each line
2024-11-30 04:22:16 +08:00
for ( let i = 0 ; i < lines . length ; i ++ ) {
const line = lines [ i ] ;
2024-12-03 00:01:23 +08:00
// Iterate through each line in the file
2024-11-30 04:22:16 +08:00
for ( const matcher of this . parameters . definition _matchers ) {
const match = matcher . pattern . exec ( line ) ;
console . log ( 'match object' , match ) ;
2024-12-03 00:01:23 +08:00
// Check if there is a match for any of the definition patterns
2024-11-30 04:22:16 +08:00
if ( match ) {
definitions . push ( {
... matcher . handler ( match ) ,
line : i ,
} ) ;
break ;
}
}
}
return { definitions } ;
}
}
const js _processor = new JavascriptFileProcessor ( context , {
definition _matchers : [
// {
// name: 'require',
// pattern: /const (\w+) = require\(['"](.+)['"]\);/,
// handler: (match) => {
// const [ , name, path ] = match;
// return {
// type: 'require',
// name,
// path,
// };
// }
// },
{
name : 'class' ,
pattern : /class (\w+)(?: extends (.+))? {/ ,
handler : ( match ) => {
const [ , name , parent ] = match ;
return {
type : 'class' ,
name ,
parent ,
} ;
}
} ,
2024-12-02 23:56:57 +08:00
{
name : 'if' ,
pattern : /^\s*if\s*\(.*\)\s*{/ ,
2024-12-03 00:01:23 +08:00
/ * *
* Matches code patterns against a line to identify if it ' s an if statement
* @ param { string } line - The line of code to check
* @ returns { Object } Returns an object with type : 'if' if pattern matches
* @ description Identifies if statements by matching the pattern / ^ \ s * if \ s * \ ( . * \ ) \ s * { /
* This handles basic if statement syntax with optional whitespace and any condition
* within the parentheses
* /
2024-12-02 23:56:57 +08:00
handler : ( ) => {
return { type : 'if' } ;
}
} ,
{
name : 'while' ,
pattern : /^\s*while\s*\(.*\)\s*{/ ,
2024-12-03 00:01:23 +08:00
/ * *
* Matches lines that begin with a while loop structure .
* @ param { void } - Takes no parameters
* @ returns { Object } Returns an object with type : 'while' to indicate this is a while loop definition
* @ description Used by the definition matcher system to identify while loop structures in code .
* The pattern looks for lines that start with optional whitespace , followed by 'while' ,
* followed by parentheses containing any characters , and ending with an opening curly brace .
* /
2024-12-02 23:56:57 +08:00
handler : ( ) => {
return { type : 'while' } ;
}
} ,
{
name : 'for' ,
pattern : /^\s*for\s*\(.*\)\s*{/ ,
2024-12-03 00:01:23 +08:00
/ * *
* Matches for loop patterns in code and returns a 'for' type definition .
* Used by the JavascriptFileProcessor to identify for loop structures .
* @ returns { Object } An object with type 'for' indicating a for loop was found
* /
2024-12-02 23:56:57 +08:00
handler : ( ) => {
return { type : 'for' } ;
}
} ,
2024-11-30 04:22:16 +08:00
{
name : 'method' ,
pattern : /^\s*async .*\(.*\).*{/ ,
handler : ( match ) => {
const [ , name ] = match ;
return {
async : true ,
type : 'method' ,
name ,
} ;
}
} ,
{
name : 'method' ,
pattern : /^\s*[A-Za-z_\$]+.*\(\).*{/ ,
handler : ( match ) => {
const [ , name ] = match ;
2024-12-03 00:01:23 +08:00
// Extract method name from match array and handle special cases for 'if' and 'while'
2024-12-02 23:56:57 +08:00
if ( name === 'if' ) {
return { type : 'if' } ;
}
2024-12-03 00:01:23 +08:00
// Check if the name is 'while' and return appropriate type
2024-12-02 23:56:57 +08:00
if ( name === 'while' ) {
return { type : 'while' } ;
}
2024-11-30 04:22:16 +08:00
return {
type : 'method' ,
name ,
} ;
}
} ,
{
name : 'function' ,
pattern : /^\s*function .*\(.*\).*{/ ,
handler : ( match ) => {
const [ , name ] = match ;
return {
type : 'function' ,
scope : 'function' ,
name ,
} ;
}
} ,
{
name : 'function' ,
pattern : /const [A-Za-z_]+\s*=\s*\(.*\)\s*=>\s*{/ ,
handler : ( match ) => {
const [ , name , args ] = match ;
return {
type : 'function' ,
scope : 'lexical' ,
name ,
2024-12-02 23:22:58 +08:00
args : ( args ? ? '' ) . split ( ',' ) . map ( arg => arg . trim ( ) ) ,
2024-11-30 04:22:16 +08:00
} ;
}
} ,
{
name : 'const' ,
// pattern to match only uppercase-lettered variable names
pattern : /const ([A-Z_]+) = (.+);/ ,
handler : ( match ) => {
const [ , name , value ] = match ;
return {
type : 'const' ,
name ,
value ,
} ;
}
}
] ,
} ) ;
2024-12-03 00:01:23 +08:00
/ * *
* Creates a limited view of the code file by showing specific ranges around key lines .
* Takes an array of lines and key places ( anchors with context ranges ) and returns
* a formatted string showing relevant code sections with line numbers and descriptions .
* Merges overlapping ranges to avoid duplication .
* @ param { string [ ] } lines - Array of code lines from the file
* @ param { Object [ ] } key _places - Array of objects defining important locations and context
* @ returns { string } Formatted string containing the limited code view
* /
2024-11-30 04:22:16 +08:00
const create _limited _view = ( lines , key _places ) => {
// Sort key places by starting line
key _places . sort ( ( a , b ) => {
const a _start = Math . max ( 0 , a . anchor - a . lines _above ) ;
const b _start = Math . max ( 0 , b . anchor - b . lines _above ) ;
return a _start - b _start ;
} ) ;
const visible _ranges = [ ] ;
// Create visible ranges for each key place
2024-12-03 00:01:23 +08:00
// Create visible ranges for each key place in the limited view
2024-11-30 04:22:16 +08:00
for ( const key _place of key _places ) {
const anchor = key _place . anchor ;
const lines _above = key _place . lines _above ;
const lines _below = key _place . lines _below ;
const start = Math . max ( 0 , anchor - lines _above ) ;
const end = Math . min ( lines . length , anchor + lines _below ) ;
visible _ranges . push ( {
anchor : key _place . anchor ,
comment : key _place . comment ,
start ,
end ,
} ) ;
}
// Merge overlapping visible ranges
const merged _ranges = [ ] ;
2024-12-03 00:01:23 +08:00
// Iterate through each visible range and merge overlapping ones
2024-11-30 04:22:16 +08:00
for ( const range of visible _ranges ) {
range . comments = [ {
anchor : range . anchor ,
text : range . comment
} ] ;
2024-12-03 00:01:23 +08:00
// If no merged ranges exist yet, add this range as the first one
2024-11-30 04:22:16 +08:00
if ( ! merged _ranges . length ) {
merged _ranges . push ( range ) ;
continue ;
}
const last _range = merged _ranges [ merged _ranges . length - 1 ] ;
2024-12-03 00:01:23 +08:00
// Check if the current range overlaps with the last range in merged_ranges
2024-11-30 04:22:16 +08:00
if ( last _range . end >= range . start ) {
last _range . end = Math . max ( last _range . end , range . end ) ;
last _range . comments . push ( {
anchor : range . anchor ,
text : range . comment
} ) ;
} else {
merged _ranges . push ( range ) ;
}
}
// Create the limited view, adding line numbers and comments
let limited _view = '' ;
let previous _visible _range = null ;
2024-12-03 00:01:23 +08:00
// Iterate through visible ranges and add line numbers and comments
2024-11-30 04:22:16 +08:00
for ( let i = 0 ; i < lines . length ; i ++ ) {
const line = lines [ i ] ;
let visible _range = null ;
if ( i === 22 ) debugger ;
2024-12-03 00:01:23 +08:00
// Iterate through merged ranges to find which range contains the current line
2024-11-30 04:22:16 +08:00
for ( const range of merged _ranges ) {
2024-12-03 00:01:23 +08:00
// Check if current line is within any of the merged ranges
2024-11-30 04:22:16 +08:00
if ( i >= range . start && i < range . end ) {
visible _range = range ;
break ;
}
}
// console.log('visible_range', visible_range, i);
2024-12-03 00:01:23 +08:00
// Check if this line is visible in the current range
2024-11-30 04:22:16 +08:00
if ( visible _range === null ) {
continue ;
}
2024-12-03 00:01:23 +08:00
// Check if visible range is different from previous range
2024-11-30 04:22:16 +08:00
if ( visible _range !== previous _visible _range ) {
if ( i !== 0 ) limited _view += '\n' ;
2024-12-03 00:01:23 +08:00
// Check if we're starting a new visible range and add appropriate header
2024-11-30 04:22:16 +08:00
if ( visible _range . comments . length === 1 ) {
const comment = visible _range . comments [ 0 ] ;
limited _view += ` window around line ${ comment . anchor } : ${ comment . text } \n ` ;
} else {
limited _view += ` window around lines ${ visible _range . comments . length } key lines: \n ` ;
2024-12-03 00:01:23 +08:00
// Iterate through visible range comments and add them to the limited view
2024-11-30 04:22:16 +08:00
for ( const comment of visible _range . comments ) {
limited _view += ` - line ${ comment . anchor } : ${ comment . text } \n ` ;
}
}
}
previous _visible _range = visible _range ;
limited _view += ` ${ i + 1 } : ${ line } \n ` ;
}
return limited _view ;
} ;
/ * *
* Inject comments into lines
* @ param { * } lines - Array of original file lines
* @ param { * } comments - Array of comment objects
*
* Comment object structure :
* {
* position : 0 , // Position in lines array
* lines : [ 'comment line 1' , 'comment line 2' , ... ]
* }
* /
2024-12-03 00:01:23 +08:00
/ * *
* Injects comments into an array of code lines at specified positions
* @ param { string [ ] } lines - Array of original file lines
* @ param { Object [ ] } comments - Array of comment objects specifying where and what to inject
* @ param { number } comments [ ] . position - Line number where comment should be inserted
* @ param { string [ ] } comments [ ] . lines - Array of comment text lines to insert
* /
2024-11-30 04:22:16 +08:00
const inject _comments = ( lines , comments ) => {
// Sort comments in reverse order
comments . sort ( ( a , b ) => b . position - a . position ) ;
// Inject comments into lines
2024-12-03 00:01:23 +08:00
// Inject comments into lines array based on comment objects
2024-11-30 04:22:16 +08:00
for ( const comment of comments ) {
// AI might have been stupid and added a comment above a blank line,
// despite that we told it not to do that. So we need to adjust the position.
2024-12-03 00:01:23 +08:00
// Adjust comment position if it would be above a blank line
2024-11-30 04:22:16 +08:00
while ( comment . position < lines . length && ! lines [ comment . position ] . trim ( ) ) {
comment . position ++ ;
}
const indentation = lines [ comment . position ] . match ( /^\s*/ ) [ 0 ] ;
console . log ( '????' , comment . position , lines [ comment . position ] , '|' + indentation + '|' ) ;
const comment _lines = comment . lines . map ( line => ` ${ indentation } ${ line } ` ) ;
lines . splice ( comment . position , 0 , ... comment _lines ) ;
2024-12-02 23:22:58 +08:00
// If the first line of the comment lines starts with '/*`, ensure there is
// a blank line above it.
2024-12-03 00:01:23 +08:00
// Check if comment starts with '/*' to ensure proper spacing above JSDoc comments
2024-12-02 23:22:58 +08:00
if ( comment _lines [ 0 ] . trim ( ) . startsWith ( '/*' ) ) {
2024-12-03 00:01:23 +08:00
// Check if comment starts with JSDoc style to add blank line above
2024-12-02 23:22:58 +08:00
if ( comment . position > 0 && lines [ comment . position - 1 ] . trim ( ) === '' ) {
lines . splice ( comment . position , 0 , '' ) ;
}
}
2024-11-30 04:22:16 +08:00
}
}
const textutil = { } ;
textutil . format = text => {
return wrap ( dedent ( text ) , {
width : 80 ,
indent : '| '
} ) ;
} ;
context . ai = new AI ( context ) ;
2024-12-03 00:01:23 +08:00
/ * *
* Creates a new AI instance for handling chat completions
* @ param { Object } context - The application context object
* @ description Initializes an AI instance that interfaces with the Puter chat completion API .
* The AI instance is used to generate comments and other text responses through the
* chat completion interface .
* /
2024-11-30 04:22:16 +08:00
const main = async ( ) => {
// const message = await context.ai.complete({
// messages: [
// {
// role: 'user',
// content: `
// Introduce yourself as the Puter Comment Writer. You are an AI that will
// write comments in code files. A file walker will be used to iterate over
// the source files and present them one at a time, and the user will accept,
// reject, or request edits interactively. For each new file, a clean AI
// context will be created.
// `.trim()
// }
// ]
// });
// const intro = message.content;
const intro = textutil . format ( `
Hello ! I am the Puter Comment Writer , an AI designed to enhance your code files with meaningful comments . As you walk through your source files , I will provide insights , explanations , and clarifications tailored to the specific content of each file . You can choose to accept my comments , request edits for more clarity or detail , or even reject them if they don 't meet your needs. Each time we move to a new file, I' ll start fresh with a clean context , ready to help you improve your code documentation . Let ' s get started !
` );
console . log ( intro ) ;
console . log ( ` Enter a path relative to: ${ process . cwd ( ) } ` ) ;
console . log ( 'arg?' , process . argv [ 2 ] ) ;
let rootpath = process . argv [ 2 ] ? { path : process . argv [ 2 ] } : await enq . prompt ( {
type : 'input' ,
name : 'path' ,
message : 'Enter path:'
} ) ;
rootpath = path _ . resolve ( rootpath . path ) ;
console . log ( 'rootpath:' , rootpath ) ;
const walk _iter = walk ( {
excludes : FILE _EXCLUDES ,
} , rootpath ) ;
2024-12-03 00:09:50 +08:00
let i = 0 , limit = undefined ;
2024-11-30 04:22:16 +08:00
for await ( const value of walk _iter ) {
2024-12-03 00:09:50 +08:00
if ( limit !== undefined && i >= limit ) break ;
2024-12-02 23:22:58 +08:00
i ++ ;
2024-12-03 00:09:50 +08:00
2024-12-03 00:01:23 +08:00
// Exit after processing 12 files
2024-11-30 04:22:16 +08:00
if ( value . is _dir ) {
console . log ( 'directory:' , value . path ) ;
continue ;
}
2024-12-03 00:01:23 +08:00
// Check if file is not a JavaScript file and skip it
2024-11-30 04:22:16 +08:00
if ( ! value . name . endsWith ( '.js' ) ) {
continue ;
}
console . log ( 'file:' , value . path ) ;
const lines = fs . readFileSync ( value . path , 'utf8' ) . split ( '\n' ) ;
2024-12-02 23:22:58 +08:00
let metadata , has _metadata _line = false ;
2024-12-03 00:01:23 +08:00
// Check if metadata line exists and parse it
2024-11-30 04:22:16 +08:00
if ( lines [ 0 ] . startsWith ( '// METADATA // ' ) ) {
2024-12-02 23:22:58 +08:00
has _metadata _line = true ;
metadata = JSON . parse ( lines [ 0 ] . slice ( '// METADATA // ' . length ) ) ;
2024-12-03 00:01:23 +08:00
// Check if metadata exists and has been parsed from the first line
2024-11-30 04:22:16 +08:00
if ( metadata [ 'ai-commented' ] ) {
console . log ( 'File was already commented by AI; skipping...' ) ;
continue ;
}
2024-12-03 00:09:50 +08:00
} else metadata = { } ;
2024-11-30 04:22:16 +08:00
2024-12-02 23:22:58 +08:00
let refs = null ;
2024-12-03 00:01:23 +08:00
// Check if there are any references in the metadata
2024-12-02 23:22:58 +08:00
if ( metadata [ 'ai-refs' ] ) {
const relative _file _paths = metadata [ 'ai-refs' ] ;
// name of file is the key, value is the contents
const references = { } ;
let n = 0 ;
2024-12-03 00:01:23 +08:00
// Iterate through each relative file path in the metadata
2024-12-02 23:22:58 +08:00
for ( const relative _file _path of relative _file _paths ) {
n ++ ;
const full _path = path _ . join ( path _ . dirname ( value . path ) , relative _file _path ) ;
const ref _text = fs . readFileSync ( full _path , 'utf8' ) ;
references [ relative _file _path ] = ref _text ;
}
2024-12-03 00:01:23 +08:00
// Check if there are any references in the metadata and process them
2024-12-02 23:22:58 +08:00
if ( n === 1 ) {
refs = dedent ( `
The following documentation contains relevant information about the code .
The code will follow after this documentation .
` );
refs += '\n\n' + dedent ( references [ Object . keys ( references ) [ 0 ] ] ) ;
} else if ( n > 2 ) {
refs = dedent ( `
The following documentation contains relevant information about the code .
The code will follow after a number of documentation files .
` );
2024-12-03 00:01:23 +08:00
// Iterate through each key in the references object
2024-12-02 23:22:58 +08:00
for ( const key of Object . keys ( references ) ) {
refs += '\n\n' + dedent ( references [ key ] ) ;
}
}
}
2024-12-03 00:09:50 +08:00
const action = limit === undefined ? await enq . prompt ( {
2024-11-30 04:22:16 +08:00
type : 'select' ,
name : 'action' ,
message : 'Select action:' ,
choices : [
'generate' ,
'skip' ,
2024-12-03 00:09:50 +08:00
'all' ,
'limit' ,
2024-11-30 04:22:16 +08:00
'exit' ,
]
2024-12-03 00:09:50 +08:00
} ) : 'generate' ;
2024-12-02 23:22:58 +08:00
// const action = 'generate';
2024-11-30 04:22:16 +08:00
2024-12-03 00:01:23 +08:00
// Check if user wants to exit the program
2024-11-30 04:22:16 +08:00
if ( action . action === 'exit' ) {
break ;
}
2024-12-03 00:01:23 +08:00
// Skip if user chose to exit
2024-11-30 04:22:16 +08:00
if ( action . action === 'skip' ) {
continue ;
}
2024-12-03 00:09:50 +08:00
if ( action . action === 'limit' ) {
limit = await enq . prompt ( {
type : 'input' ,
name : 'limit' ,
message : 'Enter limit:'
} ) ;
i = 1 ;
limit = Number ( limit . limit ) ;
}
if ( action . action === 'all' ) {
i = 1 ;
2024-12-03 00:45:13 +08:00
limit = Infinity ;
2024-12-03 00:09:50 +08:00
}
2024-11-30 04:22:16 +08:00
const { definitions } = js _processor . process ( lines ) ;
const key _places = [ ] ;
key _places . push ( {
anchor : 0 ,
lines _above : 2 ,
lines _below : 200 ,
comment : ` Top of file: ${ value . path } `
} ) ;
key _places . push ( {
anchor : lines . length - 1 ,
lines _above : 200 ,
lines _below : 2 ,
comment : ` Bottom of ${ value . name } `
} ) ;
2024-12-03 00:01:23 +08:00
// Iterate through each definition and add comments based on its type
2024-11-30 04:22:16 +08:00
for ( const definition of definitions ) {
key _places . push ( {
anchor : definition . line ,
lines _above : 40 ,
lines _below : 40 ,
comment : ` ${ definition . type } . `
} ) ;
}
let limited _view = create _limited _view ( lines , key _places ) ;
console . log ( '--- view ---' ) ;
console . log ( limited _view ) ;
const comments = [ ] ;
// comments.push({
// position: 0,
// });
// for ( const definition of definitions ) {
// comments.push({
// position: definition.line,
// definition,
// });
// }
// This was worth a try but the LLM is very bad at this
/ *
const message = await context . ai . complete ( {
messages : [
{
role : 'user' ,
content : dedent ( `
Respond with comma - separated numbers only , with no surrounding text .
Please write the numbers of the lines above which a comment should be added .
Do not include numbers of lines that are blank , already have comments , or are part of a comment .
Prefer comment locations in a higher level scope , such as a classes , function definitions and class methods ,
` ).trim() + ' \n \n ' + limited_view
}
]
} ) ;
const numbers = message . content . split ( ',' ) . map ( n => Number ( n ) ) ;
2024-12-03 00:01:23 +08:00
// Iterate through each number in the array of line numbers
2024-11-30 04:22:16 +08:00
for ( const n of numbers ) {
2024-12-03 00:01:23 +08:00
// Check if the line number is valid and not NaN before adding comment
2024-11-30 04:22:16 +08:00
if ( Number . isNaN ( n ) ) {
console . log ( 'Invalid number:' , n ) ;
continue ;
}
comments . push ( {
position : n - 1 ,
} ) ;
}
* /
2024-12-03 00:01:23 +08:00
// Iterate through each definition to add comments
2024-11-30 04:22:16 +08:00
for ( const def of definitions ) {
console . log ( 'def?' , def ) ;
let instruction = '' ;
2024-12-03 00:01:23 +08:00
// Check if the line starts with an if statement and has curly braces
2024-11-30 04:22:16 +08:00
if ( def . type === 'class' ) {
instruction = dedent ( `
Since the comment is going above a class definition , please write a JSDoc style comment .
Make the comment as descriptive as possible , including the class name and its purpose .
` );
}
2024-12-03 00:01:23 +08:00
// Check if comment is for an if/while/for control structure
2024-12-02 23:56:57 +08:00
if ( def . type === 'if' || def . type === 'while' || def . type === 'for' ) {
if ( metadata [ 'comment-verbosity' ] !== 'high' ) continue ;
instruction = dedent ( `
Since the comment is going above a control structure , please write a short concise comment .
The comment should be only one or two lines long , and should use line comments .
` );
}
2024-12-03 00:01:23 +08:00
// Check if comment is going above a method definition
2024-11-30 04:22:16 +08:00
if ( def . type === 'method' ) {
instruction = dedent ( `
Since the comment is going above a method , please write a JSDoc style comment .
The comment should include a short concise description of the method ' s purpose ,
notes about its behavior , and any parameters or return values .
` );
}
2024-12-03 00:01:23 +08:00
// Check if comment is for a constant definition and set appropriate instruction
2024-11-30 04:22:16 +08:00
if ( def . type === 'const' ) {
instruction = dedent ( `
Since the comment is going above a constant definition , please write a comment that explains
the purpose of the constant and how it is used in the code .
The comment should be only one or two lines long , and should use line comments .
` );
}
comments . push ( {
position : def . line ,
instruction : instruction ,
} ) ;
}
2024-12-02 23:22:58 +08:00
const driver _params = metadata [ 'ai-params' ] ? ?
models _to _try [ Math . floor ( Math . random ( ) * models _to _try . length ) ] ;
2024-11-30 04:22:16 +08:00
2024-12-03 00:01:23 +08:00
// Iterate through each comment object to add comments to the code
2024-11-30 04:22:16 +08:00
for ( const comment of comments ) {
// This doesn't work very well yet
/ *
const ranges _message = await context . ai . complete ( {
messages : [
{
role : 'user' ,
content : dedent ( `
Please only respond with comma - separated number ranges in this format with no surrounding text :
11 - 21 , 25 - 30 , 35 - 40
You may also respond with "none" .
A comment will be added above line $ { comment . position } in the code which follows .
You are seeing a limited view of the code that includes chunks around interesting lines .
Please specify ranges of lines that might provide useful context for this comment .
Do not include in any range lines which are already visible in the limited view .
Avoid specifying more than a couple hundred lines .
` ).trim() + ' \n \n ' + limited_view
}
]
} ) ;
2024-12-03 00:01:23 +08:00
// Check if the comment lines start with '/*' and ensure there's a blank line above it
2024-11-30 04:22:16 +08:00
if ( ranges _message . content . trim ( ) !== 'none' ) {
const ranges = ranges _message . content . split ( ',' ) . map ( range => {
const [ start , end ] = range . split ( '-' ) . map ( n => Number ( n ) ) ;
return { start , end } ;
} ) ;
2024-12-03 00:01:23 +08:00
// Iterate through ranges and add key places for each range
2024-11-30 04:22:16 +08:00
for ( const range of ranges ) {
key _places . push ( {
anchor : range . start ,
lines _above : 0 ,
lines _below : range . end - range . start ,
comment : ` Requested range by AI agent: ${ range . start } - ${ range . end } `
} ) ;
}
limited _view = create _limited _view ( lines , key _places ) ;
console . log ( '--- updated view ---' ) ;
console . log ( limited _view ) ;
}
* /
2024-12-02 23:22:58 +08:00
const prompt =
dedent ( `
Please write a comment to be added above line $ { comment . position } .
Do not write any surrounding text ; just the comment itself .
Please include comment markers . If the comment is on a class , function , or method , please use jsdoc style .
The code is written in JavaScript .
` ).trim() +
( refs ? '\n\n' + dedent ( refs ) : '' ) +
( comment . instruction ? '\n\n' + dedent ( comment . instruction ) : '' ) +
'\n\n' + limited _view
;
// console.log('prompt:', prompt);
2024-11-30 04:22:16 +08:00
const message = await context . ai . complete ( {
messages : [
{
role : 'user' ,
2024-12-02 23:22:58 +08:00
content : prompt
2024-11-30 04:22:16 +08:00
}
2024-12-02 23:22:58 +08:00
] ,
driver _params ,
2024-11-30 04:22:16 +08:00
} ) ;
console . log ( 'message:' , message ) ;
2024-12-02 23:22:58 +08:00
comment . lines = ai _message _to _lines ( message . content ) ;
2024-11-30 04:22:16 +08:00
// Remove leading and trailing blank lines
2024-12-03 00:01:23 +08:00
// Remove leading and trailing blank lines from comment lines array
2024-11-30 04:22:16 +08:00
while ( comment . lines . length && ! comment . lines [ 0 ] . trim ( ) ) {
comment . lines . shift ( ) ;
}
2024-12-03 00:01:23 +08:00
// Remove trailing blank lines from comment lines array
2024-11-30 04:22:16 +08:00
while ( comment . lines . length && ! comment . lines [ comment . lines . length - 1 ] . trim ( ) ) {
comment . lines . pop ( ) ;
}
2024-12-03 00:01:23 +08:00
// Remove leading "```" or "```<language>" lines
2024-11-30 04:22:16 +08:00
// Remove leading "```" or "```<language>" lines
if ( comment . lines [ 0 ] . startsWith ( '```' ) ) {
comment . lines . shift ( ) ;
}
// Remove trailing "```" lines
2024-12-03 00:01:23 +08:00
// Remove trailing "```" lines if present
2024-11-30 04:22:16 +08:00
if ( comment . lines [ comment . lines . length - 1 ] . startsWith ( '```' ) ) {
comment . lines . pop ( ) ;
}
comment . lines = dedent ( comment . lines . join ( '\n' ) ) . split ( '\n' ) ;
}
inject _comments ( lines , comments ) ;
console . log ( '--- lines ---' ) ;
console . log ( lines ) ;
2024-12-03 00:01:23 +08:00
// Check if file has metadata line and remove it before adding new metadata
2024-12-02 23:22:58 +08:00
if ( has _metadata _line ) {
lines . shift ( ) ;
}
2024-11-30 04:22:16 +08:00
lines . unshift ( '// METADATA // ' + JSON . stringify ( {
2024-12-02 23:22:58 +08:00
... metadata ,
'ai-commented' : driver _params ,
2024-11-30 04:22:16 +08:00
} ) ) ;
// Write the modified file
fs . writeFileSync ( value . path , lines . join ( '\n' ) ) ;
}
} ;
main ( ) ;