first pass at the newspaper builder
Test / test (push) Has been cancelled

This commit is contained in:
2026-09-14 11:57:22 +10:00
commit bec1eaac87
497 changed files with 178953 additions and 0 deletions
@@ -0,0 +1,94 @@
# Code Generation Plan — unit: funnies
> Implements the funnies library: derives theme words from content, builds an
> AI-drafted 10x10 crossword + a find-a-word grid, selects/embeds an xkcd cartoon
> via build-time content-enrichment, and returns a graceful fallback/empty
> FunniesResult. Per confirmed functional + NFR + infra design.
## Steps
- [ ] **Step 1: Module skeleton** — `newspaper/funnies/` with `__init__.py`.
- [ ] **Step 2: Test runner** — pytest configured (already in pyproject); record the unit command.
- [ ] **Step 3: Data model** — Crossword, FindAWord, Cartoon, Comic, FunniesResult, FunniesBrief (functional-design entities, Contract 2/4).
- [ ] **Step 4: Theme derivation** — derive theme words + context from content (BR1.1).
- [ ] **Step 5: Find-a-word** — place theme words into a grid (BR1.3).
- [ ] **Step 6: AI-drafted crossword** — injectable clue-drafter lays out a 10x10 grid (BR1.2).
- [ ] **Step 7: Cartoon selection** — injectable xkcd/enrichment fetch, embedded locally, tasteful fallback (BR2.1/BR2.2, BR3.1).
- [ ] **Step 8: FunniesResult + graceful empty/fallback** — status ready/fallback/empty + statusMessage (BR3.2, Q5=A).
- [ ] **Step 9: Unit tests** — test-after (find-a-word, crossword layout, fallback, empty, self-contained).
- [ ] **Step 10: Traceability** — write traceability.json + code-summary + source-manifest.
## Testing Contract
```json
{
"version": 1,
"methodology": "test-after",
"source": "team",
"ordering": "implement the transform and layout, then write and run tests that",
"scope": "classic",
"test_strategy": "standard",
"project_type": "greenfield",
"applicable_notes": [
{
"layer": "org",
"text": "We treat tests as a first-class deliverable in every Bolt. The specific\nmethodology (TDD, BDD, ATDD, or classic test-after) is affirmed at\npractices-discovery and recorded in `team.md` under this heading with explicit\n`Methodology` and `Ordering` fields; Code Generation resolves those fields\nindependently from coverage, tooling, and scope notes.\n\nWhen no posture has been affirmed, our default per scope is:\n- **Methodology**: test-after\n- **Ordering**: implement each applicable testable layer, then write and run\n that layer's tests.\n- `mvp`, `enterprise`, `feature`, `infra`, `classic` add an 80% line-coverage\n floor and CI execution before merge.\n- `bugfix`, `security-patch` add a targeted regression for the specific\n bug/vulnerability and require the existing suite to remain green.\n- `express` uses the Minimal strategy: requirement-driven unit tests (one per\n requirement, with a happy-path floor per component); existing tests remain\n green.\n- `poc`, `refactor`, `workshop` add no extra new-test floor and require the\n existing suite to remain green.\n\nThe active `Test Strategy` still applies in every scope and determines test\nvolume/types. Scope floors are additive; they never reduce or replace the\nselected strategy.\n\nBuild and Test verifies defined coverage floors and affirmed quality targets;\nthey may not be weakened to make a step pass.\n\nAffirm a stricter posture in `team.md` if the team commits to one."
},
{
"layer": "team",
"text": "Unit tests for the markdown→HTML transform. The primary testable surface is the\nrenderer: sample article markdown must always map to complete, correctly\nstructured newspaper HTML with no dropped sections or mangled escaping, and the\nprint stylesheet must keep content inside an A4 sheet. We write focused tests for\nthe transform logic rather than an exhaustive suite; the print result remains the\nreal-world acceptance check.\n\nMethodology: test-after\nOrdering: implement the transform and layout, then write and run tests that\nverify markdown→HTML fidelity and A4 print containment."
}
],
"obligations": {
"strategy": "standard",
"strategy_volume": [
"Five to eight tests per component.",
"Unit tests plus integration tests for key boundaries.",
"Add E2E, performance, or security tests when requirements demand them."
],
"scope_floor": [
"Keep the existing test suite green.",
"This scope adds no extra new-test floor beyond the selected test strategy."
],
"combination_rule": "Apply every selected-strategy obligation and every scope-floor obligation; neither replaces the other, and a targeted scope regression may add the narrowest necessary test type beyond the strategy default."
},
"plan_profile": {
"methodology": "test-after",
"runner_step": "Bootstrap the minimal test runner/configuration and record the exact unit-scoped command.",
"runner_ready_before_first_test": true,
"testable_layers": [
"Data model / database behavior",
"Repository / data access",
"Business logic",
"API / endpoint",
"Frontend behavior"
],
"steps": [
"Project structure and production configuration skeleton.",
"Bootstrap the minimal test runner/configuration and record the exact unit-scoped command.",
"Data model / database behavior - implement.",
"Data model / database behavior - write and run its tests after implementation.",
"Repository / data access - implement.",
"Repository / data access - write and run its tests after implementation.",
"Business logic - implement.",
"Business logic - write and run its tests after implementation.",
"API / endpoint - implement.",
"API / endpoint - write and run its tests after implementation.",
"Frontend behavior - implement.",
"Frontend behavior - write and run its tests after implementation.",
"Environment/build configuration.",
"Documentation and traceability."
]
},
"input_sha256": "sha256:7df1c2c0484b987ee3ac61ed4a435eddbecc0e5cf219e85e0eec73e54809a607",
"contract_sha256": "sha256:69c65f5ed93c67be42f6b31cb7ec9cb0e0c18861358949313d9d44b8f9335f99"
}
```
## Plan Approval
[Approval Fingerprint]: _to_fill_
[Planned Source]: _to_fill_
- Approve Plan — proceed to code generation
- Request Changes — revise the plan
[Answer]:
@@ -0,0 +1,11 @@
# Code Generation — Plan Approval (unit: funnies)
## Plan Approval
[Approval Fingerprint]: sha256:v3:1099307c4158f51b024d25992d47195498efd4af261e70550c4bc789533f8ce1
[Planned Source]: 6ac8169da4312c26cddd94a0eb68809fb1173b030f54818600f697a76c682280
- Approve Plan — proceed to code generation
- Request Changes — revise the plan
[Answer]: Approve Plan
@@ -0,0 +1,35 @@
# Code Generation Summary — unit: funnies
## Files created
- `newspaper/funnies/__init__.py` — public API.
- `newspaper/funnies/model.py` — FunniesBrief, Crossword, FindAWord, Cartoon,
Comic, FunniesResult (+ is_self_contained BR4.1/NFR4 check).
- `newspaper/funnies/find_a_word.py` — theme derivation (BR1.1) + deterministic
find-a-word grid construction (BR1.3).
- `newspaper/funnies/crossword.py` — 10×10 crossword with injectable AI
clue-drafter (BR1.2, Q1=B/Q2=B).
- `newspaper/funnies/cartoon.py` — xkcd/enrichment fetch + tasteful content-derived
fallback (BR2.1/BR2.2), comic build (BR3.1).
- `newspaper/funnies/builder.py` — FunniesBuilder orchestrator, graceful
ready/fallback/empty (BR3.2, Q5=A).
- `newspaper/tests/test_funnies.py` — 7 unit tests, all passing.
## Key implementation decisions
- **Injectable clue-drafter + cartoon source** — no real network in CI; the
sanctioned content-enrichment fetch is behind narrow interfaces.
- **Deterministic grid layou**t — find-a-word uses longest-first systematic
placement with a fixed seed; crossword lays out on a 10×10 grid.
- **Graceful empty/fallback** — no viable content returns an empty FunniesResult
+ statusMessage (BR3.2, Q5=A); cartoon-not-found falls back to a tasteful
generated strip, never a remote URL at print (BR2.2).
- **Self-contained** — all art_refs are local/bundled (never http(s)), BR4.1/NFR4.
## Test coverage summary
- 7 tests passing: theme derivation, find-a-word placement, 10×10 crossword +
clues, cartoon success (local embed), cartoon failure fallback, empty-no-content,
self-contained output.
## Deviations from plan
- Find-a-word grid is 12×12 (not fixed by plan) and may omit theme words on very
dense inputs by capacity — an inherent fixed-grid limit, not a defect; the
realistic bounded set fits deterministically. No other deviations.
@@ -0,0 +1,14 @@
{
"stage": "code-generation",
"unit": "funnies",
"version": 1,
"writes": [
{ "path": "newspaper/funnies/__init__.py" },
{ "path": "newspaper/funnies/model.py" },
{ "path": "newspaper/funnies/find_a_word.py" },
{ "path": "newspaper/funnies/crossword.py" },
{ "path": "newspaper/funnies/cartoon.py" },
{ "path": "newspaper/funnies/builder.py" },
{ "path": "newspaper/tests/test_funnies.py" }
]
}
@@ -0,0 +1,21 @@
{
"stage": "code-generation",
"unit": "funnies",
"upstream_ids": ["AC3.1.1", "AC3.1.2", "AC3.1.3", "AC3.1.4", "AC3.1.5", "BR1.1", "BR1.2", "BR1.3", "BR2.1", "BR2.2", "BR3.1", "BR3.2", "BR4.1", "NFR4"],
"coverage": [
{ "id": "AC3.1.1", "status": "OK", "target": "newspaper/funnies/crossword.py" },
{ "id": "AC3.1.2", "status": "OK", "target": "newspaper/funnies/find_a_word.py" },
{ "id": "AC3.1.3", "status": "OK", "target": "newspaper/funnies/cartoon.py" },
{ "id": "AC3.1.4", "status": "OK", "target": "newspaper/funnies/cartoon.py" },
{ "id": "AC3.1.5", "status": "OK", "target": "newspaper/funnies/model.py" },
{ "id": "BR1.1", "status": "OK", "target": "newspaper/funnies/find_a_word.py" },
{ "id": "BR1.2", "status": "OK", "target": "newspaper/funnies/crossword.py" },
{ "id": "BR1.3", "status": "OK", "target": "newspaper/funnies/find_a_word.py" },
{ "id": "BR2.1", "status": "OK", "target": "newspaper/funnies/cartoon.py" },
{ "id": "BR2.2", "status": "OK", "target": "newspaper/funnies/cartoon.py" },
{ "id": "BR3.1", "status": "OK", "target": "newspaper/funnies/cartoon.py" },
{ "id": "BR3.2", "status": "OK", "target": "newspaper/funnies/builder.py" },
{ "id": "BR4.1", "status": "OK", "target": "newspaper/funnies/model.py" },
{ "id": "NFR4", "status": "OK", "target": "newspaper/funnies/model.py" }
]
}
@@ -0,0 +1,24 @@
# Unit Test Instructions — unit: funnies
## Test framework / config
- pytest, under `newspaper/tests/`.
## How to run THIS UNIT's tests (exact command)
```bash
uv run --project newspaper pytest newspaper/tests/test_funnies.py -q
```
## Strategy / coverage (Standard)
- 5-8 unit tests: theme word derivation, find-a-word placement, crossword grid layout, cartoon fallback, empty FunniesResult, self-contained output.
## Mocking
- Mock the clue-drafter (cloud model) and the cartoon/enrichment fetch — no real network in CI.
## Test cases
1. theme words derived from content
2. find-a-word places every word
3. crossword builds a 10x10 grid + clues
4. cartoon fetch success → embedded local ref
5. cartoon fetch fails → tasteful fallback strip (not a remote URL)
6. empty FunniesResult on no viable content + statusMessage
7. funnies output is self-contained (no remote URL at print)