@camaradesuk/git-worktree-tools 1.5.0 → 1.7.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 (274) hide show
  1. package/README.md +330 -370
  2. package/dist/cli/cleanpr.js +35 -4
  3. package/dist/cli/cleanpr.js.map +1 -1
  4. package/dist/cli/cleanpr.test.js +256 -0
  5. package/dist/cli/cleanpr.test.js.map +1 -1
  6. package/dist/cli/lswt.js +54 -6
  7. package/dist/cli/lswt.js.map +1 -1
  8. package/dist/cli/lswt.test.js +207 -0
  9. package/dist/cli/lswt.test.js.map +1 -1
  10. package/dist/cli/newpr.js +135 -76
  11. package/dist/cli/newpr.js.map +1 -1
  12. package/dist/cli/newpr.test.js +35 -16
  13. package/dist/cli/newpr.test.js.map +1 -1
  14. package/dist/cli/wt/clean.d.ts +17 -0
  15. package/dist/cli/wt/clean.d.ts.map +1 -0
  16. package/dist/cli/wt/clean.js +74 -0
  17. package/dist/cli/wt/clean.js.map +1 -0
  18. package/dist/cli/wt/completion.d.ts +12 -0
  19. package/dist/cli/wt/completion.d.ts.map +1 -0
  20. package/dist/cli/wt/completion.js +246 -0
  21. package/dist/cli/wt/completion.js.map +1 -0
  22. package/dist/cli/wt/completion.test.d.ts +5 -0
  23. package/dist/cli/wt/completion.test.d.ts.map +1 -0
  24. package/dist/cli/wt/completion.test.js +173 -0
  25. package/dist/cli/wt/completion.test.js.map +1 -0
  26. package/dist/cli/wt/config.d.ts +13 -0
  27. package/dist/cli/wt/config.d.ts.map +1 -0
  28. package/dist/cli/wt/config.js +175 -0
  29. package/dist/cli/wt/config.js.map +1 -0
  30. package/dist/cli/wt/config.test.d.ts +5 -0
  31. package/dist/cli/wt/config.test.d.ts.map +1 -0
  32. package/dist/cli/wt/config.test.js +260 -0
  33. package/dist/cli/wt/config.test.js.map +1 -0
  34. package/dist/cli/wt/entry.test.d.ts +8 -0
  35. package/dist/cli/wt/entry.test.d.ts.map +1 -0
  36. package/dist/cli/wt/entry.test.js +201 -0
  37. package/dist/cli/wt/entry.test.js.map +1 -0
  38. package/dist/cli/wt/init.d.ts +14 -0
  39. package/dist/cli/wt/init.d.ts.map +1 -0
  40. package/dist/cli/wt/init.js +209 -0
  41. package/dist/cli/wt/init.js.map +1 -0
  42. package/dist/cli/wt/init.test.d.ts +5 -0
  43. package/dist/cli/wt/init.test.d.ts.map +1 -0
  44. package/dist/cli/wt/init.test.js +165 -0
  45. package/dist/cli/wt/init.test.js.map +1 -0
  46. package/dist/cli/wt/init.unit.test.d.ts +5 -0
  47. package/dist/cli/wt/init.unit.test.d.ts.map +1 -0
  48. package/dist/cli/wt/init.unit.test.js +432 -0
  49. package/dist/cli/wt/init.unit.test.js.map +1 -0
  50. package/dist/cli/wt/interactive-menu.d.ts +41 -0
  51. package/dist/cli/wt/interactive-menu.d.ts.map +1 -0
  52. package/dist/cli/wt/interactive-menu.js +639 -0
  53. package/dist/cli/wt/interactive-menu.js.map +1 -0
  54. package/dist/cli/wt/interactive-menu.test.d.ts +10 -0
  55. package/dist/cli/wt/interactive-menu.test.d.ts.map +1 -0
  56. package/dist/cli/wt/interactive-menu.test.js +711 -0
  57. package/dist/cli/wt/interactive-menu.test.js.map +1 -0
  58. package/dist/cli/wt/link.d.ts +22 -0
  59. package/dist/cli/wt/link.d.ts.map +1 -0
  60. package/dist/cli/wt/link.js +115 -0
  61. package/dist/cli/wt/link.js.map +1 -0
  62. package/dist/cli/wt/list.d.ts +16 -0
  63. package/dist/cli/wt/list.d.ts.map +1 -0
  64. package/dist/cli/wt/list.js +65 -0
  65. package/dist/cli/wt/list.js.map +1 -0
  66. package/dist/cli/wt/new.d.ts +24 -0
  67. package/dist/cli/wt/new.d.ts.map +1 -0
  68. package/dist/cli/wt/new.js +130 -0
  69. package/dist/cli/wt/new.js.map +1 -0
  70. package/dist/cli/wt/run-command.d.ts +31 -0
  71. package/dist/cli/wt/run-command.d.ts.map +1 -0
  72. package/dist/cli/wt/run-command.js +49 -0
  73. package/dist/cli/wt/run-command.js.map +1 -0
  74. package/dist/cli/wt/run-command.test.d.ts +5 -0
  75. package/dist/cli/wt/run-command.test.d.ts.map +1 -0
  76. package/dist/cli/wt/run-command.test.js +88 -0
  77. package/dist/cli/wt/run-command.test.js.map +1 -0
  78. package/dist/cli/wt/state.d.ts +13 -0
  79. package/dist/cli/wt/state.d.ts.map +1 -0
  80. package/dist/cli/wt/state.js +38 -0
  81. package/dist/cli/wt/state.js.map +1 -0
  82. package/dist/cli/wt/wt.test.d.ts +8 -0
  83. package/dist/cli/wt/wt.test.d.ts.map +1 -0
  84. package/dist/cli/wt/wt.test.js +521 -0
  85. package/dist/cli/wt/wt.test.js.map +1 -0
  86. package/dist/cli/wt.d.ts +26 -0
  87. package/dist/cli/wt.d.ts.map +1 -0
  88. package/dist/cli/wt.js +169 -0
  89. package/dist/cli/wt.js.map +1 -0
  90. package/dist/cli/wt.unit.test.d.ts +7 -0
  91. package/dist/cli/wt.unit.test.d.ts.map +1 -0
  92. package/dist/cli/wt.unit.test.js +182 -0
  93. package/dist/cli/wt.unit.test.js.map +1 -0
  94. package/dist/cli/wtconfig.js +22 -8
  95. package/dist/cli/wtconfig.js.map +1 -1
  96. package/dist/cli/wtconfig.test.js +18 -16
  97. package/dist/cli/wtconfig.test.js.map +1 -1
  98. package/dist/cli/wtlink.js +66 -9
  99. package/dist/cli/wtlink.js.map +1 -1
  100. package/dist/cli/wtlink.test.js +101 -0
  101. package/dist/cli/wtlink.test.js.map +1 -1
  102. package/dist/e2e/cli.e2e.test.js +97 -1
  103. package/dist/e2e/cli.e2e.test.js.map +1 -1
  104. package/dist/e2e/lswt/lswt.e2e.test.js +33 -0
  105. package/dist/e2e/lswt/lswt.e2e.test.js.map +1 -1
  106. package/dist/e2e/newpr/scenarios.e2e.test.js +7 -7
  107. package/dist/e2e/newpr/scenarios.e2e.test.js.map +1 -1
  108. package/dist/e2e/wt/wt.e2e.test.d.ts +9 -0
  109. package/dist/e2e/wt/wt.e2e.test.d.ts.map +1 -0
  110. package/dist/e2e/wt/wt.e2e.test.js +384 -0
  111. package/dist/e2e/wt/wt.e2e.test.js.map +1 -0
  112. package/dist/e2e/wtlink/wtlink.e2e.test.js +52 -0
  113. package/dist/e2e/wtlink/wtlink.e2e.test.js.map +1 -1
  114. package/dist/lib/ai/base-provider.d.ts +22 -0
  115. package/dist/lib/ai/base-provider.d.ts.map +1 -1
  116. package/dist/lib/ai/base-provider.js +180 -99
  117. package/dist/lib/ai/base-provider.js.map +1 -1
  118. package/dist/lib/ai/base-provider.test.js +13 -14
  119. package/dist/lib/ai/base-provider.test.js.map +1 -1
  120. package/dist/lib/ai/cli-provider.d.ts +11 -7
  121. package/dist/lib/ai/cli-provider.d.ts.map +1 -1
  122. package/dist/lib/ai/cli-provider.js +19 -49
  123. package/dist/lib/ai/cli-provider.js.map +1 -1
  124. package/dist/lib/ai/cli-provider.test.js +47 -49
  125. package/dist/lib/ai/cli-provider.test.js.map +1 -1
  126. package/dist/lib/ai/index.d.ts +2 -1
  127. package/dist/lib/ai/index.d.ts.map +1 -1
  128. package/dist/lib/ai/index.js +2 -0
  129. package/dist/lib/ai/index.js.map +1 -1
  130. package/dist/lib/ai/provider-manager.js +2 -2
  131. package/dist/lib/ai/provider-manager.js.map +1 -1
  132. package/dist/lib/ai/repo-docs.d.ts +43 -0
  133. package/dist/lib/ai/repo-docs.d.ts.map +1 -0
  134. package/dist/lib/ai/repo-docs.js +274 -0
  135. package/dist/lib/ai/repo-docs.js.map +1 -0
  136. package/dist/lib/ai/repo-docs.test.d.ts +5 -0
  137. package/dist/lib/ai/repo-docs.test.d.ts.map +1 -0
  138. package/dist/lib/ai/repo-docs.test.js +357 -0
  139. package/dist/lib/ai/repo-docs.test.js.map +1 -0
  140. package/dist/lib/ai/types.d.ts +18 -2
  141. package/dist/lib/ai/types.d.ts.map +1 -1
  142. package/dist/lib/ai/types.js.map +1 -1
  143. package/dist/lib/config-editor.d.ts +21 -0
  144. package/dist/lib/config-editor.d.ts.map +1 -0
  145. package/dist/lib/config-editor.js +729 -0
  146. package/dist/lib/config-editor.js.map +1 -0
  147. package/dist/lib/config-editor.test.d.ts +11 -0
  148. package/dist/lib/config-editor.test.d.ts.map +1 -0
  149. package/dist/lib/config-editor.test.js +526 -0
  150. package/dist/lib/config-editor.test.js.map +1 -0
  151. package/dist/lib/config-validation.d.ts +28 -0
  152. package/dist/lib/config-validation.d.ts.map +1 -0
  153. package/dist/lib/config-validation.js +534 -0
  154. package/dist/lib/config-validation.js.map +1 -0
  155. package/dist/lib/config-validation.test.d.ts +5 -0
  156. package/dist/lib/config-validation.test.d.ts.map +1 -0
  157. package/dist/lib/config-validation.test.js +398 -0
  158. package/dist/lib/config-validation.test.js.map +1 -0
  159. package/dist/lib/config.d.ts +115 -6
  160. package/dist/lib/config.d.ts.map +1 -1
  161. package/dist/lib/config.js +251 -55
  162. package/dist/lib/config.js.map +1 -1
  163. package/dist/lib/config.test.js +2 -1
  164. package/dist/lib/config.test.js.map +1 -1
  165. package/dist/lib/constants.d.ts +50 -1
  166. package/dist/lib/constants.d.ts.map +1 -1
  167. package/dist/lib/constants.js +67 -1
  168. package/dist/lib/constants.js.map +1 -1
  169. package/dist/lib/constants.test.d.ts +5 -0
  170. package/dist/lib/constants.test.d.ts.map +1 -0
  171. package/dist/lib/constants.test.js +121 -0
  172. package/dist/lib/constants.test.js.map +1 -0
  173. package/dist/lib/git.d.ts +12 -0
  174. package/dist/lib/git.d.ts.map +1 -1
  175. package/dist/lib/git.js +26 -0
  176. package/dist/lib/git.js.map +1 -1
  177. package/dist/lib/global-check.d.ts +38 -0
  178. package/dist/lib/global-check.d.ts.map +1 -0
  179. package/dist/lib/global-check.js +135 -0
  180. package/dist/lib/global-check.js.map +1 -0
  181. package/dist/lib/global-check.test.d.ts +5 -0
  182. package/dist/lib/global-check.test.d.ts.map +1 -0
  183. package/dist/lib/global-check.test.js +153 -0
  184. package/dist/lib/global-check.test.js.map +1 -0
  185. package/dist/lib/global-config.d.ts +102 -0
  186. package/dist/lib/global-config.d.ts.map +1 -0
  187. package/dist/lib/global-config.js +234 -0
  188. package/dist/lib/global-config.js.map +1 -0
  189. package/dist/lib/global-config.test.d.ts +5 -0
  190. package/dist/lib/global-config.test.d.ts.map +1 -0
  191. package/dist/lib/global-config.test.js +282 -0
  192. package/dist/lib/global-config.test.js.map +1 -0
  193. package/dist/lib/json-output.d.ts +11 -1
  194. package/dist/lib/json-output.d.ts.map +1 -1
  195. package/dist/lib/json-output.js +42 -1
  196. package/dist/lib/json-output.js.map +1 -1
  197. package/dist/lib/json-output.test.js +2 -0
  198. package/dist/lib/json-output.test.js.map +1 -1
  199. package/dist/lib/logger.d.ts +175 -0
  200. package/dist/lib/logger.d.ts.map +1 -0
  201. package/dist/lib/logger.js +475 -0
  202. package/dist/lib/logger.js.map +1 -0
  203. package/dist/lib/logger.test.d.ts +5 -0
  204. package/dist/lib/logger.test.d.ts.map +1 -0
  205. package/dist/lib/logger.test.js +292 -0
  206. package/dist/lib/logger.test.js.map +1 -0
  207. package/dist/lib/lswt/action-executors.test.js +2 -0
  208. package/dist/lib/lswt/action-executors.test.js.map +1 -1
  209. package/dist/lib/lswt/formatters.d.ts +1 -0
  210. package/dist/lib/lswt/formatters.d.ts.map +1 -1
  211. package/dist/lib/lswt/formatters.js +6 -1
  212. package/dist/lib/lswt/formatters.js.map +1 -1
  213. package/dist/lib/lswt/formatters.test.js +2 -2
  214. package/dist/lib/lswt/formatters.test.js.map +1 -1
  215. package/dist/lib/lswt/fuzzy-search.d.ts +27 -0
  216. package/dist/lib/lswt/fuzzy-search.d.ts.map +1 -0
  217. package/dist/lib/lswt/fuzzy-search.js +130 -0
  218. package/dist/lib/lswt/fuzzy-search.js.map +1 -0
  219. package/dist/lib/lswt/fuzzy-search.test.d.ts +5 -0
  220. package/dist/lib/lswt/fuzzy-search.test.d.ts.map +1 -0
  221. package/dist/lib/lswt/fuzzy-search.test.js +207 -0
  222. package/dist/lib/lswt/fuzzy-search.test.js.map +1 -0
  223. package/dist/lib/lswt/index.d.ts +1 -0
  224. package/dist/lib/lswt/index.d.ts.map +1 -1
  225. package/dist/lib/lswt/index.js +2 -0
  226. package/dist/lib/lswt/index.js.map +1 -1
  227. package/dist/lib/lswt/interactive.d.ts +8 -0
  228. package/dist/lib/lswt/interactive.d.ts.map +1 -1
  229. package/dist/lib/lswt/interactive.js +169 -20
  230. package/dist/lib/lswt/interactive.js.map +1 -1
  231. package/dist/lib/newpr/action-deps.test.d.ts +5 -0
  232. package/dist/lib/newpr/action-deps.test.d.ts.map +1 -0
  233. package/dist/lib/newpr/action-deps.test.js +111 -0
  234. package/dist/lib/newpr/action-deps.test.js.map +1 -0
  235. package/dist/lib/newpr/args.d.ts.map +1 -1
  236. package/dist/lib/newpr/args.js +15 -4
  237. package/dist/lib/newpr/args.js.map +1 -1
  238. package/dist/lib/newpr/args.test.js +210 -2
  239. package/dist/lib/newpr/args.test.js.map +1 -1
  240. package/dist/lib/newpr/types.d.ts +2 -0
  241. package/dist/lib/newpr/types.d.ts.map +1 -1
  242. package/dist/lib/prompts.d.ts +10 -0
  243. package/dist/lib/prompts.d.ts.map +1 -1
  244. package/dist/lib/prompts.js +200 -1
  245. package/dist/lib/prompts.js.map +1 -1
  246. package/dist/lib/prompts.test.js +351 -1
  247. package/dist/lib/prompts.test.js.map +1 -1
  248. package/dist/lib/schema.test.d.ts +10 -0
  249. package/dist/lib/schema.test.d.ts.map +1 -0
  250. package/dist/lib/schema.test.js +309 -0
  251. package/dist/lib/schema.test.js.map +1 -0
  252. package/dist/lib/wtconfig/environment.d.ts.map +1 -1
  253. package/dist/lib/wtconfig/environment.js +6 -4
  254. package/dist/lib/wtconfig/environment.js.map +1 -1
  255. package/dist/lib/wtconfig/environment.test.js +2 -7
  256. package/dist/lib/wtconfig/environment.test.js.map +1 -1
  257. package/dist/lib/wtconfig/types.d.ts +3 -1
  258. package/dist/lib/wtconfig/types.d.ts.map +1 -1
  259. package/dist/lib/wtlink/link-configs.test.js +282 -2
  260. package/dist/lib/wtlink/link-configs.test.js.map +1 -1
  261. package/dist/lib/wtlink/main-menu.js +1 -0
  262. package/dist/lib/wtlink/main-menu.js.map +1 -1
  263. package/dist/lib/wtlink/main-menu.test.d.ts +5 -0
  264. package/dist/lib/wtlink/main-menu.test.d.ts.map +1 -0
  265. package/dist/lib/wtlink/main-menu.test.js +124 -0
  266. package/dist/lib/wtlink/main-menu.test.js.map +1 -0
  267. package/dist/lib/wtlink/manage-manifest.d.ts +5 -0
  268. package/dist/lib/wtlink/manage-manifest.d.ts.map +1 -1
  269. package/dist/lib/wtlink/manage-manifest.js +65 -2
  270. package/dist/lib/wtlink/manage-manifest.js.map +1 -1
  271. package/dist/lib/wtlink/manage-manifest.test.js +282 -2
  272. package/dist/lib/wtlink/manage-manifest.test.js.map +1 -1
  273. package/package.json +3 -1
  274. package/schemas/worktreerc.schema.json +416 -0
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Repository Documentation Discovery
3
+ *
4
+ * Finds and reads README and other documentation files to provide
5
+ * context for AI generation.
6
+ */
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ /**
10
+ * Documentation file patterns to look for, in priority order
11
+ */
12
+ const DOC_FILE_PATTERNS = [
13
+ 'README.md',
14
+ 'README',
15
+ 'readme.md',
16
+ 'README.rst',
17
+ 'README.txt',
18
+ 'CONTRIBUTING.md',
19
+ 'ARCHITECTURE.md',
20
+ 'docs/README.md',
21
+ 'doc/README.md',
22
+ ];
23
+ /**
24
+ * Maximum length for README content in prompts
25
+ */
26
+ const MAX_README_LENGTH = 2000;
27
+ /**
28
+ * Maximum length for project description
29
+ */
30
+ const MAX_DESCRIPTION_LENGTH = 500;
31
+ /**
32
+ * Find and read the README file from a repository
33
+ */
34
+ function findReadme(repoRoot) {
35
+ for (const pattern of DOC_FILE_PATTERNS) {
36
+ const filePath = path.join(repoRoot, pattern);
37
+ if (fs.existsSync(filePath)) {
38
+ try {
39
+ const content = fs.readFileSync(filePath, 'utf-8');
40
+ return { content, source: pattern };
41
+ }
42
+ catch {
43
+ // Continue to next pattern
44
+ }
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+ /**
50
+ * Extract project info from package.json
51
+ */
52
+ function extractPackageJsonInfo(repoRoot) {
53
+ const packagePath = path.join(repoRoot, 'package.json');
54
+ if (!fs.existsSync(packagePath)) {
55
+ return {};
56
+ }
57
+ try {
58
+ const content = fs.readFileSync(packagePath, 'utf-8');
59
+ const pkg = JSON.parse(content);
60
+ const techStack = [];
61
+ // Add main technology
62
+ if (pkg.devDependencies?.typescript || pkg.dependencies?.typescript) {
63
+ techStack.push('TypeScript');
64
+ }
65
+ else {
66
+ techStack.push('JavaScript');
67
+ }
68
+ // Check for framework
69
+ if (pkg.dependencies?.react)
70
+ techStack.push('React');
71
+ if (pkg.dependencies?.vue)
72
+ techStack.push('Vue');
73
+ if (pkg.dependencies?.angular || pkg.dependencies?.['@angular/core'])
74
+ techStack.push('Angular');
75
+ if (pkg.dependencies?.express)
76
+ techStack.push('Express');
77
+ if (pkg.dependencies?.next)
78
+ techStack.push('Next.js');
79
+ if (pkg.dependencies?.nest || pkg.dependencies?.['@nestjs/core'])
80
+ techStack.push('NestJS');
81
+ // Check for testing framework
82
+ if (pkg.devDependencies?.vitest)
83
+ techStack.push('Vitest');
84
+ if (pkg.devDependencies?.jest)
85
+ techStack.push('Jest');
86
+ if (pkg.devDependencies?.mocha)
87
+ techStack.push('Mocha');
88
+ return {
89
+ description: pkg.description,
90
+ techStack,
91
+ };
92
+ }
93
+ catch {
94
+ return {};
95
+ }
96
+ }
97
+ /**
98
+ * Extract project info from pyproject.toml
99
+ */
100
+ function extractPyProjectInfo(repoRoot) {
101
+ const pyprojectPath = path.join(repoRoot, 'pyproject.toml');
102
+ if (!fs.existsSync(pyprojectPath)) {
103
+ return {};
104
+ }
105
+ try {
106
+ const content = fs.readFileSync(pyprojectPath, 'utf-8');
107
+ const techStack = ['Python'];
108
+ // Simple TOML parsing for description
109
+ const descMatch = content.match(/description\s*=\s*["']([^"']+)["']/);
110
+ const description = descMatch ? descMatch[1] : undefined;
111
+ // Check for common frameworks in dependencies
112
+ if (content.includes('django'))
113
+ techStack.push('Django');
114
+ if (content.includes('flask'))
115
+ techStack.push('Flask');
116
+ if (content.includes('fastapi'))
117
+ techStack.push('FastAPI');
118
+ if (content.includes('pytest'))
119
+ techStack.push('pytest');
120
+ return { description, techStack };
121
+ }
122
+ catch {
123
+ return {};
124
+ }
125
+ }
126
+ /**
127
+ * Extract project info from Cargo.toml (Rust)
128
+ */
129
+ function extractCargoInfo(repoRoot) {
130
+ const cargoPath = path.join(repoRoot, 'Cargo.toml');
131
+ if (!fs.existsSync(cargoPath)) {
132
+ return {};
133
+ }
134
+ try {
135
+ const content = fs.readFileSync(cargoPath, 'utf-8');
136
+ const techStack = ['Rust'];
137
+ // Simple TOML parsing for description
138
+ const descMatch = content.match(/description\s*=\s*["']([^"']+)["']/);
139
+ const description = descMatch ? descMatch[1] : undefined;
140
+ // Check for common frameworks
141
+ if (content.includes('tokio'))
142
+ techStack.push('Tokio');
143
+ if (content.includes('actix'))
144
+ techStack.push('Actix');
145
+ if (content.includes('rocket'))
146
+ techStack.push('Rocket');
147
+ return { description, techStack };
148
+ }
149
+ catch {
150
+ return {};
151
+ }
152
+ }
153
+ /**
154
+ * Extract project info from go.mod (Go)
155
+ */
156
+ function extractGoModInfo(repoRoot) {
157
+ const goModPath = path.join(repoRoot, 'go.mod');
158
+ if (!fs.existsSync(goModPath)) {
159
+ return {};
160
+ }
161
+ try {
162
+ const content = fs.readFileSync(goModPath, 'utf-8');
163
+ const techStack = ['Go'];
164
+ // Check for common frameworks
165
+ if (content.includes('gin-gonic'))
166
+ techStack.push('Gin');
167
+ if (content.includes('gorilla/mux'))
168
+ techStack.push('Gorilla Mux');
169
+ if (content.includes('echo'))
170
+ techStack.push('Echo');
171
+ return { techStack };
172
+ }
173
+ catch {
174
+ return {};
175
+ }
176
+ }
177
+ /**
178
+ * Truncate content intelligently, preferring complete sections
179
+ */
180
+ function truncateReadme(content, maxLength) {
181
+ if (content.length <= maxLength) {
182
+ return content;
183
+ }
184
+ // Try to truncate at a section boundary (## heading)
185
+ const truncated = content.slice(0, maxLength);
186
+ const lastHeading = truncated.lastIndexOf('\n## ');
187
+ if (lastHeading > maxLength * 0.5) {
188
+ // Only truncate at heading if we keep at least half the content
189
+ return truncated.slice(0, lastHeading).trim() + '\n\n[...truncated]';
190
+ }
191
+ // Try to truncate at paragraph boundary
192
+ const lastParagraph = truncated.lastIndexOf('\n\n');
193
+ if (lastParagraph > maxLength * 0.7) {
194
+ return truncated.slice(0, lastParagraph).trim() + '\n\n[...truncated]';
195
+ }
196
+ // Fall back to hard truncate at word boundary
197
+ const lastSpace = truncated.lastIndexOf(' ');
198
+ if (lastSpace > maxLength * 0.9) {
199
+ return truncated.slice(0, lastSpace).trim() + '...[truncated]';
200
+ }
201
+ return truncated + '...[truncated]';
202
+ }
203
+ /**
204
+ * Gather repository documentation for AI context
205
+ *
206
+ * @param repoRoot - Root directory of the repository
207
+ * @param options - Options for documentation gathering
208
+ * @returns Documentation context for AI prompts
209
+ */
210
+ export function gatherRepoDocumentation(repoRoot, options = {}) {
211
+ const { maxReadmeLength = MAX_README_LENGTH, includeReadme = true, includeTechStack = true, } = options;
212
+ const result = {};
213
+ // Find and read README
214
+ if (includeReadme) {
215
+ const readme = findReadme(repoRoot);
216
+ if (readme) {
217
+ result.readme = truncateReadme(readme.content, maxReadmeLength);
218
+ result.readmeSource = readme.source;
219
+ }
220
+ }
221
+ // Extract project info from package files
222
+ if (includeTechStack) {
223
+ // Try each package format in order of prevalence
224
+ const packageInfo = extractPackageJsonInfo(repoRoot);
225
+ const pyInfo = extractPyProjectInfo(repoRoot);
226
+ const cargoInfo = extractCargoInfo(repoRoot);
227
+ const goInfo = extractGoModInfo(repoRoot);
228
+ // Combine tech stacks (first found wins for description)
229
+ const allTechStack = new Set();
230
+ [packageInfo, pyInfo, cargoInfo, goInfo].forEach((info) => {
231
+ if (info.techStack) {
232
+ info.techStack.forEach((t) => allTechStack.add(t));
233
+ }
234
+ });
235
+ result.techStack = Array.from(allTechStack);
236
+ // Use first description found
237
+ result.projectDescription =
238
+ packageInfo.description || pyInfo.description || cargoInfo.description;
239
+ // Truncate description if too long
240
+ if (result.projectDescription && result.projectDescription.length > MAX_DESCRIPTION_LENGTH) {
241
+ result.projectDescription =
242
+ result.projectDescription.slice(0, MAX_DESCRIPTION_LENGTH) + '...';
243
+ }
244
+ }
245
+ return result;
246
+ }
247
+ /**
248
+ * Format documentation for inclusion in prompts
249
+ *
250
+ * @param docs - Repository documentation
251
+ * @returns Formatted string for prompt inclusion
252
+ */
253
+ export function formatDocsForPrompt(docs) {
254
+ const parts = [];
255
+ if (docs.projectDescription) {
256
+ parts.push(`Project: ${docs.projectDescription}`);
257
+ }
258
+ if (docs.techStack && docs.techStack.length > 0) {
259
+ parts.push(`Tech: ${docs.techStack.join(', ')}`);
260
+ }
261
+ if (docs.readme) {
262
+ // Include a condensed version of the README
263
+ parts.push(`README:\n${docs.readme}`);
264
+ }
265
+ return parts.join('\n');
266
+ }
267
+ /**
268
+ * Check if repository has meaningful documentation
269
+ */
270
+ export function hasDocumentation(repoRoot) {
271
+ const readme = findReadme(repoRoot);
272
+ return readme !== null && readme.content.length > 50;
273
+ }
274
+ //# sourceMappingURL=repo-docs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-docs.js","sourceRoot":"","sources":["../../../src/lib/ai/repo-docs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for repository documentation discovery
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=repo-docs.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-docs.test.d.ts","sourceRoot":"","sources":["../../../src/lib/ai/repo-docs.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Tests for repository documentation discovery
3
+ */
4
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import os from 'os';
8
+ import { gatherRepoDocumentation, formatDocsForPrompt, hasDocumentation } from './repo-docs.js';
9
+ describe('repo-docs', () => {
10
+ let tempDir;
11
+ beforeEach(() => {
12
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'repo-docs-test-'));
13
+ });
14
+ afterEach(() => {
15
+ if (tempDir) {
16
+ fs.rmSync(tempDir, { recursive: true, force: true });
17
+ }
18
+ });
19
+ describe('gatherRepoDocumentation', () => {
20
+ describe('README discovery', () => {
21
+ it('finds README.md', () => {
22
+ fs.writeFileSync(path.join(tempDir, 'README.md'), '# My Project\n\nThis is a test project.');
23
+ const docs = gatherRepoDocumentation(tempDir);
24
+ expect(docs.readme).toContain('# My Project');
25
+ expect(docs.readme).toContain('This is a test project');
26
+ expect(docs.readmeSource).toBe('README.md');
27
+ });
28
+ it('finds lowercase readme.md', () => {
29
+ fs.writeFileSync(path.join(tempDir, 'readme.md'), '# Lower Case');
30
+ const docs = gatherRepoDocumentation(tempDir);
31
+ expect(docs.readme).toContain('# Lower Case');
32
+ // On case-insensitive filesystems (macOS, Windows), the pattern 'README.md'
33
+ // matches 'readme.md', so the source may be reported as either case
34
+ expect(docs.readmeSource?.toLowerCase()).toBe('readme.md');
35
+ });
36
+ it('finds README without extension', () => {
37
+ fs.writeFileSync(path.join(tempDir, 'README'), 'Plain text readme');
38
+ const docs = gatherRepoDocumentation(tempDir);
39
+ expect(docs.readme).toContain('Plain text readme');
40
+ expect(docs.readmeSource).toBe('README');
41
+ });
42
+ it('prefers README.md over README', () => {
43
+ fs.writeFileSync(path.join(tempDir, 'README.md'), '# Markdown');
44
+ fs.writeFileSync(path.join(tempDir, 'README'), 'Plain text');
45
+ const docs = gatherRepoDocumentation(tempDir);
46
+ expect(docs.readme).toContain('# Markdown');
47
+ expect(docs.readmeSource).toBe('README.md');
48
+ });
49
+ it('finds README in docs/ directory', () => {
50
+ fs.mkdirSync(path.join(tempDir, 'docs'));
51
+ fs.writeFileSync(path.join(tempDir, 'docs', 'README.md'), '# Docs Readme');
52
+ const docs = gatherRepoDocumentation(tempDir);
53
+ expect(docs.readme).toContain('# Docs Readme');
54
+ expect(docs.readmeSource).toBe('docs/README.md');
55
+ });
56
+ it('returns undefined readme when none exists', () => {
57
+ const docs = gatherRepoDocumentation(tempDir);
58
+ expect(docs.readme).toBeUndefined();
59
+ expect(docs.readmeSource).toBeUndefined();
60
+ });
61
+ it('respects includeReadme option', () => {
62
+ fs.writeFileSync(path.join(tempDir, 'README.md'), '# My Project');
63
+ const docs = gatherRepoDocumentation(tempDir, { includeReadme: false });
64
+ expect(docs.readme).toBeUndefined();
65
+ });
66
+ });
67
+ describe('README truncation', () => {
68
+ it('truncates long README at section boundary', () => {
69
+ const longReadme = `# Project
70
+
71
+ ## Section 1
72
+ This is the first section with some content.
73
+
74
+ ## Section 2
75
+ This is the second section with more content.
76
+
77
+ ## Section 3
78
+ This is the third section.`;
79
+ fs.writeFileSync(path.join(tempDir, 'README.md'), longReadme);
80
+ const docs = gatherRepoDocumentation(tempDir, { maxReadmeLength: 100 });
81
+ expect(docs.readme).toBeDefined();
82
+ expect(docs.readme.length).toBeLessThanOrEqual(150); // Allow some buffer
83
+ expect(docs.readme).toContain('[...truncated]');
84
+ });
85
+ it('truncates at paragraph boundary when no section boundary', () => {
86
+ const longReadme = `# Project
87
+
88
+ This is a very long paragraph that goes on and on without any section breaks.
89
+
90
+ This is another paragraph that adds more content to the readme file.
91
+
92
+ And yet another paragraph with even more content.`;
93
+ fs.writeFileSync(path.join(tempDir, 'README.md'), longReadme);
94
+ const docs = gatherRepoDocumentation(tempDir, { maxReadmeLength: 100 });
95
+ expect(docs.readme).toBeDefined();
96
+ expect(docs.readme).toContain('[...truncated]');
97
+ });
98
+ it('does not truncate short README', () => {
99
+ const shortReadme = '# Short\n\nBrief description.';
100
+ fs.writeFileSync(path.join(tempDir, 'README.md'), shortReadme);
101
+ const docs = gatherRepoDocumentation(tempDir, { maxReadmeLength: 2000 });
102
+ expect(docs.readme).toBe(shortReadme);
103
+ expect(docs.readme).not.toContain('truncated');
104
+ });
105
+ });
106
+ describe('package.json extraction', () => {
107
+ it('extracts description from package.json', () => {
108
+ const pkg = {
109
+ name: 'test-project',
110
+ description: 'A test project for unit testing',
111
+ };
112
+ fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(pkg));
113
+ const docs = gatherRepoDocumentation(tempDir);
114
+ expect(docs.projectDescription).toBe('A test project for unit testing');
115
+ });
116
+ it('detects TypeScript from devDependencies', () => {
117
+ const pkg = {
118
+ name: 'ts-project',
119
+ devDependencies: { typescript: '^5.0.0' },
120
+ };
121
+ fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(pkg));
122
+ const docs = gatherRepoDocumentation(tempDir);
123
+ expect(docs.techStack).toContain('TypeScript');
124
+ });
125
+ it('detects JavaScript when no TypeScript', () => {
126
+ const pkg = {
127
+ name: 'js-project',
128
+ dependencies: { lodash: '^4.0.0' },
129
+ };
130
+ fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(pkg));
131
+ const docs = gatherRepoDocumentation(tempDir);
132
+ expect(docs.techStack).toContain('JavaScript');
133
+ });
134
+ it('detects React framework', () => {
135
+ const pkg = {
136
+ name: 'react-app',
137
+ dependencies: { react: '^18.0.0' },
138
+ };
139
+ fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(pkg));
140
+ const docs = gatherRepoDocumentation(tempDir);
141
+ expect(docs.techStack).toContain('React');
142
+ });
143
+ it('detects Vue framework', () => {
144
+ const pkg = {
145
+ name: 'vue-app',
146
+ dependencies: { vue: '^3.0.0' },
147
+ };
148
+ fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(pkg));
149
+ const docs = gatherRepoDocumentation(tempDir);
150
+ expect(docs.techStack).toContain('Vue');
151
+ });
152
+ it('detects Next.js framework', () => {
153
+ const pkg = {
154
+ name: 'nextjs-app',
155
+ dependencies: { next: '^13.0.0', react: '^18.0.0' },
156
+ };
157
+ fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(pkg));
158
+ const docs = gatherRepoDocumentation(tempDir);
159
+ expect(docs.techStack).toContain('Next.js');
160
+ expect(docs.techStack).toContain('React');
161
+ });
162
+ it('detects Vitest testing framework', () => {
163
+ const pkg = {
164
+ name: 'test-project',
165
+ devDependencies: { vitest: '^1.0.0' },
166
+ };
167
+ fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(pkg));
168
+ const docs = gatherRepoDocumentation(tempDir);
169
+ expect(docs.techStack).toContain('Vitest');
170
+ });
171
+ it('detects Jest testing framework', () => {
172
+ const pkg = {
173
+ name: 'test-project',
174
+ devDependencies: { jest: '^29.0.0' },
175
+ };
176
+ fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(pkg));
177
+ const docs = gatherRepoDocumentation(tempDir);
178
+ expect(docs.techStack).toContain('Jest');
179
+ });
180
+ it('handles malformed package.json gracefully', () => {
181
+ fs.writeFileSync(path.join(tempDir, 'package.json'), 'not valid json');
182
+ const docs = gatherRepoDocumentation(tempDir);
183
+ // Should not throw, just return empty tech stack
184
+ expect(docs.techStack).toEqual([]);
185
+ });
186
+ });
187
+ describe('pyproject.toml extraction', () => {
188
+ it('extracts description from pyproject.toml', () => {
189
+ const content = `[project]
190
+ name = "my-python-project"
191
+ description = "A Python project for testing"
192
+ `;
193
+ fs.writeFileSync(path.join(tempDir, 'pyproject.toml'), content);
194
+ const docs = gatherRepoDocumentation(tempDir);
195
+ expect(docs.projectDescription).toBe('A Python project for testing');
196
+ expect(docs.techStack).toContain('Python');
197
+ });
198
+ it('detects Django framework', () => {
199
+ const content = `[project]
200
+ dependencies = ["django>=4.0"]
201
+ `;
202
+ fs.writeFileSync(path.join(tempDir, 'pyproject.toml'), content);
203
+ const docs = gatherRepoDocumentation(tempDir);
204
+ expect(docs.techStack).toContain('Django');
205
+ });
206
+ it('detects FastAPI framework', () => {
207
+ const content = `[project]
208
+ dependencies = ["fastapi>=0.100.0"]
209
+ `;
210
+ fs.writeFileSync(path.join(tempDir, 'pyproject.toml'), content);
211
+ const docs = gatherRepoDocumentation(tempDir);
212
+ expect(docs.techStack).toContain('FastAPI');
213
+ });
214
+ });
215
+ describe('Cargo.toml extraction', () => {
216
+ it('extracts description from Cargo.toml', () => {
217
+ const content = `[package]
218
+ name = "my-rust-project"
219
+ description = "A Rust project for testing"
220
+ `;
221
+ fs.writeFileSync(path.join(tempDir, 'Cargo.toml'), content);
222
+ const docs = gatherRepoDocumentation(tempDir);
223
+ expect(docs.projectDescription).toBe('A Rust project for testing');
224
+ expect(docs.techStack).toContain('Rust');
225
+ });
226
+ it('detects Tokio runtime', () => {
227
+ const content = `[dependencies]
228
+ tokio = { version = "1.0", features = ["full"] }
229
+ `;
230
+ fs.writeFileSync(path.join(tempDir, 'Cargo.toml'), content);
231
+ const docs = gatherRepoDocumentation(tempDir);
232
+ expect(docs.techStack).toContain('Tokio');
233
+ });
234
+ });
235
+ describe('go.mod extraction', () => {
236
+ it('detects Go from go.mod', () => {
237
+ const content = `module github.com/user/project
238
+
239
+ go 1.21
240
+ `;
241
+ fs.writeFileSync(path.join(tempDir, 'go.mod'), content);
242
+ const docs = gatherRepoDocumentation(tempDir);
243
+ expect(docs.techStack).toContain('Go');
244
+ });
245
+ it('detects Gin framework', () => {
246
+ const content = `module github.com/user/project
247
+
248
+ require github.com/gin-gonic/gin v1.9.0
249
+ `;
250
+ fs.writeFileSync(path.join(tempDir, 'go.mod'), content);
251
+ const docs = gatherRepoDocumentation(tempDir);
252
+ expect(docs.techStack).toContain('Gin');
253
+ });
254
+ });
255
+ describe('combined sources', () => {
256
+ it('combines tech stack from multiple sources', () => {
257
+ // package.json with TypeScript
258
+ const pkg = {
259
+ name: 'full-stack',
260
+ devDependencies: { typescript: '^5.0.0' },
261
+ };
262
+ fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(pkg));
263
+ // pyproject.toml with Python
264
+ const pyproject = `[project]
265
+ dependencies = ["fastapi"]
266
+ `;
267
+ fs.writeFileSync(path.join(tempDir, 'pyproject.toml'), pyproject);
268
+ const docs = gatherRepoDocumentation(tempDir);
269
+ expect(docs.techStack).toContain('TypeScript');
270
+ expect(docs.techStack).toContain('Python');
271
+ expect(docs.techStack).toContain('FastAPI');
272
+ });
273
+ it('uses first found description', () => {
274
+ // package.json description
275
+ const pkg = {
276
+ name: 'project',
277
+ description: 'From package.json',
278
+ };
279
+ fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(pkg));
280
+ // pyproject.toml description
281
+ const pyproject = `[project]
282
+ description = "From pyproject.toml"
283
+ `;
284
+ fs.writeFileSync(path.join(tempDir, 'pyproject.toml'), pyproject);
285
+ const docs = gatherRepoDocumentation(tempDir);
286
+ expect(docs.projectDescription).toBe('From package.json');
287
+ });
288
+ it('respects includeTechStack option', () => {
289
+ const pkg = {
290
+ name: 'project',
291
+ devDependencies: { typescript: '^5.0.0' },
292
+ };
293
+ fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(pkg));
294
+ const docs = gatherRepoDocumentation(tempDir, { includeTechStack: false });
295
+ expect(docs.techStack).toBeUndefined();
296
+ expect(docs.projectDescription).toBeUndefined();
297
+ });
298
+ });
299
+ describe('empty repository', () => {
300
+ it('returns empty documentation for empty directory', () => {
301
+ const docs = gatherRepoDocumentation(tempDir);
302
+ expect(docs.readme).toBeUndefined();
303
+ expect(docs.projectDescription).toBeUndefined();
304
+ expect(docs.techStack).toEqual([]);
305
+ });
306
+ });
307
+ });
308
+ describe('formatDocsForPrompt', () => {
309
+ it('formats documentation with all fields', () => {
310
+ const docs = {
311
+ readme: '# Test\n\nThis is a test.',
312
+ projectDescription: 'A test project',
313
+ techStack: ['TypeScript', 'React'],
314
+ };
315
+ const formatted = formatDocsForPrompt(docs);
316
+ expect(formatted).toContain('Project: A test project');
317
+ expect(formatted).toContain('Tech: TypeScript, React');
318
+ expect(formatted).toContain('README:');
319
+ expect(formatted).toContain('# Test');
320
+ });
321
+ it('formats documentation with only project description', () => {
322
+ const docs = {
323
+ projectDescription: 'Just a description',
324
+ };
325
+ const formatted = formatDocsForPrompt(docs);
326
+ expect(formatted).toContain('Project: Just a description');
327
+ expect(formatted).not.toContain('Tech:');
328
+ expect(formatted).not.toContain('README:');
329
+ });
330
+ it('returns empty string for empty docs', () => {
331
+ const docs = {};
332
+ const formatted = formatDocsForPrompt(docs);
333
+ expect(formatted).toBe('');
334
+ });
335
+ it('handles docs with only tech stack', () => {
336
+ const docs = {
337
+ techStack: ['Go', 'Gin'],
338
+ };
339
+ const formatted = formatDocsForPrompt(docs);
340
+ expect(formatted).toContain('Tech: Go, Gin');
341
+ });
342
+ });
343
+ describe('hasDocumentation', () => {
344
+ it('returns true when README exists with content', () => {
345
+ fs.writeFileSync(path.join(tempDir, 'README.md'), '# Project\n\nThis is a substantial readme with more than 50 characters of content.');
346
+ expect(hasDocumentation(tempDir)).toBe(true);
347
+ });
348
+ it('returns false when no README exists', () => {
349
+ expect(hasDocumentation(tempDir)).toBe(false);
350
+ });
351
+ it('returns false when README is too short', () => {
352
+ fs.writeFileSync(path.join(tempDir, 'README.md'), '# Hi');
353
+ expect(hasDocumentation(tempDir)).toBe(false);
354
+ });
355
+ });
356
+ });
357
+ //# sourceMappingURL=repo-docs.test.js.map