From 82db00547bc08b5204ffea4fe3d78a3e03fda44f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 23:06:21 +0000 Subject: [PATCH 1/7] Initial plan From 590c67bc92a1480c43ec9fa04f85a46fc5a807c3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 23:20:03 +0000 Subject: [PATCH 2/7] Modify main.go and tests to output three separate files: persona.md, memories.md, and task.md Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- integration_test.go | 184 ++++++++++++++++++++++++++------------------ main.go | 39 +++++++--- 2 files changed, 134 insertions(+), 89 deletions(-) diff --git a/integration_test.go b/integration_test.go index 840ed36..5b2c164 100644 --- a/integration_test.go +++ b/integration_test.go @@ -99,10 +99,19 @@ Please help with this task. } } - // 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") + // Check that the three output files were created + personaOutput := filepath.Join(outputDir, "persona.md") + memoriesOutput := filepath.Join(outputDir, "memories.md") + taskOutput := filepath.Join(outputDir, "task.md") + + if _, err := os.Stat(personaOutput); os.IsNotExist(err) { + t.Errorf("persona.md file was not created") + } + if _, err := os.Stat(memoriesOutput); os.IsNotExist(err) { + t.Errorf("memories.md file was not created") + } + if _, err := os.Stat(taskOutput); os.IsNotExist(err) { + t.Errorf("task.md file was not created") } } @@ -259,10 +268,15 @@ Please help with this task. 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") + // Check that the three output files were still created + memoriesOutput := filepath.Join(outputDir, "memories.md") + taskOutput := filepath.Join(outputDir, "task.md") + + if _, err := os.Stat(memoriesOutput); os.IsNotExist(err) { + t.Errorf("memories.md file was not created") + } + if _, err := os.Stat(taskOutput); os.IsNotExist(err) { + t.Errorf("task.md file was not created") } } @@ -376,10 +390,10 @@ func TestSelectorFiltering(t *testing.T) { t.Fatalf("failed to run binary: %v\n%s", err, output) } - promptOutput := filepath.Join(outputDir, "prompt.md") - content, err := os.ReadFile(promptOutput) + memoriesOutput := filepath.Join(outputDir, "memories.md") + content, err := os.ReadFile(memoriesOutput) if err != nil { - t.Fatalf("failed to read prompt output: %v", err) + t.Fatalf("failed to read memories output: %v", err) } contentStr := string(content) if !strings.Contains(contentStr, "Prod content") { @@ -406,9 +420,9 @@ func TestSelectorFiltering(t *testing.T) { t.Fatalf("failed to run binary: %v\n%s", err, output) } - content, err = os.ReadFile(promptOutput) + content, err = os.ReadFile(memoriesOutput) if err != nil { - t.Fatalf("failed to read prompt output: %v", err) + t.Fatalf("failed to read memories output: %v", err) } contentStr = string(content) if !strings.Contains(contentStr, "Prod content") { @@ -434,9 +448,9 @@ func TestSelectorFiltering(t *testing.T) { t.Fatalf("failed to run binary: %v\n%s", err, output) } - content, err = os.ReadFile(promptOutput) + content, err = os.ReadFile(memoriesOutput) if err != nil { - t.Fatalf("failed to read prompt output: %v", err) + t.Fatalf("failed to read memories output: %v", err) } contentStr = string(content) if strings.Contains(contentStr, "Prod content") { @@ -462,9 +476,9 @@ func TestSelectorFiltering(t *testing.T) { t.Fatalf("failed to run binary: %v\n%s", err, output) } - content, err = os.ReadFile(promptOutput) + content, err = os.ReadFile(memoriesOutput) if err != nil { - t.Fatalf("failed to read prompt output: %v", err) + t.Fatalf("failed to read memories output: %v", err) } contentStr = string(content) if !strings.Contains(contentStr, "Prod content") { @@ -490,9 +504,9 @@ func TestSelectorFiltering(t *testing.T) { t.Fatalf("failed to run binary: %v\n%s", err, output) } - content, err = os.ReadFile(promptOutput) + content, err = os.ReadFile(memoriesOutput) if err != nil { - t.Fatalf("failed to read prompt output: %v", err) + t.Fatalf("failed to read memories output: %v", err) } contentStr = string(content) if !strings.Contains(contentStr, "Prod content") { @@ -555,11 +569,11 @@ The project is for $company. t.Fatalf("failed to run binary: %v\n%s", err, output) } - // Read the output - promptOutput := filepath.Join(outputDir, "prompt.md") - content, err := os.ReadFile(promptOutput) + // Read the task output (template expansion happens in task.md) + taskOutput := filepath.Join(outputDir, "task.md") + content, err := os.ReadFile(taskOutput) if err != nil { - t.Fatalf("failed to read prompt output: %v", err) + t.Fatalf("failed to read task output: %v", err) } contentStr := string(content) @@ -617,11 +631,11 @@ Missing var: ${missingVar} t.Fatalf("failed to run binary: %v\n%s", err, output) } - // Read the output - promptOutput := filepath.Join(outputDir, "prompt.md") - content, err := os.ReadFile(promptOutput) + // Read the task output (template expansion happens in task.md) + taskOutput := filepath.Join(outputDir, "task.md") + content, err := os.ReadFile(taskOutput) if err != nil { - t.Fatalf("failed to read prompt output: %v", err) + t.Fatalf("failed to read task output: %v", err) } contentStr := string(content) @@ -919,10 +933,10 @@ func TestTaskNameBuiltinFilter(t *testing.T) { t.Fatalf("failed to run binary: %v\n%s", err, output) } - promptOutput := filepath.Join(outputDir, "prompt.md") - content, err := os.ReadFile(promptOutput) + memoriesOutput := filepath.Join(outputDir, "memories.md") + content, err := os.ReadFile(memoriesOutput) if err != nil { - t.Fatalf("failed to read prompt output: %v", err) + t.Fatalf("failed to read memories output: %v", err) } contentStr := string(content) if !strings.Contains(contentStr, "Deploy-specific content") { @@ -945,9 +959,9 @@ func TestTaskNameBuiltinFilter(t *testing.T) { t.Fatalf("failed to run binary: %v\n%s", err, output) } - content, err = os.ReadFile(promptOutput) + content, err = os.ReadFile(memoriesOutput) if err != nil { - t.Fatalf("failed to read prompt output: %v", err) + t.Fatalf("failed to read memories output: %v", err) } contentStr = string(content) if strings.Contains(contentStr, "Deploy-specific content") { @@ -1030,44 +1044,46 @@ Please help with ${feature}. t.Fatalf("failed to run binary: %v\n%s", err, output) } - // Check the output - promptOutput := filepath.Join(outputDir, "prompt.md") - content, err := os.ReadFile(promptOutput) + // Check the output - now we have three separate files + personaOutput := filepath.Join(outputDir, "persona.md") + personaBytes, err := os.ReadFile(personaOutput) if err != nil { - t.Fatalf("failed to read prompt output: %v", err) + t.Fatalf("failed to read persona output: %v", err) } - - contentStr := string(content) - - // Verify persona appears first - expertIdx := strings.Index(contentStr, "Expert Persona") - contextIdx := strings.Index(contentStr, "# Context") - taskIdx := strings.Index(contentStr, "# Task") - - if expertIdx == -1 { - t.Errorf("Expected to find 'Expert Persona' in output") - } - if contextIdx == -1 { - t.Errorf("Expected to find '# Context' in output") + + memoriesOutput := filepath.Join(outputDir, "memories.md") + memoriesBytes, err2 := os.ReadFile(memoriesOutput) + if err2 != nil { + t.Fatalf("failed to read memories output: %v", err2) } - if taskIdx == -1 { - t.Errorf("Expected to find '# Task' in output") + + taskOutput := filepath.Join(outputDir, "task.md") + taskBytes, err3 := os.ReadFile(taskOutput) + if err3 != nil { + t.Fatalf("failed to read task output: %v", err3) } - // Verify order: persona -> context -> task - if expertIdx > contextIdx { - t.Errorf("Persona should appear before context. Persona at %d, Context at %d", expertIdx, contextIdx) + // Verify persona content + personaStr := string(personaBytes) + if !strings.Contains(personaStr, "Expert Persona") { + t.Errorf("Expected to find 'Expert Persona' in persona.md") } - if contextIdx > taskIdx { - t.Errorf("Context should appear before task. Context at %d, Task at %d", contextIdx, taskIdx) + if !strings.Contains(personaStr, "You are an expert in Go") { + t.Errorf("Expected persona content to remain as-is without template expansion") } - // Verify persona content is not expanded (no template substitution) - if !strings.Contains(contentStr, "You are an expert in Go") { - t.Errorf("Expected persona content to remain as-is without template expansion") + // Verify memories content + memoriesStr := string(memoriesBytes) + if !strings.Contains(memoriesStr, "# Context") { + t.Errorf("Expected to find '# Context' in memories.md") + } + + // Verify task content + taskStr := string(taskBytes) + if !strings.Contains(taskStr, "# Task") { + t.Errorf("Expected to find '# Task' in task.md") } - // Verify task template substitution still works - if !strings.Contains(contentStr, "Please help with auth") { + if !strings.Contains(taskStr, "Please help with auth") { t.Errorf("Expected task template to be expanded with feature=auth") } } @@ -1125,21 +1141,25 @@ Please help. t.Fatalf("failed to run binary without persona: %v\n%s", err, output) } - // Check the output - promptOutput := filepath.Join(outputDir, "prompt.md") - content, err := os.ReadFile(promptOutput) + // Check the memories and task outputs + memoriesOutput := filepath.Join(outputDir, "memories.md") + memoriesBytes, err := os.ReadFile(memoriesOutput) if err != nil { - t.Fatalf("failed to read prompt output: %v", err) + t.Fatalf("failed to read memories output: %v", err) } - contentStr := string(content) + taskOutput := filepath.Join(outputDir, "task.md") + taskBytes, err2 := os.ReadFile(taskOutput) + if err2 != nil { + t.Fatalf("failed to read task output: %v", err2) + } // Verify context and task are present - if !strings.Contains(contentStr, "# Context") { - t.Errorf("Expected to find '# Context' in output") + if !strings.Contains(string(memoriesBytes), "# Context") { + t.Errorf("Expected to find '# Context' in memories.md") } - if !strings.Contains(contentStr, "# Task") { - t.Errorf("Expected to find '# Task' in output") + if !strings.Contains(string(taskBytes), "# Task") { + t.Errorf("Expected to find '# Task' in task.md") } } @@ -1241,19 +1261,29 @@ func TestWorkDirOption(t *testing.T) { t.Fatalf("failed to run binary with -C option: %v\n%s", err, output) } - // Verify that prompt.md was created in the output directory - promptFile := filepath.Join(outputDir, "prompt.md") - if _, err := os.Stat(promptFile); os.IsNotExist(err) { - t.Errorf("prompt.md was not created in output directory") + // Verify that the three output files were created in the output directory + memoriesOutFile := filepath.Join(outputDir, "memories.md") + taskOutFile := filepath.Join(outputDir, "task.md") + personaOutFile := filepath.Join(outputDir, "persona.md") + + var statErr error + if _, statErr = os.Stat(memoriesOutFile); os.IsNotExist(statErr) { + t.Errorf("memories.md was not created in output directory") + } + if _, statErr = os.Stat(taskOutFile); os.IsNotExist(statErr) { + t.Errorf("task.md was not created in output directory") + } + if _, statErr = os.Stat(personaOutFile); os.IsNotExist(statErr) { + t.Errorf("persona.md was not created in output directory") } // Verify the content includes the memory - content, err := os.ReadFile(promptFile) + content, err := os.ReadFile(memoriesOutFile) if err != nil { - t.Fatalf("failed to read prompt.md: %v", err) + t.Fatalf("failed to read memories.md: %v", err) } if !strings.Contains(string(content), "Test Memory") { - t.Errorf("prompt.md does not contain expected memory content") + t.Errorf("memories.md does not contain expected memory content") } } diff --git a/main.go b/main.go index e87286b..ab87d05 100644 --- a/main.go +++ b/main.go @@ -120,16 +120,17 @@ func run(ctx context.Context, args []string) error { return fmt.Errorf("failed to create bootstrap dir: %w", err) } - output, err := os.Create(filepath.Join(outputDir, "prompt.md")) - if err != nil { - return fmt.Errorf("failed to create prompt file: %w", err) - } - defer output.Close() - // Track total tokens var totalTokens int - // Process persona first if provided (should be first in output) + // Create persona.md file + personaOutput, err := os.Create(filepath.Join(outputDir, "persona.md")) + if err != nil { + return fmt.Errorf("failed to create persona file: %w", err) + } + defer personaOutput.Close() + + // Process persona first if provided if personaName != "" { personaFound := false for _, path := range personas { @@ -159,7 +160,7 @@ func run(ctx context.Context, args []string) error { fmt.Fprintf(os.Stdout, "Using persona file: %s (~%d tokens)\n", path, tokens) // Personas don't need variable expansion or filters - if _, err := output.WriteString(content + "\n\n"); err != nil { + if _, err := personaOutput.WriteString(content); err != nil { return fmt.Errorf("failed to write persona: %w", err) } @@ -172,6 +173,13 @@ func run(ctx context.Context, args []string) error { } } + // Create memories.md file + memoriesOutput, err := os.Create(filepath.Join(outputDir, "memories.md")) + if err != nil { + return fmt.Errorf("failed to create memories file: %w", err) + } + defer memoriesOutput.Close() + for _, memory := range memories { // Skip if the path doesn't exist @@ -231,8 +239,8 @@ func run(ctx context.Context, args []string) error { } } - if _, err := output.WriteString(content + "\n\n"); err != nil { - return fmt.Errorf("failed to write to output file: %w", err) + if _, err := memoriesOutput.WriteString(content + "\n\n"); err != nil { + return fmt.Errorf("failed to write to memories file: %w", err) } return nil @@ -247,6 +255,13 @@ func run(ctx context.Context, args []string) error { return fmt.Errorf("failed to write bootstrap file: %w", err) } + // Create task.md file + taskOutput, err := os.Create(filepath.Join(outputDir, "task.md")) + if err != nil { + return fmt.Errorf("failed to create task file: %w", err) + } + defer taskOutput.Close() + for _, path := range tasks { stat, err := os.Stat(path) if os.IsNotExist(err) { @@ -281,8 +296,8 @@ func run(ctx context.Context, args []string) error { totalTokens += tokens fmt.Fprintf(os.Stdout, "Using task file: %s (~%d tokens)\n", path, tokens) - if _, err := output.WriteString(expanded); err != nil { - return fmt.Errorf("failed to write expanded prompt: %w", err) + if _, err := taskOutput.WriteString(expanded); err != nil { + return fmt.Errorf("failed to write expanded task: %w", err) } // Print total token count From 95d7db2e8ce61e331851e9e71be54dadc4b40378 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 23:21:55 +0000 Subject: [PATCH 3/7] Update README documentation to reflect three separate output files Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- README.md | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index c87d0d2..dcc834f 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ When working with AI coding agents (like GitHub Copilot, ChatGPT, Claude, etc.), 2. **Creating task-specific prompts** - Define templated prompts for common tasks (e.g., "add feature", "fix bug", "refactor") 3. **Automating environment setup** - Package bootstrap scripts that prepare the environment before an agent starts work 4. **Filtering context dynamically** - Use selectors to include only relevant context (e.g., production vs. development, Python vs. Go) -5. **Composing everything together** - Generate a single `prompt.md` file combining all relevant context and the task prompt +5. **Composing everything together** - Generate three separate markdown files: `persona.md`, `memories.md`, and `task.md` ## When to Use @@ -36,10 +36,12 @@ The basic workflow is: 1. **Organize your context** - Create persona files (optional), memory files (shared context), and task files (task-specific instructions) 2. **Run the CLI** - Execute `coding-context [options] [persona-name]` 3. **Get assembled output** - The tool generates: - - `prompt.md` - Combined persona (if specified) + memories + task with template variables filled in + - `persona.md` - Persona content (if persona is specified) + - `memories.md` - All included memory files combined + - `task.md` - Task prompt with template variables filled in - `bootstrap` - Executable script to set up the environment - `bootstrap.d/` - Individual bootstrap scripts from your memory files -4. **Use with AI agents** - Share `prompt.md` with your AI coding agent, or run `./bootstrap` to prepare the environment first +4. **Use with AI agents** - Share the generated markdown files with your AI coding agent, or run `./bootstrap` to prepare the environment first **Visual flow:** ``` @@ -48,16 +50,12 @@ The basic workflow is: | (optional) | | | | (task-name.md) | +----------+-----------+ +----------+----------+ +------------+-------------+ | | | - | Apply template params | Filter by selectors | Apply template params + | No expansion | Filter by selectors | Apply template params v v v +----------------------+ +---------------------+ +--------------------------+ -| Rendered Persona +------>+ Filtered Memories +------>+ Rendered Task | -+----------------------+ +---------------------+ +------------+-------------+ - | - v - +----------------------------+ - | prompt.md (combined output)| - +----------------------------+ +| persona.md | | memories.md | | task.md | ++----------------------+ +---------------------+ +--------------------------+ +``` ``` ## Installation @@ -177,22 +175,27 @@ coding-context -p taskName="Fix Bug" -p language=Go my-task coding-context -p taskName="Fix Bug" -p language=Go my-task expert ``` -**Result:** This generates `./prompt.md` combining your persona (if specified), memories, and the task prompt with template variables filled in. You can now share this complete context with your AI coding agent! +**Result:** This generates three files: `./persona.md` (if persona is specified), `./memories.md`, and `./task.md` with template variables filled in. You can now share these files with your AI coding agent! + +**What you'll see in the generated files (with persona):** -**What you'll see in `prompt.md` (with persona):** +`persona.md`: ```markdown # Expert Developer You are an expert developer with deep knowledge of best practices. +``` - +`memories.md`: +```markdown # Project Context - Framework: Go CLI - Purpose: Manage AI agent context +``` - - +`task.md`: +```markdown # Task: Fix Bug Please help me with this task. The project uses Go. @@ -222,7 +225,7 @@ Each directory should contain: ### Persona Files -Optional persona files define the role or character the AI agent should assume. Personas are output **first** in the generated `prompt.md`, before memories and tasks. +Optional persona files define the role or character the AI agent should assume. Personas are output to `persona.md` when specified. **Important:** Persona files do NOT support template variable expansion. They are included as-is in the output. @@ -239,7 +242,7 @@ Run with: coding-context add-feature expert ``` -This will look for `expert.md` in the persona directories. The persona is optional - if you don't specify a persona name as the second argument, the output will contain only memories and the task. +This will look for `expert.md` in the persona directories and output it to `persona.md`. The persona is optional - if you don't specify a persona name as the second argument, only `memories.md` and `task.md` will be generated. ### Prompt Files @@ -369,7 +372,9 @@ If no selectors are specified, all memory files are included. ## Output Files -- **`prompt.md`** - Combined output with all memories and the task prompt +- **`persona.md`** - Persona content (only created when a persona is specified) +- **`memories.md`** - Combined output with all filtered memory files +- **`task.md`** - Task prompt with template variables expanded - **`bootstrap`** - Executable script that runs all bootstrap scripts from memories - **`bootstrap.d/`** - Individual bootstrap scripts (SHA256 named) From 8cba931ce83e99dfbefd7c18076918811a4fb648 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 23:51:35 +0000 Subject: [PATCH 4/7] Update README to clarify that persona.md is always created Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dcc834f..59b9adb 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ The basic workflow is: 1. **Organize your context** - Create persona files (optional), memory files (shared context), and task files (task-specific instructions) 2. **Run the CLI** - Execute `coding-context [options] [persona-name]` 3. **Get assembled output** - The tool generates: - - `persona.md` - Persona content (if persona is specified) + - `persona.md` - Persona content (always created, can be empty if no persona is specified) - `memories.md` - All included memory files combined - `task.md` - Task prompt with template variables filled in - `bootstrap` - Executable script to set up the environment @@ -372,7 +372,7 @@ If no selectors are specified, all memory files are included. ## Output Files -- **`persona.md`** - Persona content (only created when a persona is specified) +- **`persona.md`** - Persona content (always created, can be empty if no persona is specified) - **`memories.md`** - Combined output with all filtered memory files - **`task.md`** - Task prompt with template variables expanded - **`bootstrap`** - Executable script that runs all bootstrap scripts from memories From 5bfa23e897ae6de7cf0e5e7ed5a49a32baa727bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 00:40:15 +0000 Subject: [PATCH 5/7] Merge conflict resolution: Add memoryBasenames duplicate check Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- main.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/main.go b/main.go index ab87d05..2e9f990 100644 --- a/main.go +++ b/main.go @@ -180,6 +180,8 @@ func run(ctx context.Context, args []string) error { } defer memoriesOutput.Close() + memoryBasenames := make(map[string]bool) + for _, memory := range memories { // Skip if the path doesn't exist @@ -217,6 +219,14 @@ func run(ctx context.Context, args []string) error { return nil } + // Check for duplicate basenames + basename := filepath.Base(path) + if memoryBasenames[basename] { + fmt.Fprintf(os.Stdout, "Excluding memory file (other memory with same basename found): %s\n", path) + return nil + } + memoryBasenames[basename] = true + // Estimate tokens for this file tokens := estimateTokens(content) totalTokens += tokens From bbc6623db12058eea2247f0a1b0cc0117457c4ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 00:46:08 +0000 Subject: [PATCH 6/7] Add deduplication documentation and test from main branch Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- README.md | 24 ++++++++++++++ memory_name_test.go | 81 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 memory_name_test.go diff --git a/README.md b/README.md index 59b9adb..b2690ae 100644 --- a/README.md +++ b/README.md @@ -369,6 +369,30 @@ INFO Including memory file path=.prompts/memories/nofrontmatter.md If no selectors are specified, all memory files are included. +### Deduplicating Memories + +When you have multiple memory files with the same filename (basename), only the first one encountered will be included. This allows you to override default memories with project-specific ones by using the same filename. + +**Example:** + +If you have two directories with memory files: + +`~/.coding-context/memories/general/setup.md`: +```markdown +--- +--- +This is the default setup memory. +``` + +`./memories/setup.md`: +```markdown +--- +--- +This is a project-specific setup memory. +``` + +When the tool processes these two files, it will include only one of them based on which is encountered first during filesystem traversal. **The order depends on the order of memory paths specified and filesystem traversal order, which is not guaranteed to be alphabetical or consistent.** This mechanism is useful for overriding default memories with project-specific ones when you use the same filename. + ## Output Files diff --git a/memory_name_test.go b/memory_name_test.go new file mode 100644 index 0000000..a86ce6b --- /dev/null +++ b/memory_name_test.go @@ -0,0 +1,81 @@ +package main + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestMemoryNameExclusion(t *testing.T) { + // Create a temporary directory for our test files + tmpDir, err := os.MkdirTemp("", "memory-name-test") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Create two subdirectories with memory files with the same basename + dir1 := filepath.Join(tmpDir, "dir1") + dir2 := filepath.Join(tmpDir, "dir2") + if err := os.MkdirAll(dir1, 0755); err != nil { + t.Fatalf("Failed to create dir1: %v", err) + } + if err := os.MkdirAll(dir2, 0755); err != nil { + t.Fatalf("Failed to create dir2: %v", err) + } + + memory1Content := `--- +--- +This is the first memory.` + memory1Path := filepath.Join(dir1, "memory.md") + if err := os.WriteFile(memory1Path, []byte(memory1Content), 0644); err != nil { + t.Fatalf("Failed to write memory.md in dir1: %v", err) + } + + memory2Content := `--- +--- +This is the second memory.` + memory2Path := filepath.Join(dir2, "memory.md") + if err := os.WriteFile(memory2Path, []byte(memory2Content), 0644); err != nil { + t.Fatalf("Failed to write memory.md in dir2: %v", err) + } + + // Create a dummy task file + taskContent := "This is the task." + taskPath := filepath.Join(tmpDir, "task.md") + if err := os.WriteFile(taskPath, []byte(taskContent), 0644); err != nil { + t.Fatalf("Failed to write task.md: %v", err) + } + + // Set up the arguments for the run function + args := []string{"task"} + memories = []string{dir1, dir2} + tasks = []string{tmpDir} + outputDir = tmpDir + params = make(paramMap) + includes = make(selectorMap) + excludes = make(selectorMap) + runBootstrap = false + workDir = tmpDir + + // Run the application + if err := run(context.Background(), args); err != nil { + t.Fatalf("run() failed: %v", err) + } + + // Check the output + memoriesBytes, err := os.ReadFile(filepath.Join(tmpDir, "memories.md")) + if err != nil { + t.Fatalf("Failed to read memories.md: %v", err) + } + memoriesContent := string(memoriesBytes) + + // We expect only one of the memories to be included + hasFirst := strings.Contains(memoriesContent, "This is the first memory.") + hasSecond := strings.Contains(memoriesContent, "This is the second memory.") + if hasFirst == hasSecond { + t.Errorf("Expected only one memory to be included, but got: %s", memoriesContent) + } +} From 34b12907ecee70358836d1e9f25d9ceaf1268a2b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 16:09:44 +0000 Subject: [PATCH 7/7] Fix broken test and README documentation issues Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- README.md | 3 +-- memory_name_test.go | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b2690ae..40dc096 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,6 @@ The basic workflow is: | persona.md | | memories.md | | task.md | +----------------------+ +---------------------+ +--------------------------+ ``` -``` ## Installation @@ -242,7 +241,7 @@ Run with: coding-context add-feature expert ``` -This will look for `expert.md` in the persona directories and output it to `persona.md`. The persona is optional - if you don't specify a persona name as the second argument, only `memories.md` and `task.md` will be generated. +This will look for `expert.md` in the persona directories and output it to `persona.md`. The persona is optional – if you don't specify a persona name as the second argument, `persona.md` will still be generated but will be empty, alongside `memories.md` and `task.md`. ### Prompt Files diff --git a/memory_name_test.go b/memory_name_test.go index 30a9b95..a86ce6b 100644 --- a/memory_name_test.go +++ b/memory_name_test.go @@ -66,16 +66,16 @@ This is the second memory.` } // Check the output - promptBytes, err := os.ReadFile(filepath.Join(tmpDir, "prompt.md")) + memoriesBytes, err := os.ReadFile(filepath.Join(tmpDir, "memories.md")) if err != nil { - t.Fatalf("Failed to read prompt.md: %v", err) + t.Fatalf("Failed to read memories.md: %v", err) } - prompt := string(promptBytes) + memoriesContent := string(memoriesBytes) // We expect only one of the memories to be included - hasFirst := strings.Contains(prompt, "This is the first memory.") - hasSecond := strings.Contains(prompt, "This is the second memory.") + hasFirst := strings.Contains(memoriesContent, "This is the first memory.") + hasSecond := strings.Contains(memoriesContent, "This is the second memory.") if hasFirst == hasSecond { - t.Errorf("Expected only one memory to be included, but got: %s", prompt) + t.Errorf("Expected only one memory to be included, but got: %s", memoriesContent) } }