devsplain 1.5.4 → 1.5.6

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 (2) hide show
  1. package/bin/cli.js +15 -14
  2. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
 
2
3
  const { getComments } = require('../lib/llm.js');
3
4
  const { getConfig } = require('../lib/config.js');
@@ -9,7 +10,7 @@ const { execSync } = require('child_process');
9
10
  let rl;
10
11
  let askQuestion;
11
12
 
12
- /** Checks if the current Git repository has uncommitted changes */
13
+ /** Checks if the current Git repository is dirty by inspecting status. */
13
14
  function isGitDirty() {
14
15
  try {
15
16
  const gitDir = execSync('git rev-parse --is-inside-work-tree', { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' }).trim();
@@ -22,7 +23,7 @@ function isGitDirty() {
22
23
  return false;
23
24
  }
24
25
 
25
- /** Determines if a specific line index falls within a string literal */
26
+ /** Determines if a specific line index is within a string literal (handling quotes/backticks). */
26
27
  function isLineInsideString(lines, targetLineIndex, ext = '') {
27
28
  const isPython = ext.toLowerCase() === '.py';
28
29
  let inBacktick = false;
@@ -31,7 +32,6 @@ function isLineInsideString(lines, targetLineIndex, ext = '') {
31
32
  let inSingle = false;
32
33
  let inDouble = false;
33
34
 
34
- // Iterate through lines prior to the target to track string/block state
35
35
  for (let i = 0; i < targetLineIndex; i++) {
36
36
  const line = lines[i];
37
37
  let j = 0;
@@ -52,7 +52,6 @@ function isLineInsideString(lines, targetLineIndex, ext = '') {
52
52
  }
53
53
  }
54
54
  } else {
55
- // Check for unescaped backtick (JS template strings) or quotes
56
55
  if (!inSingle && !inDouble && line[j] === '`') {
57
56
  let escaped = false;
58
57
  let k = j - 1;
@@ -98,7 +97,7 @@ function isLineInsideString(lines, targetLineIndex, ext = '') {
98
97
  return inBacktick || inTripleDouble || inTripleSingle || inSingle || inDouble;
99
98
  }
100
99
 
101
- /** Performs a lexical analysis to categorize code lines and comment blocks */
100
+ /** Parses a file to identify pure comments and block structures. */
102
101
  function analyzeComments(lines, ext = '') {
103
102
  const isPython = ext.toLowerCase() === '.py';
104
103
  const isHTML = ['.html', '.vue', '.svelte'].includes(ext.toLowerCase());
@@ -110,7 +109,6 @@ function analyzeComments(lines, ext = '') {
110
109
  let inDouble = false;
111
110
  let inBlockJS = false;
112
111
  let inBlockHTML = false;
113
- // Iterate through each line character by character to detect comment boundaries
114
112
  for (let i = 0; i < lines.length; i++) {
115
113
  const line = lines[i];
116
114
  let commentStartIndex = -1;
@@ -250,9 +248,8 @@ function analyzeComments(lines, ext = '') {
250
248
  return analysis;
251
249
  }
252
250
 
253
- /** Splices generated comments into the source data or removes existing ones */
251
+ /** Splices comments into code or cleans existing ones, with safety checks. */
254
252
  function spliceComments(data, comments, mode = 'default', ext = '') {
255
- // Determine platform-specific line endings
256
253
  const hasCRLF = data.includes('\r\n');
257
254
  const lineEnding = hasCRLF ? '\r\n' : '\n';
258
255
  const originalLines = data.split(/\r?\n/);
@@ -262,12 +259,14 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
262
259
  const annotated = originalLines.map((text, index) => ({ text, originalIndex: index }));
263
260
  let analysis = null;
264
261
 
265
- // 'clean' mode removes all existing comments/documentation
266
262
  if (mode === 'clean') {
267
263
  analysis = analyzeComments(originalLines, ext);
268
264
  const finalDeletions = new Set();
269
265
  for (let i = 0; i < originalLines.length; i++) {
270
266
  const lineNum = i + 1;
267
+ if (originalLines[i].trim().startsWith('#!')) {
268
+ continue;
269
+ }
271
270
  if (analysis[i].isPureComment) {
272
271
  finalDeletions.add(lineNum);
273
272
  } else if (analysis[i].commentStartIndex !== -1) {
@@ -290,6 +289,10 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
290
289
  const trimmedLine = targetLine.trim();
291
290
 
292
291
  const lineAnalysis = analysis[lineNum - 1];
292
+ if (trimmedLine.startsWith('#!')) {
293
+ continue;
294
+ }
295
+
293
296
  const isCommentLine =
294
297
  lineAnalysis.isInsideBlock ||
295
298
  lineAnalysis.isPureComment ||
@@ -311,7 +314,6 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
311
314
  annotated.splice(lineNum - 1, 1);
312
315
  }
313
316
  } else {
314
- // 'default'/'light'/'full' mode: Inject AI-generated comments
315
317
  for (const c of validComments) {
316
318
  if (isLineInsideString(originalLines, c.line - 1, ext)) {
317
319
  console.warn(`[devsplain] Skipping comment insertion at line ${c.line} to avoid string literal corruption.`);
@@ -374,7 +376,7 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
374
376
  return annotated.map(line => line.text).join(lineEnding);
375
377
  }
376
378
 
377
- /** Main entry point for the CLI tool logic */
379
+ /** Main entry point for the CLI tool. */
378
380
  async function runCLI() {
379
381
  rl = readline.createInterface({ input: process.stdin, output: process.stdout });
380
382
  askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
@@ -491,7 +493,7 @@ Options:
491
493
  let successCount = 0;
492
494
  let failCount = 0;
493
495
 
494
- /** Recursively traverses the file system to identify and process source files */
496
+ /** Recursively processes files or directories to apply AI-generated comments. */
495
497
  async function processPath(targetPath) {
496
498
  const stats = fs.statSync(targetPath);
497
499
 
@@ -536,7 +538,6 @@ Options:
536
538
 
537
539
  console.log(` Analyzing ${filename} in ${mode} mode...`);
538
540
  try {
539
- // Logic to either clean existing comments or replace/insert new ones
540
541
  let comments = [];
541
542
  let commentedCode;
542
543
  if (mode !== 'clean') {
@@ -552,7 +553,6 @@ Options:
552
553
  console.log(`---------------------------------------\n`);
553
554
  const answer = await askQuestion("Type 'write' to save to file, or press any key to discard: ");
554
555
  if (answer.toLowerCase() === 'write') {
555
- // Use temporary file for atomic write operations
556
556
  const tempPath = targetPath + '.tmp';
557
557
  fs.writeFileSync(tempPath, commentedCode, 'utf8');
558
558
  fs.renameSync(tempPath, targetPath);
@@ -590,6 +590,7 @@ Options:
590
590
  rl.close();
591
591
  }
592
592
 
593
+ // Check if the script is run directly vs required as a module
593
594
  if (require.main === module) {
594
595
  runCLI().catch(err => {
595
596
  console.error(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devsplain",
3
- "version": "1.5.4",
3
+ "version": "1.5.6",
4
4
  "description": "An agent-agnostic CLI tool that automatically adds JSDoc and inline comments to your code using free LLMs.",
5
5
  "author": "mwahaj36",
6
6
  "license": "MIT",