@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.
- package/README.md +330 -370
- package/dist/cli/cleanpr.js +35 -4
- package/dist/cli/cleanpr.js.map +1 -1
- package/dist/cli/cleanpr.test.js +256 -0
- package/dist/cli/cleanpr.test.js.map +1 -1
- package/dist/cli/lswt.js +54 -6
- package/dist/cli/lswt.js.map +1 -1
- package/dist/cli/lswt.test.js +207 -0
- package/dist/cli/lswt.test.js.map +1 -1
- package/dist/cli/newpr.js +135 -76
- package/dist/cli/newpr.js.map +1 -1
- package/dist/cli/newpr.test.js +35 -16
- package/dist/cli/newpr.test.js.map +1 -1
- package/dist/cli/wt/clean.d.ts +17 -0
- package/dist/cli/wt/clean.d.ts.map +1 -0
- package/dist/cli/wt/clean.js +74 -0
- package/dist/cli/wt/clean.js.map +1 -0
- package/dist/cli/wt/completion.d.ts +12 -0
- package/dist/cli/wt/completion.d.ts.map +1 -0
- package/dist/cli/wt/completion.js +246 -0
- package/dist/cli/wt/completion.js.map +1 -0
- package/dist/cli/wt/completion.test.d.ts +5 -0
- package/dist/cli/wt/completion.test.d.ts.map +1 -0
- package/dist/cli/wt/completion.test.js +173 -0
- package/dist/cli/wt/completion.test.js.map +1 -0
- package/dist/cli/wt/config.d.ts +13 -0
- package/dist/cli/wt/config.d.ts.map +1 -0
- package/dist/cli/wt/config.js +175 -0
- package/dist/cli/wt/config.js.map +1 -0
- package/dist/cli/wt/config.test.d.ts +5 -0
- package/dist/cli/wt/config.test.d.ts.map +1 -0
- package/dist/cli/wt/config.test.js +260 -0
- package/dist/cli/wt/config.test.js.map +1 -0
- package/dist/cli/wt/entry.test.d.ts +8 -0
- package/dist/cli/wt/entry.test.d.ts.map +1 -0
- package/dist/cli/wt/entry.test.js +201 -0
- package/dist/cli/wt/entry.test.js.map +1 -0
- package/dist/cli/wt/init.d.ts +14 -0
- package/dist/cli/wt/init.d.ts.map +1 -0
- package/dist/cli/wt/init.js +209 -0
- package/dist/cli/wt/init.js.map +1 -0
- package/dist/cli/wt/init.test.d.ts +5 -0
- package/dist/cli/wt/init.test.d.ts.map +1 -0
- package/dist/cli/wt/init.test.js +165 -0
- package/dist/cli/wt/init.test.js.map +1 -0
- package/dist/cli/wt/init.unit.test.d.ts +5 -0
- package/dist/cli/wt/init.unit.test.d.ts.map +1 -0
- package/dist/cli/wt/init.unit.test.js +432 -0
- package/dist/cli/wt/init.unit.test.js.map +1 -0
- package/dist/cli/wt/interactive-menu.d.ts +41 -0
- package/dist/cli/wt/interactive-menu.d.ts.map +1 -0
- package/dist/cli/wt/interactive-menu.js +639 -0
- package/dist/cli/wt/interactive-menu.js.map +1 -0
- package/dist/cli/wt/interactive-menu.test.d.ts +10 -0
- package/dist/cli/wt/interactive-menu.test.d.ts.map +1 -0
- package/dist/cli/wt/interactive-menu.test.js +711 -0
- package/dist/cli/wt/interactive-menu.test.js.map +1 -0
- package/dist/cli/wt/link.d.ts +22 -0
- package/dist/cli/wt/link.d.ts.map +1 -0
- package/dist/cli/wt/link.js +115 -0
- package/dist/cli/wt/link.js.map +1 -0
- package/dist/cli/wt/list.d.ts +16 -0
- package/dist/cli/wt/list.d.ts.map +1 -0
- package/dist/cli/wt/list.js +65 -0
- package/dist/cli/wt/list.js.map +1 -0
- package/dist/cli/wt/new.d.ts +24 -0
- package/dist/cli/wt/new.d.ts.map +1 -0
- package/dist/cli/wt/new.js +130 -0
- package/dist/cli/wt/new.js.map +1 -0
- package/dist/cli/wt/run-command.d.ts +31 -0
- package/dist/cli/wt/run-command.d.ts.map +1 -0
- package/dist/cli/wt/run-command.js +49 -0
- package/dist/cli/wt/run-command.js.map +1 -0
- package/dist/cli/wt/run-command.test.d.ts +5 -0
- package/dist/cli/wt/run-command.test.d.ts.map +1 -0
- package/dist/cli/wt/run-command.test.js +88 -0
- package/dist/cli/wt/run-command.test.js.map +1 -0
- package/dist/cli/wt/state.d.ts +13 -0
- package/dist/cli/wt/state.d.ts.map +1 -0
- package/dist/cli/wt/state.js +38 -0
- package/dist/cli/wt/state.js.map +1 -0
- package/dist/cli/wt/wt.test.d.ts +8 -0
- package/dist/cli/wt/wt.test.d.ts.map +1 -0
- package/dist/cli/wt/wt.test.js +521 -0
- package/dist/cli/wt/wt.test.js.map +1 -0
- package/dist/cli/wt.d.ts +26 -0
- package/dist/cli/wt.d.ts.map +1 -0
- package/dist/cli/wt.js +169 -0
- package/dist/cli/wt.js.map +1 -0
- package/dist/cli/wt.unit.test.d.ts +7 -0
- package/dist/cli/wt.unit.test.d.ts.map +1 -0
- package/dist/cli/wt.unit.test.js +182 -0
- package/dist/cli/wt.unit.test.js.map +1 -0
- package/dist/cli/wtconfig.js +22 -8
- package/dist/cli/wtconfig.js.map +1 -1
- package/dist/cli/wtconfig.test.js +18 -16
- package/dist/cli/wtconfig.test.js.map +1 -1
- package/dist/cli/wtlink.js +66 -9
- package/dist/cli/wtlink.js.map +1 -1
- package/dist/cli/wtlink.test.js +101 -0
- package/dist/cli/wtlink.test.js.map +1 -1
- package/dist/e2e/cli.e2e.test.js +97 -1
- package/dist/e2e/cli.e2e.test.js.map +1 -1
- package/dist/e2e/lswt/lswt.e2e.test.js +33 -0
- package/dist/e2e/lswt/lswt.e2e.test.js.map +1 -1
- package/dist/e2e/newpr/scenarios.e2e.test.js +7 -7
- package/dist/e2e/newpr/scenarios.e2e.test.js.map +1 -1
- package/dist/e2e/wt/wt.e2e.test.d.ts +9 -0
- package/dist/e2e/wt/wt.e2e.test.d.ts.map +1 -0
- package/dist/e2e/wt/wt.e2e.test.js +384 -0
- package/dist/e2e/wt/wt.e2e.test.js.map +1 -0
- package/dist/e2e/wtlink/wtlink.e2e.test.js +52 -0
- package/dist/e2e/wtlink/wtlink.e2e.test.js.map +1 -1
- package/dist/lib/ai/base-provider.d.ts +22 -0
- package/dist/lib/ai/base-provider.d.ts.map +1 -1
- package/dist/lib/ai/base-provider.js +180 -99
- package/dist/lib/ai/base-provider.js.map +1 -1
- package/dist/lib/ai/base-provider.test.js +13 -14
- package/dist/lib/ai/base-provider.test.js.map +1 -1
- package/dist/lib/ai/cli-provider.d.ts +11 -7
- package/dist/lib/ai/cli-provider.d.ts.map +1 -1
- package/dist/lib/ai/cli-provider.js +19 -49
- package/dist/lib/ai/cli-provider.js.map +1 -1
- package/dist/lib/ai/cli-provider.test.js +47 -49
- package/dist/lib/ai/cli-provider.test.js.map +1 -1
- package/dist/lib/ai/index.d.ts +2 -1
- package/dist/lib/ai/index.d.ts.map +1 -1
- package/dist/lib/ai/index.js +2 -0
- package/dist/lib/ai/index.js.map +1 -1
- package/dist/lib/ai/provider-manager.js +2 -2
- package/dist/lib/ai/provider-manager.js.map +1 -1
- package/dist/lib/ai/repo-docs.d.ts +43 -0
- package/dist/lib/ai/repo-docs.d.ts.map +1 -0
- package/dist/lib/ai/repo-docs.js +274 -0
- package/dist/lib/ai/repo-docs.js.map +1 -0
- package/dist/lib/ai/repo-docs.test.d.ts +5 -0
- package/dist/lib/ai/repo-docs.test.d.ts.map +1 -0
- package/dist/lib/ai/repo-docs.test.js +357 -0
- package/dist/lib/ai/repo-docs.test.js.map +1 -0
- package/dist/lib/ai/types.d.ts +18 -2
- package/dist/lib/ai/types.d.ts.map +1 -1
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/config-editor.d.ts +21 -0
- package/dist/lib/config-editor.d.ts.map +1 -0
- package/dist/lib/config-editor.js +729 -0
- package/dist/lib/config-editor.js.map +1 -0
- package/dist/lib/config-editor.test.d.ts +11 -0
- package/dist/lib/config-editor.test.d.ts.map +1 -0
- package/dist/lib/config-editor.test.js +526 -0
- package/dist/lib/config-editor.test.js.map +1 -0
- package/dist/lib/config-validation.d.ts +28 -0
- package/dist/lib/config-validation.d.ts.map +1 -0
- package/dist/lib/config-validation.js +534 -0
- package/dist/lib/config-validation.js.map +1 -0
- package/dist/lib/config-validation.test.d.ts +5 -0
- package/dist/lib/config-validation.test.d.ts.map +1 -0
- package/dist/lib/config-validation.test.js +398 -0
- package/dist/lib/config-validation.test.js.map +1 -0
- package/dist/lib/config.d.ts +115 -6
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +251 -55
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/config.test.js +2 -1
- package/dist/lib/config.test.js.map +1 -1
- package/dist/lib/constants.d.ts +50 -1
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +67 -1
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/constants.test.d.ts +5 -0
- package/dist/lib/constants.test.d.ts.map +1 -0
- package/dist/lib/constants.test.js +121 -0
- package/dist/lib/constants.test.js.map +1 -0
- package/dist/lib/git.d.ts +12 -0
- package/dist/lib/git.d.ts.map +1 -1
- package/dist/lib/git.js +26 -0
- package/dist/lib/git.js.map +1 -1
- package/dist/lib/global-check.d.ts +38 -0
- package/dist/lib/global-check.d.ts.map +1 -0
- package/dist/lib/global-check.js +135 -0
- package/dist/lib/global-check.js.map +1 -0
- package/dist/lib/global-check.test.d.ts +5 -0
- package/dist/lib/global-check.test.d.ts.map +1 -0
- package/dist/lib/global-check.test.js +153 -0
- package/dist/lib/global-check.test.js.map +1 -0
- package/dist/lib/global-config.d.ts +102 -0
- package/dist/lib/global-config.d.ts.map +1 -0
- package/dist/lib/global-config.js +234 -0
- package/dist/lib/global-config.js.map +1 -0
- package/dist/lib/global-config.test.d.ts +5 -0
- package/dist/lib/global-config.test.d.ts.map +1 -0
- package/dist/lib/global-config.test.js +282 -0
- package/dist/lib/global-config.test.js.map +1 -0
- package/dist/lib/json-output.d.ts +11 -1
- package/dist/lib/json-output.d.ts.map +1 -1
- package/dist/lib/json-output.js +42 -1
- package/dist/lib/json-output.js.map +1 -1
- package/dist/lib/json-output.test.js +2 -0
- package/dist/lib/json-output.test.js.map +1 -1
- package/dist/lib/logger.d.ts +175 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +475 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/logger.test.d.ts +5 -0
- package/dist/lib/logger.test.d.ts.map +1 -0
- package/dist/lib/logger.test.js +292 -0
- package/dist/lib/logger.test.js.map +1 -0
- package/dist/lib/lswt/action-executors.test.js +2 -0
- package/dist/lib/lswt/action-executors.test.js.map +1 -1
- package/dist/lib/lswt/formatters.d.ts +1 -0
- package/dist/lib/lswt/formatters.d.ts.map +1 -1
- package/dist/lib/lswt/formatters.js +6 -1
- package/dist/lib/lswt/formatters.js.map +1 -1
- package/dist/lib/lswt/formatters.test.js +2 -2
- package/dist/lib/lswt/formatters.test.js.map +1 -1
- package/dist/lib/lswt/fuzzy-search.d.ts +27 -0
- package/dist/lib/lswt/fuzzy-search.d.ts.map +1 -0
- package/dist/lib/lswt/fuzzy-search.js +130 -0
- package/dist/lib/lswt/fuzzy-search.js.map +1 -0
- package/dist/lib/lswt/fuzzy-search.test.d.ts +5 -0
- package/dist/lib/lswt/fuzzy-search.test.d.ts.map +1 -0
- package/dist/lib/lswt/fuzzy-search.test.js +207 -0
- package/dist/lib/lswt/fuzzy-search.test.js.map +1 -0
- package/dist/lib/lswt/index.d.ts +1 -0
- package/dist/lib/lswt/index.d.ts.map +1 -1
- package/dist/lib/lswt/index.js +2 -0
- package/dist/lib/lswt/index.js.map +1 -1
- package/dist/lib/lswt/interactive.d.ts +8 -0
- package/dist/lib/lswt/interactive.d.ts.map +1 -1
- package/dist/lib/lswt/interactive.js +169 -20
- package/dist/lib/lswt/interactive.js.map +1 -1
- package/dist/lib/newpr/action-deps.test.d.ts +5 -0
- package/dist/lib/newpr/action-deps.test.d.ts.map +1 -0
- package/dist/lib/newpr/action-deps.test.js +111 -0
- package/dist/lib/newpr/action-deps.test.js.map +1 -0
- package/dist/lib/newpr/args.d.ts.map +1 -1
- package/dist/lib/newpr/args.js +15 -4
- package/dist/lib/newpr/args.js.map +1 -1
- package/dist/lib/newpr/args.test.js +210 -2
- package/dist/lib/newpr/args.test.js.map +1 -1
- package/dist/lib/newpr/types.d.ts +2 -0
- package/dist/lib/newpr/types.d.ts.map +1 -1
- package/dist/lib/prompts.d.ts +10 -0
- package/dist/lib/prompts.d.ts.map +1 -1
- package/dist/lib/prompts.js +200 -1
- package/dist/lib/prompts.js.map +1 -1
- package/dist/lib/prompts.test.js +351 -1
- package/dist/lib/prompts.test.js.map +1 -1
- package/dist/lib/schema.test.d.ts +10 -0
- package/dist/lib/schema.test.d.ts.map +1 -0
- package/dist/lib/schema.test.js +309 -0
- package/dist/lib/schema.test.js.map +1 -0
- package/dist/lib/wtconfig/environment.d.ts.map +1 -1
- package/dist/lib/wtconfig/environment.js +6 -4
- package/dist/lib/wtconfig/environment.js.map +1 -1
- package/dist/lib/wtconfig/environment.test.js +2 -7
- package/dist/lib/wtconfig/environment.test.js.map +1 -1
- package/dist/lib/wtconfig/types.d.ts +3 -1
- package/dist/lib/wtconfig/types.d.ts.map +1 -1
- package/dist/lib/wtlink/link-configs.test.js +282 -2
- package/dist/lib/wtlink/link-configs.test.js.map +1 -1
- package/dist/lib/wtlink/main-menu.js +1 -0
- package/dist/lib/wtlink/main-menu.js.map +1 -1
- package/dist/lib/wtlink/main-menu.test.d.ts +5 -0
- package/dist/lib/wtlink/main-menu.test.d.ts.map +1 -0
- package/dist/lib/wtlink/main-menu.test.js +124 -0
- package/dist/lib/wtlink/main-menu.test.js.map +1 -0
- package/dist/lib/wtlink/manage-manifest.d.ts +5 -0
- package/dist/lib/wtlink/manage-manifest.d.ts.map +1 -1
- package/dist/lib/wtlink/manage-manifest.js +65 -2
- package/dist/lib/wtlink/manage-manifest.js.map +1 -1
- package/dist/lib/wtlink/manage-manifest.test.js +282 -2
- package/dist/lib/wtlink/manage-manifest.test.js.map +1 -1
- package/package.json +3 -1
- 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 @@
|
|
|
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
|