Skip to main content

Data Generator

Overview

The Data Generator manages the data repository for each work - a GitHub repository containing YAML configuration and JSON item data. It orchestrates the items generation pipeline and persists results to GitHub.

Architecture

┌─────────────────────────────────────────────────────────────────┐
│ DataGeneratorService │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ initialize() │ │
│ │ │ │
│ │ 1. Load existing data from GitHub │ │
│ │ 2. Call ItemsGeneratorService.generateItems() │ │
│ │ 3. Merge new items with existing │ │
│ │ 4. Write updated data to GitHub │ │
│ │ 5. Create PR if configured │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Uses: DataRepository (YAML/JSON file operations) │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ GitHub Data Repository │
│ │
│ {owner}/{work-slug}-data/ │
│ ├── .works/works.yml # Work configuration │
│ ├── items/ # Item JSON files │
│ │ ├── item-slug-1.json │
│ │ ├── item-slug-2.json │
│ │ └── ... │
│ ├── categories.yml # Category definitions │
│ ├── tags.yml # Tag definitions │
│ └── brands.yml # Brand definitions │
└─────────────────────────────────────────────────────────────────┘

Data Flow

TriggerGenerationOrchestrator


DataGeneratorService.initialize(payload)

├── Clone/pull data repository

├── Read existing .works/works.yml, items/, categories.yml, tags.yml

├── Call ItemsGeneratorService.generateItems()
│ └── Returns: {items, categories, tags, brands, metrics, contentCache}

├── Merge results with existing data
│ ├── Update existing items (if same slug)
│ ├── Add new items
│ ├── Preserve manual edits (featured, order)

├── Write to repository
│ ├── Update .works/works.yml (increment version)
│ ├── Write item JSON files
│ ├── Update categories.yml
│ ├── Update tags.yml
│ ├── Update brands.yml

└── Commit & Push (or create PR)


Returns: {success, prUpdate?, stats}

DataRepository Class

The DataRepository class handles all file operations for the data repository.

Key Methods

class DataRepository {
// Configuration
readConfig(): Promise<WorkConfig>;
writeConfig(config: WorkConfig): Promise<void>;

// Items
readItems(): Promise<ItemData[]>;
writeItem(item: ItemData): Promise<void>;
removeItem(slug: string): Promise<void>;

// Categories
readCategories(): Promise<Category[]>;
writeCategories(categories: Category[]): Promise<void>;

// Tags
readTags(): Promise<Tag[]>;
writeTags(tags: Tag[]): Promise<void>;

// Brands
readBrands(): Promise<Brand[]>;
writeBrands(brands: Brand[]): Promise<void>;

// Git operations
clone(): Promise<void>;
pull(): Promise<void>;
commit(message: string): Promise<void>;
push(): Promise<void>;
createBranch(name: string): Promise<void>;
switchBranch(name: string): Promise<void>;
}

Repository Structure

.works/works.yml

name: "My Work"
description: "A curated list of tools..."
slug: "my-work"
version: 3 # Auto-incremented on each update
metadata:
initial_prompt: "Find the best..."
last_request_data:
prompt: "Find the best..."
config:
max_search_queries: 10
...
created_at: "2024-01-15T10:30:00Z"
updated_at: "2024-01-16T14:20:00Z"

items/item-slug.json

{
"name": "Tool Name",
"slug": "tool-name",
"description": "A powerful tool for...",
"source_url": "https://tool.example.com",
"category": "monitoring",
"tags": ["open-source", "cloud-native"],
"featured": false,
"order": null,
"badges": {
"open_source": true
},
"brand": "company-name",
"brand_logo_url": "https://...",
"images": ["https://..."],
"markdown": "## Tool Name\n\n..."
}

categories.yml

- id: 'monitoring'
name: 'Monitoring'
priority: 1

- id: 'logging'
name: 'Logging'
priority: 2

- id: 'other'
name: 'Other'

tags.yml

- id: 'open-source'
name: 'Open Source'

- id: 'cloud-native'
name: 'Cloud Native'

- id: 'self-hosted'
name: 'Self Hosted'

brands.yml

- id: 'datadog'
name: 'Datadog'
logo_url: 'https://...'

- id: 'grafana'
name: 'Grafana'
logo_url: 'https://...'

Interfaces

DataGeneratorService Input

interface DataGeneratorPayload {
workId: string;
userId: string;
mode: 'create' | 'update';
dto: CreateItemsGeneratorDto;
historyId: string;
}

DataGeneratorService Output

interface DataGeneratorResult {
success: boolean;
prUpdate?: {
branch: string;
title: string;
body: string;
number: number;
url: string;
};
stats: {
newItemsCount: number;
updatedItemsCount: number;
totalItemsCount: number;
metrics: ItemsGeneratorMetrics;
};
error?: string;
}

WorkConfig

interface WorkConfig {
name: string;
description: string;
slug: string;
version: number;
metadata: {
initial_prompt?: string;
last_request_data?: CreateItemsGeneratorDto;
created_at: string;
updated_at: string;
};
}

Generation Modes

CREATE_UPDATE (Default)

  • Merges new items with existing
  • Updates existing items if slug matches
  • Preserves manual edits (featured, order)
  • Increments version

RECREATE

  • Deletes all existing items
  • Writes only newly generated items
  • Resets categories and tags
  • Preserves config metadata

Pull Request Mode

When update_with_pull_request: true:

  1. Creates new branch: ever-update-{timestamp}
  2. Commits changes to branch
  3. Creates PR to main branch
  4. Returns PR details in result
// PR creation
const prUpdate = {
branch: 'ever-update-1705330800',
title: 'Ever Works: Update work items',
body: `## Changes\n\n- Added ${newCount} new items\n- Updated ${updatedCount} items`,
number: 42,
url: 'https://github.com/user/repo/pull/42'
};

Version Management

The version field in .works/works.yml auto-increments on each update:

// In DataGeneratorService
async updateConfig(config: WorkConfig) {
config.version = (config.version || 0) + 1;
config.metadata.updated_at = new Date().toISOString();
await this.dataRepository.writeConfig(config);
}

This allows:

  • Tracking update frequency
  • Cache invalidation on website
  • Rollback identification

Item Merging Logic

function mergeItems(existing: ItemData[], newItems: ItemData[]): ItemData[] {
const merged = new Map<string, ItemData>();

// Add existing items
for (const item of existing) {
merged.set(item.slug, item);
}

// Merge new items
for (const newItem of newItems) {
const existing = merged.get(newItem.slug);
if (existing) {
// Update but preserve manual edits
merged.set(newItem.slug, {
...newItem,
featured: existing.featured, // Preserve
order: existing.order // Preserve
});
} else {
merged.set(newItem.slug, newItem);
}
}

return Array.from(merged.values());
}

Error Handling

ErrorHandling
Git clone failsRetry with backoff, fail if persistent
Write failsRollback changes, report error
Push failsCheck permissions, retry, fail gracefully
PR creation failsFall back to direct commit

File Locations

/packages/agent/src/data-generator/
├── data-generator.service.ts # Main service
├── data-repository.ts # File operations
├── texts.ts # License, legal text
└── interfaces/
└── data-generator.interfaces.ts

Configuration

OptionDescription
update_with_pull_requestCreate PR instead of direct commit
generation_methodCREATE_UPDATE or RECREATE

Integration Points

  • Input: ItemsGeneratorService.generateItems() result
  • Output: GitHub data repository
  • Consumer: MarkdownGenerator, Website

See Also