feat: Milestone 2 — narrative layer (NarrativeSpec, WorldSpec, dataset card)#5
Merged
feat: Milestone 2 — narrative layer (NarrativeSpec, WorldSpec, dataset card)#5
Conversation
…t card) - narrative/spec.py: frozen dataclasses for NarrativeSpec hierarchy (CompanySpec, ProductSpec, MarketSpec, GtmMotionSpec, PersonaSpec, FunnelStageSpec) with validated from_dict() classmethods - narrative/dataset_card.py: render_dataset_card() produces Markdown dataset card from WorldSpec (header, narrative summary, task, stubs for table inventory and feature categories, use cases, caveats) - core/models.py: WorldSpec.narrative field (NarrativeSpec | None) - api/generator.py: world_spec property; from_recipe() resolves the recipe's narrative.yaml into a NarrativeSpec and populates WorldSpec - 51 new tests covering spec validation, card rendering, and Generator integration (110 total); ruff + mypy clean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Introduces Milestone 2’s “narrative layer” by adding typed, validated narrative spec models, wiring them into WorldSpec/Generator, and providing a Markdown dataset-card renderer with accompanying tests.
Changes:
- Add frozen dataclass hierarchy for
NarrativeSpecand sub-specs withfrom_dict()parsing/validation. - Add
render_dataset_card(WorldSpec) -> strto produce a Markdown dataset card (with stubs for future milestones). - Extend
WorldSpecwith an optionalnarrativefield and haveGenerator.from_recipe()populate/expose aworld_spec.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
leadforge/narrative/spec.py |
New narrative datamodels + validation helpers. |
leadforge/narrative/dataset_card.py |
Markdown dataset card renderer based on WorldSpec. |
leadforge/core/models.py |
Add WorldSpec.narrative (optional) + docstring update. |
leadforge/api/generator.py |
Populate/expose world_spec (incl. narrative) from recipes; update constructor. |
tests/narrative/test_spec.py |
Tests for parsing/validation, immutability, and real YAML parsing. |
tests/narrative/test_dataset_card.py |
Tests for dataset card rendering and generator integration. |
tests/narrative/__init__.py |
New test package marker. |
.agent-plan.md |
Project plan updated to reflect Milestone 2 completion and Milestone 3 next steps. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This comment has been minimized.
This comment has been minimized.
- spec.py: _require_keys now guards against non-dict input (COPILOT-3) - spec.py: NarrativeSpec.from_dict validates each personas/funnel_stages element is a dict before passing to sub-from_dict (COPILOT-3) - spec.py: GtmMotionSpec.from_dict validates channels is a list of strings, rejects bools for share floats, and enforces [0, 1] range (COPILOT-1) - spec.py: PersonaSpec.from_dict validates title_variants is a list of strings instead of silently splitting a bare string (COPILOT-2) - spec.py: ProductSpec.from_dict requires free_trial_available / demo_available to be actual bools; rejects int/str coercion (COPILOT-6) - spec.py: MarketSpec.from_dict validates icp_industries and geographies are lists of strings (COPILOT-7) - generator.py: Generator.__init__ takes only world_spec; config property derives from world_spec.config (single source of truth) (COPILOT-4) - dataset_card.py: stub text changed to "Narrative unavailable for this dataset." (COPILOT-5); test updated to match Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
pr-agent-context report: This is a refreshed snapshot of the current PR state.
This run includes an unresolved review comment on PR #5.
For each unresolved review comment, recommend one of: resolve as irrelevant, accept and implement
the recommended solution, open a separate issue and resolve as out-of-scope for this PR, accept and
implement a different solution, or resolve as already treated by the code.
After I reply with my decision per item, implement the accepted actions, resolve the corresponding
PR comments, and push all of these changes in a single commit.
# Copilot Comments
## COPILOT-1
Location: leadforge/narrative/dataset_card.py:103
URL: https://github.com/leadforge-dev/leadforge/pull/5#discussion_r3115717615
Root author: copilot-pull-request-reviewer
Comment:
The "Primary task" section is hard-coded to `converted_within_90_days` and a 90-day label definition. That matches the current shipped recipe, but it will drift once additional recipes/tasks exist (or if task parameters become configurable). Consider carrying `primary_task` (and any label-window parameter) into `WorldSpec` during `Generator.from_recipe()` so the dataset card renders from resolved spec rather than literals.
~~~suggestion
primary_task = getattr(
world_spec,
"primary_task",
getattr(cfg, "primary_task", "converted_within_90_days"),
)
label_window_days = getattr(
world_spec,
"label_window_days",
getattr(cfg, "label_window_days", 90),
)
lines += [
"## Primary task",
"",
f"**Task:** `{primary_task}`",
"",
"**Label definition:** A lead is considered converted if a `closed_won` event "
f"is recorded within {label_window_days} days of the lead's snapshot anchor date. "
~~~Run metadata: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
leadforge/narrative/spec.py— frozen dataclasses for the fullNarrativeSpechierarchy:CompanySpec,ProductSpec,MarketSpec,GtmMotionSpec,PersonaSpec,FunnelStageSpec. Each has a validatedfrom_dict()classmethod that rejects bools masquerading as ints, enforces list-pair shapes, and raisesInvalidRecipeErroron bad input.leadforge/narrative/dataset_card.py—render_dataset_card(world_spec: WorldSpec) -> strproducing a Markdown card with: metadata header table, narrative summary (vendor/product/market/GTM/personas), primary task + label definition, stub sections for table inventory and feature categories (v0.3.0+), suggested use cases, and caveats.leadforge/core/models.py—WorldSpecgains anarrative: NarrativeSpec | None = Nonefield.leadforge/api/generator.py—Generator.from_recipe()loads and parsesnarrative.yamlinto aNarrativeSpec, wraps it in aWorldSpec, and exposes it via theworld_specproperty.tests/narrative/test_spec.pyandtests/narrative/test_dataset_card.py— covering sub-model validation, bool rejection, frozen-dataclass immutability, real YAML round-trip, card content assertions, and Generator integration. Total: 110 tests passing.Test plan
pytest— 110 tests passruff check . && ruff format --check .— cleanmypy leadforge/— no errorstest_real_narrative_yaml_parses— verifiesb2b_saas_procurement_v1/narrative.yamlround-tripstest_card_with_narrative_contains_company_name— verifies card contains "Veridian Technologies"test_narrative_spec_frozen— confirmsFrozenInstanceErroron mutation attempt🤖 Generated with Claude Code