feat(index-builder): generate data/meta/index.json

Adds MetaIndex struct and generateMetaIndex function to create
data/meta/index.json listing all available meta data files
(archetypes.json, rivalries.json) with descriptions.

Also adds the new file to the R2 warm cache upload list.

Closes: bf-66rk

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-05-26 13:08:17 -04:00
parent c9503d2588
commit c37f68e08b
3 changed files with 87 additions and 0 deletions

View file

@ -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.

View file

@ -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",

View file

@ -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")
}
}