ctxman 3.0.0 → 3.1.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.
Files changed (223) 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 +380 -136
  16. package/bin/cli.js +1880 -529
  17. package/bin/cm-ask.js +18 -13
  18. package/bin/cm-audit.js +124 -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 +683 -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 +675 -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 +113 -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 +294 -0
  93. package/lib/policy/enforce.js +125 -0
  94. package/lib/policy/ignore-files.js +169 -0
  95. package/lib/policy/index.js +52 -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/sources/FileDataSource.js +6 -6
  108. package/lib/rag/stores/BaseVectorStore.js +5 -5
  109. package/lib/rag/stores/ChromaDBStore.js +10 -10
  110. package/lib/rag/stores/LanceDBStore.js +29 -19
  111. package/lib/rag/stores/PineconeStore.js +11 -9
  112. package/lib/search/bm25.js +433 -0
  113. package/lib/search/code-units.js +201 -0
  114. package/lib/search/symbols.js +189 -0
  115. package/lib/ui/dashboard.js +120 -89
  116. package/lib/ui/index.js +1 -6
  117. package/lib/ui/init-wizard-ui.js +550 -0
  118. package/lib/ui/progress-bar.js +94 -88
  119. package/lib/ui/select-input.js +40 -32
  120. package/lib/ui/template-selector.js +99 -0
  121. package/lib/ui/wizard.js +374 -329
  122. package/lib/utils/claude-token-counter.js +151 -0
  123. package/lib/utils/cli-output.js +17 -0
  124. package/lib/utils/clipboard-utils.js +49 -50
  125. package/lib/utils/config-utils.js +81 -66
  126. package/lib/utils/context-versioning.js +532 -0
  127. package/lib/utils/error-handler.js +112 -112
  128. package/lib/utils/file-utils.js +153 -69
  129. package/lib/utils/format-converter.js +356 -283
  130. package/lib/utils/git-utils.js +366 -294
  131. package/lib/utils/llm-cost-estimator.js +281 -0
  132. package/lib/utils/llm-detector.js +242 -78
  133. package/lib/utils/logger.js +243 -237
  134. package/lib/utils/messages.js +355 -0
  135. package/lib/utils/multi-repo-manager.js +364 -0
  136. package/lib/utils/profile-manager.js +380 -0
  137. package/lib/utils/safe-read.js +61 -0
  138. package/lib/utils/secret-redactor.js +61 -0
  139. package/lib/utils/snapshot-manager.js +679 -0
  140. package/lib/utils/template-manager.js +378 -0
  141. package/lib/utils/token-utils.js +201 -170
  142. package/lib/utils/tokenizer-adapter.js +421 -377
  143. package/lib/utils/updater.js +539 -506
  144. package/lib/watch/FileWatcher.js +12 -8
  145. package/lib/watch/IncrementalAnalyzer.js +7 -7
  146. package/lib/why/coupling.js +81 -0
  147. package/lib/why/errors.js +23 -0
  148. package/lib/why/git.js +169 -0
  149. package/lib/why/history.js +108 -0
  150. package/lib/why/index.js +720 -0
  151. package/lib/why/ownership.js +79 -0
  152. package/lib/why/paths.js +207 -0
  153. package/lib/why/references.js +259 -0
  154. package/lib/why/render.js +722 -0
  155. package/lib/why/search.js +358 -0
  156. package/lib/why/target.js +389 -0
  157. package/lib/wizards/config-generator.js +255 -0
  158. package/lib/wizards/project-detector.js +571 -0
  159. package/package.json +44 -10
  160. package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
  161. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  162. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  163. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  164. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  165. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  166. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  167. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  168. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  169. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  170. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  171. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  172. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  173. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  174. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  175. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  176. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  177. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  178. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  179. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  180. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  181. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  182. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  183. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  184. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  185. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  186. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  187. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  188. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  189. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  190. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  191. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  192. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  193. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  194. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  195. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  196. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  197. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  198. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  199. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  200. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  201. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  202. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  203. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  204. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  205. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  206. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  207. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  208. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  209. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  210. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  211. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  212. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  213. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  214. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  215. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  216. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  217. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  218. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  219. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  220. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  221. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  222. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  223. package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
@@ -6,122 +6,128 @@
6
6
  import fs from 'fs';
7
7
 
8
8
  class GitIgnoreParser {
9
- constructor(gitignorePath, contextIgnorePath, contextIncludePath) {
10
- this.patterns = [];
11
- this.contextPatterns = [];
12
- this.hasIncludeFile = false;
13
- this._lastIgnoreReason = null;
14
-
15
- this.loadPatterns(gitignorePath, contextIgnorePath, contextIncludePath);
9
+ constructor(gitignorePath, contextIgnorePath, contextIncludePath) {
10
+ this.patterns = [];
11
+ this.contextPatterns = [];
12
+ this.hasIncludeFile = false;
13
+ this._lastIgnoreReason = null;
14
+
15
+ this.loadPatterns(gitignorePath, contextIgnorePath, contextIncludePath);
16
+ }
17
+
18
+ loadPatterns(gitignorePath, contextIgnorePath, contextIncludePath) {
19
+ // Load .gitignore
20
+ if (gitignorePath && fs.existsSync(gitignorePath)) {
21
+ this.patterns = this.parsePatternFile(gitignorePath);
16
22
  }
17
23
 
18
- loadPatterns(gitignorePath, contextIgnorePath, contextIncludePath) {
19
- // Load .gitignore
20
- if (fs.existsSync(gitignorePath)) {
21
- this.patterns = this.parsePatternFile(gitignorePath);
22
- }
23
-
24
- // Load context patterns (include takes priority)
25
- if (contextIncludePath && fs.existsSync(contextIncludePath)) {
26
- this.contextPatterns = this.parsePatternFile(contextIncludePath);
27
- this.hasIncludeFile = true;
28
- console.log(`📅 Context include rules loaded: ${this.contextPatterns.length} patterns`);
29
- } else if (contextIgnorePath && fs.existsSync(contextIgnorePath)) {
30
- this.contextPatterns = this.parsePatternFile(contextIgnorePath);
31
- console.log(`📋 Context ignore rules loaded: ${this.contextPatterns.length} patterns`);
32
- }
24
+ // Load context patterns (include takes priority)
25
+ if (contextIncludePath && fs.existsSync(contextIncludePath)) {
26
+ this.contextPatterns = this.parsePatternFile(contextIncludePath);
27
+ this.hasIncludeFile = true;
28
+ console.log(`📅 Context include rules loaded: ${this.contextPatterns.length} patterns`);
29
+ } else if (contextIgnorePath && fs.existsSync(contextIgnorePath)) {
30
+ this.contextPatterns = this.parsePatternFile(contextIgnorePath);
31
+ console.log(`📋 Context ignore rules loaded: ${this.contextPatterns.length} patterns`);
33
32
  }
34
-
35
- parsePatternFile(filePath) {
36
- try {
37
- // Check if path is actually a file (not a directory)
38
- const stats = fs.statSync(filePath);
39
- if (stats.isDirectory()) {
40
- console.warn(`Warning: ${filePath} is a directory, expected a file`);
41
- return [];
42
- }
43
-
44
- return fs.readFileSync(filePath, 'utf8')
45
- .split('\n')
46
- .map(line => line.trim())
47
- .filter(line => line && !line.startsWith('#'))
48
- .map(pattern => this.convertToRegex(pattern));
49
- } catch (error) {
50
- // File doesn't exist or can't be read
51
- return [];
52
- }
33
+ }
34
+
35
+ parsePatternFile(filePath) {
36
+ try {
37
+ return fs
38
+ .readFileSync(filePath, 'utf8')
39
+ .split('\n')
40
+ .map((line) => line.trim())
41
+ .filter((line) => line && !line.startsWith('#'))
42
+ .map((pattern) => this.convertToRegex(pattern));
43
+ } catch (error) {
44
+ // A directory, or a file that doesn't exist or can't be read
45
+ if (error.code === 'EISDIR') {
46
+ console.warn(`Warning: ${filePath} is a directory, expected a file`);
47
+ }
48
+ return [];
53
49
  }
54
-
55
- convertToRegex(pattern) {
56
- const isDirectory = pattern.endsWith('/');
57
- const isNegation = pattern.startsWith('!');
58
- const original = pattern;
59
-
60
- pattern = pattern.replace(/^[!/]/, '').replace(/\/$/, '');
61
-
62
- let regexPattern = pattern
63
- .replace(/[.+^${}()|[\]\\]/g, '\\$&')
64
- .replace(/\*\*/g, '.*')
65
- .replace(/\*/g, '[^/]*')
66
- .replace(/\?/g, '[^/]');
67
-
68
- regexPattern = original.startsWith('/')
69
- ? `^${regexPattern}`
70
- : `(^|/)${regexPattern}`;
71
-
72
- regexPattern += isDirectory ? '(/.*)?$' : '$';
73
-
74
- return { regex: new RegExp(regexPattern), isNegation, original, isDirectory };
50
+ }
51
+
52
+ convertToRegex(pattern) {
53
+ const isDirectory = pattern.endsWith('/');
54
+ const isNegation = pattern.startsWith('!');
55
+ const original = pattern;
56
+
57
+ pattern = pattern.replace(/^[!/]/, '').replace(/\/$/, '');
58
+
59
+ // Placeholders keep the `*` inside `.*` from being rewritten by the single-star rule
60
+ let regexPattern = pattern
61
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
62
+ .replace(/\*\*\//g, '\u0001') // `**/`: zero or more directories
63
+ .replace(/\*\*/g, '\u0002') // any other `**`: anything, across directories
64
+ .replace(/\*/g, '[^/]*')
65
+ .replace(/\?/g, '[^/]')
66
+ .replace(/\u0001/g, '(?:.*/)?')
67
+ .replace(/\u0002/g, '.*');
68
+
69
+ regexPattern = original.startsWith('/') ? `^${regexPattern}` : `(^|/)${regexPattern}`;
70
+
71
+ regexPattern += isDirectory ? '(/.*)?$' : '$';
72
+
73
+ return { regex: new RegExp(regexPattern), isNegation, original, isDirectory };
74
+ }
75
+
76
+ isIgnored(filePath, relativePath) {
77
+ let ignored = this.testPatterns(this.patterns, relativePath, 'gitignore');
78
+
79
+ if (this.hasIncludeFile) {
80
+ if (ignored) return true; // Keep gitignore exclusions
81
+
82
+ if (this.isDirectory(filePath)) {
83
+ const shouldTraverse = this.contextPatterns.some(
84
+ (p) =>
85
+ p.original.startsWith(relativePath) || p.original.includes('**') || relativePath === ''
86
+ );
87
+ ignored = !shouldTraverse;
88
+ } else {
89
+ const included = this.testPatternsWithNegation(this.contextPatterns, relativePath);
90
+ ignored = !included;
91
+ }
92
+
93
+ if (ignored) this._lastIgnoreReason = 'context-include';
94
+ } else if (!ignored) {
95
+ ignored = this.testPatterns(this.contextPatterns, relativePath, 'context');
75
96
  }
76
97
 
77
- isIgnored(filePath, relativePath) {
78
- let ignored = this.testPatterns(this.patterns, relativePath, 'gitignore');
79
-
80
- if (this.hasIncludeFile) {
81
- if (ignored) return true; // Keep gitignore exclusions
82
-
83
- const stat = fs.statSync(filePath);
84
- if (stat.isDirectory()) {
85
- const shouldTraverse = this.contextPatterns.some(p =>
86
- p.original.startsWith(relativePath) ||
87
- p.original.includes('**') ||
88
- relativePath === ''
89
- );
90
- ignored = !shouldTraverse;
91
- } else {
92
- const included = this.testPatternsWithNegation(this.contextPatterns, relativePath);
93
- ignored = !included;
94
- }
95
-
96
- if (ignored) this._lastIgnoreReason = 'context-include';
97
- } else if (!ignored) {
98
- ignored = this.testPatterns(this.contextPatterns, relativePath, 'context');
99
- }
100
-
101
- return ignored;
102
- }
98
+ return ignored;
99
+ }
103
100
 
104
- testPatterns(patterns, relativePath, reason) {
105
- let ignored = false;
106
- for (const pattern of patterns) {
107
- if (pattern.regex.test(relativePath)) {
108
- ignored = !pattern.isNegation;
109
- if (ignored) this._lastIgnoreReason = reason;
110
- }
111
- }
112
- return ignored;
101
+ isDirectory(filePath) {
102
+ try {
103
+ return fs.statSync(filePath).isDirectory();
104
+ } catch (_error) {
105
+ // Deleted (e.g. a watch-mode "rename" event) or unreadable: treat it as a file
106
+ return false;
113
107
  }
114
-
115
- testPatternsWithNegation(patterns, relativePath) {
116
- let included = false;
117
- for (const pattern of patterns) {
118
- if (pattern.regex.test(relativePath)) {
119
- included = pattern.isNegation ? false : true;
120
- if (pattern.isNegation) break;
121
- }
122
- }
123
- return included;
108
+ }
109
+
110
+ testPatterns(patterns, relativePath, reason) {
111
+ let ignored = false;
112
+ for (const pattern of patterns) {
113
+ if (pattern.regex.test(relativePath)) {
114
+ ignored = !pattern.isNegation;
115
+ if (ignored) this._lastIgnoreReason = reason;
116
+ }
117
+ }
118
+ return ignored;
119
+ }
120
+
121
+ testPatternsWithNegation(patterns, relativePath) {
122
+ let included = false;
123
+ for (const pattern of patterns) {
124
+ if (pattern.regex.test(relativePath)) {
125
+ included = pattern.isNegation ? false : true;
126
+ if (pattern.isNegation) break;
127
+ }
124
128
  }
129
+ return included;
130
+ }
125
131
  }
126
132
 
127
133
  export default GitIgnoreParser;
@@ -6,45 +6,62 @@
6
6
  import fs from 'fs';
7
7
 
8
8
  class MethodFilterParser {
9
- constructor(methodIncludePath, methodIgnorePath) {
10
- this.includePatterns = [];
11
- this.ignorePatterns = [];
12
- this.hasIncludeFile = false;
13
-
14
- if (methodIncludePath && fs.existsSync(methodIncludePath)) {
15
- this.includePatterns = this.parseMethodFile(methodIncludePath);
16
- this.hasIncludeFile = true;
17
- console.log(`🔧 Method include rules loaded: ${this.includePatterns.length} patterns`);
18
- }
19
-
20
- if (methodIgnorePath && fs.existsSync(methodIgnorePath)) {
21
- this.ignorePatterns = this.parseMethodFile(methodIgnorePath);
22
- console.log(`🚫 Method ignore rules loaded: ${this.ignorePatterns.length} patterns`);
23
- }
9
+ constructor(methodIncludePath, methodIgnorePath) {
10
+ this.includePatterns = [];
11
+ this.ignorePatterns = [];
12
+ this.hasIncludeFile = false;
13
+
14
+ if (methodIncludePath && fs.existsSync(methodIncludePath)) {
15
+ this.includePatterns = this.parseMethodFile(methodIncludePath);
16
+ this.hasIncludeFile = true;
17
+ console.log(`🔧 Method include rules loaded: ${this.includePatterns.length} patterns`);
24
18
  }
25
19
 
26
- parseMethodFile(filePath) {
27
- return fs.readFileSync(filePath, 'utf8')
28
- .split('\n')
29
- .map(line => line.trim())
30
- .filter(line => line && !line.startsWith('#'))
31
- .map(pattern => ({
32
- pattern: pattern,
33
- regex: new RegExp(pattern.replace(/\*/g, '.*'), 'i')
34
- }));
20
+ if (methodIgnorePath && fs.existsSync(methodIgnorePath)) {
21
+ this.ignorePatterns = this.parseMethodFile(methodIgnorePath);
22
+ console.log(`🚫 Method ignore rules loaded: ${this.ignorePatterns.length} patterns`);
35
23
  }
24
+ }
25
+
26
+ parseMethodFile(filePath) {
27
+ return (
28
+ fs
29
+ .readFileSync(filePath, 'utf8')
30
+ .split('\n')
31
+ // Trailing comments (`*Handler # all handlers`) as in the documented examples
32
+ .map((line) => line.replace(/\s+#.*$/, '').trim())
33
+ .filter((line) => line && !line.startsWith('#'))
34
+ .map((line) => {
35
+ const negated = line.startsWith('!');
36
+ const pattern = negated ? line.slice(1) : line;
37
+ return {
38
+ pattern: line,
39
+ negated,
40
+ // Anchored so a plain name is an exact match and `*Handler` / `handle*` are suffix /
41
+ // prefix matches, as documented. `*` is the wildcard; other regex characters are
42
+ // literal (Ruby's `valid?`, `$emit`). `.` stays a regex dot: it still matches the
43
+ // `file.method` separator, and patterns such as `get.*Data` rely on it.
44
+ regex: new RegExp(
45
+ `^${pattern.replace(/[+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*')}$`,
46
+ 'i'
47
+ ),
48
+ };
49
+ })
50
+ );
51
+ }
36
52
 
37
- shouldIncludeMethod(methodName, fileName) {
38
- if (this.hasIncludeFile) {
39
- return this.includePatterns.some(p =>
40
- p.regex.test(methodName) || p.regex.test(`${fileName}.${methodName}`)
41
- );
42
- }
53
+ shouldIncludeMethod(methodName, fileName) {
54
+ const matches = (p) => p.regex.test(methodName) || p.regex.test(`${fileName}.${methodName}`);
43
55
 
44
- return !this.ignorePatterns.some(p =>
45
- p.regex.test(methodName) || p.regex.test(`${fileName}.${methodName}`)
46
- );
56
+ // The last matching pattern decides, so a later `!pattern` overrides a broader one
57
+ if (this.hasIncludeFile) {
58
+ const match = this.includePatterns.findLast(matches);
59
+ return Boolean(match) && !match.negated;
47
60
  }
61
+
62
+ const match = this.ignorePatterns.findLast(matches);
63
+ return !match || match.negated;
64
+ }
48
65
  }
49
66
 
50
67
  export default MethodFilterParser;
@@ -24,7 +24,7 @@ export class ExporterPlugin {
24
24
  extension: this.extension,
25
25
  mimeType: this.mimeType,
26
26
  supportsChunking: this.supportsChunking(),
27
- supportsCompression: this.supportsCompression()
27
+ supportsCompression: this.supportsCompression(),
28
28
  };
29
29
  }
30
30
 
@@ -50,7 +50,7 @@ export class ExporterPlugin {
50
50
  * @param {object} options
51
51
  * @returns {string}
52
52
  */
53
- encode(context, options = {}) {
53
+ encode(context, _options = {}) {
54
54
  throw new Error('encode() must be implemented by subclass');
55
55
  }
56
56
 
@@ -59,7 +59,7 @@ export class ExporterPlugin {
59
59
  * @param {string} content
60
60
  * @returns {Context}
61
61
  */
62
- decode(content) {
62
+ decode(_content) {
63
63
  throw new Error('decode() not supported for this format');
64
64
  }
65
65
 
@@ -23,7 +23,7 @@ export class LanguagePlugin {
23
23
  extensions: this.extensions,
24
24
  supportsAST: this.supportsAST(),
25
25
  supportsMethodExtraction: this.supportsMethodExtraction(),
26
- supportsFrameworkDetection: this.supportsFrameworkDetection()
26
+ supportsFrameworkDetection: this.supportsFrameworkDetection(),
27
27
  };
28
28
  }
29
29
 
@@ -57,7 +57,7 @@ export class LanguagePlugin {
57
57
  * @param {string} filePath - File path
58
58
  * @returns {Array<Method>}
59
59
  */
60
- extractMethods(content, filePath) {
60
+ extractMethods(_content, _filePath) {
61
61
  throw new Error('extractMethods() must be implemented by subclass');
62
62
  }
63
63
 
@@ -66,7 +66,7 @@ export class LanguagePlugin {
66
66
  * @param {string} content - File content
67
67
  * @returns {string|null} Framework name or null
68
68
  */
69
- detectFramework(content) {
69
+ detectFramework(_content) {
70
70
  return null;
71
71
  }
72
72
 
@@ -21,14 +21,10 @@ export class PluginManager extends EventEmitter {
21
21
  super();
22
22
 
23
23
  this.options = {
24
- pluginPaths: [
25
- './lib/languages',
26
- './lib/exporters',
27
- './plugins'
28
- ],
24
+ pluginPaths: ['./lib/languages', './lib/exporters', './plugins'],
29
25
  autoLoad: true,
30
26
  lazy: true, // Lazy load plugins
31
- ...options
27
+ ...options,
32
28
  };
33
29
 
34
30
  this.plugins = new Map();
@@ -36,7 +32,7 @@ export class PluginManager extends EventEmitter {
36
32
  this.stats = {
37
33
  registered: 0,
38
34
  loaded: 0,
39
- errors: 0
35
+ errors: 0,
40
36
  };
41
37
  }
42
38
 
@@ -103,12 +99,11 @@ export class PluginManager extends EventEmitter {
103
99
  name: pluginName,
104
100
  path: pluginPath,
105
101
  loaded: false,
106
- instance: null
102
+ instance: null,
107
103
  });
108
104
 
109
105
  this.stats.registered++;
110
106
  logger.debug(`Registered plugin: ${pluginName}`);
111
-
112
107
  } catch (error) {
113
108
  logger.error(`Failed to register plugin ${pluginPath}: ${error.message}`);
114
109
  this.stats.errors++;
@@ -158,7 +153,6 @@ export class PluginManager extends EventEmitter {
158
153
  this.emit('plugin:loaded', { name: pluginName, elapsed });
159
154
 
160
155
  return instance;
161
-
162
156
  } catch (error) {
163
157
  logger.error(`Failed to load plugin ${pluginName}: ${error.message}`);
164
158
  this.stats.errors++;
@@ -173,7 +167,7 @@ export class PluginManager extends EventEmitter {
173
167
  * @returns {Promise<Array<Plugin>>}
174
168
  */
175
169
  async loadMultiple(pluginNames) {
176
- const promises = pluginNames.map(name => this.load(name));
170
+ const promises = pluginNames.map((name) => this.load(name));
177
171
  return Promise.all(promises);
178
172
  }
179
173
 
@@ -266,7 +260,7 @@ export class PluginManager extends EventEmitter {
266
260
  name: plugin.name,
267
261
  loaded: plugin.loaded,
268
262
  path: plugin.path,
269
- hasInstance: plugin.instance !== null
263
+ hasInstance: plugin.instance !== null,
270
264
  };
271
265
  }
272
266
 
@@ -288,7 +282,7 @@ export class PluginManager extends EventEmitter {
288
282
  name,
289
283
  path: null,
290
284
  loaded: true,
291
- instance
285
+ instance,
292
286
  });
293
287
 
294
288
  this.stats.registered++;