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
package/lib/ui/wizard.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import React, { useState, useCallback, useMemo } from 'react';
2
2
  import { Box, Text } from 'ink';
3
3
  import SelectInput from './select-input.js';
4
- import { LLMDetector } from '../utils/llm-detector.js';
4
+ import { LLMDetector, DEFAULT_TARGET_MODEL } from '../utils/llm-detector.js';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
  import { fileURLToPath } from 'url';
@@ -19,341 +19,386 @@ const __dirname = path.dirname(__filename);
19
19
  * Discover available wizard profiles from .ctxman/wizard-profiles/
20
20
  */
21
21
  function discoverProfiles() {
22
- const profilesDir = path.join(__dirname, '../../.ctxman/wizard-profiles');
23
- const profiles = [];
24
-
25
- try {
26
- if (fs.existsSync(profilesDir)) {
27
- const dirs = fs.readdirSync(profilesDir, { withFileTypes: true });
28
-
29
- dirs.forEach(dirent => {
30
- if (dirent.isDirectory()) {
31
- const profilePath = path.join(profilesDir, dirent.name);
32
- const metadataPath = path.join(profilePath, 'profile.json');
33
-
34
- if (fs.existsSync(metadataPath)) {
35
- try {
36
- const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
37
- profiles.push({
38
- label: `${metadata.icon} ${metadata.name}`,
39
- value: metadata.id,
40
- path: profilePath,
41
- metadata: metadata
42
- });
43
- } catch (error) {
44
- console.error(`Error reading profile metadata ${dirent.name}:`, error);
45
- }
46
- }
47
- }
48
- });
49
- }
50
- } catch (error) {
51
- console.error('Error discovering profiles:', error);
52
- }
53
-
54
- // Add custom option at the end
55
- profiles.push({
56
- label: '⚙️ Custom (use existing root config)',
57
- value: 'custom',
58
- path: null,
59
- metadata: null
60
- });
61
-
62
- return profiles;
22
+ const profilesDir = path.join(__dirname, '../../.ctxman/wizard-profiles');
23
+ const profiles = [];
24
+
25
+ try {
26
+ if (fs.existsSync(profilesDir)) {
27
+ const dirs = fs.readdirSync(profilesDir, { withFileTypes: true });
28
+
29
+ dirs.forEach((dirent) => {
30
+ if (dirent.isDirectory()) {
31
+ const profilePath = path.join(profilesDir, dirent.name);
32
+ const metadataPath = path.join(profilePath, 'profile.json');
33
+
34
+ if (fs.existsSync(metadataPath)) {
35
+ try {
36
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
37
+ profiles.push({
38
+ label: `${metadata.icon} ${metadata.name}`,
39
+ value: metadata.id,
40
+ path: profilePath,
41
+ metadata: metadata,
42
+ });
43
+ } catch (error) {
44
+ console.error(`Error reading profile metadata ${dirent.name}:`, error);
45
+ }
46
+ }
47
+ }
48
+ });
49
+ }
50
+ } catch (error) {
51
+ console.error('Error discovering profiles:', error);
52
+ }
53
+
54
+ // Add custom option at the end
55
+ profiles.push({
56
+ label: '⚙️ Custom (use existing root config)',
57
+ value: 'custom',
58
+ path: null,
59
+ metadata: null,
60
+ });
61
+
62
+ return profiles;
63
63
  }
64
64
 
65
65
  /**
66
66
  * Copy profile configuration files to project root with profile name suffix
67
67
  */
68
68
  function copyProfileFiles(profilePath, profileId, projectRoot) {
69
- const filesToCopy = [
70
- '.contextinclude',
71
- '.contextignore',
72
- '.methodinclude',
73
- '.methodignore'
74
- ];
75
- const copiedFiles = [];
76
-
77
- filesToCopy.forEach(fileName => {
78
- const sourcePath = path.join(profilePath, fileName);
79
- // Copy with profile name suffix: .contextinclude-code-review
80
- const destFileName = `${fileName}-${profileId}`;
81
- const destPath = path.join(projectRoot, destFileName);
82
-
83
- if (fs.existsSync(sourcePath)) {
84
- try {
85
- fs.copyFileSync(sourcePath, destPath);
86
- copiedFiles.push(destFileName);
87
- } catch (error) {
88
- console.error(`Error copying ${fileName}:`, error);
89
- }
90
- }
91
- });
92
-
93
- // Create/update symlinks for active config
94
- // .contextinclude -> .contextinclude-code-review
95
- const symlinkFiles = [
96
- ['.contextinclude', `.contextinclude-${profileId}`],
97
- ['.contextignore', `.contextignore-${profileId}`],
98
- ['.methodinclude', `.methodinclude-${profileId}`],
99
- ['.methodignore', `.methodignore-${profileId}`]
100
- ];
101
-
102
- symlinkFiles.forEach(([linkName, targetName]) => {
103
- const linkPath = path.join(projectRoot, linkName);
104
- const targetPath = path.join(projectRoot, targetName);
105
-
106
- try {
107
- // Remove existing symlink/file
108
- if (fs.existsSync(linkPath)) {
109
- fs.unlinkSync(linkPath);
110
- }
111
-
112
- // Create copy instead of symlink for better compatibility
113
- if (fs.existsSync(targetPath)) {
114
- fs.copyFileSync(targetPath, linkPath);
115
- }
116
- } catch (error) {
117
- console.error(`Error creating link ${linkName}:`, error);
118
- }
119
- });
120
-
121
- return copiedFiles;
69
+ const filesToCopy = ['.contextinclude', '.contextignore', '.methodinclude', '.methodignore'];
70
+ const copiedFiles = [];
71
+
72
+ filesToCopy.forEach((fileName) => {
73
+ const sourcePath = path.join(profilePath, fileName);
74
+ // Copy with profile name suffix: .contextinclude-code-review
75
+ const destFileName = `${fileName}-${profileId}`;
76
+ const destPath = path.join(projectRoot, destFileName);
77
+
78
+ if (fs.existsSync(sourcePath)) {
79
+ try {
80
+ fs.copyFileSync(sourcePath, destPath);
81
+ copiedFiles.push(destFileName);
82
+ } catch (error) {
83
+ console.error(`Error copying ${fileName}:`, error);
84
+ }
85
+ }
86
+ });
87
+
88
+ // Create/update symlinks for active config
89
+ // .contextinclude -> .contextinclude-code-review
90
+ const symlinkFiles = [
91
+ ['.contextinclude', `.contextinclude-${profileId}`],
92
+ ['.contextignore', `.contextignore-${profileId}`],
93
+ ['.methodinclude', `.methodinclude-${profileId}`],
94
+ ['.methodignore', `.methodignore-${profileId}`],
95
+ ];
96
+
97
+ symlinkFiles.forEach(([linkName, targetName]) => {
98
+ const linkPath = path.join(projectRoot, linkName);
99
+ const targetPath = path.join(projectRoot, targetName);
100
+
101
+ try {
102
+ // Remove existing symlink/file
103
+ if (fs.existsSync(linkPath)) {
104
+ fs.unlinkSync(linkPath);
105
+ }
106
+
107
+ // Create copy instead of symlink for better compatibility
108
+ if (fs.existsSync(targetPath)) {
109
+ fs.copyFileSync(targetPath, linkPath);
110
+ }
111
+ } catch (error) {
112
+ console.error(`Error creating link ${linkName}:`, error);
113
+ }
114
+ });
115
+
116
+ return copiedFiles;
122
117
  }
123
118
 
124
119
  export default function Wizard({ onComplete }) {
125
- const [step, setStep] = useState('profile');
126
- const [answers, setAnswers] = useState({});
127
-
128
- // Dynamic profile discovery
129
- const profileItems = useMemo(() => discoverProfiles(), []);
130
-
131
- // Dynamic LLM model list from JSON config
132
- const targetModelItems = useMemo(() => {
133
- const models = LLMDetector.getModelList();
134
- const items = [
135
- { label: '✨ Auto-detect from environment', value: 'auto' }
136
- ];
137
-
138
- // Add models grouped by vendor
139
- const anthropicModels = models.filter(m => m.vendor === 'Anthropic');
140
- const openaiModels = models.filter(m => m.vendor === 'OpenAI');
141
- const googleModels = models.filter(m => m.vendor === 'Google');
142
- const otherModels = models.filter(m => !['Anthropic', 'OpenAI', 'Google'].includes(m.vendor));
143
-
144
- // Anthropic (Claude)
145
- anthropicModels.forEach(model => {
146
- const contextK = Math.floor(model.contextWindow / 1000);
147
- items.push({
148
- label: `${model.name} (${contextK}k context)`,
149
- value: model.id
150
- });
151
- });
152
-
153
- // OpenAI (GPT-4)
154
- openaiModels.forEach(model => {
155
- const contextK = Math.floor(model.contextWindow / 1000);
156
- items.push({
157
- label: `${model.name} (${contextK}k context)`,
158
- value: model.id
159
- });
160
- });
161
-
162
- // Google (Gemini)
163
- googleModels.forEach(model => {
164
- const contextK = model.contextWindow >= 1000000
165
- ? Math.floor(model.contextWindow / 1000000) + 'M'
166
- : Math.floor(model.contextWindow / 1000) + 'k';
167
- items.push({
168
- label: `${model.name} (${contextK} context)`,
169
- value: model.id
170
- });
171
- });
172
-
173
- // Others
174
- otherModels.forEach(model => {
175
- const contextK = Math.floor(model.contextWindow / 1000);
176
- items.push({
177
- label: `${model.name} (${contextK}k context)`,
178
- value: model.id
179
- });
180
- });
181
-
182
- return items;
183
- }, []);
184
-
185
- const outputFormatItems = [
186
- { label: 'TOON (most efficient, 40-50% reduction)', value: 'toon' },
187
- { label: 'JSON (standard format)', value: 'json' },
188
- { label: 'GitIngest (single file)', value: 'gitingest' },
189
- { label: 'Markdown (documentation)', value: 'markdown' },
190
- { label: 'YAML (human-readable)', value: 'yaml' },
191
- { label: 'CSV (spreadsheet)', value: 'csv' }
192
- ];
193
-
194
- const handleProfileSelect = useCallback((item) => {
195
- // Copy profile files if not custom
196
- if (item.value !== 'custom' && item.path) {
197
- const projectRoot = process.cwd();
198
- const copiedFiles = copyProfileFiles(item.path, item.value, projectRoot);
199
-
200
- setAnswers(prev => ({
201
- ...prev,
202
- profile: item.value,
203
- copiedFiles: copiedFiles,
204
- profileMetadata: item.metadata
205
- }));
206
- } else {
207
- // Custom - use existing root config files
208
- setAnswers(prev => ({
209
- ...prev,
210
- profile: 'custom',
211
- copiedFiles: [],
212
- profileMetadata: null
213
- }));
214
- }
215
-
216
- setStep('target-model');
217
- }, []);
218
-
219
- const handleTargetModelSelect = useCallback((item) => {
220
- let selectedModel = item.value;
221
-
222
- // Handle auto-detect
223
- if (selectedModel === 'auto') {
224
- selectedModel = LLMDetector.detect();
225
- if (selectedModel === 'unknown') {
226
- // Fallback to default if detection fails
227
- selectedModel = 'claude-sonnet-4.5';
228
- }
229
- }
230
-
231
- setAnswers(prev => ({ ...prev, targetModel: selectedModel }));
232
- setStep('output-format');
233
- }, []);
234
-
235
- const handleOutputFormatSelect = useCallback((item) => {
236
- const finalAnswers = { ...answers, outputFormat: item.value };
237
- setAnswers(finalAnswers);
238
- setStep('complete');
239
-
240
- // Call completion callback
241
- if (onComplete) {
242
- onComplete(finalAnswers);
243
- }
244
- }, [answers, onComplete]);
245
-
246
- // Build children array and filter out falsy values
247
- const children = [
248
- React.createElement(Box, { key: 'title', marginBottom: 1 },
249
- React.createElement(Text, { bold: true, color: 'cyan' }, '🧙 Context Generation Wizard')
250
- )
251
- ];
252
-
253
- // Step 1: Profile Selection
254
- if (step === 'profile') {
255
- children.push(
256
- React.createElement(Box, { key: 'profile-step', flexDirection: 'column' },
257
- React.createElement(Box, { key: 'question-box', marginBottom: 1 },
258
- React.createElement(Text, null, 'Select analysis profile:')
259
- ),
260
- React.createElement(SelectInput, {
261
- key: 'select-input',
262
- items: profileItems,
263
- onSelect: handleProfileSelect
264
- }),
265
- React.createElement(Box, { key: 'help-box', marginTop: 1 },
266
- React.createElement(Text, { dimColor: true }, '[↑↓] Navigate [Enter] Select [Esc] Cancel')
267
- )
268
- )
269
- );
270
- }
271
-
272
- // Step 2: Target Model
273
- if (step === 'target-model') {
274
- children.push(
275
- React.createElement(Box, { key: 'target-model-step', flexDirection: 'column' },
276
- React.createElement(Box, { key: 'question-box-2', marginBottom: 1 },
277
- React.createElement(Text, null, 'Target LLM?')
278
- ),
279
- React.createElement(SelectInput, {
280
- key: 'select-input-2',
281
- items: targetModelItems,
282
- onSelect: handleTargetModelSelect
283
- }),
284
- React.createElement(Box, { key: 'help-box-2', marginTop: 1 },
285
- React.createElement(Text, { dimColor: true }, '[↑↓] Navigate [Enter] Select [Esc] Cancel')
286
- )
287
- )
288
- );
289
- }
290
-
291
- // Step 3: Output Format
292
- if (step === 'output-format') {
293
- children.push(
294
- React.createElement(Box, { key: 'output-format-step', flexDirection: 'column' },
295
- React.createElement(Box, { key: 'question-box-3', marginBottom: 1 },
296
- React.createElement(Text, null, 'Output format?')
297
- ),
298
- React.createElement(SelectInput, {
299
- key: 'select-input-3',
300
- items: outputFormatItems,
301
- onSelect: handleOutputFormatSelect
302
- }),
303
- React.createElement(Box, { key: 'help-box-3', marginTop: 1 },
304
- React.createElement(Text, { dimColor: true }, '[↑↓] Navigate [Enter] Select [Esc] Cancel')
305
- )
306
- )
307
- );
308
- }
309
-
310
- // Complete
311
- if (step === 'complete') {
312
- const completeBoxChildren = [
313
- React.createElement(Box, { key: 'complete-title', marginBottom: 1 },
314
- React.createElement(Text, { bold: true, color: 'green' }, '✓ Configuration Complete!')
315
- ),
316
- React.createElement(Box, { key: 'complete-info', flexDirection: 'column', marginBottom: 1 },
317
- React.createElement(Text, null, `Profile: ${answers.profile}`),
318
- React.createElement(Text, null, `Target Model: ${answers.targetModel}`),
319
- React.createElement(Text, null, `Output Format: ${answers.outputFormat}`)
320
- )
321
- ];
322
-
323
- // Show copied files if any
324
- if (answers.copiedFiles && answers.copiedFiles.length > 0) {
325
- completeBoxChildren.push(
326
- React.createElement(Box, { key: 'copied-files', flexDirection: 'column', marginTop: 1 },
327
- React.createElement(Text, { color: 'cyan' }, 'Profile configuration files:'),
328
- ...answers.copiedFiles.slice(0, 4).map((file, idx) =>
329
- React.createElement(Text, { key: `file-${idx}`, color: 'gray' }, ` ✓ ${file}`)
330
- )
331
- )
332
- );
333
-
334
- completeBoxChildren.push(
335
- React.createElement(Box, { key: 'active-files', flexDirection: 'column', marginTop: 1 },
336
- React.createElement(Text, { color: 'green' }, 'Active configuration (symlinked):'),
337
- React.createElement(Text, { color: 'gray' }, ' → .contextinclude'),
338
- React.createElement(Text, { color: 'gray' }, ' → .contextignore'),
339
- React.createElement(Text, { color: 'gray' }, ' → .methodinclude'),
340
- React.createElement(Text, { color: 'gray' }, ' → .methodignore')
341
- )
342
- );
343
- }
344
-
345
- children.push(
346
- React.createElement(Box, { key: 'complete-step', flexDirection: 'column' },
347
- ...completeBoxChildren
348
- )
349
- );
350
- }
351
-
352
- return React.createElement(Box, {
353
- flexDirection: 'column',
354
- padding: 1,
355
- borderStyle: 'round',
356
- borderColor: 'cyan',
357
- width: 76
358
- }, ...children);
120
+ const [step, setStep] = useState('profile');
121
+ const [answers, setAnswers] = useState({});
122
+
123
+ // Dynamic profile discovery
124
+ const profileItems = useMemo(() => discoverProfiles(), []);
125
+
126
+ // Dynamic LLM model list from JSON config
127
+ const targetModelItems = useMemo(() => {
128
+ const models = LLMDetector.getModelList();
129
+ const items = [{ label: '✨ Auto-detect from environment', value: 'auto' }];
130
+
131
+ // Add models grouped by vendor
132
+ const anthropicModels = models.filter((m) => m.vendor === 'Anthropic');
133
+ const openaiModels = models.filter((m) => m.vendor === 'OpenAI');
134
+ const googleModels = models.filter((m) => m.vendor === 'Google');
135
+ const otherModels = models.filter((m) => !['Anthropic', 'OpenAI', 'Google'].includes(m.vendor));
136
+
137
+ // Anthropic (Claude)
138
+ anthropicModels.forEach((model) => {
139
+ const contextK = Math.floor(model.contextWindow / 1000);
140
+ items.push({
141
+ label: `${model.name} (${contextK}k context)`,
142
+ value: model.id,
143
+ });
144
+ });
145
+
146
+ // OpenAI (GPT-4)
147
+ openaiModels.forEach((model) => {
148
+ const contextK = Math.floor(model.contextWindow / 1000);
149
+ items.push({
150
+ label: `${model.name} (${contextK}k context)`,
151
+ value: model.id,
152
+ });
153
+ });
154
+
155
+ // Google (Gemini)
156
+ googleModels.forEach((model) => {
157
+ const contextK =
158
+ model.contextWindow >= 1000000
159
+ ? Math.floor(model.contextWindow / 1000000) + 'M'
160
+ : Math.floor(model.contextWindow / 1000) + 'k';
161
+ items.push({
162
+ label: `${model.name} (${contextK} context)`,
163
+ value: model.id,
164
+ });
165
+ });
166
+
167
+ // Others
168
+ otherModels.forEach((model) => {
169
+ const contextK = Math.floor(model.contextWindow / 1000);
170
+ items.push({
171
+ label: `${model.name} (${contextK}k context)`,
172
+ value: model.id,
173
+ });
174
+ });
175
+
176
+ return items;
177
+ }, []);
178
+
179
+ const outputFormatItems = [
180
+ { label: 'TOON (most efficient, 40-50% reduction)', value: 'toon' },
181
+ { label: 'JSON (standard format)', value: 'json' },
182
+ { label: 'GitIngest (single file)', value: 'gitingest' },
183
+ { label: 'Markdown (documentation)', value: 'markdown' },
184
+ { label: 'YAML (human-readable)', value: 'yaml' },
185
+ { label: 'CSV (spreadsheet)', value: 'csv' },
186
+ ];
187
+
188
+ const handleProfileSelect = useCallback((item) => {
189
+ // Copy profile files if not custom
190
+ if (item.value !== 'custom' && item.path) {
191
+ const projectRoot = process.cwd();
192
+ const copiedFiles = copyProfileFiles(item.path, item.value, projectRoot);
193
+
194
+ setAnswers((prev) => ({
195
+ ...prev,
196
+ profile: item.value,
197
+ copiedFiles: copiedFiles,
198
+ profileMetadata: item.metadata,
199
+ }));
200
+ } else {
201
+ // Custom - use existing root config files
202
+ setAnswers((prev) => ({
203
+ ...prev,
204
+ profile: 'custom',
205
+ copiedFiles: [],
206
+ profileMetadata: null,
207
+ }));
208
+ }
209
+
210
+ setStep('target-model');
211
+ }, []);
212
+
213
+ const handleTargetModelSelect = useCallback((item) => {
214
+ let selectedModel = item.value;
215
+
216
+ // Handle auto-detect
217
+ if (selectedModel === 'auto') {
218
+ selectedModel = LLMDetector.detect();
219
+ if (selectedModel === 'unknown') {
220
+ // Fallback to default if detection fails
221
+ selectedModel = DEFAULT_TARGET_MODEL;
222
+ }
223
+ }
224
+
225
+ setAnswers((prev) => ({ ...prev, targetModel: selectedModel }));
226
+ setStep('output-format');
227
+ }, []);
228
+
229
+ const handleOutputFormatSelect = useCallback(
230
+ (item) => {
231
+ const finalAnswers = { ...answers, outputFormat: item.value };
232
+ setAnswers(finalAnswers);
233
+ setStep('complete');
234
+
235
+ // Call completion callback
236
+ if (onComplete) {
237
+ onComplete(finalAnswers);
238
+ }
239
+ },
240
+ [answers, onComplete]
241
+ );
242
+
243
+ // Build children array and filter out falsy values
244
+ const children = [
245
+ React.createElement(
246
+ Box,
247
+ { key: 'title', marginBottom: 1 },
248
+ React.createElement(Text, { bold: true, color: 'cyan' }, '🧙 Context Generation Wizard')
249
+ ),
250
+ ];
251
+
252
+ // Step 1: Profile Selection
253
+ if (step === 'profile') {
254
+ children.push(
255
+ React.createElement(
256
+ Box,
257
+ { key: 'profile-step', flexDirection: 'column' },
258
+ React.createElement(
259
+ Box,
260
+ { key: 'question-box', marginBottom: 1 },
261
+ React.createElement(Text, null, 'Select analysis profile:')
262
+ ),
263
+ React.createElement(SelectInput, {
264
+ key: 'select-input',
265
+ items: profileItems,
266
+ onSelect: handleProfileSelect,
267
+ }),
268
+ React.createElement(
269
+ Box,
270
+ { key: 'help-box', marginTop: 1 },
271
+ React.createElement(
272
+ Text,
273
+ { dimColor: true },
274
+ '[↑↓] Navigate [Enter] Select [Esc] Cancel'
275
+ )
276
+ )
277
+ )
278
+ );
279
+ }
280
+
281
+ // Step 2: Target Model
282
+ if (step === 'target-model') {
283
+ children.push(
284
+ React.createElement(
285
+ Box,
286
+ { key: 'target-model-step', flexDirection: 'column' },
287
+ React.createElement(
288
+ Box,
289
+ { key: 'question-box-2', marginBottom: 1 },
290
+ React.createElement(Text, null, 'Target LLM?')
291
+ ),
292
+ React.createElement(SelectInput, {
293
+ key: 'select-input-2',
294
+ items: targetModelItems,
295
+ onSelect: handleTargetModelSelect,
296
+ }),
297
+ React.createElement(
298
+ Box,
299
+ { key: 'help-box-2', marginTop: 1 },
300
+ React.createElement(
301
+ Text,
302
+ { dimColor: true },
303
+ '[↑↓] Navigate [Enter] Select [Esc] Cancel'
304
+ )
305
+ )
306
+ )
307
+ );
308
+ }
309
+
310
+ // Step 3: Output Format
311
+ if (step === 'output-format') {
312
+ children.push(
313
+ React.createElement(
314
+ Box,
315
+ { key: 'output-format-step', flexDirection: 'column' },
316
+ React.createElement(
317
+ Box,
318
+ { key: 'question-box-3', marginBottom: 1 },
319
+ React.createElement(Text, null, 'Output format?')
320
+ ),
321
+ React.createElement(SelectInput, {
322
+ key: 'select-input-3',
323
+ items: outputFormatItems,
324
+ onSelect: handleOutputFormatSelect,
325
+ }),
326
+ React.createElement(
327
+ Box,
328
+ { key: 'help-box-3', marginTop: 1 },
329
+ React.createElement(
330
+ Text,
331
+ { dimColor: true },
332
+ '[↑↓] Navigate [Enter] Select [Esc] Cancel'
333
+ )
334
+ )
335
+ )
336
+ );
337
+ }
338
+
339
+ // Complete
340
+ if (step === 'complete') {
341
+ const completeBoxChildren = [
342
+ React.createElement(
343
+ Box,
344
+ { key: 'complete-title', marginBottom: 1 },
345
+ React.createElement(Text, { bold: true, color: 'green' }, '✓ Configuration Complete!')
346
+ ),
347
+ React.createElement(
348
+ Box,
349
+ { key: 'complete-info', flexDirection: 'column', marginBottom: 1 },
350
+ React.createElement(Text, null, `Profile: ${answers.profile}`),
351
+ React.createElement(Text, null, `Target Model: ${answers.targetModel}`),
352
+ React.createElement(Text, null, `Output Format: ${answers.outputFormat}`)
353
+ ),
354
+ ];
355
+
356
+ // Show copied files if any
357
+ if (answers.copiedFiles && answers.copiedFiles.length > 0) {
358
+ completeBoxChildren.push(
359
+ React.createElement(
360
+ Box,
361
+ { key: 'copied-files', flexDirection: 'column', marginTop: 1 },
362
+ React.createElement(Text, { color: 'cyan' }, 'Profile configuration files:'),
363
+ ...answers.copiedFiles
364
+ .slice(0, 4)
365
+ .map((file, idx) =>
366
+ React.createElement(Text, { key: `file-${idx}`, color: 'gray' }, ` ✓ ${file}`)
367
+ )
368
+ )
369
+ );
370
+
371
+ completeBoxChildren.push(
372
+ React.createElement(
373
+ Box,
374
+ { key: 'active-files', flexDirection: 'column', marginTop: 1 },
375
+ React.createElement(Text, { color: 'green' }, 'Active configuration (symlinked):'),
376
+ React.createElement(Text, { color: 'gray' }, ' → .contextinclude'),
377
+ React.createElement(Text, { color: 'gray' }, ' → .contextignore'),
378
+ React.createElement(Text, { color: 'gray' }, ' → .methodinclude'),
379
+ React.createElement(Text, { color: 'gray' }, ' → .methodignore')
380
+ )
381
+ );
382
+ }
383
+
384
+ children.push(
385
+ React.createElement(
386
+ Box,
387
+ { key: 'complete-step', flexDirection: 'column' },
388
+ ...completeBoxChildren
389
+ )
390
+ );
391
+ }
392
+
393
+ return React.createElement(
394
+ Box,
395
+ {
396
+ flexDirection: 'column',
397
+ padding: 1,
398
+ borderStyle: 'round',
399
+ borderColor: 'cyan',
400
+ width: 76,
401
+ },
402
+ ...children
403
+ );
359
404
  }