ctxman 3.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.
Files changed (190) hide show
  1. package/.ctxman/custom-profiles.example.json +20 -0
  2. package/.ctxman/llm-profiles.json +131 -0
  3. package/.ctxman/logs/ctxman-2026-07-02.log +156 -0
  4. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  5. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  6. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  7. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  8. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  9. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  10. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  11. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  12. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  13. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  14. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  15. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  16. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  17. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  18. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  19. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  20. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  21. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  22. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  23. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  24. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  25. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  26. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  27. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  28. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  29. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  30. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  31. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  32. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  33. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  34. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  35. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  36. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  37. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  38. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  39. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  40. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  41. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  42. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  43. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  44. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  45. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  46. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  47. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  48. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  49. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  50. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  51. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  52. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  53. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  54. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  55. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  56. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  57. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  58. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  59. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  60. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  61. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  62. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  63. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  64. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  65. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  66. package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
  67. package/.ctxman/wizard-profiles/code-review/.contextignore +41 -0
  68. package/.ctxman/wizard-profiles/code-review/.contextinclude +30 -0
  69. package/.ctxman/wizard-profiles/code-review/.methodignore +26 -0
  70. package/.ctxman/wizard-profiles/code-review/.methodinclude +47 -0
  71. package/.ctxman/wizard-profiles/code-review/profile.json +26 -0
  72. package/.ctxman/wizard-profiles/documentation/.contextignore +21 -0
  73. package/.ctxman/wizard-profiles/documentation/.contextinclude +24 -0
  74. package/.ctxman/wizard-profiles/documentation/.methodignore +19 -0
  75. package/.ctxman/wizard-profiles/documentation/.methodinclude +44 -0
  76. package/.ctxman/wizard-profiles/documentation/profile.json +27 -0
  77. package/.ctxman/wizard-profiles/full/.contextignore +23 -0
  78. package/.ctxman/wizard-profiles/full/.contextinclude +38 -0
  79. package/.ctxman/wizard-profiles/full/.methodignore +5 -0
  80. package/.ctxman/wizard-profiles/full/.methodinclude +5 -0
  81. package/.ctxman/wizard-profiles/full/profile.json +28 -0
  82. package/.ctxman/wizard-profiles/llm-explain/.contextignore +25 -0
  83. package/.ctxman/wizard-profiles/llm-explain/.contextinclude +33 -0
  84. package/.ctxman/wizard-profiles/llm-explain/.methodignore +19 -0
  85. package/.ctxman/wizard-profiles/llm-explain/.methodinclude +48 -0
  86. package/.ctxman/wizard-profiles/llm-explain/profile.json +27 -0
  87. package/.ctxman/wizard-profiles/minimal/.contextignore +27 -0
  88. package/.ctxman/wizard-profiles/minimal/.contextinclude +14 -0
  89. package/.ctxman/wizard-profiles/minimal/.methodignore +22 -0
  90. package/.ctxman/wizard-profiles/minimal/.methodinclude +18 -0
  91. package/.ctxman/wizard-profiles/minimal/profile.json +26 -0
  92. package/.ctxman/wizard-profiles/security-audit/.contextignore +36 -0
  93. package/.ctxman/wizard-profiles/security-audit/.contextinclude +37 -0
  94. package/.ctxman/wizard-profiles/security-audit/.methodignore +29 -0
  95. package/.ctxman/wizard-profiles/security-audit/.methodinclude +60 -0
  96. package/.ctxman/wizard-profiles/security-audit/profile.json +34 -0
  97. package/README.md +1167 -0
  98. package/bin/cli.js +650 -0
  99. package/bin/cm-ask.js +95 -0
  100. package/bin/cm-gitingest.js +134 -0
  101. package/bin/cm-update.js +175 -0
  102. package/bin/mcp-server.js +31 -0
  103. package/ctxman.js +236 -0
  104. package/examples/README.md +298 -0
  105. package/examples/custom-llm-profiles.example.json +19 -0
  106. package/examples/wizard-profiles/code-review/.contextignore +41 -0
  107. package/examples/wizard-profiles/code-review/.contextinclude +30 -0
  108. package/examples/wizard-profiles/code-review/.methodignore +26 -0
  109. package/examples/wizard-profiles/code-review/.methodinclude +47 -0
  110. package/examples/wizard-profiles/code-review/profile.json +26 -0
  111. package/examples/wizard-profiles/documentation/.contextignore +21 -0
  112. package/examples/wizard-profiles/documentation/.contextinclude +24 -0
  113. package/examples/wizard-profiles/documentation/.methodignore +19 -0
  114. package/examples/wizard-profiles/documentation/.methodinclude +44 -0
  115. package/examples/wizard-profiles/documentation/profile.json +27 -0
  116. package/examples/wizard-profiles/full/.contextignore +23 -0
  117. package/examples/wizard-profiles/full/.contextinclude +38 -0
  118. package/examples/wizard-profiles/full/.methodignore +5 -0
  119. package/examples/wizard-profiles/full/.methodinclude +5 -0
  120. package/examples/wizard-profiles/full/profile.json +28 -0
  121. package/examples/wizard-profiles/llm-explain/.contextignore +25 -0
  122. package/examples/wizard-profiles/llm-explain/.contextinclude +33 -0
  123. package/examples/wizard-profiles/llm-explain/.methodignore +19 -0
  124. package/examples/wizard-profiles/llm-explain/.methodinclude +48 -0
  125. package/examples/wizard-profiles/llm-explain/profile.json +27 -0
  126. package/examples/wizard-profiles/minimal/.contextignore +27 -0
  127. package/examples/wizard-profiles/minimal/.contextinclude +14 -0
  128. package/examples/wizard-profiles/minimal/.methodignore +22 -0
  129. package/examples/wizard-profiles/minimal/.methodinclude +18 -0
  130. package/examples/wizard-profiles/minimal/profile.json +26 -0
  131. package/examples/wizard-profiles/security-audit/.contextignore +36 -0
  132. package/examples/wizard-profiles/security-audit/.contextinclude +37 -0
  133. package/examples/wizard-profiles/security-audit/.methodignore +29 -0
  134. package/examples/wizard-profiles/security-audit/.methodinclude +60 -0
  135. package/examples/wizard-profiles/security-audit/profile.json +34 -0
  136. package/index.js +68 -0
  137. package/lib/agents/pr-failure-agent.js +201 -0
  138. package/lib/analyzers/go-method-analyzer.js +126 -0
  139. package/lib/analyzers/method-analyzer.js +457 -0
  140. package/lib/analyzers/token-calculator.js +577 -0
  141. package/lib/api/mcp/prompts.js +319 -0
  142. package/lib/api/mcp/resources.js +269 -0
  143. package/lib/api/mcp/server.js +339 -0
  144. package/lib/api/mcp/transports/HttpSseTransport.js +63 -0
  145. package/lib/api/rest/server.js +405 -0
  146. package/lib/cache/CacheManager.js +388 -0
  147. package/lib/core/Analyzer.js +180 -0
  148. package/lib/core/ContextBuilder.js +325 -0
  149. package/lib/core/Reporter.js +270 -0
  150. package/lib/core/Scanner.js +129 -0
  151. package/lib/formatters/format-registry.js +419 -0
  152. package/lib/formatters/gitingest-formatter.js +746 -0
  153. package/lib/formatters/toon-formatter-v1.3.js +731 -0
  154. package/lib/formatters/toon-formatter.js +291 -0
  155. package/lib/integrations/git/BlameTracker.js +142 -0
  156. package/lib/integrations/git/DiffAnalyzer.js +260 -0
  157. package/lib/integrations/git/GitClient.js +336 -0
  158. package/lib/parsers/gitignore-parser.js +127 -0
  159. package/lib/parsers/method-filter-parser.js +50 -0
  160. package/lib/plugins/ExporterPlugin.js +102 -0
  161. package/lib/plugins/LanguagePlugin.js +100 -0
  162. package/lib/plugins/PluginManager.js +302 -0
  163. package/lib/rag/DataSourcePlugin.js +31 -0
  164. package/lib/rag/EmbeddingProvider.js +75 -0
  165. package/lib/rag/Indexer.js +83 -0
  166. package/lib/rag/VectorStoreFactory.js +24 -0
  167. package/lib/rag/sources/FileDataSource.js +70 -0
  168. package/lib/rag/stores/BaseVectorStore.js +43 -0
  169. package/lib/rag/stores/ChromaDBStore.js +85 -0
  170. package/lib/rag/stores/LanceDBStore.js +109 -0
  171. package/lib/rag/stores/PineconeStore.js +74 -0
  172. package/lib/ui/dashboard.js +109 -0
  173. package/lib/ui/index.js +15 -0
  174. package/lib/ui/progress-bar.js +110 -0
  175. package/lib/ui/select-input.js +51 -0
  176. package/lib/ui/wizard.js +359 -0
  177. package/lib/utils/clipboard-utils.js +66 -0
  178. package/lib/utils/config-utils.js +94 -0
  179. package/lib/utils/error-handler.js +145 -0
  180. package/lib/utils/file-utils.js +89 -0
  181. package/lib/utils/format-converter.js +325 -0
  182. package/lib/utils/git-utils.js +337 -0
  183. package/lib/utils/llm-detector.js +409 -0
  184. package/lib/utils/logger.js +278 -0
  185. package/lib/utils/token-utils.js +200 -0
  186. package/lib/utils/tokenizer-adapter.js +454 -0
  187. package/lib/utils/updater.js +565 -0
  188. package/lib/watch/FileWatcher.js +208 -0
  189. package/lib/watch/IncrementalAnalyzer.js +206 -0
  190. package/package.json +118 -0
@@ -0,0 +1,336 @@
1
+ /**
2
+ * GitClient - Git Operations Module
3
+ * v3.0.0 - Complete Git integration
4
+ *
5
+ * Responsibilities:
6
+ * - Execute git commands
7
+ * - Parse git output
8
+ * - Provide high-level git operations
9
+ * - Cache git metadata
10
+ */
11
+
12
+ import { execSync } from 'child_process';
13
+ import fs from 'fs';
14
+ import path from 'path';
15
+ import { getLogger } from '../../utils/logger.js';
16
+
17
+ const logger = getLogger('GitClient');
18
+
19
+ export class GitClient {
20
+ constructor(repositoryPath) {
21
+ this.repoPath = repositoryPath;
22
+ this.isGitRepo = this.checkIsGitRepository();
23
+ }
24
+
25
+ /**
26
+ * Check if directory is a git repository
27
+ * @returns {boolean}
28
+ */
29
+ checkIsGitRepository() {
30
+ try {
31
+ const gitDir = path.join(this.repoPath, '.git');
32
+ return fs.existsSync(gitDir);
33
+ } catch (error) {
34
+ return false;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Validate git reference to prevent command injection
40
+ * @param {string} ref
41
+ * @returns {boolean}
42
+ */
43
+ validateGitRef(ref) {
44
+ if (!ref) return true; // null/undefined is safe (handled logic elsewhere)
45
+ // Allow alphanumeric, /, -, _, ., @, ^, ~
46
+ // Disallow ; | & $ > < ` \ !
47
+ return /^[a-zA-Z0-9/\-_.\@\^\~]+$/.test(ref);
48
+ }
49
+
50
+ /**
51
+ * Execute git command
52
+ * @param {string} command - Git command (without 'git' prefix)
53
+ * @returns {string} Command output
54
+ */
55
+ exec(command) {
56
+ if (!this.isGitRepo) {
57
+ throw new Error('Not a git repository');
58
+ }
59
+
60
+ // Basic sanity check against shell injection if the command string was constructed with user input
61
+ // This is a last line of defense; specific methods should validate their inputs.
62
+ if (command.includes(';') || command.includes('&')) {
63
+ logger.warn(`Potential injection detected in command: ${command}`);
64
+ throw new Error('Invalid characters in git command');
65
+ }
66
+
67
+ try {
68
+ const fullCommand = `git ${command}`;
69
+ const output = execSync(fullCommand, {
70
+ cwd: this.repoPath,
71
+ encoding: 'utf-8',
72
+ stdio: ['pipe', 'pipe', 'pipe']
73
+ });
74
+
75
+ return output.trim();
76
+ } catch (error) {
77
+ logger.error(`Git command failed: ${command}`);
78
+ throw new Error(`Git command failed: ${error.message}`);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Get current branch name
84
+ * @returns {string}
85
+ */
86
+ getCurrentBranch() {
87
+ return this.exec('rev-parse --abbrev-ref HEAD');
88
+ }
89
+
90
+ /**
91
+ * Get default branch name
92
+ * @returns {string}
93
+ */
94
+ getDefaultBranch() {
95
+ try {
96
+ return this.exec('symbolic-ref refs/remotes/origin/HEAD --short').split('/').pop();
97
+ } catch (error) {
98
+ return 'main'; // Fallback
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Get changed files
104
+ * @param {string} since - Commit/branch reference (e.g., 'main', 'HEAD~5')
105
+ * @returns {Array<string>}
106
+ */
107
+ getChangedFiles(since = null) {
108
+ let command;
109
+
110
+ if (since) {
111
+ // Files changed between commits/branches
112
+ command = `diff --name-only ${since}`;
113
+ } else {
114
+ // Files with uncommitted changes
115
+ command = 'diff --name-only';
116
+ }
117
+
118
+ const output = this.exec(command);
119
+ return output ? output.split('\n').filter(Boolean) : [];
120
+ }
121
+
122
+ /**
123
+ * Get staged files
124
+ * @returns {Array<string>}
125
+ */
126
+ getStagedFiles() {
127
+ const output = this.exec('diff --cached --name-only');
128
+ return output ? output.split('\n').filter(Boolean) : [];
129
+ }
130
+
131
+ /**
132
+ * Get unstaged files
133
+ * @returns {Array<string>}
134
+ */
135
+ getUnstagedFiles() {
136
+ const output = this.exec('diff --name-only');
137
+ return output ? output.split('\n').filter(Boolean) : [];
138
+ }
139
+
140
+ /**
141
+ * Get untracked files
142
+ * @returns {Array<string>}
143
+ */
144
+ getUntrackedFiles() {
145
+ const output = this.exec('ls-files --others --exclude-standard');
146
+ return output ? output.split('\n').filter(Boolean) : [];
147
+ }
148
+
149
+ /**
150
+ * Get all modified files (staged + unstaged + untracked)
151
+ * @returns {Array<string>}
152
+ */
153
+ getAllModifiedFiles() {
154
+ const staged = this.getStagedFiles();
155
+ const unstaged = this.getUnstagedFiles();
156
+ const untracked = this.getUntrackedFiles();
157
+
158
+ // Deduplicate
159
+ return [...new Set([...staged, ...unstaged, ...untracked])];
160
+ }
161
+
162
+ /**
163
+ * Get file history
164
+ * @param {string} filePath - File path
165
+ * @param {number} limit - Number of commits
166
+ * @returns {Array<Commit>}
167
+ */
168
+ getFileHistory(filePath, limit = 10) {
169
+ const command = `log --follow --pretty=format:"%H|%an|%ae|%at|%s" -n ${limit} -- "${filePath}"`;
170
+ const output = this.exec(command);
171
+
172
+ if (!output) return [];
173
+
174
+ return output.split('\n').map(line => {
175
+ const [hash, author, email, timestamp, subject] = line.split('|');
176
+ return {
177
+ hash,
178
+ author,
179
+ email,
180
+ timestamp: parseInt(timestamp, 10),
181
+ date: new Date(parseInt(timestamp, 10) * 1000),
182
+ subject
183
+ };
184
+ });
185
+ }
186
+
187
+ /**
188
+ * Get blame information for file
189
+ * @param {string} filePath - File path
190
+ * @returns {Array<BlameLine>}
191
+ */
192
+ getBlame(filePath) {
193
+ try {
194
+ const command = `blame --line-porcelain "${filePath}"`;
195
+ const output = this.exec(command);
196
+
197
+ return this.parseBlameOutput(output);
198
+ } catch (error) {
199
+ logger.warn(`Blame failed for ${filePath}: ${error.message}`);
200
+ return [];
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Parse git blame porcelain output
206
+ * @param {string} output
207
+ * @returns {Array<BlameLine>}
208
+ */
209
+ parseBlameOutput(output) {
210
+ const lines = output.split('\n');
211
+ const blameInfo = [];
212
+ let currentCommit = null;
213
+ let currentAuthor = null;
214
+ let currentTimestamp = null;
215
+
216
+ for (const line of lines) {
217
+ if (line.match(/^[0-9a-f]{40}/)) {
218
+ // Commit hash line
219
+ currentCommit = line.split(' ')[0];
220
+ } else if (line.startsWith('author ')) {
221
+ currentAuthor = line.substring(7);
222
+ } else if (line.startsWith('author-time ')) {
223
+ currentTimestamp = parseInt(line.substring(12), 10);
224
+ } else if (line.startsWith('\t')) {
225
+ // Code line
226
+ blameInfo.push({
227
+ commit: currentCommit,
228
+ author: currentAuthor,
229
+ timestamp: currentTimestamp,
230
+ date: new Date(currentTimestamp * 1000),
231
+ code: line.substring(1)
232
+ });
233
+ }
234
+ }
235
+
236
+ return blameInfo;
237
+ }
238
+
239
+ /**
240
+ * Get commit count for file
241
+ * @param {string} filePath
242
+ * @returns {number}
243
+ */
244
+ getCommitCount(filePath) {
245
+ try {
246
+ const output = this.exec(`log --follow --oneline "${filePath}"`);
247
+ return output ? output.split('\n').length : 0;
248
+ } catch (error) {
249
+ return 0;
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Get file authors
255
+ * @param {string} filePath
256
+ * @returns {Array<Author>}
257
+ */
258
+ getFileAuthors(filePath) {
259
+ try {
260
+ const command = `log --follow --pretty=format:"%an|%ae" -- "${filePath}"`;
261
+ const output = this.exec(command);
262
+
263
+ if (!output) return [];
264
+
265
+ const authorsMap = new Map();
266
+
267
+ output.split('\n').forEach(line => {
268
+ const [name, email] = line.split('|');
269
+ if (!authorsMap.has(email)) {
270
+ authorsMap.set(email, { name, email, commits: 0 });
271
+ }
272
+ authorsMap.get(email).commits++;
273
+ });
274
+
275
+ return Array.from(authorsMap.values())
276
+ .sort((a, b) => b.commits - a.commits);
277
+
278
+ } catch (error) {
279
+ return [];
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Get repository statistics
285
+ * @returns {object}
286
+ */
287
+ getRepoStats() {
288
+ if (!this.isGitRepo) {
289
+ return null;
290
+ }
291
+
292
+ try {
293
+ const totalCommits = this.exec('rev-list --count HEAD');
294
+ const totalFiles = this.exec('ls-files').split('\n').length;
295
+ const contributors = this.exec('shortlog -sn --all').split('\n').length;
296
+
297
+ return {
298
+ totalCommits: parseInt(totalCommits, 10),
299
+ totalFiles,
300
+ contributors,
301
+ currentBranch: this.getCurrentBranch()
302
+ };
303
+ } catch (error) {
304
+ logger.warn(`Failed to get repo stats: ${error.message}`);
305
+ return null;
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Get last commit info for file
311
+ * @param {string} filePath
312
+ * @returns {Commit|null}
313
+ */
314
+ getLastCommit(filePath) {
315
+ try {
316
+ const command = `log -1 --pretty=format:"%H|%an|%ae|%at|%s" -- "${filePath}"`;
317
+ const output = this.exec(command);
318
+
319
+ if (!output) return null;
320
+
321
+ const [hash, author, email, timestamp, subject] = output.split('|');
322
+ return {
323
+ hash,
324
+ author,
325
+ email,
326
+ timestamp: parseInt(timestamp, 10),
327
+ date: new Date(parseInt(timestamp, 10) * 1000),
328
+ subject
329
+ };
330
+ } catch (error) {
331
+ return null;
332
+ }
333
+ }
334
+ }
335
+
336
+ export default GitClient;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * GitIgnore Parser
3
+ * Parses .gitignore, .contextignore, and .contextinclude files
4
+ */
5
+
6
+ import fs from 'fs';
7
+
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);
16
+ }
17
+
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
+ }
33
+ }
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
+ }
53
+ }
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 };
75
+ }
76
+
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
+ }
103
+
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;
113
+ }
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;
124
+ }
125
+ }
126
+
127
+ export default GitIgnoreParser;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Method Filter Parser
3
+ * Parses .methodinclude and .methodignore files for method-level filtering
4
+ */
5
+
6
+ import fs from 'fs';
7
+
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
+ }
24
+ }
25
+
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
+ }));
35
+ }
36
+
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
+ }
43
+
44
+ return !this.ignorePatterns.some(p =>
45
+ p.regex.test(methodName) || p.regex.test(`${fileName}.${methodName}`)
46
+ );
47
+ }
48
+ }
49
+
50
+ export default MethodFilterParser;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * ExporterPlugin - Base Class for Format Exporter Plugins
3
+ * v3.0.0 - Plugin architecture
4
+ *
5
+ * All format exporters should extend this class
6
+ */
7
+
8
+ export class ExporterPlugin {
9
+ constructor() {
10
+ this.name = 'base';
11
+ this.extension = '.txt';
12
+ this.mimeType = 'text/plain';
13
+ this.version = '1.0.0';
14
+ }
15
+
16
+ /**
17
+ * Get plugin metadata
18
+ * @returns {object}
19
+ */
20
+ getMetadata() {
21
+ return {
22
+ name: this.name,
23
+ version: this.version,
24
+ extension: this.extension,
25
+ mimeType: this.mimeType,
26
+ supportsChunking: this.supportsChunking(),
27
+ supportsCompression: this.supportsCompression()
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Check if this exporter supports chunking
33
+ * @returns {boolean}
34
+ */
35
+ supportsChunking() {
36
+ return false;
37
+ }
38
+
39
+ /**
40
+ * Check if this exporter supports compression
41
+ * @returns {boolean}
42
+ */
43
+ supportsCompression() {
44
+ return false;
45
+ }
46
+
47
+ /**
48
+ * Encode context to this format
49
+ * @param {Context} context
50
+ * @param {object} options
51
+ * @returns {string}
52
+ */
53
+ encode(context, options = {}) {
54
+ throw new Error('encode() must be implemented by subclass');
55
+ }
56
+
57
+ /**
58
+ * Decode this format to context
59
+ * @param {string} content
60
+ * @returns {Context}
61
+ */
62
+ decode(content) {
63
+ throw new Error('decode() not supported for this format');
64
+ }
65
+
66
+ /**
67
+ * Validate encoded output
68
+ * @param {string} content
69
+ * @returns {boolean}
70
+ */
71
+ validate(content) {
72
+ return typeof content === 'string' && content.length > 0;
73
+ }
74
+
75
+ /**
76
+ * Estimate token count of encoded output
77
+ * @param {Context} context
78
+ * @returns {number}
79
+ */
80
+ estimateTokens(context) {
81
+ const encoded = this.encode(context);
82
+ return Math.ceil(encoded.length / 4); // Rough estimate
83
+ }
84
+
85
+ /**
86
+ * Compress output (optional)
87
+ * @param {string} content
88
+ * @returns {string}
89
+ */
90
+ compress(content) {
91
+ return content; // Override if compression supported
92
+ }
93
+
94
+ /**
95
+ * Cleanup resources
96
+ */
97
+ cleanup() {
98
+ // Override if needed
99
+ }
100
+ }
101
+
102
+ export default ExporterPlugin;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * LanguagePlugin - Base Class for Language Plugins
3
+ * v3.0.0 - Plugin architecture
4
+ *
5
+ * All language analyzers should extend this class
6
+ */
7
+
8
+ export class LanguagePlugin {
9
+ constructor() {
10
+ this.name = 'base';
11
+ this.extensions = [];
12
+ this.version = '1.0.0';
13
+ }
14
+
15
+ /**
16
+ * Get plugin metadata
17
+ * @returns {object}
18
+ */
19
+ getMetadata() {
20
+ return {
21
+ name: this.name,
22
+ version: this.version,
23
+ extensions: this.extensions,
24
+ supportsAST: this.supportsAST(),
25
+ supportsMethodExtraction: this.supportsMethodExtraction(),
26
+ supportsFrameworkDetection: this.supportsFrameworkDetection()
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Check if this plugin supports AST parsing
32
+ * @returns {boolean}
33
+ */
34
+ supportsAST() {
35
+ return false;
36
+ }
37
+
38
+ /**
39
+ * Check if this plugin supports method extraction
40
+ * @returns {boolean}
41
+ */
42
+ supportsMethodExtraction() {
43
+ return true;
44
+ }
45
+
46
+ /**
47
+ * Check if this plugin supports framework detection
48
+ * @returns {boolean}
49
+ */
50
+ supportsFrameworkDetection() {
51
+ return false;
52
+ }
53
+
54
+ /**
55
+ * Extract methods from code
56
+ * @param {string} content - File content
57
+ * @param {string} filePath - File path
58
+ * @returns {Array<Method>}
59
+ */
60
+ extractMethods(content, filePath) {
61
+ throw new Error('extractMethods() must be implemented by subclass');
62
+ }
63
+
64
+ /**
65
+ * Detect framework (optional)
66
+ * @param {string} content - File content
67
+ * @returns {string|null} Framework name or null
68
+ */
69
+ detectFramework(content) {
70
+ return null;
71
+ }
72
+
73
+ /**
74
+ * Analyze code complexity (optional)
75
+ * @param {string} content - Method content
76
+ * @returns {number} Complexity score
77
+ */
78
+ analyzeComplexity(content) {
79
+ // Default: Simple line count
80
+ return content.split('\n').length;
81
+ }
82
+
83
+ /**
84
+ * Validate file content
85
+ * @param {string} content - File content
86
+ * @returns {boolean}
87
+ */
88
+ validate(content) {
89
+ return typeof content === 'string' && content.length > 0;
90
+ }
91
+
92
+ /**
93
+ * Cleanup resources
94
+ */
95
+ cleanup() {
96
+ // Override if needed
97
+ }
98
+ }
99
+
100
+ export default LanguagePlugin;