claude-git-hooks 1.5.4 → 2.0.0
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/CHANGELOG.md +89 -1
- package/README.md +130 -35
- package/bin/claude-hooks +253 -287
- package/lib/hooks/pre-commit.js +335 -0
- package/lib/hooks/prepare-commit-msg.js +283 -0
- package/lib/utils/claude-client.js +373 -0
- package/lib/utils/file-operations.js +409 -0
- package/lib/utils/git-operations.js +341 -0
- package/lib/utils/logger.js +141 -0
- package/lib/utils/prompt-builder.js +283 -0
- package/lib/utils/resolution-prompt.js +291 -0
- package/package.json +52 -40
- package/templates/pre-commit +58 -411
- package/templates/prepare-commit-msg +62 -118
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: git-operations.js
|
|
3
|
+
* Purpose: Provides abstraction layer for git commands
|
|
4
|
+
*
|
|
5
|
+
* Key responsibilities:
|
|
6
|
+
* - Execute git commands safely with error handling
|
|
7
|
+
* - Provide cross-platform git operations
|
|
8
|
+
* - Abstract git complexity from business logic
|
|
9
|
+
*
|
|
10
|
+
* Dependencies:
|
|
11
|
+
* - child_process: For executing git commands
|
|
12
|
+
* - logger: For debug and error logging
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { execSync } from 'child_process';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import logger from './logger.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Custom error for git operation failures
|
|
21
|
+
* Why: Provides structured error handling with git-specific context
|
|
22
|
+
*/
|
|
23
|
+
class GitError extends Error {
|
|
24
|
+
constructor(message, { command, cause, output } = {}) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = 'GitError';
|
|
27
|
+
this.command = command;
|
|
28
|
+
this.cause = cause;
|
|
29
|
+
this.output = output;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Executes a git command safely with error handling
|
|
35
|
+
* Why: Centralizes git command execution with consistent error handling
|
|
36
|
+
* and logging across all git operations
|
|
37
|
+
*
|
|
38
|
+
* @param {string} command - Git command to execute (without 'git' prefix)
|
|
39
|
+
* @param {Object} options - Options for execSync
|
|
40
|
+
* @returns {string} Command output (trimmed)
|
|
41
|
+
* @throws {GitError} If command fails
|
|
42
|
+
*/
|
|
43
|
+
const execGitCommand = (command, options = {}) => {
|
|
44
|
+
const fullCommand = `git ${command}`;
|
|
45
|
+
|
|
46
|
+
logger.debug(
|
|
47
|
+
'git-operations - execGitCommand',
|
|
48
|
+
'Executing git command',
|
|
49
|
+
{ command: fullCommand }
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const output = execSync(fullCommand, {
|
|
54
|
+
encoding: 'utf8',
|
|
55
|
+
stdio: ['pipe', 'pipe', 'pipe'], // Capture stderr
|
|
56
|
+
...options
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
logger.debug(
|
|
60
|
+
'git-operations - execGitCommand',
|
|
61
|
+
'Command executed successfully',
|
|
62
|
+
{ command: fullCommand, outputLength: output.length }
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return output.trim();
|
|
66
|
+
|
|
67
|
+
} catch (error) {
|
|
68
|
+
logger.error(
|
|
69
|
+
'git-operations - execGitCommand',
|
|
70
|
+
`Git command failed: ${fullCommand}`,
|
|
71
|
+
error
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
throw new GitError('Git command failed', {
|
|
75
|
+
command: fullCommand,
|
|
76
|
+
cause: error,
|
|
77
|
+
output: error.stderr || error.stdout
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Gets list of staged files
|
|
84
|
+
* Why: Pre-commit hooks need to analyze only staged changes, not all working tree
|
|
85
|
+
*
|
|
86
|
+
* @param {Object} options - Filter options
|
|
87
|
+
* @param {Array<string>} options.extensions - File extensions to filter (e.g., ['.java', '.xml'])
|
|
88
|
+
* @param {boolean} options.includeDeleted - Include deleted files (default: false)
|
|
89
|
+
* @returns {Array<string>} Array of staged file paths
|
|
90
|
+
*
|
|
91
|
+
* Git diff filter codes:
|
|
92
|
+
* A = Added, C = Copied, M = Modified, R = Renamed
|
|
93
|
+
* D = Deleted, T = Type changed, U = Unmerged, X = Unknown
|
|
94
|
+
*/
|
|
95
|
+
const getStagedFiles = ({ extensions = [], includeDeleted = false } = {}) => {
|
|
96
|
+
logger.debug(
|
|
97
|
+
'git-operations - getStagedFiles',
|
|
98
|
+
'Getting staged files',
|
|
99
|
+
{ extensions, includeDeleted }
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Why: --diff-filter excludes deleted files unless explicitly requested
|
|
103
|
+
// ACM = Added, Copied, Modified (excludes Deleted, Renamed, etc.)
|
|
104
|
+
const filter = includeDeleted ? 'ACMR' : 'ACM';
|
|
105
|
+
const output = execGitCommand(`diff --cached --name-only --diff-filter=${filter}`);
|
|
106
|
+
|
|
107
|
+
if (!output) {
|
|
108
|
+
logger.debug('git-operations - getStagedFiles', 'No staged files found');
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Why: Split by LF or CRLF to handle Windows line endings
|
|
113
|
+
const files = output.split(/\r?\n/).filter(f => f.length > 0);
|
|
114
|
+
|
|
115
|
+
// Filter by extensions if provided
|
|
116
|
+
if (extensions.length > 0) {
|
|
117
|
+
const filtered = files.filter(file =>
|
|
118
|
+
extensions.some(ext => file.endsWith(ext))
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
logger.debug(
|
|
122
|
+
'git-operations - getStagedFiles',
|
|
123
|
+
'Filtered files by extension',
|
|
124
|
+
{ totalFiles: files.length, filteredFiles: filtered.length, extensions }
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
return filtered;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return files;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Gets the diff for a specific file
|
|
135
|
+
* Why: Shows what changed in a file, essential for code review
|
|
136
|
+
*
|
|
137
|
+
* @param {string} filePath - Path to the file
|
|
138
|
+
* @param {Object} options - Diff options
|
|
139
|
+
* @param {boolean} options.cached - Get staged changes (default: true)
|
|
140
|
+
* @param {number} options.context - Lines of context around changes (default: 3)
|
|
141
|
+
* @returns {string} Diff output
|
|
142
|
+
*/
|
|
143
|
+
const getFileDiff = (filePath, { cached = true, context = 3 } = {}) => {
|
|
144
|
+
logger.debug(
|
|
145
|
+
'git-operations - getFileDiff',
|
|
146
|
+
'Getting file diff',
|
|
147
|
+
{ filePath, cached, context }
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const cachedFlag = cached ? '--cached' : '';
|
|
151
|
+
const contextFlag = `-U${context}`;
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
return execGitCommand(`diff ${cachedFlag} ${contextFlag} -- "${filePath}"`);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
// Why: Empty diff is valid (e.g., new file with no changes), don't throw error
|
|
157
|
+
if (error.output && error.output.includes('')) {
|
|
158
|
+
logger.debug('git-operations - getFileDiff', 'Empty diff', { filePath });
|
|
159
|
+
return '';
|
|
160
|
+
}
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Gets file content from git staging area
|
|
167
|
+
* Why: Reads the staged version of a file, not the working directory version
|
|
168
|
+
* This ensures we analyze what will be committed, not uncommitted changes
|
|
169
|
+
*
|
|
170
|
+
* @param {string} filePath - Path to the file
|
|
171
|
+
* @returns {string} File content from staging area
|
|
172
|
+
*/
|
|
173
|
+
const getFileContentFromStaging = (filePath) => {
|
|
174
|
+
logger.debug(
|
|
175
|
+
'git-operations - getFileContentFromStaging',
|
|
176
|
+
'Reading file from staging area',
|
|
177
|
+
{ filePath }
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// Why: git show :path reads from index (staging area), not working tree
|
|
181
|
+
return execGitCommand(`show ":${filePath}"`);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Checks if a file is newly added (not in previous commits)
|
|
186
|
+
* Why: New files need full content analysis, not just diffs
|
|
187
|
+
*
|
|
188
|
+
* @param {string} filePath - Path to the file
|
|
189
|
+
* @returns {boolean} True if file is newly added
|
|
190
|
+
*/
|
|
191
|
+
const isNewFile = (filePath) => {
|
|
192
|
+
logger.debug(
|
|
193
|
+
'git-operations - isNewFile',
|
|
194
|
+
'Checking if file is new',
|
|
195
|
+
{ filePath }
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const status = execGitCommand(`diff --cached --name-status -- "${filePath}"`);
|
|
200
|
+
// Why: Status starts with 'A' for Added files
|
|
201
|
+
const isNew = status.startsWith('A\t');
|
|
202
|
+
|
|
203
|
+
logger.debug(
|
|
204
|
+
'git-operations - isNewFile',
|
|
205
|
+
'File status checked',
|
|
206
|
+
{ filePath, isNew, status }
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
return isNew;
|
|
210
|
+
|
|
211
|
+
} catch (error) {
|
|
212
|
+
logger.error('git-operations - isNewFile', 'Failed to check file status', error);
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Gets repository root directory
|
|
219
|
+
* Why: Needed to resolve relative paths and locate configuration files
|
|
220
|
+
*
|
|
221
|
+
* @returns {string} Absolute path to repository root
|
|
222
|
+
*/
|
|
223
|
+
const getRepoRoot = () => {
|
|
224
|
+
logger.debug('git-operations - getRepoRoot', 'Getting repository root');
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
return execGitCommand('rev-parse --show-toplevel');
|
|
228
|
+
} catch (error) {
|
|
229
|
+
throw new GitError('Not a git repository or no git found', { cause: error });
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Gets current branch name
|
|
235
|
+
* Why: Used for context in prompts and logging
|
|
236
|
+
*
|
|
237
|
+
* @returns {string} Current branch name
|
|
238
|
+
*/
|
|
239
|
+
const getCurrentBranch = () => {
|
|
240
|
+
logger.debug('git-operations - getCurrentBranch', 'Getting current branch');
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
return execGitCommand('branch --show-current');
|
|
244
|
+
} catch (error) {
|
|
245
|
+
logger.error('git-operations - getCurrentBranch', 'Failed to get branch', error);
|
|
246
|
+
return 'unknown';
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Gets repository name from root directory
|
|
252
|
+
* Why: Used for context in prompts and reports
|
|
253
|
+
*
|
|
254
|
+
* @returns {string} Repository name (last component of path)
|
|
255
|
+
*/
|
|
256
|
+
const getRepoName = () => {
|
|
257
|
+
logger.debug('git-operations - getRepoName', 'Getting repository name');
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const repoRoot = getRepoRoot();
|
|
261
|
+
// Why: Use path.basename() for cross-platform path handling
|
|
262
|
+
return path.basename(repoRoot);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
logger.error('git-operations - getRepoName', 'Failed to get repo name', error);
|
|
265
|
+
return 'unknown';
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Gets statistics about staged files
|
|
271
|
+
* Why: Provides overview for commit message generation
|
|
272
|
+
*
|
|
273
|
+
* @returns {Object} Statistics object with file counts and changes
|
|
274
|
+
* Statistics structure:
|
|
275
|
+
* {
|
|
276
|
+
* totalFiles: number, // Total staged files
|
|
277
|
+
* addedFiles: number, // Newly added files
|
|
278
|
+
* modifiedFiles: number, // Modified files
|
|
279
|
+
* deletedFiles: number, // Deleted files
|
|
280
|
+
* insertions: number, // Total lines added
|
|
281
|
+
* deletions: number // Total lines deleted
|
|
282
|
+
* }
|
|
283
|
+
*/
|
|
284
|
+
const getStagedStats = () => {
|
|
285
|
+
logger.debug('git-operations - getStagedStats', 'Getting staged file statistics');
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
const shortstat = execGitCommand('diff --cached --shortstat');
|
|
289
|
+
const numstat = execGitCommand('diff --cached --numstat');
|
|
290
|
+
const nameStatus = execGitCommand('diff --cached --name-status');
|
|
291
|
+
|
|
292
|
+
// Parse shortstat: "X files changed, Y insertions(+), Z deletions(-)"
|
|
293
|
+
const statsMatch = shortstat.match(/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/);
|
|
294
|
+
const insertions = statsMatch?.[2] ? parseInt(statsMatch[2], 10) : 0;
|
|
295
|
+
const deletions = statsMatch?.[3] ? parseInt(statsMatch[3], 10) : 0;
|
|
296
|
+
|
|
297
|
+
// Count file types from name-status
|
|
298
|
+
// Why: Split by LF or CRLF to handle Windows line endings
|
|
299
|
+
const statusLines = nameStatus.split(/\r?\n/).filter(l => l.length > 0);
|
|
300
|
+
const added = statusLines.filter(l => l.startsWith('A\t')).length;
|
|
301
|
+
const modified = statusLines.filter(l => l.startsWith('M\t')).length;
|
|
302
|
+
const deleted = statusLines.filter(l => l.startsWith('D\t')).length;
|
|
303
|
+
|
|
304
|
+
const stats = {
|
|
305
|
+
totalFiles: statusLines.length,
|
|
306
|
+
addedFiles: added,
|
|
307
|
+
modifiedFiles: modified,
|
|
308
|
+
deletedFiles: deleted,
|
|
309
|
+
insertions,
|
|
310
|
+
deletions
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
logger.debug('git-operations - getStagedStats', 'Statistics calculated', stats);
|
|
314
|
+
|
|
315
|
+
return stats;
|
|
316
|
+
|
|
317
|
+
} catch (error) {
|
|
318
|
+
logger.error('git-operations - getStagedStats', 'Failed to get statistics', error);
|
|
319
|
+
// Return empty stats on error
|
|
320
|
+
return {
|
|
321
|
+
totalFiles: 0,
|
|
322
|
+
addedFiles: 0,
|
|
323
|
+
modifiedFiles: 0,
|
|
324
|
+
deletedFiles: 0,
|
|
325
|
+
insertions: 0,
|
|
326
|
+
deletions: 0
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
export {
|
|
332
|
+
GitError,
|
|
333
|
+
getStagedFiles,
|
|
334
|
+
getFileDiff,
|
|
335
|
+
getFileContentFromStaging,
|
|
336
|
+
isNewFile,
|
|
337
|
+
getRepoRoot,
|
|
338
|
+
getCurrentBranch,
|
|
339
|
+
getRepoName,
|
|
340
|
+
getStagedStats
|
|
341
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: logger.js
|
|
3
|
+
* Purpose: Provides three-level logging system (user, debug, error)
|
|
4
|
+
*
|
|
5
|
+
* Key features:
|
|
6
|
+
* - User logs: Simple, colored messages for end users
|
|
7
|
+
* - Debug logs: Technical details with [file/class - method] format
|
|
8
|
+
* - Error logs: Full error context with [file/class - method] format
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import logger from './logger.js';
|
|
12
|
+
* logger.info('User message');
|
|
13
|
+
* logger.debug('file-name - methodName', 'Debug message', { data });
|
|
14
|
+
* logger.error('file-name - methodName', 'Error message', error);
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
class Logger {
|
|
18
|
+
constructor({ debugMode = false } = {}) {
|
|
19
|
+
this.debugMode = debugMode || process.env.DEBUG === 'true';
|
|
20
|
+
this.colors = {
|
|
21
|
+
reset: '\x1b[0m',
|
|
22
|
+
red: '\x1b[31m',
|
|
23
|
+
green: '\x1b[32m',
|
|
24
|
+
yellow: '\x1b[33m',
|
|
25
|
+
blue: '\x1b[34m',
|
|
26
|
+
gray: '\x1b[90m'
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* User-facing informational message (always shown)
|
|
32
|
+
* Why: Provides feedback about what the tool is doing
|
|
33
|
+
*
|
|
34
|
+
* @param {string} message - Simple message for end users
|
|
35
|
+
*/
|
|
36
|
+
info(message) {
|
|
37
|
+
console.log(`${this.colors.blue}ℹ️ ${message}${this.colors.reset}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* User-facing success message (always shown)
|
|
42
|
+
* Why: Confirms successful operations to the user
|
|
43
|
+
*
|
|
44
|
+
* @param {string} message - Success message
|
|
45
|
+
*/
|
|
46
|
+
success(message) {
|
|
47
|
+
console.log(`${this.colors.green}✅ ${message}${this.colors.reset}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* User-facing warning message (always shown)
|
|
52
|
+
* Why: Alerts user to non-critical issues
|
|
53
|
+
*
|
|
54
|
+
* @param {string} message - Warning message
|
|
55
|
+
*/
|
|
56
|
+
warning(message) {
|
|
57
|
+
console.log(`${this.colors.yellow}⚠️ ${message}${this.colors.reset}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Developer-facing debug message (only in debug mode)
|
|
62
|
+
* Why: Provides technical details for troubleshooting without cluttering normal output
|
|
63
|
+
*
|
|
64
|
+
* Format: [DEBUG timestamp] [context] message
|
|
65
|
+
* Context format: "file/class - method"
|
|
66
|
+
*
|
|
67
|
+
* @param {string} context - Location identifier (e.g., "file-analyzer - analyzeFile")
|
|
68
|
+
* @param {string} message - Debug message
|
|
69
|
+
* @param {Object} data - Optional data to log as JSON
|
|
70
|
+
*/
|
|
71
|
+
debug(context, message, data = {}) {
|
|
72
|
+
if (!this.debugMode) return;
|
|
73
|
+
|
|
74
|
+
const timestamp = new Date().toISOString();
|
|
75
|
+
console.log(
|
|
76
|
+
`${this.colors.gray}[DEBUG ${timestamp}] [${context}] ${message}${this.colors.reset}`
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (Object.keys(data).length > 0) {
|
|
80
|
+
console.log(this.colors.gray, JSON.stringify(data, null, 2), this.colors.reset);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Error message with full context (always shown)
|
|
86
|
+
* Why: Provides complete error information for debugging issues
|
|
87
|
+
*
|
|
88
|
+
* Format: ❌ [context] message
|
|
89
|
+
* Context format: "file/class - method"
|
|
90
|
+
*
|
|
91
|
+
* @param {string} context - Location identifier (e.g., "file-analyzer - analyzeFile")
|
|
92
|
+
* @param {string} message - Error message
|
|
93
|
+
* @param {Error} error - Optional error object with stack trace and context
|
|
94
|
+
*/
|
|
95
|
+
error(context, message, error = null) {
|
|
96
|
+
console.error(`${this.colors.red}❌ [${context}] ${message}${this.colors.reset}`);
|
|
97
|
+
|
|
98
|
+
if (error) {
|
|
99
|
+
console.error(`${this.colors.red}Error details:${this.colors.reset}`, error.message || error);
|
|
100
|
+
|
|
101
|
+
// Show stack trace only in debug mode to avoid overwhelming users
|
|
102
|
+
if (error.stack && this.debugMode) {
|
|
103
|
+
console.error(`${this.colors.gray}Stack trace:${this.colors.reset}`);
|
|
104
|
+
console.error(error.stack);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Log additional context if available (custom error properties)
|
|
108
|
+
if (error.context) {
|
|
109
|
+
console.error(`${this.colors.gray}Context:${this.colors.reset}`, error.context);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Log cause chain if available (Error.cause from Node.js 16.9+)
|
|
113
|
+
if (error.cause) {
|
|
114
|
+
console.error(`${this.colors.gray}Caused by:${this.colors.reset}`, error.cause);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Set debug mode dynamically
|
|
121
|
+
* Why: Allows enabling debug mode at runtime
|
|
122
|
+
*
|
|
123
|
+
* @param {boolean} enabled - Whether to enable debug mode
|
|
124
|
+
*/
|
|
125
|
+
setDebugMode(enabled) {
|
|
126
|
+
this.debugMode = enabled;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if debug mode is enabled
|
|
131
|
+
*
|
|
132
|
+
* @returns {boolean} True if debug mode is active
|
|
133
|
+
*/
|
|
134
|
+
isDebugMode() {
|
|
135
|
+
return this.debugMode;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Export singleton instance
|
|
140
|
+
// Why: Single logger instance ensures consistent debug mode across entire application
|
|
141
|
+
export default new Logger({ debugMode: process.env.DEBUG === 'true' });
|