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
@@ -1,4 +1,4 @@
1
- import React from 'react.js';
1
+ import React from 'react';
2
2
 
3
3
  /**
4
4
  * Progress Bar Component for Ink Terminal UI
@@ -7,104 +7,110 @@ import React from 'react.js';
7
7
  * Note: Components are passed via props to avoid ESM import issues
8
8
  */
9
9
  const ProgressBar = ({ current, total, tokens, maxTokens, currentFile, components }) => {
10
- const { Box, Text } = components || {};
11
-
12
- if (!Box || !Text) {
13
- throw new Error('Box and Text components required in props');
14
- }
15
-
16
- const percentage = total > 0 ? Math.round((current / total) * 100) : 0;
17
- const barWidth = 40;
18
- const filled = Math.round((percentage / 100) * barWidth);
19
- const empty = barWidth - filled;
20
-
21
- const bar = '━'.repeat(filled) + '─'.repeat(empty);
22
- const tokenPercentage = maxTokens > 0 ? ((tokens / maxTokens) * 100).toFixed(1) : 0;
23
-
24
- const children = [];
25
-
26
- // Title
10
+ const { Box, Text } = components || {};
11
+
12
+ if (!Box || !Text) {
13
+ throw new Error('Box and Text components required in props');
14
+ }
15
+
16
+ const percentage = total > 0 ? Math.round((current / total) * 100) : 0;
17
+ const barWidth = 40;
18
+ const filled = Math.round((percentage / 100) * barWidth);
19
+ const empty = barWidth - filled;
20
+
21
+ const bar = '━'.repeat(filled) + '─'.repeat(empty);
22
+ const tokenPercentage = maxTokens > 0 ? ((tokens / maxTokens) * 100).toFixed(1) : 0;
23
+
24
+ const children = [];
25
+
26
+ // Title
27
+ children.push(
28
+ React.createElement(
29
+ Box,
30
+ { key: 'title' },
31
+ React.createElement(Text, { bold: true, color: 'cyan' }, '📊 Project Analysis')
32
+ )
33
+ );
34
+
35
+ // Progress bar
36
+ children.push(
37
+ React.createElement(
38
+ Box,
39
+ { key: 'bar', marginTop: 1 },
40
+ React.createElement(Text, { color: 'green' }, bar),
41
+ React.createElement(Text, { color: 'yellow' }, ` ${percentage}%`),
42
+ React.createElement(Text, { color: 'gray' }, ` (${current}/${total} files)`)
43
+ )
44
+ );
45
+
46
+ // Current file
47
+ if (currentFile) {
27
48
  children.push(
28
- React.createElement(Box, { key: 'title' },
29
- React.createElement(Text, { bold: true, color: 'cyan' }, '📊 Project Analysis')
30
- )
49
+ React.createElement(
50
+ Box,
51
+ { key: 'current', marginTop: 1 },
52
+ React.createElement(Text, { color: 'gray' }, 'Current: '),
53
+ React.createElement(Text, null, currentFile)
54
+ )
31
55
  );
32
-
33
- // Progress bar
34
- children.push(
35
- React.createElement(Box, { key: 'bar', marginTop: 1 },
36
- React.createElement(Text, { color: 'green' }, bar),
37
- React.createElement(Text, { color: 'yellow' }, ` ${percentage}%`),
38
- React.createElement(Text, { color: 'gray' }, ` (${current}/${total} files)`)
39
- )
56
+ }
57
+
58
+ // Tokens
59
+ const tokenColor = tokenPercentage > 90 ? 'red' : 'green';
60
+ const tokenChildren = [
61
+ React.createElement(Text, { key: 't1', color: 'gray' }, 'Tokens: '),
62
+ React.createElement(Text, { key: 't2', color: tokenColor }, tokens.toLocaleString()),
63
+ ];
64
+
65
+ if (maxTokens > 0) {
66
+ tokenChildren.push(
67
+ React.createElement(
68
+ Text,
69
+ { key: 't3', color: 'gray' },
70
+ ` / ${maxTokens.toLocaleString()} (${tokenPercentage}%)`
71
+ )
40
72
  );
73
+ }
41
74
 
42
- // Current file
43
- if (currentFile) {
44
- children.push(
45
- React.createElement(Box, { key: 'current', marginTop: 1 },
46
- React.createElement(Text, { color: 'gray' }, 'Current: '),
47
- React.createElement(Text, null, currentFile)
48
- )
49
- );
50
- }
51
-
52
- // Tokens
53
- const tokenColor = tokenPercentage > 90 ? 'red' : 'green';
54
- const tokenChildren = [
55
- React.createElement(Text, { key: 't1', color: 'gray' }, 'Tokens: '),
56
- React.createElement(Text, { key: 't2', color: tokenColor }, tokens.toLocaleString())
57
- ];
58
-
59
- if (maxTokens > 0) {
60
- tokenChildren.push(
61
- React.createElement(Text, { key: 't3', color: 'gray' },
62
- ` / ${maxTokens.toLocaleString()} (${tokenPercentage}%)`)
63
- );
64
- }
75
+ children.push(React.createElement(Box, { key: 'tokens', marginTop: 1 }, ...tokenChildren));
65
76
 
66
- children.push(
67
- React.createElement(Box, { key: 'tokens', marginTop: 1 }, ...tokenChildren)
68
- );
69
-
70
- return React.createElement(Box, { flexDirection: 'column' }, ...children);
77
+ return React.createElement(Box, { flexDirection: 'column' }, ...children);
71
78
  };
72
79
 
73
80
  /**
74
81
  * Spinner Component with message
75
82
  */
76
83
  const SpinnerWithText = ({ text, status = 'pending', components }) => {
77
- const { Box, Text, Spinner } = components || {};
78
-
79
- if (!Box || !Text) {
80
- throw new Error('Box and Text components required in props');
81
- }
82
-
83
- const statusIcons = {
84
- pending: '○',
85
- active: Spinner ? React.createElement(Spinner, { type: 'dots' }) : '⏳',
86
- complete: '✓',
87
- error: '✗'
88
- };
89
-
90
- const statusColors = {
91
- pending: 'gray',
92
- active: 'cyan',
93
- complete: 'green',
94
- error: 'red'
95
- };
96
-
97
- const icon = status === 'active' && Spinner
98
- ? React.createElement(Spinner, { type: 'dots' })
99
- : statusIcons[status];
100
-
101
- return React.createElement(Box, null,
102
- React.createElement(Text, { color: statusColors[status] },
103
- icon,
104
- ' ',
105
- text
106
- )
107
- );
84
+ const { Box, Text, Spinner } = components || {};
85
+
86
+ if (!Box || !Text) {
87
+ throw new Error('Box and Text components required in props');
88
+ }
89
+
90
+ const statusIcons = {
91
+ pending: '○',
92
+ active: Spinner ? React.createElement(Spinner, { type: 'dots' }) : '⏳',
93
+ complete: '✓',
94
+ error: '✗',
95
+ };
96
+
97
+ const statusColors = {
98
+ pending: 'gray',
99
+ active: 'cyan',
100
+ complete: 'green',
101
+ error: 'red',
102
+ };
103
+
104
+ const icon =
105
+ status === 'active' && Spinner
106
+ ? React.createElement(Spinner, { type: 'dots' })
107
+ : statusIcons[status];
108
+
109
+ return React.createElement(
110
+ Box,
111
+ null,
112
+ React.createElement(Text, { color: statusColors[status] }, icon, ' ', text)
113
+ );
108
114
  };
109
115
 
110
116
  export { ProgressBar, SpinnerWithText };
@@ -10,42 +10,50 @@ import { Box, Text, useInput, useStdin } from 'ink';
10
10
  * @param {Function} onSelect - Callback when item is selected
11
11
  */
12
12
  export default function SelectInput({ items = [], onSelect }) {
13
- const [selectedIndex, setSelectedIndex] = useState(0);
14
- const { isRawModeSupported } = useStdin();
13
+ const [selectedIndex, setSelectedIndex] = useState(0);
14
+ const { isRawModeSupported } = useStdin();
15
15
 
16
- useInput(useCallback((input, key) => {
17
- if (key.upArrow) {
18
- setSelectedIndex(prev =>
19
- prev === 0 ? items.length - 1 : prev - 1
20
- );
21
- }
16
+ useInput(
17
+ useCallback(
18
+ (input, key) => {
19
+ if (key.upArrow) {
20
+ setSelectedIndex((prev) => (prev === 0 ? items.length - 1 : prev - 1));
21
+ }
22
22
 
23
- if (key.downArrow) {
24
- setSelectedIndex(prev =>
25
- prev === items.length - 1 ? 0 : prev + 1
26
- );
27
- }
23
+ if (key.downArrow) {
24
+ setSelectedIndex((prev) => (prev === items.length - 1 ? 0 : prev + 1));
25
+ }
28
26
 
29
- if (key.return) {
30
- if (items[selectedIndex] && onSelect) {
31
- onSelect(items[selectedIndex]);
32
- }
33
- }
27
+ if (key.return) {
28
+ if (items[selectedIndex] && onSelect) {
29
+ onSelect(items[selectedIndex]);
30
+ }
31
+ }
34
32
 
35
- if (key.escape) {
36
- process.exit(0);
37
- }
38
- }, [selectedIndex, items, onSelect]), { isActive: isRawModeSupported });
33
+ if (key.escape) {
34
+ process.exit(0);
35
+ }
36
+ },
37
+ [selectedIndex, items, onSelect]
38
+ ),
39
+ { isActive: isRawModeSupported }
40
+ );
39
41
 
40
- // Show ALL items (no scrolling/limit) - per Ink docs
41
- return React.createElement(Box, { flexDirection: 'column' },
42
- ...items.map((item, index) => {
43
- const isSelected = index === selectedIndex;
42
+ // Show ALL items (no scrolling/limit) - per Ink docs
43
+ return React.createElement(
44
+ Box,
45
+ { flexDirection: 'column' },
46
+ ...items.map((item, index) => {
47
+ const isSelected = index === selectedIndex;
44
48
 
45
- return React.createElement(Text, {
46
- key: item.value || item.label || index, // Use unique value as key
47
- color: isSelected ? 'cyan' : undefined
48
- }, `${isSelected ? '❯ ' : ' '}${item.label}`);
49
- })
50
- );
49
+ return React.createElement(
50
+ Text,
51
+ {
52
+ key: item.value || item.label || index, // Use unique value as key
53
+ color: isSelected ? 'cyan' : undefined,
54
+ },
55
+ `${isSelected ? '❯ ' : ' '}${item.label}`
56
+ );
57
+ })
58
+ );
51
59
  }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Template Selector UI Component
3
+ * Interactive template selection for Ink UI
4
+ * FEAT-009: Context Templates
5
+ */
6
+
7
+ import React, { useState, useCallback, useMemo } from 'react';
8
+ import { Box, Text } from 'ink';
9
+ import SelectInput from './select-input.js';
10
+ import TemplateManager from '../utils/template-manager.js';
11
+
12
+ /**
13
+ * Template Selector Component
14
+ * Provides interactive template selection in wizard mode
15
+ */
16
+ export default function TemplateSelector({ onSelect, projectRoot = process.cwd() }) {
17
+ const [selectedTemplate, setSelectedTemplate] = useState(null);
18
+ const manager = useMemo(() => new TemplateManager(projectRoot), [projectRoot]);
19
+
20
+ // Build template items for selection
21
+ const templateItems = useMemo(() => {
22
+ const templates = manager.list();
23
+
24
+ return templates.map((t) => ({
25
+ label: `${t.name} - ${t.description}`,
26
+ value: t.id,
27
+ template: t,
28
+ }));
29
+ }, [manager]);
30
+
31
+ const handleSelect = useCallback(
32
+ (item) => {
33
+ const template = manager.get(item.value);
34
+ setSelectedTemplate(template);
35
+
36
+ if (onSelect) {
37
+ onSelect(template);
38
+ }
39
+ },
40
+ [manager, onSelect]
41
+ );
42
+
43
+ if (selectedTemplate) {
44
+ return React.createElement(
45
+ Box,
46
+ { flexDirection: 'column' },
47
+ React.createElement(
48
+ Box,
49
+ { marginBottom: 1 },
50
+ React.createElement(Text, { bold: true, color: 'green' }, '✓ Template Selected:'),
51
+ React.createElement(Text, { color: 'cyan' }, ` ${selectedTemplate.name}`)
52
+ ),
53
+ React.createElement(
54
+ Box,
55
+ { flexDirection: 'column', marginBottom: 1 },
56
+ React.createElement(
57
+ Text,
58
+ { dimColor: true },
59
+ ` Description: ${selectedTemplate.description}`
60
+ ),
61
+ React.createElement(Text, { dimColor: true }, ` Source: ${selectedTemplate.source}`)
62
+ )
63
+ );
64
+ }
65
+
66
+ return React.createElement(
67
+ Box,
68
+ { flexDirection: 'column' },
69
+ React.createElement(
70
+ Box,
71
+ { marginBottom: 1 },
72
+ React.createElement(Text, null, 'Select a context template:')
73
+ ),
74
+ React.createElement(SelectInput, {
75
+ items: templateItems,
76
+ onSelect: handleSelect,
77
+ }),
78
+ React.createElement(
79
+ Box,
80
+ { marginTop: 1 },
81
+ React.createElement(Text, { dimColor: true }, '[↑↓] Navigate [Enter] Select [Esc] Cancel')
82
+ )
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Get template choices for wizard integration
88
+ * @param {string} projectRoot - Project root directory
89
+ * @returns {Array} Array of template choice objects
90
+ */
91
+ export function getTemplateChoices(projectRoot = process.cwd()) {
92
+ const manager = new TemplateManager(projectRoot);
93
+ const templates = manager.list();
94
+
95
+ return templates.map((t) => ({
96
+ label: `${t.name} - ${t.description}`,
97
+ value: t.id,
98
+ }));
99
+ }