From de9f00557c1181b6efdb1fe352d66fdd182da472 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:14:32 +0000 Subject: [PATCH 1/4] Initial plan From 9eb2c42d7493b733cfc0cc426f9443662178a5cd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:21:21 +0000 Subject: [PATCH 2/4] Implement manifest URL support using GetFile - Use getter.GetFile() to download manifest files (not directories) - Parse manifest file to get search paths (one per line) - Download manifest before downloading other directories - Add comprehensive test for manifest file functionality - All existing tests pass Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- integration_test.go | 82 ++++++++++++++++++++++++++++++++++++ pkg/codingcontext/context.go | 19 +++++---- 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/integration_test.go b/integration_test.go index 75b0652e..a15fc154 100644 --- a/integration_test.go +++ b/integration_test.go @@ -1145,3 +1145,85 @@ Please fix issue number ${1} with priority ${2}. t.Errorf("positional arguments not substituted in content. Output:\n%s", output) } } + +func TestManifestFile(t *testing.T) { + // Create main project directory + mainDir := t.TempDir() + mainRulesDir := filepath.Join(mainDir, ".agents", "rules") + mainTasksDir := filepath.Join(mainDir, ".agents", "tasks") + + if err := os.MkdirAll(mainRulesDir, 0o755); err != nil { + t.Fatalf("failed to create main rules dir: %v", err) + } + if err := os.MkdirAll(mainTasksDir, 0o755); err != nil { + t.Fatalf("failed to create main tasks dir: %v", err) + } + + // Create a task file in the main directory + taskFile := filepath.Join(mainTasksDir, "test-task.md") + taskContent := `--- +task_name: test-task +--- +# Test Task + +This is a test task. +` + if err := os.WriteFile(taskFile, []byte(taskContent), 0o644); err != nil { + t.Fatalf("failed to write task file: %v", err) + } + + // Create a rule file in the main directory (should be included) + mainRuleFile := filepath.Join(mainRulesDir, "main-rule.md") + mainRuleContent := `--- +--- +# Main Rule + +This rule is in the main project. +` + if err := os.WriteFile(mainRuleFile, []byte(mainRuleContent), 0o644); err != nil { + t.Fatalf("failed to write main rule file: %v", err) + } + + // Create a remote directory with rules + remoteDir := t.TempDir() + remoteRulesDir := filepath.Join(remoteDir, ".agents", "rules") + if err := os.MkdirAll(remoteRulesDir, 0o755); err != nil { + t.Fatalf("failed to create remote rules dir: %v", err) + } + + remoteRuleFile := filepath.Join(remoteRulesDir, "remote-rule.md") + remoteRuleContent := `--- +--- +# Remote Rule + +This rule is from a remote directory. +` + if err := os.WriteFile(remoteRuleFile, []byte(remoteRuleContent), 0o644); err != nil { + t.Fatalf("failed to write remote rule file: %v", err) + } + + // Create a manifest file that references the remote directory + manifestFile := filepath.Join(t.TempDir(), "manifest.txt") + manifestContent := "file://" + remoteDir + "\n" + if err := os.WriteFile(manifestFile, []byte(manifestContent), 0o644); err != nil { + t.Fatalf("failed to write manifest file: %v", err) + } + + // Run the tool with the manifest file + output := runTool(t, "-C", mainDir, "-m", "file://"+manifestFile, "/test-task") + + // Check that the main rule is included + if !strings.Contains(output, "# Main Rule") { + t.Errorf("main rule not found in stdout. Output:\n%s", output) + } + + // Check that the remote rule from the manifest is included + if !strings.Contains(output, "# Remote Rule") { + t.Errorf("remote rule from manifest not found in stdout. Output:\n%s", output) + } + + // Check that the task is included + if !strings.Contains(output, "# Test Task") { + t.Errorf("task not found in stdout. Output:\n%s", output) + } +} diff --git a/pkg/codingcontext/context.go b/pkg/codingcontext/context.go index c0365fa9..f585d529 100644 --- a/pkg/codingcontext/context.go +++ b/pkg/codingcontext/context.go @@ -115,6 +115,14 @@ func (cc *Context) expandParams(content string) string { // - A free-text prompt (used directly as task content) // - A prompt containing a slash command (e.g., "/fix-bug 123") which triggers task lookup func (cc *Context) Run(ctx context.Context, taskPrompt string) (*Result, error) { + // Parse manifest file first to get additional search paths + manifestPaths, err := cc.parseManifestFile(ctx) + if err != nil { + return nil, fmt.Errorf("failed to parse manifest file: %w", err) + } + cc.searchPaths = append(cc.searchPaths, manifestPaths...) + + // Download all remote directories (including those from manifest) if err := cc.downloadRemoteDirectories(ctx); err != nil { return nil, fmt.Errorf("failed to download remote directories: %w", err) } @@ -130,12 +138,6 @@ func (cc *Context) Run(ctx context.Context, taskPrompt string) (*Result, error) return nil, fmt.Errorf("failed to get user home directory: %w", err) } - searchPaths, err := cc.parseManifestFile(ctx) - if err != nil { - return nil, fmt.Errorf("failed to parse manifest file: %w", err) - } - cc.searchPaths = append(cc.searchPaths, searchPaths...) - // Expand parameters in taskPrompt to allow slash commands in parameters expandedTaskPrompt := cc.expandParams(taskPrompt) @@ -226,8 +228,9 @@ func (cc *Context) parseManifestFile(ctx context.Context) ([]string, error) { manifestFile := downloadDir(cc.manifestURL) - // Download the manifest file using go-getter - if _, err := getter.Get(ctx, manifestFile, cc.manifestURL); err != nil { + // Download the manifest file using go-getter's GetFile function + // GetFile is specifically for downloading single files (not directories) + if _, err := getter.GetFile(ctx, manifestFile, cc.manifestURL); err != nil { return nil, fmt.Errorf("failed to download manifest file %s: %w", cc.manifestURL, err) } defer os.RemoveAll(manifestFile) From c6d61cb5c20e3422340faeaa84fbf82ee35ab2dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:23:34 +0000 Subject: [PATCH 3/4] Address code review feedback - Filter out empty lines from manifest file - Use fmt.Sprintf for better readability in tests - Add test for manifest files with empty lines - Add fmt import to integration_test.go Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- integration_test.go | 64 +++++++++++++++++++++++++++++++++++- pkg/codingcontext/context.go | 6 +++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/integration_test.go b/integration_test.go index a15fc154..c6da21d6 100644 --- a/integration_test.go +++ b/integration_test.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "fmt" "os" "os/exec" "path/filepath" @@ -1204,7 +1205,7 @@ This rule is from a remote directory. // Create a manifest file that references the remote directory manifestFile := filepath.Join(t.TempDir(), "manifest.txt") - manifestContent := "file://" + remoteDir + "\n" + manifestContent := fmt.Sprintf("file://%s\n", remoteDir) if err := os.WriteFile(manifestFile, []byte(manifestContent), 0o644); err != nil { t.Fatalf("failed to write manifest file: %v", err) } @@ -1227,3 +1228,64 @@ This rule is from a remote directory. t.Errorf("task not found in stdout. Output:\n%s", output) } } + +func TestManifestFileWithEmptyLines(t *testing.T) { + // Create main project directory + mainDir := t.TempDir() + mainTasksDir := filepath.Join(mainDir, ".agents", "tasks") + + if err := os.MkdirAll(mainTasksDir, 0o755); err != nil { + t.Fatalf("failed to create main tasks dir: %v", err) + } + + // Create a task file + taskFile := filepath.Join(mainTasksDir, "test-task.md") + taskContent := `--- +task_name: test-task +--- +# Test Task + +This is a test task. +` + if err := os.WriteFile(taskFile, []byte(taskContent), 0o644); err != nil { + t.Fatalf("failed to write task file: %v", err) + } + + // Create a remote directory with rules + remoteDir := t.TempDir() + remoteRulesDir := filepath.Join(remoteDir, ".agents", "rules") + if err := os.MkdirAll(remoteRulesDir, 0o755); err != nil { + t.Fatalf("failed to create remote rules dir: %v", err) + } + + remoteRuleFile := filepath.Join(remoteRulesDir, "remote-rule.md") + remoteRuleContent := `--- +--- +# Remote Rule + +This rule is from a remote directory. +` + if err := os.WriteFile(remoteRuleFile, []byte(remoteRuleContent), 0o644); err != nil { + t.Fatalf("failed to write remote rule file: %v", err) + } + + // Create a manifest file with empty lines and comments-like lines + manifestFile := filepath.Join(t.TempDir(), "manifest.txt") + manifestContent := fmt.Sprintf("\n\nfile://%s\n\n\n", remoteDir) + if err := os.WriteFile(manifestFile, []byte(manifestContent), 0o644); err != nil { + t.Fatalf("failed to write manifest file: %v", err) + } + + // Run the tool with the manifest file - should not fail despite empty lines + output := runTool(t, "-C", mainDir, "-m", "file://"+manifestFile, "/test-task") + + // Check that the remote rule from the manifest is included + if !strings.Contains(output, "# Remote Rule") { + t.Errorf("remote rule from manifest not found in stdout. Output:\n%s", output) + } + + // Check that the task is included + if !strings.Contains(output, "# Test Task") { + t.Errorf("task not found in stdout. Output:\n%s", output) + } +} diff --git a/pkg/codingcontext/context.go b/pkg/codingcontext/context.go index f585d529..4657bce8 100644 --- a/pkg/codingcontext/context.go +++ b/pkg/codingcontext/context.go @@ -247,7 +247,11 @@ func (cc *Context) parseManifestFile(ctx context.Context) ([]string, error) { var paths []string scanner := bufio.NewScanner(file) for scanner.Scan() { - paths = append(paths, scanner.Text()) + line := scanner.Text() + // Skip empty lines + if line != "" { + paths = append(paths, line) + } } if err := scanner.Err(); err != nil { From bd26f9299047c1aff90176a71ed41a1e3337cba4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:38:45 +0000 Subject: [PATCH 4/4] Remove empty line filtering from manifest parsing Blank lines are not allowed in manifest files per the format specification, so we don't need to filter them out. Removed the empty line check and the associated test. Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- integration_test.go | 61 ------------------------------------ pkg/codingcontext/context.go | 6 +--- 2 files changed, 1 insertion(+), 66 deletions(-) diff --git a/integration_test.go b/integration_test.go index c6da21d6..bb1a04c1 100644 --- a/integration_test.go +++ b/integration_test.go @@ -1228,64 +1228,3 @@ This rule is from a remote directory. t.Errorf("task not found in stdout. Output:\n%s", output) } } - -func TestManifestFileWithEmptyLines(t *testing.T) { - // Create main project directory - mainDir := t.TempDir() - mainTasksDir := filepath.Join(mainDir, ".agents", "tasks") - - if err := os.MkdirAll(mainTasksDir, 0o755); err != nil { - t.Fatalf("failed to create main tasks dir: %v", err) - } - - // Create a task file - taskFile := filepath.Join(mainTasksDir, "test-task.md") - taskContent := `--- -task_name: test-task ---- -# Test Task - -This is a test task. -` - if err := os.WriteFile(taskFile, []byte(taskContent), 0o644); err != nil { - t.Fatalf("failed to write task file: %v", err) - } - - // Create a remote directory with rules - remoteDir := t.TempDir() - remoteRulesDir := filepath.Join(remoteDir, ".agents", "rules") - if err := os.MkdirAll(remoteRulesDir, 0o755); err != nil { - t.Fatalf("failed to create remote rules dir: %v", err) - } - - remoteRuleFile := filepath.Join(remoteRulesDir, "remote-rule.md") - remoteRuleContent := `--- ---- -# Remote Rule - -This rule is from a remote directory. -` - if err := os.WriteFile(remoteRuleFile, []byte(remoteRuleContent), 0o644); err != nil { - t.Fatalf("failed to write remote rule file: %v", err) - } - - // Create a manifest file with empty lines and comments-like lines - manifestFile := filepath.Join(t.TempDir(), "manifest.txt") - manifestContent := fmt.Sprintf("\n\nfile://%s\n\n\n", remoteDir) - if err := os.WriteFile(manifestFile, []byte(manifestContent), 0o644); err != nil { - t.Fatalf("failed to write manifest file: %v", err) - } - - // Run the tool with the manifest file - should not fail despite empty lines - output := runTool(t, "-C", mainDir, "-m", "file://"+manifestFile, "/test-task") - - // Check that the remote rule from the manifest is included - if !strings.Contains(output, "# Remote Rule") { - t.Errorf("remote rule from manifest not found in stdout. Output:\n%s", output) - } - - // Check that the task is included - if !strings.Contains(output, "# Test Task") { - t.Errorf("task not found in stdout. Output:\n%s", output) - } -} diff --git a/pkg/codingcontext/context.go b/pkg/codingcontext/context.go index 4657bce8..f585d529 100644 --- a/pkg/codingcontext/context.go +++ b/pkg/codingcontext/context.go @@ -247,11 +247,7 @@ func (cc *Context) parseManifestFile(ctx context.Context) ([]string, error) { var paths []string scanner := bufio.NewScanner(file) for scanner.Scan() { - line := scanner.Text() - // Skip empty lines - if line != "" { - paths = append(paths, line) - } + paths = append(paths, scanner.Text()) } if err := scanner.Err(); err != nil {