bluera-knowledge 0.9.31 → 0.9.34

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 (200) hide show
  1. package/.claude/commands/code-review.md +15 -0
  2. package/.claude/hooks/post-edit-check.sh +5 -3
  3. package/.claude/skills/atomic-commits/SKILL.md +3 -1
  4. package/.claude/skills/code-review-repo/skill.md +62 -0
  5. package/.husky/pre-commit +3 -2
  6. package/.prettierrc +9 -0
  7. package/.versionrc.json +1 -1
  8. package/CHANGELOG.md +35 -0
  9. package/CLAUDE.md +6 -0
  10. package/README.md +25 -13
  11. package/bun.lock +277 -33
  12. package/dist/{chunk-L2YVNC63.js → chunk-6FHWC36B.js} +9 -1
  13. package/dist/chunk-6FHWC36B.js.map +1 -0
  14. package/dist/{chunk-2SJHNRXD.js → chunk-DC7CGSGT.js} +288 -241
  15. package/dist/chunk-DC7CGSGT.js.map +1 -0
  16. package/dist/{chunk-RWSXP3PQ.js → chunk-WFNPNAAP.js} +3194 -3024
  17. package/dist/chunk-WFNPNAAP.js.map +1 -0
  18. package/dist/{chunk-OGEY66FZ.js → chunk-Z2KKVH45.js} +548 -482
  19. package/dist/chunk-Z2KKVH45.js.map +1 -0
  20. package/dist/index.js +871 -754
  21. package/dist/index.js.map +1 -1
  22. package/dist/mcp/server.js +3 -3
  23. package/dist/watch.service-BJV3TI3F.js +7 -0
  24. package/dist/workers/background-worker-cli.js +46 -45
  25. package/dist/workers/background-worker-cli.js.map +1 -1
  26. package/eslint.config.js +43 -1
  27. package/package.json +18 -11
  28. package/plugin.json +8 -0
  29. package/python/requirements.txt +1 -1
  30. package/src/analysis/ast-parser.test.ts +12 -11
  31. package/src/analysis/ast-parser.ts +28 -22
  32. package/src/analysis/code-graph.test.ts +52 -62
  33. package/src/analysis/code-graph.ts +9 -13
  34. package/src/analysis/dependency-usage-analyzer.test.ts +91 -271
  35. package/src/analysis/dependency-usage-analyzer.ts +52 -24
  36. package/src/analysis/go-ast-parser.test.ts +22 -22
  37. package/src/analysis/go-ast-parser.ts +18 -25
  38. package/src/analysis/parser-factory.test.ts +9 -9
  39. package/src/analysis/parser-factory.ts +3 -3
  40. package/src/analysis/python-ast-parser.test.ts +27 -27
  41. package/src/analysis/python-ast-parser.ts +2 -2
  42. package/src/analysis/repo-url-resolver.test.ts +82 -82
  43. package/src/analysis/rust-ast-parser.test.ts +19 -19
  44. package/src/analysis/rust-ast-parser.ts +17 -27
  45. package/src/analysis/tree-sitter-parser.test.ts +3 -3
  46. package/src/analysis/tree-sitter-parser.ts +10 -16
  47. package/src/cli/commands/crawl.test.ts +40 -24
  48. package/src/cli/commands/crawl.ts +186 -161
  49. package/src/cli/commands/index-cmd.test.ts +90 -90
  50. package/src/cli/commands/index-cmd.ts +52 -36
  51. package/src/cli/commands/mcp.test.ts +6 -6
  52. package/src/cli/commands/mcp.ts +2 -2
  53. package/src/cli/commands/plugin-api.test.ts +16 -18
  54. package/src/cli/commands/plugin-api.ts +9 -6
  55. package/src/cli/commands/search.test.ts +16 -7
  56. package/src/cli/commands/search.ts +124 -87
  57. package/src/cli/commands/serve.test.ts +67 -25
  58. package/src/cli/commands/serve.ts +18 -3
  59. package/src/cli/commands/setup.test.ts +176 -101
  60. package/src/cli/commands/setup.ts +140 -117
  61. package/src/cli/commands/store.test.ts +82 -53
  62. package/src/cli/commands/store.ts +56 -37
  63. package/src/cli/program.ts +2 -2
  64. package/src/crawl/article-converter.test.ts +4 -1
  65. package/src/crawl/article-converter.ts +46 -31
  66. package/src/crawl/bridge.test.ts +240 -132
  67. package/src/crawl/bridge.ts +87 -30
  68. package/src/crawl/claude-client.test.ts +124 -56
  69. package/src/crawl/claude-client.ts +7 -15
  70. package/src/crawl/intelligent-crawler.test.ts +65 -22
  71. package/src/crawl/intelligent-crawler.ts +86 -53
  72. package/src/crawl/markdown-utils.ts +1 -4
  73. package/src/db/embeddings.ts +4 -6
  74. package/src/db/lance.test.ts +63 -4
  75. package/src/db/lance.ts +31 -12
  76. package/src/index.ts +26 -17
  77. package/src/logging/index.ts +1 -5
  78. package/src/logging/logger.ts +3 -5
  79. package/src/logging/payload.test.ts +1 -1
  80. package/src/logging/payload.ts +3 -5
  81. package/src/mcp/commands/index.ts +2 -2
  82. package/src/mcp/commands/job.commands.ts +12 -18
  83. package/src/mcp/commands/meta.commands.ts +13 -13
  84. package/src/mcp/commands/registry.ts +5 -8
  85. package/src/mcp/commands/store.commands.ts +19 -19
  86. package/src/mcp/handlers/execute.handler.test.ts +10 -10
  87. package/src/mcp/handlers/execute.handler.ts +4 -5
  88. package/src/mcp/handlers/index.ts +10 -14
  89. package/src/mcp/handlers/job.handler.test.ts +10 -10
  90. package/src/mcp/handlers/job.handler.ts +22 -25
  91. package/src/mcp/handlers/search.handler.test.ts +36 -65
  92. package/src/mcp/handlers/search.handler.ts +135 -104
  93. package/src/mcp/handlers/store.handler.test.ts +41 -52
  94. package/src/mcp/handlers/store.handler.ts +108 -88
  95. package/src/mcp/schemas/index.test.ts +73 -68
  96. package/src/mcp/schemas/index.ts +18 -12
  97. package/src/mcp/server.test.ts +1 -1
  98. package/src/mcp/server.ts +59 -46
  99. package/src/plugin/commands.test.ts +230 -95
  100. package/src/plugin/commands.ts +24 -25
  101. package/src/plugin/dependency-analyzer.test.ts +52 -52
  102. package/src/plugin/dependency-analyzer.ts +85 -22
  103. package/src/plugin/git-clone.test.ts +24 -13
  104. package/src/plugin/git-clone.ts +3 -7
  105. package/src/server/app.test.ts +109 -109
  106. package/src/server/app.ts +32 -23
  107. package/src/server/index.test.ts +64 -66
  108. package/src/services/chunking.service.test.ts +32 -32
  109. package/src/services/chunking.service.ts +16 -9
  110. package/src/services/code-graph.service.test.ts +30 -36
  111. package/src/services/code-graph.service.ts +24 -10
  112. package/src/services/code-unit.service.test.ts +55 -11
  113. package/src/services/code-unit.service.ts +85 -11
  114. package/src/services/config.service.test.ts +37 -18
  115. package/src/services/config.service.ts +30 -7
  116. package/src/services/index.service.test.ts +49 -18
  117. package/src/services/index.service.ts +98 -48
  118. package/src/services/index.ts +8 -10
  119. package/src/services/job.service.test.ts +22 -22
  120. package/src/services/job.service.ts +18 -18
  121. package/src/services/project-root.service.test.ts +1 -3
  122. package/src/services/search.service.test.ts +248 -120
  123. package/src/services/search.service.ts +286 -156
  124. package/src/services/services.test.ts +36 -0
  125. package/src/services/snippet.service.test.ts +14 -6
  126. package/src/services/snippet.service.ts +7 -5
  127. package/src/services/store.service.test.ts +68 -29
  128. package/src/services/store.service.ts +41 -12
  129. package/src/services/watch.service.test.ts +34 -14
  130. package/src/services/watch.service.ts +11 -1
  131. package/src/types/brands.test.ts +3 -1
  132. package/src/types/index.ts +2 -13
  133. package/src/types/search.ts +10 -8
  134. package/src/utils/type-guards.test.ts +20 -15
  135. package/src/utils/type-guards.ts +1 -1
  136. package/src/workers/background-worker-cli.ts +2 -2
  137. package/src/workers/background-worker.test.ts +54 -40
  138. package/src/workers/background-worker.ts +76 -60
  139. package/src/workers/spawn-worker.test.ts +22 -10
  140. package/src/workers/spawn-worker.ts +6 -6
  141. package/tests/analysis/ast-parser.test.ts +3 -3
  142. package/tests/analysis/code-graph.test.ts +5 -5
  143. package/tests/fixtures/code-snippets/api/error-handling.ts +4 -15
  144. package/tests/fixtures/code-snippets/api/rest-controller.ts +3 -9
  145. package/tests/fixtures/code-snippets/auth/jwt-auth.ts +5 -21
  146. package/tests/fixtures/code-snippets/auth/oauth-flow.ts +4 -4
  147. package/tests/fixtures/code-snippets/database/repository-pattern.ts +11 -3
  148. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/aws-lambda/handler.ts +2 -2
  149. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-pages/handler.ts +1 -1
  150. package/tests/fixtures/corpus/oss-repos/hono/src/adapter/cloudflare-workers/serve-static.ts +2 -2
  151. package/tests/fixtures/corpus/oss-repos/hono/src/client/client.ts +2 -2
  152. package/tests/fixtures/corpus/oss-repos/hono/src/client/types.ts +22 -20
  153. package/tests/fixtures/corpus/oss-repos/hono/src/context.ts +13 -10
  154. package/tests/fixtures/corpus/oss-repos/hono/src/helper/accepts/accepts.ts +10 -7
  155. package/tests/fixtures/corpus/oss-repos/hono/src/helper/adapter/index.ts +2 -2
  156. package/tests/fixtures/corpus/oss-repos/hono/src/helper/css/index.ts +1 -1
  157. package/tests/fixtures/corpus/oss-repos/hono/src/helper/factory/index.ts +16 -16
  158. package/tests/fixtures/corpus/oss-repos/hono/src/helper/ssg/ssg.ts +2 -2
  159. package/tests/fixtures/corpus/oss-repos/hono/src/hono-base.ts +3 -3
  160. package/tests/fixtures/corpus/oss-repos/hono/src/hono.ts +1 -1
  161. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/css.ts +2 -2
  162. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/intrinsic-element/components.ts +1 -1
  163. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/dom/render.ts +7 -7
  164. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/hooks/index.ts +3 -3
  165. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/intrinsic-element/components.ts +1 -1
  166. package/tests/fixtures/corpus/oss-repos/hono/src/jsx/utils.ts +6 -6
  167. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/jsx-renderer/index.ts +3 -3
  168. package/tests/fixtures/corpus/oss-repos/hono/src/middleware/serve-static/index.ts +1 -1
  169. package/tests/fixtures/corpus/oss-repos/hono/src/preset/quick.ts +1 -1
  170. package/tests/fixtures/corpus/oss-repos/hono/src/preset/tiny.ts +1 -1
  171. package/tests/fixtures/corpus/oss-repos/hono/src/router/pattern-router/router.ts +2 -2
  172. package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/node.ts +4 -4
  173. package/tests/fixtures/corpus/oss-repos/hono/src/router/reg-exp-router/router.ts +1 -1
  174. package/tests/fixtures/corpus/oss-repos/hono/src/router/trie-router/node.ts +1 -1
  175. package/tests/fixtures/corpus/oss-repos/hono/src/types.ts +166 -169
  176. package/tests/fixtures/corpus/oss-repos/hono/src/utils/body.ts +8 -8
  177. package/tests/fixtures/corpus/oss-repos/hono/src/utils/color.ts +3 -3
  178. package/tests/fixtures/corpus/oss-repos/hono/src/utils/cookie.ts +2 -2
  179. package/tests/fixtures/corpus/oss-repos/hono/src/utils/encode.ts +2 -2
  180. package/tests/fixtures/corpus/oss-repos/hono/src/utils/types.ts +30 -33
  181. package/tests/fixtures/corpus/oss-repos/hono/src/validator/validator.ts +2 -2
  182. package/tests/fixtures/test-server.ts +3 -2
  183. package/tests/helpers/performance-metrics.ts +8 -25
  184. package/tests/helpers/search-relevance.ts +14 -69
  185. package/tests/integration/cli-consistency.test.ts +5 -4
  186. package/tests/integration/e2e-workflow.test.ts +2 -0
  187. package/tests/integration/python-bridge.test.ts +13 -3
  188. package/tests/mcp/server.test.ts +1 -1
  189. package/tests/services/code-unit.service.test.ts +48 -0
  190. package/tests/services/job.service.test.ts +124 -0
  191. package/tests/services/search.progressive-context.test.ts +2 -2
  192. package/.claude-plugin/plugin.json +0 -13
  193. package/BUGS-FOUND.md +0 -71
  194. package/dist/chunk-2SJHNRXD.js.map +0 -1
  195. package/dist/chunk-L2YVNC63.js.map +0 -1
  196. package/dist/chunk-OGEY66FZ.js.map +0 -1
  197. package/dist/chunk-RWSXP3PQ.js.map +0 -1
  198. package/dist/watch.service-YAIKKDCF.js +0 -7
  199. package/skills/atomic-commits/SKILL.md +0 -77
  200. /package/dist/{watch.service-YAIKKDCF.js.map → watch.service-BJV3TI3F.js.map} +0 -0
@@ -1,8 +1,8 @@
1
- import { createServices } from '../services/index.js';
1
+ import ora from 'ora';
2
2
  import { extractRepoName } from './git-clone.js';
3
3
  import { DependencyUsageAnalyzer } from '../analysis/dependency-usage-analyzer.js';
4
4
  import { RepoUrlResolver } from '../analysis/repo-url-resolver.js';
5
- import ora from 'ora';
5
+ import { createServices } from '../services/index.js';
6
6
 
7
7
  export async function handleSearch(args: {
8
8
  query: string;
@@ -14,9 +14,8 @@ export async function handleSearch(args: {
14
14
  const storeNames = args.stores?.split(',').map((s: string) => s.trim());
15
15
 
16
16
  const allStores = await services.store.list();
17
- const targetStores = storeNames !== undefined
18
- ? allStores.filter((s) => storeNames.includes(s.name))
19
- : allStores;
17
+ const targetStores =
18
+ storeNames !== undefined ? allStores.filter((s) => storeNames.includes(s.name)) : allStores;
20
19
 
21
20
  if (targetStores.length === 0) {
22
21
  console.error('No stores found to search');
@@ -33,7 +32,7 @@ export async function handleSearch(args: {
33
32
  stores: targetStores.map((s) => s.id),
34
33
  mode: 'hybrid',
35
34
  limit: parseInt(args.limit ?? '10', 10),
36
- detail: 'contextual'
35
+ detail: 'contextual',
37
36
  });
38
37
 
39
38
  console.log(`Found ${String(results.totalResults)} results:\n`);
@@ -61,7 +60,7 @@ export async function handleAddRepo(args: {
61
60
  name: storeName,
62
61
  type: 'repo',
63
62
  url: args.url,
64
- ...(args.branch !== undefined ? { branch: args.branch } : {})
63
+ ...(args.branch !== undefined ? { branch: args.branch } : {}),
65
64
  });
66
65
 
67
66
  if (!result.success) {
@@ -85,10 +84,7 @@ export async function handleAddRepo(args: {
85
84
  }
86
85
  }
87
86
 
88
- export async function handleAddFolder(args: {
89
- path: string;
90
- name?: string;
91
- }): Promise<void> {
87
+ export async function handleAddFolder(args: { path: string; name?: string }): Promise<void> {
92
88
  // PWD is set by Claude Code to user's project directory
93
89
  const services = await createServices(undefined, undefined, process.env['PWD']);
94
90
  const { basename } = await import('node:path');
@@ -99,7 +95,7 @@ export async function handleAddFolder(args: {
99
95
  const result = await services.store.create({
100
96
  name: storeName,
101
97
  type: 'file',
102
- path: args.path
98
+ path: args.path,
103
99
  });
104
100
 
105
101
  if (!result.success) {
@@ -123,9 +119,7 @@ export async function handleAddFolder(args: {
123
119
  }
124
120
  }
125
121
 
126
- export async function handleIndex(args: {
127
- store: string;
128
- }): Promise<void> {
122
+ export async function handleIndex(args: { store: string }): Promise<void> {
129
123
  // PWD is set by Claude Code to user's project directory
130
124
  const services = await createServices(undefined, undefined, process.env['PWD']);
131
125
  const store = await services.store.getByIdOrName(args.store);
@@ -139,7 +133,9 @@ export async function handleIndex(args: {
139
133
  const result = await services.index.indexStore(store);
140
134
 
141
135
  if (result.success) {
142
- console.log(`Indexed ${String(result.data.documentsIndexed)} documents in ${String(result.data.timeMs)}ms`);
136
+ console.log(
137
+ `Indexed ${String(result.data.documentsIndexed)} documents in ${String(result.data.timeMs)}ms`
138
+ );
143
139
  } else {
144
140
  console.error(`Error: ${result.error.message}`);
145
141
  process.exit(1);
@@ -207,7 +203,9 @@ export async function handleSuggest(): Promise<void> {
207
203
 
208
204
  const { usages, totalFilesScanned, skippedFiles } = result.data;
209
205
 
210
- console.log(`✔ Scanned ${String(totalFilesScanned)} files${skippedFiles > 0 ? ` (skipped ${String(skippedFiles)})` : ''}\n`);
206
+ console.log(
207
+ `✔ Scanned ${String(totalFilesScanned)} files${skippedFiles > 0 ? ` (skipped ${String(skippedFiles)})` : ''}\n`
208
+ );
211
209
 
212
210
  if (usages.length === 0) {
213
211
  console.log('No external dependencies found in this project.');
@@ -217,9 +215,9 @@ export async function handleSuggest(): Promise<void> {
217
215
 
218
216
  // Filter out packages already in stores
219
217
  const existingStores = await services.store.list();
220
- const existingRepoNames = new Set(existingStores.map(s => s.name));
218
+ const existingRepoNames = new Set(existingStores.map((s) => s.name));
221
219
 
222
- const newUsages = usages.filter(u => !existingRepoNames.has(u.packageName));
220
+ const newUsages = usages.filter((u) => !existingRepoNames.has(u.packageName));
223
221
 
224
222
  if (newUsages.length === 0) {
225
223
  console.log('✔ All dependencies are already in knowledge stores!');
@@ -232,24 +230,25 @@ export async function handleSuggest(): Promise<void> {
232
230
  console.log('Top dependencies by usage in this project:\n');
233
231
  topSuggestions.forEach((usage, i) => {
234
232
  console.log(`${String(i + 1)}. ${usage.packageName}`);
235
- console.log(` ${String(usage.importCount)} imports across ${String(usage.fileCount)} files\n`);
233
+ console.log(
234
+ ` ${String(usage.importCount)} imports across ${String(usage.fileCount)} files\n`
235
+ );
236
236
  });
237
237
 
238
238
  console.log('Searching for repository URLs...\n');
239
239
 
240
240
  // For each package, find repo URL
241
241
  for (const usage of topSuggestions) {
242
- const repoResult = await resolver.findRepoUrl(
243
- usage.packageName,
244
- usage.language
245
- );
242
+ const repoResult = await resolver.findRepoUrl(usage.packageName, usage.language);
246
243
 
247
244
  if (repoResult.url !== null) {
248
245
  console.log(`✔ ${usage.packageName}: ${repoResult.url}`);
249
246
  console.log(` /bluera-knowledge:add-repo ${repoResult.url} --name=${usage.packageName}\n`);
250
247
  } else {
251
248
  console.log(`✗ ${usage.packageName}: Could not find repository URL`);
252
- console.log(` You can manually add it: /bluera-knowledge:add-repo <url> --name=${usage.packageName}\n`);
249
+ console.log(
250
+ ` You can manually add it: /bluera-knowledge:add-repo <url> --name=${usage.packageName}\n`
251
+ );
253
252
  }
254
253
  }
255
254
 
@@ -21,35 +21,35 @@ describe('DependencyAnalyzer - Node.js Projects', () => {
21
21
  name: 'test-project',
22
22
  dependencies: {
23
23
  react: '^18.0.0',
24
- vue: '^3.0.0'
25
- }
24
+ vue: '^3.0.0',
25
+ },
26
26
  };
27
27
 
28
28
  await writeFile(join(tempDir, 'package.json'), JSON.stringify(packageJson));
29
29
 
30
30
  const suggestions = await analyzeDependencies(tempDir);
31
31
 
32
- expect(suggestions.some(s => s.name === 'react')).toBe(true);
33
- expect(suggestions.some(s => s.name === 'vue')).toBe(true);
32
+ expect(suggestions.some((s) => s.name === 'react')).toBe(true);
33
+ expect(suggestions.some((s) => s.name === 'vue')).toBe(true);
34
34
  });
35
35
 
36
36
  it('includes devDependencies', async () => {
37
37
  const packageJson = {
38
38
  name: 'test-project',
39
39
  dependencies: {
40
- hono: '^3.0.0'
40
+ hono: '^3.0.0',
41
41
  },
42
42
  devDependencies: {
43
- pino: '^8.0.0'
44
- }
43
+ pino: '^8.0.0',
44
+ },
45
45
  };
46
46
 
47
47
  await writeFile(join(tempDir, 'package.json'), JSON.stringify(packageJson));
48
48
 
49
49
  const suggestions = await analyzeDependencies(tempDir);
50
50
 
51
- expect(suggestions.some(s => s.name === 'hono')).toBe(true);
52
- expect(suggestions.some(s => s.name === 'pino')).toBe(true);
51
+ expect(suggestions.some((s) => s.name === 'hono')).toBe(true);
52
+ expect(suggestions.some((s) => s.name === 'pino')).toBe(true);
53
53
  });
54
54
 
55
55
  it('returns only known repositories', async () => {
@@ -57,31 +57,31 @@ describe('DependencyAnalyzer - Node.js Projects', () => {
57
57
  name: 'test-project',
58
58
  dependencies: {
59
59
  react: '^18.0.0',
60
- 'some-unknown-package': '^1.0.0'
61
- }
60
+ 'some-unknown-package': '^1.0.0',
61
+ },
62
62
  };
63
63
 
64
64
  await writeFile(join(tempDir, 'package.json'), JSON.stringify(packageJson));
65
65
 
66
66
  const suggestions = await analyzeDependencies(tempDir);
67
67
 
68
- expect(suggestions.some(s => s.name === 'react')).toBe(true);
69
- expect(suggestions.some(s => s.name === 'some-unknown-package')).toBe(false);
68
+ expect(suggestions.some((s) => s.name === 'react')).toBe(true);
69
+ expect(suggestions.some((s) => s.name === 'some-unknown-package')).toBe(false);
70
70
  });
71
71
 
72
72
  it('includes repository URL for known packages', async () => {
73
73
  const packageJson = {
74
74
  name: 'test-project',
75
75
  dependencies: {
76
- react: '^18.0.0'
77
- }
76
+ react: '^18.0.0',
77
+ },
78
78
  };
79
79
 
80
80
  await writeFile(join(tempDir, 'package.json'), JSON.stringify(packageJson));
81
81
 
82
82
  const suggestions = await analyzeDependencies(tempDir);
83
83
 
84
- const reactSuggestion = suggestions.find(s => s.name === 'react');
84
+ const reactSuggestion = suggestions.find((s) => s.name === 'react');
85
85
  expect(reactSuggestion).toBeDefined();
86
86
  expect(reactSuggestion?.url).toContain('github.com');
87
87
  });
@@ -91,16 +91,16 @@ describe('DependencyAnalyzer - Node.js Projects', () => {
91
91
  name: 'test-project',
92
92
  dependencies: {
93
93
  react: '^18.0.0',
94
- pino: '^8.0.0'
95
- }
94
+ pino: '^8.0.0',
95
+ },
96
96
  };
97
97
 
98
98
  await writeFile(join(tempDir, 'package.json'), JSON.stringify(packageJson));
99
99
 
100
100
  const suggestions = await analyzeDependencies(tempDir);
101
101
 
102
- const reactSuggestion = suggestions.find(s => s.name === 'react');
103
- const pinoSuggestion = suggestions.find(s => s.name === 'pino');
102
+ const reactSuggestion = suggestions.find((s) => s.name === 'react');
103
+ const pinoSuggestion = suggestions.find((s) => s.name === 'pino');
104
104
 
105
105
  expect(reactSuggestion?.importance).toBe('critical');
106
106
  expect(pinoSuggestion?.importance).toBe('medium');
@@ -110,15 +110,15 @@ describe('DependencyAnalyzer - Node.js Projects', () => {
110
110
  const packageJson = {
111
111
  name: 'test-project',
112
112
  dependencies: {
113
- react: '^18.0.0'
114
- }
113
+ react: '^18.0.0',
114
+ },
115
115
  };
116
116
 
117
117
  await writeFile(join(tempDir, 'package.json'), JSON.stringify(packageJson));
118
118
 
119
119
  const suggestions = await analyzeDependencies(tempDir);
120
120
 
121
- const reactSuggestion = suggestions.find(s => s.name === 'react');
121
+ const reactSuggestion = suggestions.find((s) => s.name === 'react');
122
122
  expect(reactSuggestion?.reason).toBeTruthy();
123
123
  expect(reactSuggestion?.reason).toContain('framework');
124
124
  });
@@ -172,9 +172,9 @@ django==4.2.0`;
172
172
 
173
173
  const suggestions = await analyzeDependencies(tempDir);
174
174
 
175
- expect(suggestions.some(s => s.name === 'fastapi')).toBe(true);
176
- expect(suggestions.some(s => s.name === 'pydantic')).toBe(true);
177
- expect(suggestions.some(s => s.name === 'django')).toBe(true);
175
+ expect(suggestions.some((s) => s.name === 'fastapi')).toBe(true);
176
+ expect(suggestions.some((s) => s.name === 'pydantic')).toBe(true);
177
+ expect(suggestions.some((s) => s.name === 'django')).toBe(true);
178
178
  });
179
179
 
180
180
  it('parses different version specifiers', async () => {
@@ -187,10 +187,10 @@ django>3.0`;
187
187
 
188
188
  const suggestions = await analyzeDependencies(tempDir);
189
189
 
190
- expect(suggestions.some(s => s.name === 'fastapi')).toBe(true);
191
- expect(suggestions.some(s => s.name === 'pydantic')).toBe(true);
192
- expect(suggestions.some(s => s.name === 'flask')).toBe(true);
193
- expect(suggestions.some(s => s.name === 'django')).toBe(true);
190
+ expect(suggestions.some((s) => s.name === 'fastapi')).toBe(true);
191
+ expect(suggestions.some((s) => s.name === 'pydantic')).toBe(true);
192
+ expect(suggestions.some((s) => s.name === 'flask')).toBe(true);
193
+ expect(suggestions.some((s) => s.name === 'django')).toBe(true);
194
194
  });
195
195
 
196
196
  it('ignores comments and blank lines', async () => {
@@ -205,8 +205,8 @@ pydantic>=1.10.0
205
205
 
206
206
  const suggestions = await analyzeDependencies(tempDir);
207
207
 
208
- expect(suggestions.some(s => s.name === 'fastapi')).toBe(true);
209
- expect(suggestions.some(s => s.name === 'pydantic')).toBe(true);
208
+ expect(suggestions.some((s) => s.name === 'fastapi')).toBe(true);
209
+ expect(suggestions.some((s) => s.name === 'pydantic')).toBe(true);
210
210
  });
211
211
 
212
212
  it('analyzes pyproject.toml', async () => {
@@ -221,8 +221,8 @@ dependencies = [
221
221
 
222
222
  const suggestions = await analyzeDependencies(tempDir);
223
223
 
224
- expect(suggestions.some(s => s.name === 'fastapi')).toBe(true);
225
- expect(suggestions.some(s => s.name === 'pydantic')).toBe(true);
224
+ expect(suggestions.some((s) => s.name === 'fastapi')).toBe(true);
225
+ expect(suggestions.some((s) => s.name === 'pydantic')).toBe(true);
226
226
  });
227
227
  });
228
228
 
@@ -242,8 +242,8 @@ describe('DependencyAnalyzer - Mixed Projects', () => {
242
242
  const packageJson = {
243
243
  name: 'test-project',
244
244
  dependencies: {
245
- react: '^18.0.0'
246
- }
245
+ react: '^18.0.0',
246
+ },
247
247
  };
248
248
 
249
249
  const requirements = 'fastapi==0.95.0';
@@ -253,26 +253,26 @@ describe('DependencyAnalyzer - Mixed Projects', () => {
253
253
 
254
254
  const suggestions = await analyzeDependencies(tempDir);
255
255
 
256
- expect(suggestions.some(s => s.name === 'react')).toBe(true);
257
- expect(suggestions.some(s => s.name === 'fastapi')).toBe(true);
256
+ expect(suggestions.some((s) => s.name === 'react')).toBe(true);
257
+ expect(suggestions.some((s) => s.name === 'fastapi')).toBe(true);
258
258
  });
259
259
 
260
260
  it('removes duplicates across sources', async () => {
261
261
  const packageJson = {
262
262
  name: 'test-project',
263
263
  dependencies: {
264
- react: '^18.0.0'
264
+ react: '^18.0.0',
265
265
  },
266
266
  devDependencies: {
267
- react: '^18.0.0' // Duplicate
268
- }
267
+ react: '^18.0.0', // Duplicate
268
+ },
269
269
  };
270
270
 
271
271
  await writeFile(join(tempDir, 'package.json'), JSON.stringify(packageJson));
272
272
 
273
273
  const suggestions = await analyzeDependencies(tempDir);
274
274
 
275
- const reactSuggestions = suggestions.filter(s => s.name === 'react');
275
+ const reactSuggestions = suggestions.filter((s) => s.name === 'react');
276
276
  expect(reactSuggestions).toHaveLength(1);
277
277
  });
278
278
 
@@ -280,11 +280,11 @@ describe('DependencyAnalyzer - Mixed Projects', () => {
280
280
  const packageJson = {
281
281
  name: 'test-project',
282
282
  dependencies: {
283
- pino: '^8.0.0', // medium
284
- react: '^18.0.0', // critical
285
- zod: '^3.0.0', // high
286
- express: '^4.0.0' // high
287
- }
283
+ pino: '^8.0.0', // medium
284
+ react: '^18.0.0', // critical
285
+ zod: '^3.0.0', // high
286
+ express: '^4.0.0', // high
287
+ },
288
288
  };
289
289
 
290
290
  await writeFile(join(tempDir, 'package.json'), JSON.stringify(packageJson));
@@ -324,7 +324,7 @@ describe('DependencyAnalyzer - Edge Cases', () => {
324
324
  it('handles empty dependencies object', async () => {
325
325
  const packageJson = {
326
326
  name: 'test-project',
327
- dependencies: {}
327
+ dependencies: {},
328
328
  };
329
329
 
330
330
  await writeFile(join(tempDir, 'package.json'), JSON.stringify(packageJson));
@@ -337,7 +337,7 @@ describe('DependencyAnalyzer - Edge Cases', () => {
337
337
  it('handles package.json without dependencies field', async () => {
338
338
  const packageJson = {
339
339
  name: 'test-project',
340
- version: '1.0.0'
340
+ version: '1.0.0',
341
341
  };
342
342
 
343
343
  await writeFile(join(tempDir, 'package.json'), JSON.stringify(packageJson));
@@ -359,8 +359,8 @@ describe('DependencyAnalyzer - Edge Cases', () => {
359
359
  const packageJson = {
360
360
  name: 'test-project',
361
361
  dependencies: {
362
- react: '^18.0.0'
363
- }
362
+ react: '^18.0.0',
363
+ },
364
364
  };
365
365
 
366
366
  await writeFile(join(tempDir, 'package.json'), JSON.stringify(packageJson));
@@ -372,7 +372,7 @@ describe('DependencyAnalyzer - Edge Cases', () => {
372
372
 
373
373
  try {
374
374
  const suggestions = await analyzeDependencies();
375
- expect(suggestions.some(s => s.name === 'react')).toBe(true);
375
+ expect(suggestions.some((s) => s.name === 'react')).toBe(true);
376
376
  } finally {
377
377
  cwd.mockRestore();
378
378
  }
@@ -1,5 +1,5 @@
1
- import { readFile } from 'node:fs/promises';
2
1
  import { existsSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
 
5
5
  export interface DependencySuggestion {
@@ -14,22 +14,85 @@ function isRecord(value: unknown): value is Record<string, unknown> {
14
14
  }
15
15
 
16
16
  // Map of important libraries to their repos
17
- const KNOWN_REPOS: Record<string, { url: string; importance: 'critical' | 'high' | 'medium'; reason: string }> = {
18
- 'vue': { url: 'https://github.com/vuejs/core', importance: 'critical', reason: 'Core framework - essential for Vue.js development' },
19
- 'react': { url: 'https://github.com/facebook/react', importance: 'critical', reason: 'Core framework - essential for React development' },
20
- 'pydantic': { url: 'https://github.com/pydantic/pydantic', importance: 'critical', reason: 'Core validation library - heavily used' },
21
- 'fastapi': { url: 'https://github.com/tiangolo/fastapi', importance: 'critical', reason: 'Core web framework - central to this project' },
22
- 'hono': { url: 'https://github.com/honojs/hono', importance: 'critical', reason: 'Core web framework - central to this project' },
23
- 'express': { url: 'https://github.com/expressjs/express', importance: 'high', reason: 'Web framework - frequently referenced' },
24
- 'pinia': { url: 'https://github.com/vuejs/pinia', importance: 'high', reason: 'State management - frequently used' },
25
- 'pino': { url: 'https://github.com/pinojs/pino', importance: 'medium', reason: 'Logging library - commonly used' },
26
- 'zod': { url: 'https://github.com/colinhacks/zod', importance: 'high', reason: 'Schema validation - frequently used' },
27
- 'next': { url: 'https://github.com/vercel/next.js', importance: 'critical', reason: 'Core framework - essential for Next.js development' },
28
- 'nuxt': { url: 'https://github.com/nuxt/nuxt', importance: 'critical', reason: 'Core framework - essential for Nuxt development' },
29
- 'svelte': { url: 'https://github.com/sveltejs/svelte', importance: 'critical', reason: 'Core framework - essential for Svelte development' },
30
- 'django': { url: 'https://github.com/django/django', importance: 'critical', reason: 'Core framework - essential for Django development' },
31
- 'flask': { url: 'https://github.com/pallets/flask', importance: 'critical', reason: 'Core framework - essential for Flask development' },
32
- 'prisma': { url: 'https://github.com/prisma/prisma', importance: 'high', reason: 'ORM - database interactions' },
17
+ const KNOWN_REPOS: Record<
18
+ string,
19
+ { url: string; importance: 'critical' | 'high' | 'medium'; reason: string }
20
+ > = {
21
+ vue: {
22
+ url: 'https://github.com/vuejs/core',
23
+ importance: 'critical',
24
+ reason: 'Core framework - essential for Vue.js development',
25
+ },
26
+ react: {
27
+ url: 'https://github.com/facebook/react',
28
+ importance: 'critical',
29
+ reason: 'Core framework - essential for React development',
30
+ },
31
+ pydantic: {
32
+ url: 'https://github.com/pydantic/pydantic',
33
+ importance: 'critical',
34
+ reason: 'Core validation library - heavily used',
35
+ },
36
+ fastapi: {
37
+ url: 'https://github.com/tiangolo/fastapi',
38
+ importance: 'critical',
39
+ reason: 'Core web framework - central to this project',
40
+ },
41
+ hono: {
42
+ url: 'https://github.com/honojs/hono',
43
+ importance: 'critical',
44
+ reason: 'Core web framework - central to this project',
45
+ },
46
+ express: {
47
+ url: 'https://github.com/expressjs/express',
48
+ importance: 'high',
49
+ reason: 'Web framework - frequently referenced',
50
+ },
51
+ pinia: {
52
+ url: 'https://github.com/vuejs/pinia',
53
+ importance: 'high',
54
+ reason: 'State management - frequently used',
55
+ },
56
+ pino: {
57
+ url: 'https://github.com/pinojs/pino',
58
+ importance: 'medium',
59
+ reason: 'Logging library - commonly used',
60
+ },
61
+ zod: {
62
+ url: 'https://github.com/colinhacks/zod',
63
+ importance: 'high',
64
+ reason: 'Schema validation - frequently used',
65
+ },
66
+ next: {
67
+ url: 'https://github.com/vercel/next.js',
68
+ importance: 'critical',
69
+ reason: 'Core framework - essential for Next.js development',
70
+ },
71
+ nuxt: {
72
+ url: 'https://github.com/nuxt/nuxt',
73
+ importance: 'critical',
74
+ reason: 'Core framework - essential for Nuxt development',
75
+ },
76
+ svelte: {
77
+ url: 'https://github.com/sveltejs/svelte',
78
+ importance: 'critical',
79
+ reason: 'Core framework - essential for Svelte development',
80
+ },
81
+ django: {
82
+ url: 'https://github.com/django/django',
83
+ importance: 'critical',
84
+ reason: 'Core framework - essential for Django development',
85
+ },
86
+ flask: {
87
+ url: 'https://github.com/pallets/flask',
88
+ importance: 'critical',
89
+ reason: 'Core framework - essential for Flask development',
90
+ },
91
+ prisma: {
92
+ url: 'https://github.com/prisma/prisma',
93
+ importance: 'high',
94
+ reason: 'ORM - database interactions',
95
+ },
33
96
  };
34
97
 
35
98
  export async function analyzeDependencies(
@@ -62,7 +125,7 @@ export async function analyzeDependencies(
62
125
  }
63
126
 
64
127
  // Remove duplicates and sort by importance
65
- const unique = Array.from(new Map(suggestions.map(s => [s.name, s])).values());
128
+ const unique = Array.from(new Map(suggestions.map((s) => [s.name, s])).values());
66
129
  return unique.sort((a, b) => {
67
130
  const order = { critical: 0, high: 1, medium: 2 };
68
131
  return order[a.importance] - order[b.importance];
@@ -87,7 +150,7 @@ function analyzeNodeDependencies(pkg: Record<string, unknown>): DependencySugges
87
150
  name,
88
151
  url: known.url,
89
152
  importance: known.importance,
90
- reason: known.reason
153
+ reason: known.reason,
91
154
  });
92
155
  }
93
156
  }
@@ -105,7 +168,7 @@ function analyzePythonDependencies(content: string): DependencySuggestion[] {
105
168
 
106
169
  // Parse package name (before ==, >=, etc.)
107
170
  const match = /^([a-zA-Z0-9_-]+)/.exec(trimmed);
108
- if (match !== null && match[1] !== undefined) {
171
+ if (match?.[1] !== undefined) {
109
172
  const name = match[1].toLowerCase();
110
173
  const known = KNOWN_REPOS[name];
111
174
  if (known !== undefined) {
@@ -113,7 +176,7 @@ function analyzePythonDependencies(content: string): DependencySuggestion[] {
113
176
  name,
114
177
  url: known.url,
115
178
  importance: known.importance,
116
- reason: known.reason
179
+ reason: known.reason,
117
180
  });
118
181
  }
119
182
  }
@@ -137,7 +200,7 @@ function analyzePyProjectDependencies(content: string): DependencySuggestion[] {
137
200
  name,
138
201
  url: known.url,
139
202
  importance: known.importance,
140
- reason: known.reason
203
+ reason: known.reason,
141
204
  });
142
205
  }
143
206
  }
@@ -8,7 +8,7 @@ import { tmpdir } from 'node:os';
8
8
 
9
9
  // Mock child_process
10
10
  vi.mock('node:child_process', () => ({
11
- spawn: vi.fn()
11
+ spawn: vi.fn(),
12
12
  }));
13
13
 
14
14
  describe('GitClone - cloneRepository', () => {
@@ -42,7 +42,7 @@ describe('GitClone - cloneRepository', () => {
42
42
 
43
43
  const result = await cloneRepository({
44
44
  url: 'https://github.com/user/repo.git',
45
- targetDir: join(tempDir, 'repo')
45
+ targetDir: join(tempDir, 'repo'),
46
46
  });
47
47
 
48
48
  expect(result.success).toBe(true);
@@ -62,7 +62,7 @@ describe('GitClone - cloneRepository', () => {
62
62
 
63
63
  await cloneRepository({
64
64
  url: 'https://github.com/user/repo.git',
65
- targetDir: join(tempDir, 'repo')
65
+ targetDir: join(tempDir, 'repo'),
66
66
  });
67
67
 
68
68
  expect(mockSpawn).toHaveBeenCalledWith(
@@ -84,12 +84,20 @@ describe('GitClone - cloneRepository', () => {
84
84
  await cloneRepository({
85
85
  url: 'https://github.com/user/repo.git',
86
86
  targetDir: join(tempDir, 'repo'),
87
- branch: 'develop'
87
+ branch: 'develop',
88
88
  });
89
89
 
90
90
  expect(mockSpawn).toHaveBeenCalledWith(
91
91
  'git',
92
- ['clone', '--depth', '1', '--branch', 'develop', 'https://github.com/user/repo.git', join(tempDir, 'repo')],
92
+ [
93
+ 'clone',
94
+ '--depth',
95
+ '1',
96
+ '--branch',
97
+ 'develop',
98
+ 'https://github.com/user/repo.git',
99
+ join(tempDir, 'repo'),
100
+ ],
93
101
  expect.any(Object)
94
102
  );
95
103
  });
@@ -106,7 +114,7 @@ describe('GitClone - cloneRepository', () => {
106
114
  await cloneRepository({
107
115
  url: 'https://github.com/user/repo.git',
108
116
  targetDir: join(tempDir, 'repo'),
109
- depth: 10
117
+ depth: 10,
110
118
  });
111
119
 
112
120
  expect(mockSpawn).toHaveBeenCalledWith(
@@ -127,7 +135,7 @@ describe('GitClone - cloneRepository', () => {
127
135
 
128
136
  await cloneRepository({
129
137
  url: 'https://github.com/user/repo.git',
130
- targetDir: join(tempDir, 'repo')
138
+ targetDir: join(tempDir, 'repo'),
131
139
  });
132
140
 
133
141
  expect(mockSpawn).toHaveBeenCalledWith(
@@ -151,7 +159,7 @@ describe('GitClone - cloneRepository', () => {
151
159
 
152
160
  const result = await cloneRepository({
153
161
  url: 'https://github.com/user/nonexistent.git',
154
- targetDir: join(tempDir, 'repo')
162
+ targetDir: join(tempDir, 'repo'),
155
163
  });
156
164
 
157
165
  expect(result.success).toBe(false);
@@ -176,7 +184,7 @@ describe('GitClone - cloneRepository', () => {
176
184
 
177
185
  const result = await cloneRepository({
178
186
  url: 'https://github.com/user/repo.git',
179
- targetDir: join(tempDir, 'repo')
187
+ targetDir: join(tempDir, 'repo'),
180
188
  });
181
189
 
182
190
  expect(result.success).toBe(false);
@@ -200,7 +208,7 @@ describe('GitClone - cloneRepository', () => {
200
208
 
201
209
  const result = await cloneRepository({
202
210
  url: 'https://github.com/user/repo.git',
203
- targetDir: join(tempDir, 'repo')
211
+ targetDir: join(tempDir, 'repo'),
204
212
  });
205
213
 
206
214
  expect(result.success).toBe(false);
@@ -223,7 +231,7 @@ describe('GitClone - cloneRepository', () => {
223
231
 
224
232
  const result = await cloneRepository({
225
233
  url: 'https://github.com/user/repo.git',
226
- targetDir: join(tempDir, 'repo')
234
+ targetDir: join(tempDir, 'repo'),
227
235
  });
228
236
 
229
237
  expect(result.success).toBe(false);
@@ -238,7 +246,10 @@ describe('GitClone - cloneRepository', () => {
238
246
 
239
247
  mockSpawn.mockImplementation(() => {
240
248
  setImmediate(() => {
241
- (mockProcess.stderr as any).emit('data', Buffer.from('Remote branch nonexistent-branch not found'));
249
+ (mockProcess.stderr as any).emit(
250
+ 'data',
251
+ Buffer.from('Remote branch nonexistent-branch not found')
252
+ );
242
253
  mockProcess.emit('close', 128);
243
254
  });
244
255
  return mockProcess;
@@ -247,7 +258,7 @@ describe('GitClone - cloneRepository', () => {
247
258
  const result = await cloneRepository({
248
259
  url: 'https://github.com/user/repo.git',
249
260
  targetDir: join(tempDir, 'repo'),
250
- branch: 'nonexistent-branch'
261
+ branch: 'nonexistent-branch',
251
262
  });
252
263
 
253
264
  expect(result.success).toBe(false);