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
@@ -0,0 +1,378 @@
1
+ /**
2
+ * Template Manager
3
+ * Manages context templates for different development tasks
4
+ * FEAT-009: Context Templates
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname } from 'path';
11
+ import { DEFAULT_TARGET_MODEL } from './llm-detector.js';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+
16
+ /**
17
+ * Built-in templates defined in code for reliability
18
+ */
19
+ const BUILTIN_TEMPLATES = {
20
+ 'bug-fix': {
21
+ name: 'Bug Fix Context',
22
+ description: 'Optimized for debugging and fixing bugs',
23
+ include: ['src/**/*.js', 'lib/**/*.js', '!src/**/*.test.js', '!lib/**/*.test.js'],
24
+ exclude: [
25
+ 'docs/**',
26
+ '*.md',
27
+ '*.json',
28
+ 'examples/**',
29
+ 'test/**',
30
+ '**/*.test.js',
31
+ '**/*.spec.js',
32
+ ],
33
+ methodInclude: ['*Handler', '*Service', '*Controller', '*Manager'],
34
+ methodExclude: ['*test*', 'console*', 'debug*', 'log*'],
35
+ targetModel: DEFAULT_TARGET_MODEL,
36
+ },
37
+ feature: {
38
+ name: 'Feature Development Context',
39
+ description: 'Optimized for implementing new features',
40
+ include: ['src/**/*.js', 'lib/**/*.js', '!src/**/*.test.js'],
41
+ exclude: ['docs/**', '*.md', 'examples/**'],
42
+ methodInclude: ['*Handler', '*Service', '*Controller', '*Manager', '*Factory', '*Builder'],
43
+ methodExclude: ['*test*', 'console*'],
44
+ targetModel: DEFAULT_TARGET_MODEL,
45
+ },
46
+ 'code-review': {
47
+ name: 'Code Review Context',
48
+ description: 'Optimized for reviewing code changes',
49
+ include: ['src/**/*.js', 'lib/**/*.js', 'bin/**/*.js'],
50
+ exclude: ['docs/**', '*.md', '*.json', 'node_modules/**'],
51
+ methodInclude: ['*Handler', '*Service', '*Controller', '*Manager', '*Parser', '*Analyzer'],
52
+ methodExclude: ['*test*'],
53
+ targetModel: DEFAULT_TARGET_MODEL,
54
+ },
55
+ documentation: {
56
+ name: 'Documentation Context',
57
+ description: 'Optimized for writing documentation',
58
+ include: ['src/**/*.js', 'lib/**/*.js', 'index.js'],
59
+ exclude: ['**/*.test.js', '**/*.spec.js', 'test/**', 'examples/**', 'node_modules/**'],
60
+ methodInclude: ['*'],
61
+ methodExclude: ['_*', '*test*', '*internal*'],
62
+ targetModel: DEFAULT_TARGET_MODEL,
63
+ },
64
+ refactoring: {
65
+ name: 'Refactoring Context',
66
+ description: 'Optimized for code refactoring and restructuring',
67
+ include: ['src/**/*.js', 'lib/**/*.js', 'bin/**/*.js', 'test/**/*.js'],
68
+ exclude: ['docs/**', '*.md', 'node_modules/**'],
69
+ methodInclude: [
70
+ '*Handler',
71
+ '*Service',
72
+ '*Controller',
73
+ '*Manager',
74
+ '*Parser',
75
+ '*Analyzer',
76
+ '*Utils',
77
+ '*Helper',
78
+ ],
79
+ methodExclude: ['*test*'],
80
+ targetModel: DEFAULT_TARGET_MODEL,
81
+ },
82
+ testing: {
83
+ name: 'Testing Context',
84
+ description: 'Optimized for writing tests',
85
+ include: ['src/**/*.js', 'lib/**/*.js', 'test/**/*.js'],
86
+ exclude: ['docs/**', '*.md', 'examples/**', 'node_modules/**'],
87
+ methodInclude: ['*'],
88
+ methodExclude: ['_*', 'console*'],
89
+ targetModel: DEFAULT_TARGET_MODEL,
90
+ },
91
+ 'full-context': {
92
+ name: 'Full Context',
93
+ description: 'Complete project context for comprehensive analysis',
94
+ include: ['**/*'],
95
+ exclude: ['node_modules/**', '.git/**', 'coverage/**', 'dist/**', 'build/**'],
96
+ methodInclude: ['*'],
97
+ methodExclude: [],
98
+ targetModel: DEFAULT_TARGET_MODEL,
99
+ },
100
+ };
101
+
102
+ class TemplateManager {
103
+ /**
104
+ * Create a TemplateManager instance
105
+ * @param {string} projectRoot - Project root directory
106
+ */
107
+ constructor(projectRoot = process.cwd()) {
108
+ this.projectRoot = projectRoot;
109
+ this.customTemplatesPath = path.join(projectRoot, '.ctxman', 'templates');
110
+ this.builtinTemplatesPath = path.join(__dirname, '..', '..', '.ctxman', 'templates');
111
+ }
112
+
113
+ /**
114
+ * List all available templates (built-in + custom)
115
+ * @returns {Array} Array of template objects
116
+ */
117
+ list() {
118
+ const templates = [];
119
+
120
+ // Add built-in templates
121
+ for (const [id, template] of Object.entries(BUILTIN_TEMPLATES)) {
122
+ templates.push({
123
+ id,
124
+ ...template,
125
+ source: 'builtin',
126
+ });
127
+ }
128
+
129
+ // Add custom templates from project .ctxman/templates/
130
+ const customTemplates = this.loadCustomTemplates();
131
+ templates.push(...customTemplates);
132
+
133
+ return templates;
134
+ }
135
+
136
+ /**
137
+ * Get a specific template by ID
138
+ * @param {string} templateId - Template identifier
139
+ * @returns {object|null} Template object or null if not found
140
+ */
141
+ get(templateId) {
142
+ // Check built-in first
143
+ if (BUILTIN_TEMPLATES[templateId]) {
144
+ return {
145
+ id: templateId,
146
+ ...BUILTIN_TEMPLATES[templateId],
147
+ source: 'builtin',
148
+ };
149
+ }
150
+
151
+ // Check custom templates
152
+ const customTemplates = this.loadCustomTemplates();
153
+ const custom = customTemplates.find((t) => t.id === templateId);
154
+ if (custom) {
155
+ return custom;
156
+ }
157
+
158
+ return null;
159
+ }
160
+
161
+ /**
162
+ * Load custom templates from .ctxman/templates/
163
+ * @returns {Array} Array of custom template objects
164
+ */
165
+ loadCustomTemplates() {
166
+ const templates = [];
167
+
168
+ try {
169
+ if (!fs.existsSync(this.customTemplatesPath)) {
170
+ return templates;
171
+ }
172
+
173
+ const files = fs.readdirSync(this.customTemplatesPath);
174
+
175
+ for (const file of files) {
176
+ if (!file.endsWith('.json')) {
177
+ continue;
178
+ }
179
+
180
+ const filePath = path.join(this.customTemplatesPath, file);
181
+
182
+ try {
183
+ const content = fs.readFileSync(filePath, 'utf-8');
184
+ const template = JSON.parse(content);
185
+ const id = path.basename(file, '.json');
186
+
187
+ // Skip if already defined as built-in (built-ins take precedence)
188
+ if (BUILTIN_TEMPLATES[id]) {
189
+ continue;
190
+ }
191
+
192
+ templates.push({
193
+ id,
194
+ ...template,
195
+ source: 'custom',
196
+ });
197
+ } catch (parseError) {
198
+ console.error(`Warning: Failed to parse template ${file}: ${parseError.message}`);
199
+ }
200
+ }
201
+ } catch (error) {
202
+ console.error(`Warning: Failed to load custom templates: ${error.message}`);
203
+ }
204
+
205
+ return templates;
206
+ }
207
+
208
+ /**
209
+ * Apply a template and get configuration options
210
+ * @param {string} templateId - Template identifier
211
+ * @param {object} options - Additional options to merge
212
+ * @returns {object} Merged configuration options
213
+ */
214
+ apply(templateId, options = {}) {
215
+ const template = this.get(templateId);
216
+
217
+ if (!template) {
218
+ throw new Error(`Template '${templateId}' not found`);
219
+ }
220
+
221
+ // Build configuration from template
222
+ const config = {
223
+ templateId,
224
+ templateName: template.name,
225
+ templateDescription: template.description,
226
+ templateSource: template.source,
227
+ // Convert include/exclude patterns to ignore patterns for TokenAnalyzer
228
+ ignorePatterns: this.buildIgnorePatterns(template),
229
+ includePatterns: template.include || [],
230
+ methodInclude: template.methodInclude || [],
231
+ methodExclude: template.methodExclude || [],
232
+ targetModel: template.targetModel || options.targetModel,
233
+ ...options,
234
+ };
235
+
236
+ return config;
237
+ }
238
+
239
+ /**
240
+ * Build ignore patterns from template exclude list
241
+ * @param {object} template - Template object
242
+ * @returns {Array} Array of ignore patterns
243
+ */
244
+ buildIgnorePatterns(template) {
245
+ const patterns = [];
246
+
247
+ if (template.exclude) {
248
+ patterns.push(...template.exclude);
249
+ }
250
+
251
+ return patterns;
252
+ }
253
+
254
+ /**
255
+ * Create a custom template
256
+ * @param {string} id - Template identifier
257
+ * @param {object} template - Template configuration
258
+ * @returns {string} Path to created template file
259
+ */
260
+ createCustom(id, template) {
261
+ // Ensure .ctxman/templates directory exists
262
+ if (!fs.existsSync(this.customTemplatesPath)) {
263
+ fs.mkdirSync(this.customTemplatesPath, { recursive: true });
264
+ }
265
+
266
+ const filePath = path.join(this.customTemplatesPath, `${id}.json`);
267
+
268
+ // Validate template structure
269
+ this.validateTemplate(template);
270
+
271
+ // Write template file
272
+ try {
273
+ // 'wx' fails when the file exists, without a separate check that could go stale
274
+ fs.writeFileSync(filePath, JSON.stringify(template, null, 2), { flag: 'wx' });
275
+ } catch (error) {
276
+ if (error.code === 'EEXIST') throw new Error(`Template '${id}' already exists`);
277
+ throw error;
278
+ }
279
+
280
+ return filePath;
281
+ }
282
+
283
+ /**
284
+ * Validate template structure
285
+ * @param {object} template - Template to validate
286
+ * @throws {Error} If template is invalid
287
+ */
288
+ validateTemplate(template) {
289
+ if (!template.name || typeof template.name !== 'string') {
290
+ throw new Error('Template must have a "name" string property');
291
+ }
292
+
293
+ if (!template.description || typeof template.description !== 'string') {
294
+ throw new Error('Template must have a "description" string property');
295
+ }
296
+
297
+ if (template.include && !Array.isArray(template.include)) {
298
+ throw new Error('Template "include" must be an array');
299
+ }
300
+
301
+ if (template.exclude && !Array.isArray(template.exclude)) {
302
+ throw new Error('Template "exclude" must be an array');
303
+ }
304
+
305
+ if (template.methodInclude && !Array.isArray(template.methodInclude)) {
306
+ throw new Error('Template "methodInclude" must be an array');
307
+ }
308
+
309
+ if (template.methodExclude && !Array.isArray(template.methodExclude)) {
310
+ throw new Error('Template "methodExclude" must be an array');
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Delete a custom template
316
+ * @param {string} id - Template identifier
317
+ * @returns {boolean} True if deleted, false if not found
318
+ */
319
+ deleteCustom(id) {
320
+ // Prevent deletion of built-in templates
321
+ if (BUILTIN_TEMPLATES[id]) {
322
+ throw new Error('Cannot delete built-in templates');
323
+ }
324
+
325
+ const filePath = path.join(this.customTemplatesPath, `${id}.json`);
326
+
327
+ if (!fs.existsSync(filePath)) {
328
+ return false;
329
+ }
330
+
331
+ fs.unlinkSync(filePath);
332
+ return true;
333
+ }
334
+
335
+ /**
336
+ * Format templates for display
337
+ * @returns {string} Formatted template list
338
+ */
339
+ formatList() {
340
+ const templates = this.list();
341
+ const lines = [];
342
+
343
+ lines.push('\nšŸ“‹ Available Context Templates:\n');
344
+ lines.push('═'.repeat(70));
345
+
346
+ // Group by source
347
+ const builtin = templates.filter((t) => t.source === 'builtin');
348
+ const custom = templates.filter((t) => t.source === 'custom');
349
+
350
+ if (builtin.length > 0) {
351
+ lines.push('\nBuilt-in Templates:');
352
+ builtin.forEach((t) => {
353
+ lines.push(` ${t.id.padEnd(15)} ${t.description}`);
354
+ });
355
+ }
356
+
357
+ if (custom.length > 0) {
358
+ lines.push('\nCustom Templates:');
359
+ custom.forEach((t) => {
360
+ lines.push(` ${t.id.padEnd(15)} ${t.description}`);
361
+ });
362
+ }
363
+
364
+ lines.push('\n' + '═'.repeat(70));
365
+ lines.push('\nUsage:');
366
+ lines.push(' ctxman --template <template-id>');
367
+ lines.push(' ctxman -t bug-fix');
368
+ lines.push(' ctxman -t feature --cli');
369
+ lines.push('\nCustom Templates:');
370
+ lines.push(' Add templates to .ctxman/templates/<name>.json');
371
+ lines.push('');
372
+
373
+ return lines.join('\n');
374
+ }
375
+ }
376
+
377
+ export default TemplateManager;
378
+ export { BUILTIN_TEMPLATES };