From 85ee15035d27035270ed8f98ee1b5b318247581a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 03:23:59 +0000 Subject: [PATCH 1/6] Initial plan From bd25dd14c91e4643196ab38b917e4df1d273acf0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 03:31:26 +0000 Subject: [PATCH 2/6] Move bootstrap from frontmatter to separate files Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- integration_test.go | 236 ++++++++++++++++++++++++++++++++++++++++++++ main.go | 23 +++-- 2 files changed, 251 insertions(+), 8 deletions(-) create mode 100644 integration_test.go diff --git a/integration_test.go b/integration_test.go new file mode 100644 index 0000000..ac61041 --- /dev/null +++ b/integration_test.go @@ -0,0 +1,236 @@ +package main + +import ( + "os" + "os/exec" + "path/filepath" + "testing" +) + +func TestBootstrapFromFile(t *testing.T) { + // Build the binary + binaryPath := filepath.Join(t.TempDir(), "coding-agent-context") + cmd := exec.Command("go", "build", "-o", binaryPath, ".") + if output, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to build binary: %v\n%s", err, output) + } + + // Create a temporary directory structure + tmpDir := t.TempDir() + contextDir := filepath.Join(tmpDir, ".coding-agent-context") + memoriesDir := filepath.Join(contextDir, "memories") + promptsDir := filepath.Join(contextDir, "prompts") + outputDir := filepath.Join(tmpDir, "output") + + if err := os.MkdirAll(memoriesDir, 0755); err != nil { + t.Fatalf("failed to create memories dir: %v", err) + } + if err := os.MkdirAll(promptsDir, 0755); err != nil { + t.Fatalf("failed to create prompts dir: %v", err) + } + + // Create a memory file + memoryFile := filepath.Join(memoriesDir, "setup.md") + memoryContent := `--- +--- +# Development Setup + +This is a setup guide. +` + if err := os.WriteFile(memoryFile, []byte(memoryContent), 0644); err != nil { + t.Fatalf("failed to write memory file: %v", err) + } + + // Create a bootstrap file for the memory (setup.md -> setup-bootstrap) + bootstrapFile := filepath.Join(memoriesDir, "setup-bootstrap") + bootstrapContent := `#!/bin/bash +echo "Running bootstrap" +npm install +` + if err := os.WriteFile(bootstrapFile, []byte(bootstrapContent), 0755); err != nil { + t.Fatalf("failed to write bootstrap file: %v", err) + } + + // Create a prompt file + promptFile := filepath.Join(promptsDir, "test-task.md") + promptContent := `--- +--- +# Test Task + +Please help with this task. +` + if err := os.WriteFile(promptFile, []byte(promptContent), 0644); err != nil { + t.Fatalf("failed to write prompt file: %v", err) + } + + // Run the binary + cmd = exec.Command(binaryPath, "-d", contextDir, "-o", outputDir, "test-task") + cmd.Dir = tmpDir + if output, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to run binary: %v\n%s", err, output) + } + + // Check that the bootstrap.d directory was created + bootstrapDDir := filepath.Join(outputDir, "bootstrap.d") + if _, err := os.Stat(bootstrapDDir); os.IsNotExist(err) { + t.Errorf("bootstrap.d directory was not created") + } + + // Check that a bootstrap file exists in bootstrap.d + files, err := os.ReadDir(bootstrapDDir) + if err != nil { + t.Fatalf("failed to read bootstrap.d dir: %v", err) + } + if len(files) != 1 { + t.Errorf("expected 1 bootstrap file, got %d", len(files)) + } + + // Check that the bootstrap file has the correct content + if len(files) > 0 { + bootstrapPath := filepath.Join(bootstrapDDir, files[0].Name()) + content, err := os.ReadFile(bootstrapPath) + if err != nil { + t.Fatalf("failed to read bootstrap file: %v", err) + } + if string(content) != bootstrapContent { + t.Errorf("bootstrap content mismatch:\ngot: %q\nwant: %q", string(content), bootstrapContent) + } + } + + // Check that the prompt.md file was created + promptOutput := filepath.Join(outputDir, "prompt.md") + if _, err := os.Stat(promptOutput); os.IsNotExist(err) { + t.Errorf("prompt.md file was not created") + } +} + +func TestBootstrapFileNotRequired(t *testing.T) { + // Build the binary + binaryPath := filepath.Join(t.TempDir(), "coding-agent-context") + cmd := exec.Command("go", "build", "-o", binaryPath, ".") + if output, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to build binary: %v\n%s", err, output) + } + + // Create a temporary directory structure + tmpDir := t.TempDir() + contextDir := filepath.Join(tmpDir, ".coding-agent-context") + memoriesDir := filepath.Join(contextDir, "memories") + promptsDir := filepath.Join(contextDir, "prompts") + outputDir := filepath.Join(tmpDir, "output") + + if err := os.MkdirAll(memoriesDir, 0755); err != nil { + t.Fatalf("failed to create memories dir: %v", err) + } + if err := os.MkdirAll(promptsDir, 0755); err != nil { + t.Fatalf("failed to create prompts dir: %v", err) + } + + // Create a memory file WITHOUT a bootstrap + memoryFile := filepath.Join(memoriesDir, "info.md") + memoryContent := `--- +--- +# Project Info + +Just some information. +` + if err := os.WriteFile(memoryFile, []byte(memoryContent), 0644); err != nil { + t.Fatalf("failed to write memory file: %v", err) + } + + // Create a prompt file + promptFile := filepath.Join(promptsDir, "test-task.md") + promptContent := `--- +--- +# Test Task + +Please help with this task. +` + if err := os.WriteFile(promptFile, []byte(promptContent), 0644); err != nil { + t.Fatalf("failed to write prompt file: %v", err) + } + + // Run the binary + cmd = exec.Command(binaryPath, "-d", contextDir, "-o", outputDir, "test-task") + cmd.Dir = tmpDir + if output, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to run binary: %v\n%s", err, output) + } + + // Check that the bootstrap.d directory was created but is empty + bootstrapDDir := filepath.Join(outputDir, "bootstrap.d") + files, err := os.ReadDir(bootstrapDDir) + if err != nil { + t.Fatalf("failed to read bootstrap.d dir: %v", err) + } + if len(files) != 0 { + t.Errorf("expected 0 bootstrap files, got %d", len(files)) + } + + // Check that the prompt.md file was still created + promptOutput := filepath.Join(outputDir, "prompt.md") + if _, err := os.Stat(promptOutput); os.IsNotExist(err) { + t.Errorf("prompt.md file was not created") + } +} + +func TestMultipleBootstrapFiles(t *testing.T) { + // Build the binary + binaryPath := filepath.Join(t.TempDir(), "coding-agent-context") + cmd := exec.Command("go", "build", "-o", binaryPath, ".") + if output, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to build binary: %v\n%s", err, output) + } + + // Create a temporary directory structure + tmpDir := t.TempDir() + contextDir := filepath.Join(tmpDir, ".coding-agent-context") + memoriesDir := filepath.Join(contextDir, "memories") + promptsDir := filepath.Join(contextDir, "prompts") + outputDir := filepath.Join(tmpDir, "output") + + if err := os.MkdirAll(memoriesDir, 0755); err != nil { + t.Fatalf("failed to create memories dir: %v", err) + } + if err := os.MkdirAll(promptsDir, 0755); err != nil { + t.Fatalf("failed to create prompts dir: %v", err) + } + + // Create first memory file with bootstrap + if err := os.WriteFile(filepath.Join(memoriesDir, "setup.md"), []byte("---\n---\n# Setup\n"), 0644); err != nil { + t.Fatalf("failed to write memory file: %v", err) + } + if err := os.WriteFile(filepath.Join(memoriesDir, "setup-bootstrap"), []byte("#!/bin/bash\necho setup\n"), 0755); err != nil { + t.Fatalf("failed to write bootstrap file: %v", err) + } + + // Create second memory file with bootstrap + if err := os.WriteFile(filepath.Join(memoriesDir, "deps.md"), []byte("---\n---\n# Dependencies\n"), 0644); err != nil { + t.Fatalf("failed to write memory file: %v", err) + } + if err := os.WriteFile(filepath.Join(memoriesDir, "deps-bootstrap"), []byte("#!/bin/bash\necho deps\n"), 0755); err != nil { + t.Fatalf("failed to write bootstrap file: %v", err) + } + + // Create a prompt file + if err := os.WriteFile(filepath.Join(promptsDir, "test-task.md"), []byte("---\n---\n# Test\n"), 0644); err != nil { + t.Fatalf("failed to write prompt file: %v", err) + } + + // Run the binary + cmd = exec.Command(binaryPath, "-d", contextDir, "-o", outputDir, "test-task") + cmd.Dir = tmpDir + if output, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to run binary: %v\n%s", err, output) + } + + // Check that both bootstrap files exist in bootstrap.d + bootstrapDDir := filepath.Join(outputDir, "bootstrap.d") + files, err := os.ReadDir(bootstrapDDir) + if err != nil { + t.Fatalf("failed to read bootstrap.d dir: %v", err) + } + if len(files) != 2 { + t.Errorf("expected 2 bootstrap files, got %d", len(files)) + } +} diff --git a/main.go b/main.go index 20ef0e3..d9c12cf 100644 --- a/main.go +++ b/main.go @@ -85,21 +85,28 @@ func run(args []string) error { return nil } - slog.Info("Including memory file", "path", path) - - var frontmatter struct { - Bootstrap string `yaml:"bootstrap"` + // Only process .md files as memory files + if filepath.Ext(path) != ".md" { + return nil } - content, err := parseMarkdownFile(path, &frontmatter) + slog.Info("Including memory file", "path", path) + + content, err := parseMarkdownFile(path, &struct{}{}) if err != nil { return fmt.Errorf("failed to parse markdown file: %w", err) } - if bootstrap := frontmatter.Bootstrap; bootstrap != "" { - hash := sha256.Sum256([]byte(bootstrap)) + // Check for a bootstrap file named -bootstrap + // For example, setup.md -> setup-bootstrap + ext := filepath.Ext(path) + baseNameWithoutExt := path[:len(path)-len(ext)] + bootstrapFilePath := baseNameWithoutExt + "-bootstrap" + + if bootstrapContent, err := os.ReadFile(bootstrapFilePath); err == nil { + hash := sha256.Sum256(bootstrapContent) bootstrapPath := filepath.Join(bootstrapDir, fmt.Sprintf("%x", hash)) - if err := os.WriteFile(bootstrapPath, []byte(bootstrap), 0700); err != nil { + if err := os.WriteFile(bootstrapPath, bootstrapContent, 0700); err != nil { return fmt.Errorf("failed to write bootstrap file: %w", err) } } From 5d623061b26c740eeeda53b62645be68f45e2a8b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 03:32:51 +0000 Subject: [PATCH 3/6] Update documentation for new bootstrap file format Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- README.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 044b11c..c1a1622 100644 --- a/README.md +++ b/README.md @@ -109,20 +109,25 @@ coding-agent-context -p feature="User Login" -p language=Go add-feature ### Memory Files -Markdown files included in every generated context. Can include bootstrap scripts in frontmatter. +Markdown files included in every generated context. Bootstrap scripts can be provided in separate files. **Example** (`.coding-agent-context/memories/setup.md`): ```markdown --- -bootstrap: | - #!/bin/bash - npm install --- # Development Setup This project requires Node.js dependencies. ``` +**Bootstrap file** (`.coding-agent-context/memories/setup-bootstrap`): +```bash +#!/bin/bash +npm install +``` + +For each memory file `.md`, you can optionally create a corresponding `-bootstrap` file that will be executed during setup. + ## Output Files @@ -186,15 +191,18 @@ coding-agent-context -p featureName="Authentication" -p language=Go add-feature ```bash cat > .coding-agent-context/memories/setup.md << 'EOF' --- -bootstrap: | - #!/bin/bash - go mod download --- # Project Setup This Go project uses modules. EOF +cat > .coding-agent-context/memories/setup-bootstrap << 'EOF' +#!/bin/bash +go mod download +EOF +chmod +x .coding-agent-context/memories/setup-bootstrap + coding-agent-context -o ./output my-task cd output && ./bootstrap ``` From 1ae23e2c276158eeb78f7d189910f18e0a01f511 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 03:34:36 +0000 Subject: [PATCH 4/6] Fix trailing whitespace in main.go Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index d9c12cf..9afb7ee 100644 --- a/main.go +++ b/main.go @@ -102,7 +102,7 @@ func run(args []string) error { ext := filepath.Ext(path) baseNameWithoutExt := path[:len(path)-len(ext)] bootstrapFilePath := baseNameWithoutExt + "-bootstrap" - + if bootstrapContent, err := os.ReadFile(bootstrapFilePath); err == nil { hash := sha256.Sum256(bootstrapContent) bootstrapPath := filepath.Join(bootstrapDir, fmt.Sprintf("%x", hash)) From dc188ea338c1c02905affd6f991d3b6f01938b50 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 03:43:40 +0000 Subject: [PATCH 5/6] Fix build: skip non-existent memory directories Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- main.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/main.go b/main.go index 9afb7ee..ad159af 100644 --- a/main.go +++ b/main.go @@ -77,6 +77,12 @@ func run(args []string) error { for _, dir := range dirs { memoryDir := filepath.Join(dir, "memories") + + // Skip if the directory doesn't exist + if _, err := os.Stat(memoryDir); os.IsNotExist(err) { + continue + } + err := filepath.Walk(memoryDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err From a06f23808c2ad980dbcdb5927c0e4bc297a1ae65 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 03:50:42 +0000 Subject: [PATCH 6/6] Use strings.TrimSuffix for clearer code Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index ad159af..0cf071b 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "log/slog" "os" "path/filepath" + "strings" "text/template" ) @@ -105,8 +106,7 @@ func run(args []string) error { // Check for a bootstrap file named -bootstrap // For example, setup.md -> setup-bootstrap - ext := filepath.Ext(path) - baseNameWithoutExt := path[:len(path)-len(ext)] + baseNameWithoutExt := strings.TrimSuffix(path, ".md") bootstrapFilePath := baseNameWithoutExt + "-bootstrap" if bootstrapContent, err := os.ReadFile(bootstrapFilePath); err == nil {