Skip to content

Commit a1064dc

Browse files
authored
Fix campaign memory pattern to use recommended layout (#8272)
1 parent 0ba331d commit a1064dc

File tree

7 files changed

+101
-47
lines changed

7 files changed

+101
-47
lines changed

.github/workflows/go-file-size-reduction-project64.campaign.g.lock.yml

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/go-file-size-reduction-project64.campaign.g.md

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/go-file-size-reduction-project64.campaign.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ workflows:
99
- daily-file-diet
1010
tracker-label: "campaign:go-file-size-reduction-project64"
1111
memory-paths:
12-
- "memory/campaigns/go-file-size-reduction-project64-*/**"
12+
- "memory/campaigns/go-file-size-reduction-project64/**"
1313
metrics-glob: "memory/campaigns/go-file-size-reduction-project64-*/metrics/*.json"
1414
state: active
1515
tags:

actions/setup/js/push_repo_memory.cjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ async function main() {
180180
// If no explicit campaign ID, try to extract from patterns when memoryId is "campaigns"
181181
if (!campaignId && memoryId === "campaigns" && patterns.length > 0) {
182182
// Try to extract campaign ID from first pattern matching "<campaign-id>/**"
183+
// This only works for simple patterns without wildcards in the campaign ID portion
184+
// For patterns like "campaign-id-*/**", use GH_AW_CAMPAIGN_ID environment variable
183185
const campaignMatch = /^([^*?/]+)\/\*\*/.exec(patterns[0]);
184186
if (campaignMatch) {
185187
campaignId = campaignMatch[1];

actions/setup/js/push_repo_memory.test.cjs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,26 @@ describe("push_repo_memory.cjs - globPatternToRegex helper", () => {
109109
expect(metricsRegex.test("security-q1/cursor.json")).toBe(false);
110110
});
111111

112+
it("should match flexible campaign pattern for both dated and non-dated structures", () => {
113+
// Pattern: go-file-size-reduction-project64*/**
114+
// This should match BOTH:
115+
// - go-file-size-reduction-project64-2025-12-31/ (with date suffix)
116+
// - go-file-size-reduction-project64/ (without suffix)
117+
const flexibleRegex = globPatternToRegex("go-file-size-reduction-project64*/**");
118+
119+
// Test dated structure (with suffix)
120+
expect(flexibleRegex.test("go-file-size-reduction-project64-2025-12-31/cursor.json")).toBe(true);
121+
expect(flexibleRegex.test("go-file-size-reduction-project64-2025-12-31/metrics/2025-12-31.json")).toBe(true);
122+
123+
// Test non-dated structure (without suffix)
124+
expect(flexibleRegex.test("go-file-size-reduction-project64/cursor.json")).toBe(true);
125+
expect(flexibleRegex.test("go-file-size-reduction-project64/metrics/2025-12-31.json")).toBe(true);
126+
127+
// Should not match other campaigns
128+
expect(flexibleRegex.test("other-campaign/file.json")).toBe(false);
129+
expect(flexibleRegex.test("security-q1/cursor.json")).toBe(false);
130+
});
131+
112132
it("should match multiple file extensions", () => {
113133
const patterns = ["*.json", "*.jsonl", "*.csv", "*.md"].map(globPatternToRegex);
114134

pkg/campaign/orchestrator.go

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,62 +10,65 @@ import (
1010

1111
var orchestratorLog = logger.New("campaign:orchestrator")
1212

13-
// extractFileGlobPattern extracts the file glob pattern from memory-paths or
14-
// metrics-glob configuration. This pattern is used for the file-glob filter in
13+
// convertStringsToAny converts a slice of strings to a slice of any
14+
func convertStringsToAny(strings []string) []any {
15+
result := make([]any, len(strings))
16+
for i, s := range strings {
17+
result[i] = s
18+
}
19+
return result
20+
}
21+
22+
// extractFileGlobPatterns extracts all file glob patterns from memory-paths or
23+
// metrics-glob configuration. These patterns are used for the file-glob filter in
1524
// repo-memory configuration to match files that the agent creates.
1625
//
1726
// For campaigns that use dated directory patterns (e.g., campaign-id-*/), this
18-
// function preserves the wildcard pattern instead of using just the campaign ID.
27+
// function preserves all wildcard patterns from memory-paths to support multiple
28+
// directory structures (both dated and non-dated).
1929
//
2030
// Examples:
21-
// - memory-paths: ["memory/campaigns/project64-*/**"] -> "project64-*/**"
22-
// - metrics-glob: "memory/campaigns/project64-*/metrics/*.json" -> "project64-*/**"
23-
// - no patterns with wildcards -> "project64/**" (fallback to ID)
24-
func extractFileGlobPattern(spec *CampaignSpec) string {
25-
// Try to extract pattern from memory-paths first
26-
// Prefer patterns with wildcards in the directory name (e.g., campaign-id-*)
27-
var firstValidPattern string
31+
// - memory-paths: ["memory/campaigns/project64-*/**", "memory/campaigns/project64/**"]
32+
// -> ["project64-*/**", "project64/**"]
33+
// - memory-paths: ["memory/campaigns/project64-*/**"] -> ["project64-*/**"]
34+
// - metrics-glob: "memory/campaigns/project64-*/metrics/*.json" -> ["project64-*/**"]
35+
// - no patterns with wildcards -> ["project64/**"] (fallback to ID)
36+
func extractFileGlobPatterns(spec *CampaignSpec) []string {
37+
var patterns []string
38+
39+
// Extract all patterns from memory-paths
2840
for _, memPath := range spec.MemoryPaths {
2941
// Remove "memory/campaigns/" prefix if present
3042
pattern := strings.TrimPrefix(memPath, "memory/campaigns/")
3143
// If pattern has both wildcards and slashes, it's a valid pattern
3244
if strings.Contains(pattern, "*") && strings.Contains(pattern, "/") {
33-
// Check if wildcard is in the directory name (not just in **)
34-
if strings.Contains(strings.Split(pattern, "/")[0], "*") {
35-
// This pattern has a wildcard in the directory name - prefer it
36-
orchestratorLog.Printf("Extracted file-glob pattern from memory-paths (with wildcard): %s", pattern)
37-
return pattern
38-
}
39-
// Save this as a fallback valid pattern
40-
if firstValidPattern == "" {
41-
firstValidPattern = pattern
42-
}
45+
patterns = append(patterns, pattern)
46+
orchestratorLog.Printf("Extracted file-glob pattern from memory-paths: %s", pattern)
4347
}
4448
}
4549

46-
// If we found a valid pattern (even without directory wildcard), use it
47-
if firstValidPattern != "" {
48-
orchestratorLog.Printf("Extracted file-glob pattern from memory-paths: %s", firstValidPattern)
49-
return firstValidPattern
50+
// If we found patterns from memory-paths, return them
51+
if len(patterns) > 0 {
52+
return patterns
5053
}
5154

52-
// Try to extract pattern from metrics-glob
55+
// Try to extract pattern from metrics-glob as fallback
5356
if spec.MetricsGlob != "" {
5457
pattern := strings.TrimPrefix(spec.MetricsGlob, "memory/campaigns/")
5558
if strings.Contains(pattern, "*") {
5659
// Extract the base directory pattern (everything before /metrics/ or first file-specific part)
5760
if idx := strings.Index(pattern, "/metrics/"); idx > 0 {
5861
basePattern := pattern[:idx] + "/**"
5962
orchestratorLog.Printf("Extracted file-glob pattern from metrics-glob: %s", basePattern)
60-
return basePattern
63+
return []string{basePattern}
6164
}
6265
}
6366
}
6467

6568
// Fallback to simple ID-based pattern
6669
fallbackPattern := fmt.Sprintf("%s/**", spec.ID)
6770
orchestratorLog.Printf("Using fallback file-glob pattern: %s", fallbackPattern)
68-
return fallbackPattern
71+
return []string{fallbackPattern}
6972
}
7073

7174
// BuildOrchestrator constructs a minimal agentic workflow representation for a
@@ -259,9 +262,9 @@ func BuildOrchestrator(spec *CampaignSpec, campaignFilePath string) (*workflow.W
259262

260263
orchestratorLog.Printf("Campaign orchestrator '%s' built successfully with safe outputs enabled", spec.ID)
261264

262-
// Extract file-glob pattern from memory-paths or metrics-glob to support
263-
// dated campaign directory patterns like "campaign-id-*/**"
264-
fileGlobPattern := extractFileGlobPattern(spec)
265+
// Extract file-glob patterns from memory-paths or metrics-glob to support
266+
// multiple directory structures (e.g., both dated "campaign-id-*/**" and non-dated "campaign-id/**")
267+
fileGlobPatterns := extractFileGlobPatterns(spec)
265268

266269
data := &workflow.WorkflowData{
267270
Name: name,
@@ -283,7 +286,7 @@ func BuildOrchestrator(spec *CampaignSpec, campaignFilePath string) (*workflow.W
283286
map[string]any{
284287
"id": "campaigns",
285288
"branch-name": "memory/campaigns",
286-
"file-glob": []any{fileGlobPattern},
289+
"file-glob": convertStringsToAny(fileGlobPatterns),
287290
},
288291
},
289292
"bash": []any{"*"},

pkg/campaign/orchestrator_test.go

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -313,21 +313,44 @@ func TestBuildOrchestrator_GovernanceOverridesSafeOutputMaxima(t *testing.T) {
313313
}
314314
}
315315

316-
func TestExtractFileGlobPattern(t *testing.T) {
316+
func TestExtractFileGlobPatterns(t *testing.T) {
317317
tests := []struct {
318318
name string
319319
spec *CampaignSpec
320-
expectedGlob string
320+
expectedGlobs []string
321321
expectedLogMsg string
322322
}{
323+
{
324+
name: "flexible pattern matching both dated and non-dated",
325+
spec: &CampaignSpec{
326+
ID: "go-file-size-reduction-project64",
327+
MemoryPaths: []string{"memory/campaigns/go-file-size-reduction-project64*/**"},
328+
MetricsGlob: "memory/campaigns/go-file-size-reduction-project64-*/metrics/*.json",
329+
},
330+
expectedGlobs: []string{"go-file-size-reduction-project64*/**"},
331+
expectedLogMsg: "Extracted file-glob pattern from memory-paths",
332+
},
323333
{
324334
name: "dated pattern in memory-paths",
325335
spec: &CampaignSpec{
326336
ID: "go-file-size-reduction-project64",
327337
MemoryPaths: []string{"memory/campaigns/go-file-size-reduction-project64-*/**"},
328338
MetricsGlob: "memory/campaigns/go-file-size-reduction-project64-*/metrics/*.json",
329339
},
330-
expectedGlob: "go-file-size-reduction-project64-*/**",
340+
expectedGlobs: []string{"go-file-size-reduction-project64-*/**"},
341+
expectedLogMsg: "Extracted file-glob pattern from memory-paths",
342+
},
343+
{
344+
name: "multiple patterns in memory-paths",
345+
spec: &CampaignSpec{
346+
ID: "go-file-size-reduction-project64",
347+
MemoryPaths: []string{
348+
"memory/campaigns/go-file-size-reduction-project64-*/**",
349+
"memory/campaigns/go-file-size-reduction-project64/**",
350+
},
351+
MetricsGlob: "memory/campaigns/go-file-size-reduction-project64-*/metrics/*.json",
352+
},
353+
expectedGlobs: []string{"go-file-size-reduction-project64-*/**", "go-file-size-reduction-project64/**"},
331354
expectedLogMsg: "Extracted file-glob pattern from memory-paths",
332355
},
333356
{
@@ -336,7 +359,7 @@ func TestExtractFileGlobPattern(t *testing.T) {
336359
ID: "go-file-size-reduction-project64",
337360
MetricsGlob: "memory/campaigns/go-file-size-reduction-project64-*/metrics/*.json",
338361
},
339-
expectedGlob: "go-file-size-reduction-project64-*/**",
362+
expectedGlobs: []string{"go-file-size-reduction-project64-*/**"},
340363
expectedLogMsg: "Extracted file-glob pattern from metrics-glob",
341364
},
342365
{
@@ -345,15 +368,15 @@ func TestExtractFileGlobPattern(t *testing.T) {
345368
ID: "simple-campaign",
346369
MemoryPaths: []string{"memory/campaigns/simple-campaign/**"},
347370
},
348-
expectedGlob: "simple-campaign/**",
349-
expectedLogMsg: "Using fallback file-glob pattern",
371+
expectedGlobs: []string{"simple-campaign/**"},
372+
expectedLogMsg: "Extracted file-glob pattern from memory-paths",
350373
},
351374
{
352375
name: "no memory paths or metrics glob",
353376
spec: &CampaignSpec{
354377
ID: "minimal-campaign",
355378
},
356-
expectedGlob: "minimal-campaign/**",
379+
expectedGlobs: []string{"minimal-campaign/**"},
357380
expectedLogMsg: "Using fallback file-glob pattern",
358381
},
359382
{
@@ -365,16 +388,22 @@ func TestExtractFileGlobPattern(t *testing.T) {
365388
"memory/campaigns/multi-path-*/data/**",
366389
},
367390
},
368-
expectedGlob: "multi-path-*/data/**",
391+
expectedGlobs: []string{"multi-path-staging/**", "multi-path-*/data/**"},
369392
expectedLogMsg: "Extracted file-glob pattern from memory-paths",
370393
},
371394
}
372395

373396
for _, tt := range tests {
374397
t.Run(tt.name, func(t *testing.T) {
375-
result := extractFileGlobPattern(tt.spec)
376-
if result != tt.expectedGlob {
377-
t.Errorf("extractFileGlobPattern(%q) = %q, want %q", tt.spec.ID, result, tt.expectedGlob)
398+
result := extractFileGlobPatterns(tt.spec)
399+
if len(result) != len(tt.expectedGlobs) {
400+
t.Errorf("extractFileGlobPatterns(%q) returned %d patterns, want %d", tt.spec.ID, len(result), len(tt.expectedGlobs))
401+
return
402+
}
403+
for i, expected := range tt.expectedGlobs {
404+
if result[i] != expected {
405+
t.Errorf("extractFileGlobPatterns(%q)[%d] = %q, want %q", tt.spec.ID, i, result[i], expected)
406+
}
378407
}
379408
})
380409
}

0 commit comments

Comments
 (0)