Skip to content

Commit 1f3e624

Browse files
committed
change in some agentic logic
1 parent a98ed86 commit 1f3e624

2 files changed

Lines changed: 179 additions & 16 deletions

File tree

internal/agentic/autonomous.go

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,9 @@ IMPORTANT RULES:
190190
- Use the programming language the user requested. If no language is specified, choose the most appropriate one.
191191
- If the user asks for a web application, you MUST include both frontend AND backend folders with complete implementations.
192192
- List every single file that will be created — do not summarize with "..." or "etc".
193-
- The project name MUST appear as "Project Name: <name>" on its own line.`, c.Description)
193+
- The project name MUST appear as "Project Name: <name>" on its own line.
194+
- For Go projects: place go.mod at the PROJECT ROOT, not inside a subdirectory. Embed static assets (HTML/CSS/JS) directly in the Go binary or serve them from a subfolder — do NOT create a separate backend/ folder with its own go.mod.
195+
- Keep the project structure as FLAT as possible. Avoid unnecessary nesting unless the project genuinely requires multiple independent modules.`, c.Description)
194196

195197
plan, err := c.aicallAndTrack(prompt)
196198
if err != nil {
@@ -288,8 +290,9 @@ Rules:
288290

289291
cmdsStr = cleanAIResponse(cmdsStr)
290292

291-
// Safety: strip "go mod init" if go.mod already exists
292-
goModPath := filepath.Join(c.ProjectDir, "go.mod")
293+
// Safety: strip "go mod init" if go.mod already exists (check build root too)
294+
buildRoot := c.findBuildRoot()
295+
goModPath := filepath.Join(buildRoot, "go.mod")
293296
if _, statErr := os.Stat(goModPath); statErr == nil {
294297
cmdsStr = stripGoModInit(cmdsStr)
295298
}
@@ -1090,18 +1093,76 @@ func (c *AutonomousCreator) buildCodeContextFromDisk() string {
10901093
return sb.String()
10911094
}
10921095

1093-
// runShellCmd executes a shell command in the project directory and returns output.
1094-
func (c *AutonomousCreator) runShellCmd(cmdStr string) ([]byte, error) {
1096+
// findBuildRoot locates the actual directory containing the project's build
1097+
// manifest (go.mod, package.json, requirements.txt, pyproject.toml, Cargo.toml,
1098+
// pom.xml, build.gradle). If the manifest is not at the project root but exists
1099+
// in exactly one immediate subdirectory, that subdirectory is returned.
1100+
// Otherwise it returns c.ProjectDir unchanged.
1101+
func (c *AutonomousCreator) findBuildRoot() string {
1102+
manifests := []string{
1103+
"go.mod", "package.json", "requirements.txt",
1104+
"pyproject.toml", "Cargo.toml", "pom.xml", "build.gradle",
1105+
}
1106+
1107+
// Check project root first.
1108+
for _, m := range manifests {
1109+
if _, err := os.Stat(filepath.Join(c.ProjectDir, m)); err == nil {
1110+
return c.ProjectDir
1111+
}
1112+
}
1113+
1114+
// Scan immediate subdirectories for a single manifest match.
1115+
entries, err := os.ReadDir(c.ProjectDir)
1116+
if err != nil {
1117+
return c.ProjectDir
1118+
}
1119+
1120+
var candidate string
1121+
for _, entry := range entries {
1122+
if !entry.IsDir() {
1123+
continue
1124+
}
1125+
subDir := filepath.Join(c.ProjectDir, entry.Name())
1126+
for _, m := range manifests {
1127+
if _, err := os.Stat(filepath.Join(subDir, m)); err == nil {
1128+
if candidate != "" && candidate != subDir {
1129+
// Multiple subdirectories have manifests — ambiguous, stay at root.
1130+
return c.ProjectDir
1131+
}
1132+
candidate = subDir
1133+
break
1134+
}
1135+
}
1136+
}
1137+
1138+
if candidate != "" {
1139+
if c.logger != nil {
1140+
rel, _ := filepath.Rel(c.ProjectDir, candidate)
1141+
c.logger.Log("Build root detected in subdirectory: %s", rel)
1142+
}
1143+
return candidate
1144+
}
1145+
return c.ProjectDir
1146+
}
1147+
1148+
// runShellCmdIn executes a shell command in the given directory and returns output.
1149+
func (c *AutonomousCreator) runShellCmdIn(cmdStr, dir string) ([]byte, error) {
10951150
var cmd *exec.Cmd
10961151
if runtime.GOOS == "windows" {
10971152
cmd = exec.Command("cmd", "/C", cmdStr)
10981153
} else {
10991154
cmd = exec.Command("bash", "-c", cmdStr)
11001155
}
1101-
cmd.Dir = c.ProjectDir
1156+
cmd.Dir = dir
11021157
return cmd.CombinedOutput()
11031158
}
11041159

1160+
// runShellCmd executes a shell command in the project directory and returns output.
1161+
// It auto-detects the build root so commands run where the build manifest lives.
1162+
func (c *AutonomousCreator) runShellCmd(cmdStr string) ([]byte, error) {
1163+
return c.runShellCmdIn(cmdStr, c.findBuildRoot())
1164+
}
1165+
11051166
// aiDrivenFix asks the AI to analyze an error, fix the code, and retry the command.
11061167
// It performs up to 3 fix attempts. Returns a log of what happened or an error if
11071168
// all attempts fail.
@@ -1279,7 +1340,7 @@ func (c *AutonomousCreator) smokeTestServer(runCmd, port string) (string, error)
12791340
} else {
12801341
serverCmd = exec.Command("bash", "-c", runCmd)
12811342
}
1282-
serverCmd.Dir = c.ProjectDir
1343+
serverCmd.Dir = c.findBuildRoot()
12831344
setProcGroupAttr(serverCmd)
12841345

12851346
var stdoutBuf, stderrBuf strings.Builder
@@ -1336,24 +1397,25 @@ func (c *AutonomousCreator) smokeTestServer(runCmd, port string) (string, error)
13361397
// launchInTerminal attempts to start a command in a new terminal window.
13371398
// Returns true if successful.
13381399
func (c *AutonomousCreator) launchInTerminal(cmdStr string) bool {
1400+
buildRoot := c.findBuildRoot()
13391401
var runCmd *exec.Cmd
13401402
if runtime.GOOS == "windows" {
13411403
runCmd = exec.Command("cmd", "/c", "start", "cmd", "/k", cmdStr)
1342-
runCmd.Dir = c.ProjectDir
1404+
runCmd.Dir = buildRoot
13431405
} else if runtime.GOOS == "darwin" {
1344-
script := fmt.Sprintf("cd '%s' && %s", c.ProjectDir, cmdStr)
1406+
script := fmt.Sprintf("cd '%s' && %s", buildRoot, cmdStr)
13451407
runCmd = exec.Command("osascript", "-e",
13461408
fmt.Sprintf("tell application \"Terminal\" to do script \"%s\"", script))
13471409
} else if _, err := exec.LookPath("gnome-terminal"); err == nil {
13481410
runCmd = exec.Command("gnome-terminal", "--", "bash", "-c", cmdStr)
1349-
runCmd.Dir = c.ProjectDir
1411+
runCmd.Dir = buildRoot
13501412
} else if _, err := exec.LookPath("xterm"); err == nil {
13511413
runCmd = exec.Command("xterm", "-e", "bash", "-c", cmdStr)
1352-
runCmd.Dir = c.ProjectDir
1414+
runCmd.Dir = buildRoot
13531415
} else {
13541416
// Fallback: run in background
13551417
bgCmd := exec.Command("bash", "-c", cmdStr)
1356-
bgCmd.Dir = c.ProjectDir
1418+
bgCmd.Dir = buildRoot
13571419
if err := bgCmd.Start(); err != nil {
13581420
return false
13591421
}
@@ -1364,7 +1426,7 @@ func (c *AutonomousCreator) launchInTerminal(cmdStr string) bool {
13641426
if err := runCmd.Start(); err != nil {
13651427
// Fallback to background
13661428
bgCmd := exec.Command("bash", "-c", cmdStr)
1367-
bgCmd.Dir = c.ProjectDir
1429+
bgCmd.Dir = buildRoot
13681430
if err := bgCmd.Start(); err != nil {
13691431
return false
13701432
}

internal/agentic/autonomous_test.go

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package agentic
22

33
import (
4+
"os"
45
"path/filepath"
56
"strings"
67
"testing"
@@ -190,9 +191,6 @@ func main() {
190191
}
191192
}
192193

193-
194-
195-
196194
func TestExtractProjectName(t *testing.T) {
197195
tests := []struct {
198196
name string
@@ -430,3 +428,106 @@ func TestExtractFileFromError_NoMatch(t *testing.T) {
430428
t.Fatalf("extractFileFromError(%q, %q) = %q, want empty string", errorOutput, projectDir, got)
431429
}
432430
}
431+
432+
// TestFindBuildRoot verifies that findBuildRoot correctly locates the directory
433+
// containing the build manifest (go.mod, package.json, etc.), even when it
434+
// lives in a subdirectory rather than the project root.
435+
func TestFindBuildRoot(t *testing.T) {
436+
tests := []struct {
437+
name string
438+
files map[string]string // relative paths to create
439+
expected string // expected suffix of the returned path
440+
}{
441+
{
442+
name: "go.mod at project root",
443+
files: map[string]string{
444+
"go.mod": "module test",
445+
"main.go": "package main",
446+
},
447+
expected: "", // project root itself
448+
},
449+
{
450+
name: "go.mod in backend subdirectory",
451+
files: map[string]string{
452+
"backend/go.mod": "module test",
453+
"backend/main.go": "package main",
454+
"frontend/index.html": "<html></html>",
455+
},
456+
expected: "backend",
457+
},
458+
{
459+
name: "package.json in subdirectory",
460+
files: map[string]string{
461+
"app/package.json": `{"name":"test"}`,
462+
"app/index.js": "console.log('hi')",
463+
},
464+
expected: "app",
465+
},
466+
{
467+
name: "no manifest anywhere returns project root",
468+
files: map[string]string{
469+
"main.go": "package main",
470+
"util.go": "package main",
471+
},
472+
expected: "",
473+
},
474+
{
475+
name: "multiple subdirs with manifests returns project root (ambiguous)",
476+
files: map[string]string{
477+
"svc1/go.mod": "module svc1",
478+
"svc2/package.json": `{"name":"svc2"}`,
479+
},
480+
expected: "", // ambiguous, falls back to root
481+
},
482+
{
483+
name: "requirements.txt in subdirectory",
484+
files: map[string]string{
485+
"api/requirements.txt": "flask",
486+
"api/app.py": "print('hi')",
487+
},
488+
expected: "api",
489+
},
490+
}
491+
492+
for _, tt := range tests {
493+
t.Run(tt.name, func(t *testing.T) {
494+
tmpDir := t.TempDir()
495+
496+
// Create the file structure
497+
for relPath, content := range tt.files {
498+
absPath := filepath.Join(tmpDir, filepath.FromSlash(relPath))
499+
dir := filepath.Dir(absPath)
500+
if err := mkdirAll(dir); err != nil {
501+
t.Fatalf("failed to create dir %s: %v", dir, err)
502+
}
503+
if err := writeFile(absPath, content); err != nil {
504+
t.Fatalf("failed to write %s: %v", relPath, err)
505+
}
506+
}
507+
508+
creator := &AutonomousCreator{
509+
ProjectDir: tmpDir,
510+
}
511+
512+
got := creator.findBuildRoot()
513+
want := tmpDir
514+
if tt.expected != "" {
515+
want = filepath.Join(tmpDir, tt.expected)
516+
}
517+
518+
if got != want {
519+
t.Errorf("findBuildRoot() = %q, want %q", got, want)
520+
}
521+
})
522+
}
523+
}
524+
525+
// mkdirAll is a test helper that creates directories.
526+
func mkdirAll(path string) error {
527+
return os.MkdirAll(path, 0755)
528+
}
529+
530+
// writeFile is a test helper that writes content to a file.
531+
func writeFile(path, content string) error {
532+
return os.WriteFile(path, []byte(content), 0644)
533+
}

0 commit comments

Comments
 (0)