Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 30 additions & 46 deletions .agent-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,66 +6,50 @@

## Current System State

Repository initialized. Codebase empty. Architecture, design, and roadmap documents are locked in `docs/`. Agent-context files (`CLAUDE.md`, `llms.txt`, `.agent-plan.md`) are initialized. Branch/PR workflow rules (including label taxonomy and milestone map) are locked in `CLAUDE.md` and backed by a local `.git/hooks/pre-push` convenience hook (not versioned) and GitHub branch protection. GitHub labels and milestones (v0.1–v1.0) are created. pr-agent-context CI and refresh workflows are live in `.github/workflows/`.
**v0.1.0 shipped.** Installable package, full CLI skeleton (`list-recipes` implemented, others stubbed), core enums/exceptions/models, recipe registry, CI workflow, pre-commit config. All 20 tests pass.

---

## Active Task Breakdown — Phase 1: Milestone 0 (Project Foundation)

Goal: Create a professional-quality, installable package skeleton with working tooling and CI, so all subsequent work has a stable base.

- [ ] **1. Repository bootstrap + package skeleton**
- Create `pyproject.toml` (package name `leadforge`, entry point `leadforge = "leadforge.cli.main:app"`)
- Create `leadforge/__init__.py` and `leadforge/version.py` (initial `__version__ = "0.1.0"`)
- Create top-level directories matching canonical layout: `leadforge/{api,cli,core,narrative,schema,structure,mechanisms,simulation,render,exposure,validation,recipes,examples,sample_data}/`
- Add `__init__.py` stubs to all subpackages
- Add `LICENSE` (MIT)
- Add minimal `README.md` with one-liner, install instructions, and quickstart placeholder
- Verify: `pip install -e .` works

- [ ] **2. Tooling + CI + pre-commit**
- Add `ruff` (lint + format), `mypy` or `pyright`, `pytest` to dev dependencies
- Create `ruff.toml` or inline `[tool.ruff]` config
- Create `pre-commit-config.yaml` with ruff + type-check hooks
- Create `.github/workflows/ci.yml` — runs `pytest`, `ruff check`, type check on push/PR
- Verify: CI passes on empty codebase

- [ ] **3. CLI entrypoint skeleton**
- Implement `leadforge/cli/main.py` with a minimal app (typer or click)
- Implement stub commands: `list-recipes`, `generate`, `inspect`, `validate` (each prints "not yet implemented")
- Add `leadforge/cli/commands/` directory with one file per command
- Verify: `leadforge --help` lists all four commands
- Verify: `leadforge list-recipes` exits cleanly with a stub message

- [ ] **4. Core primitives scaffold**
- `leadforge/core/enums.py` — `ExposureMode` enum (`student_public`, `research_instructor`), `DifficultyProfile` enum (`intro`, `intermediate`, `advanced`)
- `leadforge/core/exceptions.py` — `LeadforgeError` base + `InvalidRecipeError`, `InvalidConfigError`, `SimulationError`, `RenderError`, `ValidationError`
- `leadforge/core/models.py` — empty `GenerationConfig`, `WorldSpec`, `WorldBundle` dataclass stubs
- Add pytest smoke tests for enum values and exception hierarchy

- [ ] **5. Initial recipe directory**
- Create `leadforge/recipes/b2b_saas_procurement_v1/recipe.yaml` with minimal metadata (id, title, primary_task, vertical, supported_modes, supported_difficulty)
- Create `leadforge/recipes/registry.py` with a `list_recipes()` function that reads from the recipes directory
- Wire `list-recipes` CLI command to `list_recipes()`
- Verify: `leadforge list-recipes` outputs at least the `b2b_saas_procurement_v1` entry
## Active Task Breakdown — Milestone 1: Canonical Config, Recipe & Model Objects (v0.2.0)

Goal: Establish the typed configuration and recipe system that all simulation work will depend on.

- [ ] **1. Core models + RNG utilities**
- Implement `leadforge/core/rng.py`: seeded RNG root + deterministic named substreams
- Flesh out `GenerationConfig` with full validation (config precedence rules)
- Implement `leadforge/core/hashing.py`: deterministic config hashing for manifest identity

- [ ] **2. Recipe registry and loading**
- Implement full `Recipe` typed model (dataclass) in `leadforge/api/recipes.py`
- Implement config precedence: CLI flags > override file > recipe defaults > package defaults
- Implement `leadforge/core/serialization.py`: JSON/YAML read-write helpers

- [ ] **3. Recipe assets + validation tests**
- Add `narrative.yaml`, `difficulty_profiles.yaml` to `b2b_saas_procurement_v1/`
- Implement `Generator.from_recipe(...)` skeleton (no simulation yet)
- Tests: recipe validation, config precedence, RNG determinism, `from_recipe` round-trip

---

## Context Pointers

- Full milestone scope for Phase 1 (Milestone 0): `docs/leadforge_implementation_plan.md` §5 "Milestone 0"
- Next phase (Milestone 1 — config, recipe, model objects): `docs/leadforge_implementation_plan.md` §5 "Milestone 1"
- Dependency graph across all milestones: `docs/leadforge_implementation_plan.md` §6
- Canonical package layout: `docs/leadforge_architecture_spec.md` §4
- Milestone 1 scope: `docs/leadforge_implementation_plan.md` §5 "Milestone 1"
- Full milestone dependency graph: `docs/leadforge_implementation_plan.md` §6
- Public API contract: `docs/leadforge_architecture_spec.md` §6
- CLI command spec: `docs/leadforge_architecture_spec.md` §7
- Tech stack decisions: `CLAUDE.md` "Tech Stack" section
- Config precedence rules: `docs/leadforge_architecture_spec.md` §24

---

## Completed Phases

_None yet._
### Milestone 0 — Project Foundation ✓ (v0.1.0)
- `pyproject.toml`, `README.md`, `LICENSE`, `.pre-commit-config.yaml`
- Full package skeleton with `__init__.py` stubs for all submodules
- `leadforge/core/`: `enums.py`, `exceptions.py`, `models.py`, `rng.py` (stub), `ids.py` (stub)
- `leadforge/cli/`: `main.py` + four commands (`list-recipes` implemented, others stubbed)
- `leadforge/recipes/`: registry + `b2b_saas_procurement_v1/recipe.yaml`
- `.github/workflows/ci.yml`: lint, typecheck, test matrix (3.11 + 3.12) with coverage upload
- 20 tests passing; ruff + mypy clean

---

Expand Down
59 changes: 59 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: CI

on:
push:
branches: [main]
pull_request:

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
name: Lint & format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install ruff
- run: ruff check .
- run: ruff format --check .

typecheck:
name: Type check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -e ".[dev]"
- run: mypy leadforge/

test:
name: Tests (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12"]
env:
COVERAGE_FILE: .coverage.${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install -e ".[dev]" pytest-cov
- run: pytest --cov=leadforge --cov-report=term-missing
- name: Upload coverage artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: pr-agent-context-coverage-py${{ matrix.python-version }}
path: .coverage.${{ matrix.python-version }}
include-hidden-files: true
if-no-files-found: ignore
15 changes: 15 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.5
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-merge-conflict
27 changes: 27 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# AGENTS.md — leadforge

Agent-specific conventions layered on top of CLAUDE.md.

---

## PR Review Comment Workflow

When addressing PR review comments (Copilot, human reviewers, or otherwise):

1. Triage each comment — recommend one of: resolve as irrelevant, accept and implement, open a separate issue and resolve as out-of-scope, accept a different solution, or resolve as already treated.
2. After the user confirms decisions, implement accepted changes and push **all changes in a single commit** to the PR branch.
3. **After the commit lands, resolve the corresponding GitHub review threads** using the GraphQL API:

```bash
gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: "PRRT_..."}) { thread { isResolved } } }'
```

Resolve every addressed thread — whether the action was "implement", "already treated", or "irrelevant/out-of-scope". Unresolved threads indicate open work; resolved threads mean the discussion is closed.

Do **not** leave threads unresolved after the commit is pushed.

---

## Branch & PR Conventions

See CLAUDE.md for the full mandatory branch/PR workflow (branch → commit → update `.agent-plan.md` → open PR).
75 changes: 74 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,74 @@
# leadforge
# leadforge

**Opinionated framework for generating synthetic CRM and GTM datasets from simulated commercial worlds.**

`leadforge` generates narrative-grounded synthetic revenue datasets starting with lead scoring, designed to support teaching, portfolio projects, and research. Rather than sampling rows from a distribution, it simulates a commercial world — a specific company, selling a specific product, to a specific kind of buyer — and renders realistic CRM-style outputs from that world.

---

## Installation

```bash
pip install leadforge
```

For development:

```bash
git clone https://github.com/leadforge-dev/leadforge.git
cd leadforge
pip install -e ".[dev]"
pre-commit install
```

---

## Quickstart

```bash
# List available recipes
leadforge list-recipes

# Coming in v0.2.0: generate a dataset bundle
# leadforge generate \
# --recipe b2b_saas_procurement_v1 \
# --seed 42 \
# --mode student_public \
# --difficulty intermediate \
# --n-leads 5000 \
# --out ./out/demo_bundle

# Coming in v0.4.0: inspect a generated bundle
# leadforge inspect ./out/demo_bundle

# Coming in v0.5.0: validate a generated bundle
# leadforge validate ./out/demo_bundle
```

**Python API** (coming in v0.2.0):

```python
from leadforge.api import Generator

gen = Generator.from_recipe(
"b2b_saas_procurement_v1",
seed=42,
exposure_mode="student_public",
)
bundle = gen.generate(n_leads=5000, difficulty="intermediate")
bundle.save("./out/demo_bundle")
```
Comment thread
shaypal5 marked this conversation as resolved.

---

## Documentation

- [Design document](docs/leadforge_design_doc.md)
- [Architecture spec](docs/leadforge_architecture_spec.md)
- [Implementation plan](docs/leadforge_implementation_plan.md)

---

## License

MIT. See [LICENSE](LICENSE).
5 changes: 5 additions & 0 deletions leadforge/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""leadforge — synthetic CRM and GTM dataset generation."""

from leadforge.version import __version__

__all__ = ["__version__"]
5 changes: 5 additions & 0 deletions leadforge/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""leadforge public Python API."""

from leadforge.api.generator import Generator

__all__ = ["Generator"]
65 changes: 65 additions & 0 deletions leadforge/api/generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Public Generator API — stub for Milestone 1.

The Generator class is the primary entry point for programmatic dataset
generation. It is fully specified in the architecture doc (§6) and will
be implemented across Milestones 1–9.
"""

from __future__ import annotations

from typing import Any

from leadforge.core.enums import DifficultyProfile, ExposureMode
from leadforge.core.models import GenerationConfig, WorldBundle


class Generator:
"""High-level entry point for generating a synthetic CRM dataset bundle.

Usage (once implemented)::

gen = Generator.from_recipe(
"b2b_saas_procurement_v1",
seed=42,
exposure_mode="student_public",
)
bundle = gen.generate(n_leads=5000, difficulty="intermediate")
bundle.save("./out/demo_bundle")

Implemented in Milestone 1 (config/recipe) through Milestone 9 (rendering).
"""

def __init__(self, config: GenerationConfig) -> None:
self._config = config

@classmethod
def from_recipe(
cls,
recipe_id: str,
*,
seed: int = 42,
exposure_mode: str | ExposureMode = ExposureMode.student_public,
**kwargs: Any,
) -> Generator:
"""Create a Generator from a recipe ID.

Not yet implemented — available in v0.2.0.
"""
raise NotImplementedError(
"Generator.from_recipe() is not yet implemented. Coming in v0.2.0."
)

def generate(
self,
*,
n_accounts: int | None = None,
n_contacts: int | None = None,
n_leads: int | None = None,
difficulty: str | DifficultyProfile = DifficultyProfile.intermediate,
**kwargs: Any,
) -> WorldBundle:
"""Run the world simulation and return a bundle.

Not yet implemented — available in v0.2.0.
"""
raise NotImplementedError("Generator.generate() is not yet implemented. Coming in v0.2.0.")
Empty file added leadforge/cli/__init__.py
Empty file.
Empty file.
35 changes: 35 additions & 0 deletions leadforge/cli/commands/generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""leadforge generate command."""

import typer


def generate(
recipe: str = typer.Option(..., "--recipe", "-r", help="Recipe ID to use."),
seed: int = typer.Option(..., "--seed", help="Random seed for deterministic generation."),
mode: str = typer.Option(
...,
"--mode",
help="Exposure mode: student_public or research_instructor.",
),
out: str = typer.Option(..., "--out", help="Output directory for the generated bundle."),
difficulty: str = typer.Option(
"intermediate",
"--difficulty",
help="Difficulty profile: intro, intermediate, or advanced.",
),
n_accounts: int | None = typer.Option(None, "--n-accounts", help="Number of accounts."),
n_contacts: int | None = typer.Option(None, "--n-contacts", help="Number of contacts."),
n_leads: int | None = typer.Option(None, "--n-leads", help="Number of leads."),
horizon_days: int | None = typer.Option(
None, "--horizon-days", help="Simulation horizon in days."
),
override: str | None = typer.Option(
None, "--override", help="Path to a YAML config override file."
),
) -> None:
"""Generate a synthetic CRM dataset bundle from a recipe."""
typer.echo(
"The 'generate' command is not yet implemented. Coming in v0.2.0.",
err=True,
)
raise typer.Exit(1)
Loading
Loading