Merge pull request #1326 from gsd-build/fix/1325-android-project-detection

fix: expand brownfield detection for Android, Kotlin, Gradle, and more
This commit is contained in:
Tom Boucher
2026-03-22 14:21:33 -04:00
committed by GitHub
2 changed files with 111 additions and 2 deletions

View File

@@ -252,7 +252,23 @@ function cmdInitNewProject(cwd, raw) {
let hasCode = false;
let hasPackageFile = false;
try {
const codeExtensions = new Set(['.ts', '.js', '.py', '.go', '.rs', '.swift', '.java']);
const codeExtensions = new Set([
'.ts', '.js', '.py', '.go', '.rs', '.swift', '.java',
'.kt', '.kts', // Kotlin (Android, server-side)
'.c', '.cpp', '.h', // C/C++
'.cs', // C#
'.rb', // Ruby
'.php', // PHP
'.dart', // Dart (Flutter)
'.m', '.mm', // Objective-C / Objective-C++
'.scala', // Scala
'.groovy', // Groovy (Gradle build scripts)
'.lua', // Lua
'.r', '.R', // R
'.zig', // Zig
'.ex', '.exs', // Elixir
'.clj', // Clojure
]);
const skipDirs = new Set(['node_modules', '.git', '.planning', '.claude', '__pycache__', 'target', 'dist', 'build']);
function findCodeFiles(dir, depth) {
if (depth > 3) return false;
@@ -273,7 +289,18 @@ function cmdInitNewProject(cwd, raw) {
pathExistsInternal(cwd, 'requirements.txt') ||
pathExistsInternal(cwd, 'Cargo.toml') ||
pathExistsInternal(cwd, 'go.mod') ||
pathExistsInternal(cwd, 'Package.swift');
pathExistsInternal(cwd, 'Package.swift') ||
pathExistsInternal(cwd, 'build.gradle') ||
pathExistsInternal(cwd, 'build.gradle.kts') ||
pathExistsInternal(cwd, 'pom.xml') ||
pathExistsInternal(cwd, 'Gemfile') ||
pathExistsInternal(cwd, 'composer.json') ||
pathExistsInternal(cwd, 'pubspec.yaml') ||
pathExistsInternal(cwd, 'CMakeLists.txt') ||
pathExistsInternal(cwd, 'Makefile') ||
pathExistsInternal(cwd, 'build.zig') ||
pathExistsInternal(cwd, 'mix.exs') ||
pathExistsInternal(cwd, 'project.clj');
const result = {
// Models

View File

@@ -1022,6 +1022,88 @@ describe('cmdInitNewProject', () => {
const output = JSON.parse(result.output);
assert.strictEqual(output.planning_exists, true);
});
test('brownfield with Kotlin files detected (Android project)', () => {
const srcDir = path.join(tmpDir, 'app', 'src', 'main');
fs.mkdirSync(srcDir, { recursive: true });
fs.writeFileSync(path.join(srcDir, 'MainActivity.kt'), 'class MainActivity');
const result = runGsdTools('init new-project', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.has_existing_code, true);
assert.strictEqual(output.is_brownfield, true);
});
test('brownfield with build.gradle detected (Android/Gradle project)', () => {
fs.writeFileSync(path.join(tmpDir, 'build.gradle'), 'apply plugin: "com.android.application"');
const result = runGsdTools('init new-project', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.has_package_file, true);
assert.strictEqual(output.is_brownfield, true);
assert.strictEqual(output.needs_codebase_map, true);
});
test('brownfield with build.gradle.kts detected (Kotlin DSL)', () => {
fs.writeFileSync(path.join(tmpDir, 'build.gradle.kts'), 'plugins { id("com.android.application") }');
const result = runGsdTools('init new-project', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.has_package_file, true);
assert.strictEqual(output.is_brownfield, true);
});
test('brownfield with pom.xml detected (Maven project)', () => {
fs.writeFileSync(path.join(tmpDir, 'pom.xml'), '<project></project>');
const result = runGsdTools('init new-project', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.has_package_file, true);
assert.strictEqual(output.is_brownfield, true);
});
test('brownfield with pubspec.yaml detected (Flutter/Dart project)', () => {
fs.writeFileSync(path.join(tmpDir, 'pubspec.yaml'), 'name: my_app');
const result = runGsdTools('init new-project', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.has_package_file, true);
assert.strictEqual(output.is_brownfield, true);
});
test('brownfield with Dart files detected', () => {
const libDir = path.join(tmpDir, 'lib');
fs.mkdirSync(libDir, { recursive: true });
fs.writeFileSync(path.join(libDir, 'main.dart'), 'void main() {}');
const result = runGsdTools('init new-project', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.has_existing_code, true);
assert.strictEqual(output.is_brownfield, true);
});
test('brownfield with C++ files detected', () => {
fs.writeFileSync(path.join(tmpDir, 'main.cpp'), 'int main() { return 0; }');
const result = runGsdTools('init new-project', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.has_existing_code, true);
assert.strictEqual(output.is_brownfield, true);
});
});
// ─────────────────────────────────────────────────────────────────────────────