diff --git a/cmd/acb-index-builder/generator.go b/cmd/acb-index-builder/generator.go index cae8fae..e629ddc 100644 --- a/cmd/acb-index-builder/generator.go +++ b/cmd/acb-index-builder/generator.go @@ -175,6 +175,11 @@ func generateAllIndexes(data *IndexData, outputDir string, db *sql.DB, cfg *Conf return fmt.Errorf("archetypes: %w", err) } + // Generate meta directory index (data/meta/index.json) + if err := generateMetaIndex(outputDir); err != nil { + return fmt.Errorf("meta index: %w", err) + } + // Generate community hints (data/evolution/community_hints.json) if err := generateCommunityHints(data, outputDir); err != nil { return fmt.Errorf("community hints: %w", err) @@ -1898,6 +1903,18 @@ type ArchetypesIndex struct { Archetypes []ArchetypeEntry `json:"archetypes"` } +// MetaFileEntry represents a single meta data file in the meta directory index. +type MetaFileEntry struct { + Name string `json:"name"` // e.g., "archetypes.json" + Description string `json:"description"` // e.g., "Bot archetype classifications" +} + +// MetaIndex is the top-level structure for data/meta/index.json. +type MetaIndex struct { + UpdatedAt string `json:"updated_at"` + Files []MetaFileEntry `json:"files"` +} + // classifyArchetype infers a behavioral archetype from bot name when the // archetype field is empty. func classifyArchetype(bot BotData) string { @@ -1983,6 +2000,26 @@ func generateArchetypes(data *IndexData, outputDir string) error { return writeJSON(filepath.Join(metaDir, "archetypes.json"), index) } +// generateMetaIndex builds data/meta/index.json listing all available meta data files. +func generateMetaIndex(outputDir string) error { + metaDir := filepath.Join(outputDir, "data", "meta") + if err := os.MkdirAll(metaDir, 0755); err != nil { + return err + } + + // List all available meta data files with descriptions + files := []MetaFileEntry{ + {Name: "archetypes.json", Description: "Bot archetype classifications and statistics"}, + {Name: "rivalries.json", Description: "Top rivalries between bots"}, + } + + index := MetaIndex{ + UpdatedAt: time.Now().Format(time.RFC3339), + Files: files, + } + return writeJSON(filepath.Join(metaDir, "index.json"), index) +} + // ─── Community Hints (§15.2 / §13.6) ────────────────────────────────────────── // CommunityHint is a single high-upvote tactical insight from §13.6 feedback. diff --git a/cmd/acb-index-builder/main.go b/cmd/acb-index-builder/main.go index 1a8d4f6..586ca6e 100644 --- a/cmd/acb-index-builder/main.go +++ b/cmd/acb-index-builder/main.go @@ -124,6 +124,7 @@ func uploadMetaJSONToR2(ctx context.Context, cfg *Config, outputDir string, data } static := []string{ + "data/meta/index.json", "data/meta/archetypes.json", "data/meta/rivalries.json", "data/evolution/community_hints.json", diff --git a/cmd/acb-index-builder/main_test.go b/cmd/acb-index-builder/main_test.go index 3817dd9..c4651e9 100644 --- a/cmd/acb-index-builder/main_test.go +++ b/cmd/acb-index-builder/main_test.go @@ -1546,3 +1546,52 @@ func TestGenerateMapsIndex(t *testing.T) { t.Errorf("Expected 0 cores for map_def456, got %d", len(detail2.Cores)) } } + +func TestGenerateMetaIndex(t *testing.T) { + tmpDir := t.TempDir() + + if err := generateMetaIndex(tmpDir); err != nil { + t.Fatalf("generateMetaIndex failed: %v", err) + } + + content, err := os.ReadFile(filepath.Join(tmpDir, "data", "meta", "index.json")) + if err != nil { + t.Fatalf("Failed to read meta/index.json: %v", err) + } + + var index MetaIndex + if err := json.Unmarshal(content, &index); err != nil { + t.Fatalf("Failed to parse meta/index.json: %v", err) + } + + if len(index.Files) != 2 { + t.Errorf("Expected 2 files in meta index, got %d", len(index.Files)) + } + if index.UpdatedAt == "" { + t.Error("UpdatedAt should not be empty") + } + + // Verify archetypes.json entry + foundArchetypes := false + foundRivalries := false + for _, f := range index.Files { + if f.Name == "archetypes.json" { + foundArchetypes = true + if f.Description == "" { + t.Error("archetypes.json should have a description") + } + } + if f.Name == "rivalries.json" { + foundRivalries = true + if f.Description == "" { + t.Error("rivalries.json should have a description") + } + } + } + if !foundArchetypes { + t.Error("Expected archetypes.json entry in meta index") + } + if !foundRivalries { + t.Error("Expected rivalries.json entry in meta index") + } +}