ctxman 3.0.0 → 3.1.1

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 (224) hide show
  1. package/.ctxman/llm-profiles.json +1067 -39
  2. package/.ctxman/profiles/backend-team.json +13 -0
  3. package/.ctxman/profiles/default.json +12 -0
  4. package/.ctxman/profiles/devops-team.json +21 -0
  5. package/.ctxman/profiles/frontend-team.json +20 -0
  6. package/.ctxman/templates/bug-fix.json +17 -0
  7. package/.ctxman/templates/code-review.json +9 -0
  8. package/.ctxman/templates/documentation.json +9 -0
  9. package/.ctxman/templates/feature.json +9 -0
  10. package/.ctxman/templates/full-context.json +9 -0
  11. package/.ctxman/templates/refactoring.json +18 -0
  12. package/.ctxman/templates/testing.json +9 -0
  13. package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
  14. package/LICENSE +21 -0
  15. package/README.md +381 -136
  16. package/bin/cli.js +1880 -529
  17. package/bin/cm-ask.js +18 -67
  18. package/bin/cm-audit.js +121 -0
  19. package/bin/cm-count.js +151 -0
  20. package/bin/cm-gitingest.js +128 -115
  21. package/bin/cm-map.js +242 -0
  22. package/bin/cm-mcp.js +293 -0
  23. package/bin/cm-pack.js +405 -0
  24. package/bin/cm-policy.js +170 -0
  25. package/bin/cm-pr.js +248 -0
  26. package/bin/cm-search.js +178 -0
  27. package/bin/cm-update.js +128 -128
  28. package/bin/cm-why.js +197 -0
  29. package/bin/mcp-server.js +8 -24
  30. package/ctxman.js +237 -183
  31. package/examples/README.md +19 -4
  32. package/examples/github-actions/ctxman-check.yml +49 -0
  33. package/examples/wizard-profiles/security-audit/profile.json +7 -1
  34. package/index.d.ts +1422 -0
  35. package/index.js +55 -31
  36. package/lib/agents/pr-failure-agent.js +48 -45
  37. package/lib/analyzers/ai-suggester.js +451 -0
  38. package/lib/analyzers/dependency-scanner.js +505 -0
  39. package/lib/analyzers/duplicate-detector.js +264 -0
  40. package/lib/analyzers/go-method-analyzer.js +151 -110
  41. package/lib/analyzers/import-tracker.js +431 -0
  42. package/lib/analyzers/method-analyzer.js +1055 -434
  43. package/lib/analyzers/performance-dashboard.js +687 -0
  44. package/lib/analyzers/token-calculator.js +895 -504
  45. package/lib/analyzers/unused-exports-detector.js +336 -0
  46. package/lib/api/mcp/install.js +424 -0
  47. package/lib/api/mcp/prompts.js +281 -281
  48. package/lib/api/mcp/resources.js +300 -241
  49. package/lib/api/mcp/sandbox.js +89 -0
  50. package/lib/api/mcp/server.js +772 -312
  51. package/lib/api/mcp/start.js +125 -0
  52. package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
  53. package/lib/api/rest/server.js +105 -28
  54. package/lib/cache/CacheManager.js +9 -17
  55. package/lib/core/Analyzer.js +16 -10
  56. package/lib/core/ContextBuilder.js +19 -28
  57. package/lib/core/Reporter.js +15 -14
  58. package/lib/core/Scanner.js +81 -12
  59. package/lib/count/index.js +100 -0
  60. package/lib/formatters/format-registry.js +394 -372
  61. package/lib/formatters/gitingest-formatter.js +656 -621
  62. package/lib/formatters/toon-formatter-v1.3.js +637 -632
  63. package/lib/formatters/toon-formatter.js +239 -240
  64. package/lib/integrations/git/BlameTracker.js +4 -4
  65. package/lib/integrations/git/DiffAnalyzer.js +38 -31
  66. package/lib/integrations/git/GitClient.js +71 -53
  67. package/lib/lsp/server.js +311 -0
  68. package/lib/map/graph.js +299 -0
  69. package/lib/map/imports.js +233 -0
  70. package/lib/map/index.js +33 -0
  71. package/lib/map/rank.js +253 -0
  72. package/lib/map/render.js +346 -0
  73. package/lib/map/resolver.js +574 -0
  74. package/lib/pack/budget.js +111 -0
  75. package/lib/pack/code-lexer.js +360 -0
  76. package/lib/pack/compress.js +414 -0
  77. package/lib/pack/index.js +354 -0
  78. package/lib/pack/renderers/common.js +134 -0
  79. package/lib/pack/renderers/index.js +24 -0
  80. package/lib/pack/renderers/json.js +47 -0
  81. package/lib/pack/renderers/markdown.js +54 -0
  82. package/lib/pack/renderers/plain.js +38 -0
  83. package/lib/pack/renderers/xml.js +85 -0
  84. package/lib/pack/select.js +177 -0
  85. package/lib/pack/split.js +182 -0
  86. package/lib/pack/tree.js +35 -0
  87. package/lib/parsers/gitignore-parser.js +116 -107
  88. package/lib/parsers/method-filter-parser.js +50 -33
  89. package/lib/plugins/ExporterPlugin.js +3 -3
  90. package/lib/plugins/LanguagePlugin.js +3 -3
  91. package/lib/plugins/PluginManager.js +7 -13
  92. package/lib/policy/audit.js +312 -0
  93. package/lib/policy/enforce.js +125 -0
  94. package/lib/policy/ignore-files.js +169 -0
  95. package/lib/policy/index.js +53 -0
  96. package/lib/policy/policy.js +489 -0
  97. package/lib/pr/diff-parser.js +267 -0
  98. package/lib/pr/method-mapper.js +148 -0
  99. package/lib/pr/pr-context.js +829 -0
  100. package/lib/pr/pr-git.js +149 -0
  101. package/lib/pr/pr-renderer.js +644 -0
  102. package/lib/pr/related-tests.js +296 -0
  103. package/lib/rag/DataSourcePlugin.js +5 -5
  104. package/lib/rag/EmbeddingProvider.js +19 -8
  105. package/lib/rag/Indexer.js +14 -5
  106. package/lib/rag/VectorStoreFactory.js +3 -3
  107. package/lib/rag/ask.js +122 -0
  108. package/lib/rag/sources/FileDataSource.js +12 -9
  109. package/lib/rag/stores/BaseVectorStore.js +5 -5
  110. package/lib/rag/stores/ChromaDBStore.js +10 -10
  111. package/lib/rag/stores/LanceDBStore.js +29 -19
  112. package/lib/rag/stores/PineconeStore.js +11 -9
  113. package/lib/search/bm25.js +433 -0
  114. package/lib/search/code-units.js +201 -0
  115. package/lib/search/symbols.js +189 -0
  116. package/lib/ui/dashboard.js +120 -89
  117. package/lib/ui/index.js +1 -6
  118. package/lib/ui/init-wizard-ui.js +550 -0
  119. package/lib/ui/progress-bar.js +94 -88
  120. package/lib/ui/select-input.js +40 -32
  121. package/lib/ui/template-selector.js +99 -0
  122. package/lib/ui/wizard.js +374 -329
  123. package/lib/utils/claude-token-counter.js +151 -0
  124. package/lib/utils/cli-output.js +17 -0
  125. package/lib/utils/clipboard-utils.js +49 -50
  126. package/lib/utils/config-utils.js +81 -66
  127. package/lib/utils/context-versioning.js +532 -0
  128. package/lib/utils/error-handler.js +112 -112
  129. package/lib/utils/file-utils.js +153 -69
  130. package/lib/utils/format-converter.js +356 -283
  131. package/lib/utils/git-utils.js +366 -294
  132. package/lib/utils/llm-cost-estimator.js +281 -0
  133. package/lib/utils/llm-detector.js +242 -78
  134. package/lib/utils/logger.js +246 -237
  135. package/lib/utils/messages.js +355 -0
  136. package/lib/utils/multi-repo-manager.js +364 -0
  137. package/lib/utils/profile-manager.js +380 -0
  138. package/lib/utils/safe-read.js +61 -0
  139. package/lib/utils/secret-redactor.js +61 -0
  140. package/lib/utils/snapshot-manager.js +679 -0
  141. package/lib/utils/template-manager.js +378 -0
  142. package/lib/utils/token-utils.js +201 -170
  143. package/lib/utils/tokenizer-adapter.js +421 -377
  144. package/lib/utils/updater.js +539 -506
  145. package/lib/watch/FileWatcher.js +12 -8
  146. package/lib/watch/IncrementalAnalyzer.js +7 -7
  147. package/lib/why/coupling.js +81 -0
  148. package/lib/why/errors.js +23 -0
  149. package/lib/why/git.js +169 -0
  150. package/lib/why/history.js +108 -0
  151. package/lib/why/index.js +720 -0
  152. package/lib/why/ownership.js +79 -0
  153. package/lib/why/paths.js +207 -0
  154. package/lib/why/references.js +259 -0
  155. package/lib/why/render.js +722 -0
  156. package/lib/why/search.js +358 -0
  157. package/lib/why/target.js +389 -0
  158. package/lib/wizards/config-generator.js +257 -0
  159. package/lib/wizards/project-detector.js +572 -0
  160. package/package.json +44 -10
  161. package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
  162. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  163. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  164. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  165. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  166. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  167. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  168. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  169. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  170. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  171. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  172. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  173. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  174. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  175. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  176. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  177. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  178. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  179. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  180. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  181. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  182. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  183. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  184. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  185. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  186. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  187. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  188. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  189. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  190. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  191. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  192. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  193. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  194. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  195. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  196. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  197. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  198. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  199. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  200. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  201. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  202. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  203. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  204. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  205. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  206. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  207. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  208. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  209. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  210. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  211. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  212. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  213. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  214. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  215. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  216. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  217. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  218. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  219. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  220. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  221. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  222. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  223. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  224. package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
@@ -8,247 +8,260 @@ import fs from 'fs';
8
8
  import path from 'path';
9
9
 
10
10
  class Logger {
11
- constructor(options = {}) {
12
- this.level = options.level || process.env.LOG_LEVEL || 'info';
13
- this.logToFile = options.logToFile !== false;
14
- this.logDir = options.logDir || path.join(process.cwd(), '.ctxman', 'logs');
15
- this.logFile = options.logFile || `ctxman-${this.getDateString()}.log`;
16
- this.silent = options.silent || false;
17
-
18
- // Log levels (higher number = more verbose)
19
- this.levels = {
20
- error: 0,
21
- warn: 1,
22
- info: 2,
23
- debug: 3,
24
- trace: 4
25
- };
26
-
27
- // Colors for terminal output
28
- this.colors = {
29
- error: '\x1b[31m', // Red
30
- warn: '\x1b[33m', // Yellow
31
- info: '\x1b[36m', // Cyan
32
- debug: '\x1b[35m', // Magenta
33
- trace: '\x1b[90m', // Gray
34
- reset: '\x1b[0m'
35
- };
36
-
37
- // Icons for each level
38
- this.icons = {
39
- error: '❌',
40
- warn: '⚠️',
41
- info: 'ℹ️',
42
- debug: '🔍',
43
- trace: '🔬'
44
- };
45
-
46
- this.initializeLogDirectory();
47
- }
48
-
49
- /**
50
- * Initialize log directory
51
- */
52
- initializeLogDirectory() {
53
- if (this.logToFile) {
54
- const fullLogDir = path.isAbsolute(this.logDir) ? this.logDir : path.join(process.cwd(), this.logDir);
55
- if (!fs.existsSync(fullLogDir)) {
56
- fs.mkdirSync(fullLogDir, { recursive: true });
57
- }
11
+ constructor(options = {}) {
12
+ this.level = (options.level || process.env.LOG_LEVEL || 'info').toLowerCase();
13
+ this.logToFile = options.logToFile !== false;
14
+ this.logDir = options.logDir || path.join(process.cwd(), '.ctxman', 'logs');
15
+ this.logFile = options.logFile || `ctxman-${this.getDateString()}.log`;
16
+ this.silent = options.silent || false;
17
+
18
+ // Log levels (higher number = more verbose)
19
+ this.levels = {
20
+ error: 0,
21
+ warn: 1,
22
+ info: 2,
23
+ debug: 3,
24
+ trace: 4,
25
+ };
26
+
27
+ // Colors for terminal output
28
+ this.colors = {
29
+ error: '\x1b[31m', // Red
30
+ warn: '\x1b[33m', // Yellow
31
+ info: '\x1b[36m', // Cyan
32
+ debug: '\x1b[35m', // Magenta
33
+ trace: '\x1b[90m', // Gray
34
+ reset: '\x1b[0m',
35
+ };
36
+
37
+ // Icons for each level
38
+ this.icons = {
39
+ error: '❌',
40
+ warn: '⚠️',
41
+ info: 'ℹ️',
42
+ debug: '🔍',
43
+ trace: '🔬',
44
+ };
45
+
46
+ this.initializeLogDirectory();
47
+ }
48
+
49
+ /**
50
+ * Initialize log directory
51
+ */
52
+ initializeLogDirectory() {
53
+ if (this.logToFile) {
54
+ const fullLogDir = path.isAbsolute(this.logDir)
55
+ ? this.logDir
56
+ : path.join(process.cwd(), this.logDir);
57
+ try {
58
+ if (!fs.existsSync(fullLogDir)) {
59
+ fs.mkdirSync(fullLogDir, { recursive: true });
58
60
  }
61
+ } catch (_error) {
62
+ // Loggers are created at import time, so an unwritable working directory (such as
63
+ // "/" for an MCP server spawned by a desktop app) must not crash the process
64
+ this.logToFile = false;
65
+ }
59
66
  }
60
-
61
- /**
62
- * Get current date string for log file names
63
- */
64
- getDateString() {
65
- const now = new Date();
66
- return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
67
- }
68
-
69
- /**
70
- * Get timestamp for log entries
71
- */
72
- getTimestamp() {
73
- return new Date().toISOString();
67
+ }
68
+
69
+ /**
70
+ * Get current date string for log file names
71
+ */
72
+ getDateString() {
73
+ const now = new Date();
74
+ return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
75
+ }
76
+
77
+ /**
78
+ * Get timestamp for log entries
79
+ */
80
+ getTimestamp() {
81
+ return new Date().toISOString();
82
+ }
83
+
84
+ /**
85
+ * Check if should log based on level
86
+ */
87
+ shouldLog(level) {
88
+ return this.levels[level] <= this.levels[this.level];
89
+ }
90
+
91
+ /**
92
+ * Format log message
93
+ */
94
+ formatMessage(level, message, meta = {}) {
95
+ const timestamp = this.getTimestamp();
96
+ const icon = this.icons[level];
97
+ // One entry per line: line breaks in the message (a path, an error text) are written
98
+ // escaped, so the message cannot add entries of its own
99
+ const text = String(message).replace(/\r\n|\r|\n/g, '\\n');
100
+
101
+ // Console format (with colors)
102
+ const consoleMessage = `${this.colors[level]}${icon} [${level.toUpperCase()}]${this.colors.reset} ${text}`;
103
+
104
+ // File format (plain text with metadata)
105
+ const metaString = Object.keys(meta).length > 0 ? ` | ${JSON.stringify(meta)}` : '';
106
+ const fileMessage = `[${timestamp}] [${level.toUpperCase()}] ${text}${metaString}`;
107
+
108
+ return { consoleMessage, fileMessage };
109
+ }
110
+
111
+ /**
112
+ * Write log to file
113
+ */
114
+ writeToFile(message) {
115
+ if (!this.logToFile) return;
116
+
117
+ try {
118
+ const fullLogPath = path.isAbsolute(this.logDir)
119
+ ? path.join(this.logDir, this.logFile)
120
+ : path.join(process.cwd(), this.logDir, this.logFile);
121
+
122
+ fs.appendFileSync(fullLogPath, message + '\n', 'utf8');
123
+ } catch (error) {
124
+ // Fail silently to avoid infinite loop
125
+ console.error('Failed to write to log file:', error.message);
74
126
  }
127
+ }
75
128
 
76
- /**
77
- * Check if should log based on level
78
- */
79
- shouldLog(level) {
80
- return this.levels[level] <= this.levels[this.level];
81
- }
82
-
83
- /**
84
- * Format log message
85
- */
86
- formatMessage(level, message, meta = {}) {
87
- const timestamp = this.getTimestamp();
88
- const icon = this.icons[level];
89
-
90
- // Console format (with colors)
91
- const consoleMessage = `${this.colors[level]}${icon} [${level.toUpperCase()}]${this.colors.reset} ${message}`;
92
-
93
- // File format (plain text with metadata)
94
- const metaString = Object.keys(meta).length > 0 ? ` | ${JSON.stringify(meta)}` : '';
95
- const fileMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}${metaString}`;
96
-
97
- return { consoleMessage, fileMessage };
98
- }
129
+ /**
130
+ * Core logging method
131
+ */
132
+ log(level, message, meta = {}) {
133
+ if (!this.shouldLog(level)) return;
99
134
 
100
- /**
101
- * Write log to file
102
- */
103
- writeToFile(message) {
104
- if (!this.logToFile) return;
105
-
106
- try {
107
- const fullLogPath = path.isAbsolute(this.logDir)
108
- ? path.join(this.logDir, this.logFile)
109
- : path.join(process.cwd(), this.logDir, this.logFile);
110
-
111
- fs.appendFileSync(fullLogPath, message + '\n', 'utf8');
112
- } catch (error) {
113
- // Fail silently to avoid infinite loop
114
- console.error('Failed to write to log file:', error.message);
115
- }
116
- }
117
-
118
- /**
119
- * Core logging method
120
- */
121
- log(level, message, meta = {}) {
122
- if (!this.shouldLog(level)) return;
123
-
124
- const { consoleMessage, fileMessage } = this.formatMessage(level, message, meta);
125
-
126
- // Output to console
127
- if (!this.silent) {
128
- console.log(consoleMessage);
129
- }
130
-
131
- // Output to file
132
- this.writeToFile(fileMessage);
133
- }
134
-
135
- /**
136
- * Convenience methods
137
- */
138
- error(message, meta = {}) {
139
- this.log('error', message, meta);
140
- }
135
+ const { consoleMessage, fileMessage } = this.formatMessage(level, message, meta);
141
136
 
142
- warn(message, meta = {}) {
143
- this.log('warn', message, meta);
137
+ // Output to console
138
+ if (!this.silent) {
139
+ console.log(consoleMessage);
144
140
  }
145
141
 
146
- info(message, meta = {}) {
147
- this.log('info', message, meta);
142
+ // Output to file
143
+ this.writeToFile(fileMessage);
144
+ }
145
+
146
+ /**
147
+ * Convenience methods
148
+ */
149
+ error(message, meta = {}) {
150
+ this.log('error', message, meta);
151
+ }
152
+
153
+ warn(message, meta = {}) {
154
+ this.log('warn', message, meta);
155
+ }
156
+
157
+ info(message, meta = {}) {
158
+ this.log('info', message, meta);
159
+ }
160
+
161
+ debug(message, meta = {}) {
162
+ this.log('debug', message, meta);
163
+ }
164
+
165
+ trace(message, meta = {}) {
166
+ this.log('trace', message, meta);
167
+ }
168
+
169
+ /**
170
+ * Log with custom icon
171
+ */
172
+ custom(icon, level, message, meta = {}) {
173
+ const originalIcon = this.icons[level];
174
+ this.icons[level] = icon;
175
+ this.log(level, message, meta);
176
+ this.icons[level] = originalIcon;
177
+ }
178
+
179
+ /**
180
+ * Group related logs
181
+ */
182
+ group(title) {
183
+ if (!this.silent) {
184
+ console.group(title);
148
185
  }
186
+ this.writeToFile(`\n--- ${title} ---`);
187
+ }
149
188
 
150
- debug(message, meta = {}) {
151
- this.log('debug', message, meta);
189
+ groupEnd() {
190
+ if (!this.silent) {
191
+ console.groupEnd();
152
192
  }
153
-
154
- trace(message, meta = {}) {
155
- this.log('trace', message, meta);
156
- }
157
-
158
- /**
159
- * Log with custom icon
160
- */
161
- custom(icon, level, message, meta = {}) {
162
- const originalIcon = this.icons[level];
163
- this.icons[level] = icon;
164
- this.log(level, message, meta);
165
- this.icons[level] = originalIcon;
166
- }
167
-
168
- /**
169
- * Group related logs
170
- */
171
- group(title) {
172
- if (!this.silent) {
173
- console.group(title);
174
- }
175
- this.writeToFile(`\n--- ${title} ---`);
176
- }
177
-
178
- groupEnd() {
179
- if (!this.silent) {
180
- console.groupEnd();
181
- }
182
- this.writeToFile('--- End Group ---\n');
183
- }
184
-
185
- /**
186
- * Log with timer
187
- */
188
- time(label) {
189
- console.time(label);
190
- this.writeToFile(`[TIMER START] ${label}`);
191
- }
192
-
193
- timeEnd(label) {
194
- console.timeEnd(label);
195
- this.writeToFile(`[TIMER END] ${label}`);
196
- }
197
-
198
- /**
199
- * Clear old log files (keep last N days)
200
- */
201
- clearOldLogs(daysToKeep = 7) {
202
- try {
203
- const fullLogDir = path.isAbsolute(this.logDir) ? this.logDir : path.join(process.cwd(), this.logDir);
204
- if (!fs.existsSync(fullLogDir)) return;
205
-
206
- const now = Date.now();
207
- const maxAge = daysToKeep * 24 * 60 * 60 * 1000;
208
-
209
- const files = fs.readdirSync(fullLogDir);
210
- files.forEach(file => {
211
- const filePath = path.join(fullLogDir, file);
212
- const stats = fs.statSync(filePath);
213
- const age = now - stats.mtime.getTime();
214
-
215
- if (age > maxAge) {
216
- fs.unlinkSync(filePath);
217
- this.debug(`Deleted old log file: ${file}`);
218
- }
219
- });
220
- } catch (error) {
221
- this.error('Failed to clear old logs', { error: error.message });
193
+ this.writeToFile('--- End Group ---\n');
194
+ }
195
+
196
+ /**
197
+ * Log with timer
198
+ */
199
+ time(label) {
200
+ console.time(label);
201
+ this.writeToFile(`[TIMER START] ${label}`);
202
+ }
203
+
204
+ timeEnd(label) {
205
+ console.timeEnd(label);
206
+ this.writeToFile(`[TIMER END] ${label}`);
207
+ }
208
+
209
+ /**
210
+ * Clear old log files (keep last N days)
211
+ */
212
+ clearOldLogs(daysToKeep = 7) {
213
+ try {
214
+ const fullLogDir = path.isAbsolute(this.logDir)
215
+ ? this.logDir
216
+ : path.join(process.cwd(), this.logDir);
217
+ if (!fs.existsSync(fullLogDir)) return;
218
+
219
+ const now = Date.now();
220
+ const maxAge = daysToKeep * 24 * 60 * 60 * 1000;
221
+
222
+ const files = fs.readdirSync(fullLogDir);
223
+ files.forEach((file) => {
224
+ const filePath = path.join(fullLogDir, file);
225
+ const stats = fs.statSync(filePath);
226
+ const age = now - stats.mtime.getTime();
227
+
228
+ if (age > maxAge) {
229
+ fs.unlinkSync(filePath);
230
+ this.debug(`Deleted old log file: ${file}`);
222
231
  }
232
+ });
233
+ } catch (error) {
234
+ this.error('Failed to clear old logs', { error: error.message });
223
235
  }
224
-
225
- /**
226
- * Get log file path
227
- */
228
- getLogFilePath() {
229
- return path.isAbsolute(this.logDir)
230
- ? path.join(this.logDir, this.logFile)
231
- : path.join(process.cwd(), this.logDir, this.logFile);
232
- }
233
-
234
- /**
235
- * Read recent logs
236
- */
237
- getRecentLogs(lines = 100) {
238
- try {
239
- const logPath = this.getLogFilePath();
240
- if (!fs.existsSync(logPath)) {
241
- return [];
242
- }
243
-
244
- const content = fs.readFileSync(logPath, 'utf8');
245
- const allLines = content.split('\n').filter(line => line.trim());
246
- return allLines.slice(-lines);
247
- } catch (error) {
248
- this.error('Failed to read logs', { error: error.message });
249
- return [];
250
- }
236
+ }
237
+
238
+ /**
239
+ * Get log file path
240
+ */
241
+ getLogFilePath() {
242
+ return path.isAbsolute(this.logDir)
243
+ ? path.join(this.logDir, this.logFile)
244
+ : path.join(process.cwd(), this.logDir, this.logFile);
245
+ }
246
+
247
+ /**
248
+ * Read recent logs
249
+ */
250
+ getRecentLogs(lines = 100) {
251
+ try {
252
+ const logPath = this.getLogFilePath();
253
+ if (!fs.existsSync(logPath)) {
254
+ return [];
255
+ }
256
+
257
+ const content = fs.readFileSync(logPath, 'utf8');
258
+ const allLines = content.split('\n').filter((line) => line.trim());
259
+ return allLines.slice(-lines);
260
+ } catch (error) {
261
+ this.error('Failed to read logs', { error: error.message });
262
+ return [];
251
263
  }
264
+ }
252
265
  }
253
266
 
254
267
  // Create singleton instance
@@ -258,21 +271,17 @@ let defaultLogger = null;
258
271
  * Get or create default logger instance
259
272
  */
260
273
  function getLogger(options = {}) {
261
- if (!defaultLogger) {
262
- defaultLogger = new Logger(options);
263
- }
264
- return defaultLogger;
274
+ if (!defaultLogger) {
275
+ defaultLogger = new Logger(options);
276
+ }
277
+ return defaultLogger;
265
278
  }
266
279
 
267
280
  /**
268
281
  * Create a new logger instance
269
282
  */
270
283
  function createLogger(options = {}) {
271
- return new Logger(options);
284
+ return new Logger(options);
272
285
  }
273
286
 
274
- export {
275
- Logger,
276
- getLogger,
277
- createLogger
278
- };
287
+ export { Logger, getLogger, createLogger };