From dbc5b2ab87ed4aa3490dc8f1c51396594680eb79 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sun, 22 Mar 2026 14:19:30 -0400 Subject: [PATCH] fix: expand brownfield project detection to cover Android, Kotlin, Gradle, and 15+ additional ecosystems The code/package detection in cmdInitNewProject only recognized 7 file extensions and 5 package files, missing Android (Kotlin + Gradle), Flutter (Dart + pubspec.yaml), C/C++, C#, Ruby, PHP, Scala, and others. This caused new-project to treat brownfield projects in those ecosystems as greenfield, skipping the codebase mapping step. Added 18 code extensions and 11 package/build files to the detection lists. Fixes #1325 Co-Authored-By: Claude Opus 4.6 (1M context) --- get-shit-done/bin/lib/init.cjs | 31 ++++++++++++- tests/init.test.cjs | 82 ++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/get-shit-done/bin/lib/init.cjs b/get-shit-done/bin/lib/init.cjs index 0ed669e91..fd984a64a 100644 --- a/get-shit-done/bin/lib/init.cjs +++ b/get-shit-done/bin/lib/init.cjs @@ -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 diff --git a/tests/init.test.cjs b/tests/init.test.cjs index 3622e7a7c..5c3b524c8 100644 --- a/tests/init.test.cjs +++ b/tests/init.test.cjs @@ -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'), ''); + + 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); + }); }); // ─────────────────────────────────────────────────────────────────────────────