@mrpatronz/nexusflow 0.1.11 → 0.2.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/dist/analyzers/detect-apis.d.ts.map +1 -1
- package/dist/analyzers/detect-apis.js +168 -39
- package/dist/analyzers/detect-apis.js.map +1 -1
- package/dist/analyzers/detect-deps.d.ts +26 -4
- package/dist/analyzers/detect-deps.d.ts.map +1 -1
- package/dist/analyzers/detect-deps.js +228 -66
- package/dist/analyzers/detect-deps.js.map +1 -1
- package/dist/analyzers/index.d.ts +1 -1
- package/dist/analyzers/index.d.ts.map +1 -1
- package/dist/analyzers/index.js +7 -3
- package/dist/analyzers/index.js.map +1 -1
- package/dist/analyzers/readme-summarizer.d.ts +3 -1
- package/dist/analyzers/readme-summarizer.d.ts.map +1 -1
- package/dist/analyzers/readme-summarizer.js +43 -19
- package/dist/analyzers/readme-summarizer.js.map +1 -1
- package/dist/analyzers/tech-stack.d.ts +2 -2
- package/dist/analyzers/tech-stack.d.ts.map +1 -1
- package/dist/analyzers/tech-stack.js +257 -232
- package/dist/analyzers/tech-stack.js.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +15 -8
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +31 -0
- package/dist/commands/sync.js.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +25 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/graph.d.ts.map +1 -1
- package/dist/core/graph.js +1 -2
- package/dist/core/graph.js.map +1 -1
- package/dist/core/packer.d.ts +2 -1
- package/dist/core/packer.d.ts.map +1 -1
- package/dist/core/packer.js +36 -30
- package/dist/core/packer.js.map +1 -1
- package/dist/core/packer.test.js +4 -5
- package/dist/core/packer.test.js.map +1 -1
- package/dist/core/workspace.d.ts.map +1 -1
- package/dist/core/workspace.js +22 -12
- package/dist/core/workspace.js.map +1 -1
- package/dist/generators/base.d.ts.map +1 -1
- package/dist/generators/base.js +64 -31
- package/dist/generators/base.js.map +1 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +17 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/map-generator.d.ts +15 -0
- package/dist/generators/map-generator.d.ts.map +1 -0
- package/dist/generators/map-generator.js +385 -0
- package/dist/generators/map-generator.js.map +1 -0
- package/dist/generators/map-generator.test.d.ts +2 -0
- package/dist/generators/map-generator.test.d.ts.map +1 -0
- package/dist/generators/map-generator.test.js +74 -0
- package/dist/generators/map-generator.test.js.map +1 -0
- package/dist/generators/plan-generator.d.ts +2 -0
- package/dist/generators/plan-generator.d.ts.map +1 -1
- package/dist/generators/plan-generator.js +104 -8
- package/dist/generators/plan-generator.js.map +1 -1
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/analyzers/detect-apis.ts +192 -40
- package/src/analyzers/detect-deps.ts +246 -69
- package/src/analyzers/index.ts +7 -3
- package/src/analyzers/readme-summarizer.ts +48 -19
- package/src/analyzers/tech-stack.ts +222 -194
- package/src/commands/create.ts +16 -10
- package/src/commands/sync.ts +35 -0
- package/src/core/config.ts +25 -0
- package/src/core/graph.ts +1 -2
- package/src/core/packer.test.ts +4 -6
- package/src/core/packer.ts +42 -43
- package/src/core/workspace.ts +23 -13
- package/src/generators/base.ts +68 -30
- package/src/generators/index.ts +17 -0
- package/src/generators/map-generator.test.ts +81 -0
- package/src/generators/map-generator.ts +405 -0
- package/src/generators/plan-generator.ts +117 -7
- package/src/types.ts +13 -0
- package/vitest.config.ts +13 -0
|
@@ -6,12 +6,11 @@
|
|
|
6
6
|
import * as fs from 'node:fs/promises';
|
|
7
7
|
import * as path from 'node:path';
|
|
8
8
|
|
|
9
|
-
/** Maximum chars to extract from README for summary. */
|
|
10
|
-
const MAX_SUMMARY_LENGTH = 800;
|
|
11
|
-
|
|
12
9
|
/**
|
|
13
10
|
* Reads a repository's README.md and extracts the first meaningful
|
|
14
|
-
*
|
|
11
|
+
* prose paragraph as a summary. Looks for README.md (case-insensitive).
|
|
12
|
+
*
|
|
13
|
+
* It automatically strips badges, TOC lists, headers, HTML tags, and redacts ClientIDs/Secrets.
|
|
15
14
|
*
|
|
16
15
|
* @param repoPath - Absolute path to the repository root.
|
|
17
16
|
* @returns The extracted summary text, or null if no README is found.
|
|
@@ -35,34 +34,64 @@ export async function extractReadmeSummary(
|
|
|
35
34
|
|
|
36
35
|
if (!content) return null;
|
|
37
36
|
|
|
38
|
-
// Strip
|
|
37
|
+
// 1. Strip code blocks to avoid false matching on secrets/prose
|
|
38
|
+
content = content.replace(/```[\s\S]*?```/g, '');
|
|
39
|
+
|
|
40
|
+
// 2. Scrub ClientIDs/Secrets/Credentials
|
|
41
|
+
// Match standard secret/ID patterns: GUIDs, client_id/secret variables, hex keys
|
|
42
|
+
const clientSecretsRegex = /(client_id|client_secret|appid|secret|password|key|token|credential)\s*[:=]\s*["']?[a-zA-Z0-9-_\/\+\.]{16,}["']?/gi;
|
|
43
|
+
const guidRegex = /[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/g;
|
|
44
|
+
|
|
45
|
+
content = content
|
|
46
|
+
.replace(clientSecretsRegex, (match, p1) => `${p1}: [REDACTED]`)
|
|
47
|
+
.replace(guidRegex, '[REDACTED_ID]');
|
|
48
|
+
|
|
49
|
+
// 3. Process line-by-line to find the first prose paragraph
|
|
39
50
|
const lines = content.split('\n');
|
|
40
|
-
const
|
|
41
|
-
let foundContent = false;
|
|
51
|
+
const proseLines: string[] = [];
|
|
42
52
|
|
|
43
53
|
for (const line of lines) {
|
|
44
54
|
const trimmed = line.trim();
|
|
45
55
|
|
|
46
|
-
// Skip empty lines
|
|
47
|
-
if (!
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
// Skip empty lines
|
|
57
|
+
if (!trimmed) {
|
|
58
|
+
if (proseLines.length > 0) {
|
|
59
|
+
// We found a paragraph and hit an empty line. Let's finish!
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
51
64
|
|
|
52
65
|
// Skip HTML tags
|
|
53
66
|
if (trimmed.startsWith('<') && trimmed.endsWith('>')) continue;
|
|
54
67
|
|
|
68
|
+
// Skip badges
|
|
69
|
+
if (trimmed.startsWith('[ or * 1. [Section](#section)
|
|
79
|
+
if (/^[-*+]\s*(\d+\.)?\s*\[[^\]]+\]\(#[^)]+\)/.test(trimmed)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Skip standard bullet lists if we are searching for prose (e.g. at the top of README before prose)
|
|
84
|
+
if (proseLines.length === 0 && /^[-*+]\s+/.test(trimmed)) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
60
87
|
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
if (currentLength >= MAX_SUMMARY_LENGTH) break;
|
|
88
|
+
// Accumulate prose lines
|
|
89
|
+
proseLines.push(line);
|
|
64
90
|
}
|
|
65
91
|
|
|
66
|
-
const summary =
|
|
67
|
-
|
|
92
|
+
const summary = proseLines.join(' ').replace(/\s+/g, ' ').trim();
|
|
93
|
+
if (!summary) return null;
|
|
94
|
+
|
|
95
|
+
// Limit the summary to a concise paragraph (first 250 characters)
|
|
96
|
+
return summary.length > 250 ? summary.slice(0, 250) + '...' : summary;
|
|
68
97
|
}
|
|
@@ -6,19 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
import * as fs from 'node:fs/promises';
|
|
8
8
|
import * as path from 'node:path';
|
|
9
|
+
import { globby } from 'globby';
|
|
9
10
|
|
|
10
11
|
import type { TechStack, Language, Framework, ProjectType } from '../types.js';
|
|
11
12
|
|
|
12
|
-
/** Check if a file exists in a directory. */
|
|
13
|
-
async function fileExists(dir: string, filename: string): Promise<boolean> {
|
|
14
|
-
try {
|
|
15
|
-
await fs.access(path.join(dir, filename));
|
|
16
|
-
return true;
|
|
17
|
-
} catch {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
13
|
/** Safely read and parse a JSON file. Returns null on failure. */
|
|
23
14
|
async function readJson(filePath: string): Promise<Record<string, unknown> | null> {
|
|
24
15
|
try {
|
|
@@ -29,227 +20,264 @@ async function readJson(filePath: string): Promise<Record<string, unknown> | nul
|
|
|
29
20
|
}
|
|
30
21
|
}
|
|
31
22
|
|
|
32
|
-
/** Check if any file matching a glob-like pattern exists. */
|
|
33
|
-
async function hasFileWithExtension(dir: string, ext: string): Promise<boolean> {
|
|
34
|
-
try {
|
|
35
|
-
const entries = await fs.readdir(dir);
|
|
36
|
-
return entries.some((e) => e.endsWith(ext));
|
|
37
|
-
} catch {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
23
|
/**
|
|
43
|
-
* Detects the tech stack of a repository by inspecting its
|
|
44
|
-
* for manifest files, config files, and project structures.
|
|
24
|
+
* Detects the tech stack of a repository by recursively inspecting its
|
|
25
|
+
* contents for manifest files, config files, and project structures.
|
|
45
26
|
*
|
|
46
27
|
* @param repoPath - Absolute path to the repository root.
|
|
47
28
|
* @returns A {@link TechStack} object describing the detected stack.
|
|
48
29
|
*/
|
|
49
30
|
export async function detectTechStack(repoPath: string): Promise<TechStack> {
|
|
50
|
-
const languages
|
|
51
|
-
const frameworks
|
|
52
|
-
const buildTools
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// ── Node.js / JavaScript / TypeScript ──────────────────────────────
|
|
56
|
-
const hasPackageJson = await fileExists(repoPath, 'package.json');
|
|
57
|
-
if (hasPackageJson) {
|
|
58
|
-
const pkg = await readJson(path.join(repoPath, 'package.json'));
|
|
59
|
-
|
|
60
|
-
// Detect TypeScript vs JavaScript
|
|
61
|
-
const hasTsConfig = await fileExists(repoPath, 'tsconfig.json');
|
|
62
|
-
if (hasTsConfig) {
|
|
63
|
-
languages.push('typescript');
|
|
64
|
-
} else {
|
|
65
|
-
languages.push('javascript');
|
|
66
|
-
}
|
|
31
|
+
const languages = new Set<Language>();
|
|
32
|
+
const frameworks = new Set<Framework>();
|
|
33
|
+
const buildTools = new Set<string>();
|
|
34
|
+
const projectTypes = new Set<ProjectType>();
|
|
67
35
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
36
|
+
try {
|
|
37
|
+
const files = await globby([
|
|
38
|
+
'**/package.json',
|
|
39
|
+
'**/tsconfig.json',
|
|
40
|
+
'**/*.csproj',
|
|
41
|
+
'**/*.sln',
|
|
42
|
+
'**/requirements.txt',
|
|
43
|
+
'**/pyproject.toml',
|
|
44
|
+
'**/setup.py',
|
|
45
|
+
'**/go.mod',
|
|
46
|
+
'**/pom.xml',
|
|
47
|
+
'**/build.gradle',
|
|
48
|
+
'**/build.gradle.kts',
|
|
49
|
+
'**/Cargo.toml',
|
|
50
|
+
'**/Gemfile',
|
|
51
|
+
'**/composer.json',
|
|
52
|
+
'**/Dockerfile',
|
|
53
|
+
'**/docker-compose.yml',
|
|
54
|
+
'**/docker-compose.yaml',
|
|
55
|
+
'**/compose.yaml',
|
|
56
|
+
'**/compose.yml',
|
|
57
|
+
], {
|
|
58
|
+
cwd: repoPath,
|
|
59
|
+
absolute: true,
|
|
60
|
+
ignore: ['**/node_modules/**', '**/bin/**', '**/obj/**', '**/dist/**', '**/out/**', '**/.git/**'],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const fileMap = new Map<string, string[]>();
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
const name = path.basename(file);
|
|
66
|
+
let list = fileMap.get(name);
|
|
67
|
+
if (!list) {
|
|
68
|
+
list = [];
|
|
69
|
+
fileMap.set(name, list);
|
|
70
|
+
}
|
|
71
|
+
list.push(file);
|
|
96
72
|
}
|
|
97
|
-
}
|
|
98
73
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
74
|
+
// ── Node.js / JavaScript / TypeScript ──────────────────────────────
|
|
75
|
+
const packageJsons = fileMap.get('package.json') || [];
|
|
76
|
+
if (packageJsons.length > 0) {
|
|
77
|
+
const tsconfigs = fileMap.get('tsconfig.json') || [];
|
|
78
|
+
if (tsconfigs.length > 0) {
|
|
79
|
+
languages.add('typescript');
|
|
80
|
+
} else {
|
|
81
|
+
languages.add('javascript');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const pj of packageJsons) {
|
|
85
|
+
const pkg = await readJson(pj);
|
|
86
|
+
if (pkg) {
|
|
87
|
+
const allDeps = {
|
|
88
|
+
...(pkg.dependencies as Record<string, string> | undefined),
|
|
89
|
+
...(pkg.devDependencies as Record<string, string> | undefined),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Frameworks
|
|
93
|
+
if (allDeps['next']) { frameworks.add('nextjs'); projectTypes.add('fullstack'); }
|
|
94
|
+
if (allDeps['react'] && !allDeps['next']) { frameworks.add('react'); projectTypes.add('frontend'); }
|
|
95
|
+
if (allDeps['@angular/core']) { frameworks.add('angular'); projectTypes.add('frontend'); }
|
|
96
|
+
if (allDeps['vue']) { frameworks.add('vue'); projectTypes.add('frontend'); }
|
|
97
|
+
if (allDeps['svelte']) { frameworks.add('svelte'); projectTypes.add('frontend'); }
|
|
98
|
+
if (allDeps['express']) { frameworks.add('express'); projectTypes.add('backend'); }
|
|
99
|
+
if (allDeps['@nestjs/core']) { frameworks.add('nestjs'); projectTypes.add('backend'); }
|
|
100
|
+
if (allDeps['fastify']) { frameworks.add('fastify'); projectTypes.add('backend'); }
|
|
101
|
+
if (allDeps['hono']) { frameworks.add('hono'); projectTypes.add('backend'); }
|
|
102
|
+
|
|
103
|
+
// Build tools
|
|
104
|
+
if (allDeps['vite'] || allDeps['@vitejs/plugin-react']) buildTools.add('vite');
|
|
105
|
+
if (allDeps['webpack']) buildTools.add('webpack');
|
|
106
|
+
if (allDeps['esbuild']) buildTools.add('esbuild');
|
|
107
|
+
if (allDeps['rollup']) buildTools.add('rollup');
|
|
108
|
+
if (allDeps['turbo']) buildTools.add('turborepo');
|
|
109
|
+
if (allDeps['nx']) buildTools.add('nx');
|
|
110
|
+
|
|
111
|
+
// Project type heuristics
|
|
112
|
+
if (pkg.bin) projectTypes.add('cli');
|
|
113
|
+
if (pkg.main && !pkg.bin && frameworks.size === 0) projectTypes.add('library');
|
|
118
114
|
}
|
|
119
115
|
}
|
|
120
|
-
} catch {
|
|
121
|
-
// Ignore read errors
|
|
122
116
|
}
|
|
123
117
|
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
// ── .NET / C# ─────────────────────────────────────────────────────
|
|
119
|
+
const csprojFiles = files.filter((f) => f.endsWith('.csproj'));
|
|
120
|
+
const slnFiles = files.filter((f) => f.endsWith('.sln'));
|
|
121
|
+
if (csprojFiles.length > 0 || slnFiles.length > 0) {
|
|
122
|
+
languages.add('csharp');
|
|
123
|
+
buildTools.add('dotnet');
|
|
126
124
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
} else if (hasPyproject) {
|
|
140
|
-
content = await fs.readFile(path.join(repoPath, 'pyproject.toml'), 'utf-8');
|
|
125
|
+
for (const csproj of csprojFiles) {
|
|
126
|
+
try {
|
|
127
|
+
const content = await fs.readFile(csproj, 'utf-8');
|
|
128
|
+
if (content.includes('Microsoft.NET.Sdk.Web')) {
|
|
129
|
+
frameworks.add('aspnet');
|
|
130
|
+
projectTypes.add('backend');
|
|
131
|
+
}
|
|
132
|
+
if (content.includes('Microsoft.NET.Sdk.BlazorWebAssembly') || content.includes('Blazor')) {
|
|
133
|
+
frameworks.add('blazor');
|
|
134
|
+
projectTypes.add('frontend');
|
|
135
|
+
}
|
|
136
|
+
} catch {}
|
|
141
137
|
}
|
|
142
|
-
const lower = content.toLowerCase();
|
|
143
|
-
if (lower.includes('django')) { frameworks.push('django'); projectType = 'backend'; }
|
|
144
|
-
if (lower.includes('flask')) { frameworks.push('flask'); projectType = 'backend'; }
|
|
145
|
-
if (lower.includes('fastapi')) { frameworks.push('fastapi'); projectType = 'backend'; }
|
|
146
|
-
} catch {
|
|
147
|
-
// Ignore
|
|
148
138
|
}
|
|
149
139
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
140
|
+
// ── Python ────────────────────────────────────────────────────────
|
|
141
|
+
const pyprojectTomls = fileMap.get('pyproject.toml') || [];
|
|
142
|
+
const requirementsTxts = fileMap.get('requirements.txt') || [];
|
|
143
|
+
const setupPys = fileMap.get('setup.py') || [];
|
|
144
|
+
|
|
145
|
+
if (pyprojectTomls.length > 0 || requirementsTxts.length > 0 || setupPys.length > 0) {
|
|
146
|
+
languages.add('python');
|
|
153
147
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (content.includes('github.com/gin-gonic/gin')) {
|
|
163
|
-
frameworks.push('gin');
|
|
164
|
-
projectType = 'backend';
|
|
148
|
+
for (const reqFile of [...requirementsTxts, ...pyprojectTomls]) {
|
|
149
|
+
try {
|
|
150
|
+
const content = await fs.readFile(reqFile, 'utf-8');
|
|
151
|
+
const lower = content.toLowerCase();
|
|
152
|
+
if (lower.includes('django')) { frameworks.add('django'); projectTypes.add('backend'); }
|
|
153
|
+
if (lower.includes('flask')) { frameworks.add('flask'); projectTypes.add('backend'); }
|
|
154
|
+
if (lower.includes('fastapi')) { frameworks.add('fastapi'); projectTypes.add('backend'); }
|
|
155
|
+
} catch {}
|
|
165
156
|
}
|
|
166
|
-
|
|
167
|
-
|
|
157
|
+
|
|
158
|
+
if (pyprojectTomls.length > 0) buildTools.add('pyproject');
|
|
159
|
+
if (setupPys.length > 0) buildTools.add('setuptools');
|
|
168
160
|
}
|
|
169
|
-
}
|
|
170
161
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
content = await fs.readFile(path.join(repoPath, 'build.gradle'), 'utf-8');
|
|
162
|
+
// ── Go ────────────────────────────────────────────────────────────
|
|
163
|
+
const goMods = fileMap.get('go.mod') || [];
|
|
164
|
+
if (goMods.length > 0) {
|
|
165
|
+
languages.add('go');
|
|
166
|
+
buildTools.add('go');
|
|
167
|
+
|
|
168
|
+
for (const goMod of goMods) {
|
|
169
|
+
try {
|
|
170
|
+
const content = await fs.readFile(goMod, 'utf-8');
|
|
171
|
+
if (content.includes('github.com/gin-gonic/gin')) {
|
|
172
|
+
frameworks.add('gin');
|
|
173
|
+
projectTypes.add('backend');
|
|
174
|
+
}
|
|
175
|
+
} catch {}
|
|
186
176
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── Java ──────────────────────────────────────────────────────────
|
|
180
|
+
const poms = fileMap.get('pom.xml') || [];
|
|
181
|
+
const gradles = fileMap.get('build.gradle') || [];
|
|
182
|
+
const gradleKts = fileMap.get('build.gradle.kts') || [];
|
|
183
|
+
|
|
184
|
+
if (poms.length > 0 || gradles.length > 0 || gradleKts.length > 0) {
|
|
185
|
+
languages.add('java');
|
|
186
|
+
if (poms.length > 0) buildTools.add('maven');
|
|
187
|
+
if (gradles.length > 0 || gradleKts.length > 0) buildTools.add('gradle');
|
|
188
|
+
|
|
189
|
+
for (const f of [...poms, ...gradles, ...gradleKts]) {
|
|
190
|
+
try {
|
|
191
|
+
const content = await fs.readFile(f, 'utf-8');
|
|
192
|
+
if (content.includes('spring')) {
|
|
193
|
+
frameworks.add('spring');
|
|
194
|
+
projectTypes.add('backend');
|
|
195
|
+
}
|
|
196
|
+
} catch {}
|
|
190
197
|
}
|
|
191
|
-
} catch {
|
|
192
|
-
// Ignore
|
|
193
198
|
}
|
|
194
|
-
}
|
|
195
199
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
// ── Rust ──────────────────────────────────────────────────────────
|
|
201
|
+
const cargoTomls = fileMap.get('Cargo.toml') || [];
|
|
202
|
+
if (cargoTomls.length > 0) {
|
|
203
|
+
languages.add('rust');
|
|
204
|
+
buildTools.add('cargo');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ── Ruby ──────────────────────────────────────────────────────────
|
|
208
|
+
const gemfiles = fileMap.get('Gemfile') || [];
|
|
209
|
+
if (gemfiles.length > 0) {
|
|
210
|
+
languages.add('ruby');
|
|
211
|
+
buildTools.add('bundler');
|
|
202
212
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (content.includes('rails')) {
|
|
212
|
-
frameworks.push('rails');
|
|
213
|
-
projectType = 'backend';
|
|
213
|
+
for (const gemfile of gemfiles) {
|
|
214
|
+
try {
|
|
215
|
+
const content = await fs.readFile(gemfile, 'utf-8');
|
|
216
|
+
if (content.includes('rails')) {
|
|
217
|
+
frameworks.add('rails');
|
|
218
|
+
projectTypes.add('backend');
|
|
219
|
+
}
|
|
220
|
+
} catch {}
|
|
214
221
|
}
|
|
215
|
-
} catch {
|
|
216
|
-
// Ignore
|
|
217
222
|
}
|
|
218
|
-
}
|
|
219
223
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
224
|
+
// ── PHP ───────────────────────────────────────────────────────────
|
|
225
|
+
const composerJsons = fileMap.get('composer.json') || [];
|
|
226
|
+
if (composerJsons.length > 0) {
|
|
227
|
+
languages.add('php');
|
|
228
|
+
buildTools.add('composer');
|
|
229
|
+
|
|
230
|
+
for (const comp of composerJsons) {
|
|
231
|
+
const composer = await readJson(comp);
|
|
232
|
+
if (composer) {
|
|
233
|
+
const req = composer.require as Record<string, string> | undefined;
|
|
234
|
+
if (req?.['laravel/framework']) {
|
|
235
|
+
frameworks.add('laravel');
|
|
236
|
+
projectTypes.add('backend');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
232
239
|
}
|
|
233
240
|
}
|
|
234
|
-
}
|
|
235
241
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
242
|
+
// ── Docker ────────────────────────────────────────────────────────
|
|
243
|
+
const dockerfiles = fileMap.get('Dockerfile') || [];
|
|
244
|
+
if (dockerfiles.length > 0) {
|
|
245
|
+
buildTools.add('docker');
|
|
246
|
+
}
|
|
247
|
+
const hasCompose = ['docker-compose.yml', 'docker-compose.yaml', 'compose.yaml', 'compose.yml'].some(
|
|
248
|
+
(name) => (fileMap.get(name) || []).length > 0
|
|
249
|
+
);
|
|
250
|
+
if (hasCompose) {
|
|
251
|
+
buildTools.add('docker-compose');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
} catch {
|
|
255
|
+
// Ignore errors during glob/analysis
|
|
239
256
|
}
|
|
240
|
-
|
|
241
|
-
|
|
257
|
+
|
|
258
|
+
// Fallback language
|
|
259
|
+
if (languages.size === 0) {
|
|
260
|
+
languages.add('other');
|
|
242
261
|
}
|
|
243
262
|
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
263
|
+
// Heuristic for overall projectType
|
|
264
|
+
let finalProjectType: ProjectType = 'other';
|
|
265
|
+
if (projectTypes.has('fullstack') || (projectTypes.has('frontend') && projectTypes.has('backend'))) {
|
|
266
|
+
finalProjectType = 'fullstack';
|
|
267
|
+
} else if (projectTypes.has('frontend')) {
|
|
268
|
+
finalProjectType = 'frontend';
|
|
269
|
+
} else if (projectTypes.has('backend')) {
|
|
270
|
+
finalProjectType = 'backend';
|
|
271
|
+
} else if (projectTypes.has('library')) {
|
|
272
|
+
finalProjectType = 'library';
|
|
273
|
+
} else if (projectTypes.has('cli')) {
|
|
274
|
+
finalProjectType = 'cli';
|
|
247
275
|
}
|
|
248
276
|
|
|
249
277
|
return {
|
|
250
|
-
languages,
|
|
251
|
-
frameworks,
|
|
252
|
-
buildTools,
|
|
253
|
-
projectType,
|
|
278
|
+
languages: Array.from(languages),
|
|
279
|
+
frameworks: Array.from(frameworks),
|
|
280
|
+
buildTools: Array.from(buildTools),
|
|
281
|
+
projectType: finalProjectType,
|
|
254
282
|
};
|
|
255
283
|
}
|
package/src/commands/create.ts
CHANGED
|
@@ -88,7 +88,8 @@ export async function createCommand(): Promise<void> {
|
|
|
88
88
|
id: branchName,
|
|
89
89
|
branchName,
|
|
90
90
|
description,
|
|
91
|
-
repos: selectedRepos.map((r) => r.
|
|
91
|
+
repos: selectedRepos.map((r) => path.join(workspacePath, r.name)),
|
|
92
|
+
originalRepos: selectedRepos.map((r) => r.path),
|
|
92
93
|
assistants: selectedAI,
|
|
93
94
|
workspacePath,
|
|
94
95
|
createdAt: new Date().toISOString(),
|
|
@@ -119,15 +120,20 @@ export async function createCommand(): Promise<void> {
|
|
|
119
120
|
await generateContextFiles(ctx, selectedAI, workspacePath);
|
|
120
121
|
|
|
121
122
|
// ── 8.5. Pack codebase context ──────────────────────────────────────
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
123
|
+
if (config.packContextXml) {
|
|
124
|
+
const packSpinner = ora('Packing codebase context with Repomix...').start();
|
|
125
|
+
try {
|
|
126
|
+
const packResult = await packWorkspace(workspacePath);
|
|
127
|
+
const filesCount = packResult.outputPaths?.length || 1;
|
|
128
|
+
packSpinner.succeed(
|
|
129
|
+
`Packed codebase context into ${filesCount} file(s) (${packResult.totalFiles} files total, ${(packResult.fileSize / 1024).toFixed(2)} KB)`
|
|
130
|
+
);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
packSpinner.fail('Failed to pack codebase context');
|
|
133
|
+
console.error(chalk.red(` ${error}`));
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
console.log(chalk.gray('\n ○ Skipping codebase context packing (packContextXml is disabled)'));
|
|
131
137
|
}
|
|
132
138
|
|
|
133
139
|
// ── 8. Open in editor ───────────────────────────────────────────────
|