devsplain 1.5.1 → 1.5.3

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/bin/cli.js CHANGED
@@ -22,7 +22,7 @@ function isGitDirty() {
22
22
  return false;
23
23
  }
24
24
 
25
- /** Determines if a specific line index is inside a string or multiline literal */
25
+ /** Determines if a specific line index falls within a string literal */
26
26
  function isLineInsideString(lines, targetLineIndex, ext = '') {
27
27
  const isPython = ext.toLowerCase() === '.py';
28
28
  let inBacktick = false;
@@ -31,6 +31,7 @@ function isLineInsideString(lines, targetLineIndex, ext = '') {
31
31
  let inSingle = false;
32
32
  let inDouble = false;
33
33
 
34
+ // Iterate through lines prior to the target to track string/block state
34
35
  for (let i = 0; i < targetLineIndex; i++) {
35
36
  const line = lines[i];
36
37
  let j = 0;
@@ -51,6 +52,7 @@ function isLineInsideString(lines, targetLineIndex, ext = '') {
51
52
  }
52
53
  }
53
54
  } else {
55
+ // Check for unescaped backtick (JS template strings) or quotes
54
56
  if (!inSingle && !inDouble && line[j] === '`') {
55
57
  let escaped = false;
56
58
  let k = j - 1;
@@ -96,7 +98,7 @@ function isLineInsideString(lines, targetLineIndex, ext = '') {
96
98
  return inBacktick || inTripleDouble || inTripleSingle || inSingle || inDouble;
97
99
  }
98
100
 
99
- /** Analyzes code to identify pure comment lines and block boundaries */
101
+ /** Performs a lexical analysis to categorize code lines and comment blocks */
100
102
  function analyzeComments(lines, ext = '') {
101
103
  const isPython = ext.toLowerCase() === '.py';
102
104
  const isHTML = ['.html', '.vue', '.svelte'].includes(ext.toLowerCase());
@@ -108,6 +110,7 @@ function analyzeComments(lines, ext = '') {
108
110
  let inDouble = false;
109
111
  let inBlockJS = false;
110
112
  let inBlockHTML = false;
113
+ // Iterate through each line character by character to detect comment boundaries
111
114
  for (let i = 0; i < lines.length; i++) {
112
115
  const line = lines[i];
113
116
  let commentStartIndex = -1;
@@ -247,8 +250,9 @@ function analyzeComments(lines, ext = '') {
247
250
  return analysis;
248
251
  }
249
252
 
250
- /** Splicers or removes comments from source data based on the requested mode */
253
+ /** Splices generated comments into the source data or removes existing ones */
251
254
  function spliceComments(data, comments, mode = 'default', ext = '') {
255
+ // Determine platform-specific line endings
252
256
  const hasCRLF = data.includes('\r\n');
253
257
  const lineEnding = hasCRLF ? '\r\n' : '\n';
254
258
  const originalLines = data.split(/\r?\n/);
@@ -258,6 +262,7 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
258
262
  const annotated = originalLines.map((text, index) => ({ text, originalIndex: index }));
259
263
  let analysis = null;
260
264
 
265
+ // 'clean' mode removes all existing comments/documentation
261
266
  if (mode === 'clean') {
262
267
  analysis = analyzeComments(originalLines, ext);
263
268
  const finalDeletions = new Set();
@@ -306,6 +311,7 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
306
311
  annotated.splice(lineNum - 1, 1);
307
312
  }
308
313
  } else {
314
+ // 'default'/'light'/'full' mode: Inject AI-generated comments
309
315
  for (const c of validComments) {
310
316
  if (isLineInsideString(originalLines, c.line - 1, ext)) {
311
317
  console.warn(`[devsplain] Skipping comment insertion at line ${c.line} to avoid string literal corruption.`);
@@ -368,7 +374,7 @@ function spliceComments(data, comments, mode = 'default', ext = '') {
368
374
  return annotated.map(line => line.text).join(lineEnding);
369
375
  }
370
376
 
371
- /** Main CLI entry point handler */
377
+ /** Main entry point for the CLI tool logic */
372
378
  async function runCLI() {
373
379
  rl = readline.createInterface({ input: process.stdin, output: process.stdout });
374
380
  askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
@@ -416,7 +422,8 @@ Options:
416
422
 
417
423
  if (args.includes('--setup-hook')) {
418
424
  rl.close();
419
- require('./setup-hook.js');
425
+ const installHooks = require('./setup-hook.js');
426
+ await installHooks();
420
427
  return;
421
428
  }
422
429
 
@@ -484,7 +491,7 @@ Options:
484
491
  let successCount = 0;
485
492
  let failCount = 0;
486
493
 
487
- /** Recursively traverses directories to process files */
494
+ /** Recursively traverses the file system to identify and process source files */
488
495
  async function processPath(targetPath) {
489
496
  const stats = fs.statSync(targetPath);
490
497
 
@@ -529,6 +536,7 @@ Options:
529
536
 
530
537
  console.log(` Analyzing ${filename} in ${mode} mode...`);
531
538
  try {
539
+ // Logic to either clean existing comments or replace/insert new ones
532
540
  let comments = [];
533
541
  let commentedCode;
534
542
  if (mode !== 'clean') {
@@ -544,6 +552,7 @@ Options:
544
552
  console.log(`---------------------------------------\n`);
545
553
  const answer = await askQuestion("Type 'write' to save to file, or press any key to discard: ");
546
554
  if (answer.toLowerCase() === 'write') {
555
+ // Use temporary file for atomic write operations
547
556
  const tempPath = targetPath + '.tmp';
548
557
  fs.writeFileSync(tempPath, commentedCode, 'utf8');
549
558
  fs.renameSync(tempPath, targetPath);
@@ -581,7 +590,6 @@ Options:
581
590
  rl.close();
582
591
  }
583
592
 
584
- // If running as a standalone script, start the CLI; otherwise export helpers
585
593
  if (require.main === module) {
586
594
  runCLI().catch(err => {
587
595
  console.error(err);
package/bin/setup-hook.js CHANGED
@@ -4,12 +4,12 @@ const { execSync } = require('child_process');
4
4
  const readline = require('readline');
5
5
 
6
6
  /**
7
- * Configures and installs git pre-commit and post-commit hooks.
8
- * Detects the local git repository and prompts the user for comment mode preferences.
7
+ * Initializes and installs Git pre-commit and post-commit hooks.
8
+ * Prompts the user for documentation mode configuration.
9
9
  */
10
10
  async function installHooks() {
11
11
  try {
12
- // Resolve the actual .git directory path
12
+ // Resolve the root .git directory path
13
13
  const gitDir = execSync('git rev-parse --git-dir', { encoding: 'utf8' }).trim();
14
14
  const hooksDir = path.join(gitDir, 'hooks');
15
15
  if (!fs.existsSync(hooksDir)) {
@@ -17,13 +17,13 @@ async function installHooks() {
17
17
  }
18
18
 
19
19
  let modeChoice = '1';
20
- // Check if running in an interactive terminal to prompt for preferences
20
+ // Interactive mode requires a TTY terminal
21
21
  if (process.stdout.isTTY) {
22
22
  const rl = readline.createInterface({
23
23
  input: process.stdin,
24
24
  output: process.stdout
25
25
  });
26
- // Helper to wrap readline as a Promise for async/await control flow
26
+ // Promisify the readline interface for async/await flow
27
27
  const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
28
28
 
29
29
  console.log('\nSelect default commenting mode for Git commits:');
@@ -35,7 +35,7 @@ async function installHooks() {
35
35
  rl.close();
36
36
  }
37
37
 
38
- // Map user selection to CLI argument flags for the post-commit handler
38
+ // Determine documentation style flags based on user input
39
39
  let modeArgs = '';
40
40
  if (modeChoice === '2') {
41
41
  modeArgs = ' --light';
@@ -43,7 +43,7 @@ async function installHooks() {
43
43
  modeArgs = ' --full';
44
44
  }
45
45
 
46
- // Define and write the pre-commit shell script
46
+ // Write the pre-commit script to the git hooks directory
47
47
  const preCommitHookPath = path.join(hooksDir, 'pre-commit');
48
48
  const preCommitContent = `#!/bin/sh
49
49
  # devsplain native pre-commit hook
@@ -51,12 +51,12 @@ echo "Running pre-commit tests..."
51
51
  npm test || exit 1
52
52
  `;
53
53
  fs.writeFileSync(preCommitHookPath, preCommitContent);
54
- // Ensure the shell script is executable by the system
55
54
  try {
55
+ // Ensure the hook file is executable
56
56
  fs.chmodSync(preCommitHookPath, 0o755);
57
57
  } catch (err) {}
58
58
 
59
- // Define and write the post-commit shell script
59
+ // Write the post-commit script that triggers documentation generation
60
60
  const postCommitHookPath = path.join(hooksDir, 'post-commit');
61
61
  const postCommitContent = `#!/bin/sh
62
62
  # devsplain native post-commit hook
@@ -64,7 +64,6 @@ echo "Auto-generating comments for files in the last commit..."
64
64
  node bin/post-commit.js${modeArgs} || exit 1
65
65
  `;
66
66
  fs.writeFileSync(postCommitHookPath, postCommitContent);
67
- // Ensure the post-commit shell script is executable
68
67
  try {
69
68
  fs.chmodSync(postCommitHookPath, 0o755);
70
69
  } catch (err) {}
@@ -75,5 +74,8 @@ node bin/post-commit.js${modeArgs} || exit 1
75
74
  }
76
75
  }
77
76
 
78
- // Execute the hook installation sequence
79
- installHooks();
77
+ // Execute the function automatically if the file is run directly
78
+ if (require.main === module) {
79
+ installHooks();
80
+ }
81
+ module.exports = installHooks;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devsplain",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
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",