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,17 @@
<!-- INVARIANT: examples are single-line HTML comments so a fresh template parses to total=0 (MEMORY_EMPTY). Do NOT un-comment or split across lines. t100 guards this. -->
> This file is kept up to date automatically while the stage runs. Add observations at the review step, not by editing here directly.
## Interpretations
<!-- example: 2026-05-29T10:14:32Z — chose REST over GraphQL; the consuming team only needs CRUD, revisit if subscriptions land -->
## Deviations
<!-- example: 2026-05-29T10:14:32Z — skipped the optional caching layer the stage prose suggested; the dataset is small enough that it adds risk -->
## Tradeoffs
<!-- example: 2026-05-29T10:14:32Z — picked TDD over BDD this run; the team is unit-first and the domain is well-understood -->
## Open questions
<!-- example: 2026-05-29T10:14:32Z — confirm the retention window with compliance before the next stage hardens the schema -->
2026-09-13T12:10:00Z — Interpretation — Confirmed decomposition (all A, self-guided + user pre-approved continuation): one coarse unit per component (generator/ai-draft/funnies), defined interfaces (DraftBundle JSON handoff), embedded/static single deliverable
2026-09-13T12:10:00Z — Interpretation — DAG: generator depends on ai-draft + funnies; ai-draft and funnies are independent (parallel buildable); acyclic
2026-09-13T12:15:00Z — Interpretation — Human ruling on review R-01: U1 is a library-backed one-shot local tool, never a service/server. Retagged U1 kind service→library in unit-of-work.md + dependency edge block.
@@ -0,0 +1,16 @@
{
"stage": "units-generation",
"upstream_ids": ["US1", "US2", "US3", "US4", "US5", "US6", "US7", "US8", "US9", "US10"],
"coverage": [
{ "id": "US1", "status": "OK", "target": "U1" },
{ "id": "US2", "status": "OK", "target": "U1" },
{ "id": "US3", "status": "OK", "target": "U3" },
{ "id": "US4", "status": "OK", "target": "U1" },
{ "id": "US5", "status": "OK", "target": "U1" },
{ "id": "US6", "status": "OK", "target": "U1" },
{ "id": "US7", "status": "OK", "target": "U1" },
{ "id": "US8", "status": "OK", "target": "U2" },
{ "id": "US9", "status": "OK", "target": "U2, U1" },
{ "id": "US10", "status": "OK", "target": "U1" }
]
}
@@ -0,0 +1,47 @@
# Units Generation — Unit Dependency DAG
> Topology only. Describes what can depend on what; the economic build path
> (Bolt sequence) is decided in Delivery Planning (Stage 2.9).
## Dependency Graph
```
generator ──depends on──▶ ai-draft (optional AI draft, invoked by the CLI)
generator ──depends on──▶ funnies (funnies output, invoked during layout)
ai-draft (no dependencies)
funnies (no dependencies)
```
U2 and U3 are independent libraries the U1 CLI orchestrates. There is no
dependency edge between U2 and U3; they never call each other. The graph is acyclic.
## Integration Points
| From | To | Integration | Style |
|---|---|---|---|
| generator | ai-draft | CLI invokes optional Ollama draft (`--draft`); receives DraftBundle article set | sync |
| generator | funnies | CLI asks for crossword/find-a-word/cartoon built from content theme words + context | sync |
| generator → review page | — | writes DraftBundle JSON; review page reads it via native file picker (ADR-004, NFR5) — data artifact, not a call | file |
## Parallel Development Opportunities
U2 and U3 are fully independent of each other (no dependency edge), so they can
be built (and reviewed) in parallel. U1 depends on both and can be built in
parallel with them up to the integration seams; the DAG admits multiple valid
topological orders. The economic choice of which Bolt ships first is Delivery
Planning's call, not this stage's.
## Machine-readable edge block
```yaml
units:
- name: "generator"
kind: library
depends_on: ["ai-draft", "funnies"]
- name: "ai-draft"
kind: library
depends_on: []
- name: "funnies"
kind: library
depends_on: []
```
@@ -0,0 +1,44 @@
# Units Generation — Unit-of-Work Story Map
> Every user story mapped to the unit that implements it. Cross-cutting stories
> are flagged. Coverage: every story assigned; every unit has stories.
## Story → Unit Map
| Story | Unit | Unit Directory | Notes |
|---|---|---|---|
| US1 — Multi-page newspaper output | U1 generator | u1-generator | layout + pagination |
| US2 — Classic masthead | U1 generator | u1-generator | masthead render |
| US3 — Funnies (crossword/comics/xkcd) | U3 funnies | u3-funnies | puzzle + cartoon build |
| US4 — Photos embedded | U1 generator | u1-generator | photo embed + auto-balance |
| US5 — Content ingestion & self-contained output | U1 generator | u1-generator | content parse + emit |
| US6 — Classic broadsheet aesthetic | U1 generator | u1-generator | print CSS/theme |
| US7 — Dynamic issue metadata | U1 generator | u1-generator | MastheadConfig/Issue |
| US8 — AI copy via local Ollama | U2 ai-draft | u2-ai-draft | local draft |
| US9 — Per-article human review of AI drafts | U2 ai-draft (DraftBundle) + U1 generator (review handoff/file-picker) | u2-ai-draft, u1-generator | cross-cutting: draft data + review page handoff |
| US10 — Friendly first-run / empty state | U1 generator | u1-generator | empty-state |
## Cross-cutting stories
- **US9** spans U2 (produces the DraftBundle) and U1 (writes + hand off the
bundle to the review page via the native file picker). The per-article
review contract lives with U2; the file-picker read handoff lives with U1.
## Story implementation order (within each unit)
- **U1 generator** (natural build order): US7 (metadata) → US2 (masthead) →
US5 (ingestion/emit) → US6 (aesthetic) → US1 (multi-page) → US4 (photos) →
US10 (empty state) → US9-handoff (review page data path).
- **U2 ai-draft**: US8 (local Ollama draft) → US9 (DraftBundle + per-article
review contract).
- **U3 funnies**: US3 (crossword → find-a-word → comics → cartoon embed).
> Implementation *ordering* shown here is a suggested construction sequence
> within each unit; the economic Bolt order across units is Delivery Planning's
> decision (2.9).
## Coverage verification
- Every story (US1–US10) is assigned to one or more units.
- Every unit has at least one story: U1 → US1,2,4,5,6,7,9(handoff),10; U2 →
US8,9; U3 → US3.
@@ -0,0 +1,76 @@
# Units Generation — Unit of Work
> Unit decomposition per confirmed answers (Q1–Q4 = A): one coarse unit per
> component (`generator`, `ai-draft`, `funnies`), defined internal interface
> boundaries (DraftBundle JSON as the review handoff), embedded/static single
> local deliverable. This stage produces the dependency DAG (topology) only;
> economic build ordering is decided in Delivery Planning.
## Unit Definitions
| Unit ID | Directory | Unit | Kind | Complexity |
|---|---|---|---|---|
| U1 | `u1-generator` | Generator | library (one-shot local CLI tool, not a deployable/server) | L |
| U2 | `u2-ai-draft` | AiDraft | library (optional, no standalone runtime) | M |
| U3 | `u3-funnies` | Funnies | library (no standalone runtime) | M |
**Deployment model per unit:** embedded/static — one `newspaper` command plus
templates, all bundled into a single local deliverable. U2 and U3 are libraries
the U1 CLI invokes; there is no separate deployable. Per the human ruling on
architecture-review R-01 (2026-09-13): U1 is a **library-backed one-shot local
tool**, never run as a service or server.
## Unit Responsibilities
### U1 — generator (kind: library)
- Owns the `newspaper` CLI entry point and the full build orchestrator.
- Is a one-shot local library-backed tool — never run as a service or server
(human ruling on review R-01).
- Reads + validates `config.json` (masthead metadata) and the `content/` folder.
- Parses markdown/txt into `Article` entities; owns the classic broadsheet
layout model, multi-page A4 pagination with clean page breaks, article/section
placement, and masthead rendering (US1, US2, US4, US5, US6, US7, US10).
- Assembles the funnies section output produced by U3.
- Writes the AI-draft `DraftBundle` JSON for the review page handoff (US9).
- Emits the self-contained zero-network `newspaper.html`, openable via
`file://` and printable to A4.
- Owns entities: Issue, Article, MastheadConfig, DraftBundle.
### U2 — ai-draft (kind: library)
- Optional local-Ollama copy drafting (US8, US9).
- Talks only to local Ollama (granted model `deepseek-v4-flash:cloud`); never
contacts any external/cloud service; content stays on-machine (NFR9).
- Produces the `DraftBundle` (article set) for per-article review.
- Fully optional — when `--draft` is absent it does nothing.
### U3 — funnies (kind: library)
- Builds the "fun" section from the content itself (US3, FR4, Q5=X).
- Derives theme words + context from content; produces the crossword grid +
across/down clues, a find-a-word grid, and selects/embeds an xkcd-style cartoon
using content-derived context. A build-time network fetch to enrich content is
allowed (human ruling 2026-09-13); the EMITTED page makes zero network
requests. Falls back to a tasteful content-derived strip if no cartoon found.
- Emits A4-printable, self-contained puzzle/comic blocks.
- Owns entity: Puzzle.
## Implementation Notes / Constraints per Unit
- **U1**: must keep the emitted page file://-printable and zero-network
(NFR1–NFR6). Review-page handoff: the per-article review page reads the
DraftBundle via the native file picker (not fetch of siblings) (NFR5, ADR-004).
- **U2**: local-only; must never leak content off-machine (NFR9, ADR-002). The
granted model is `deepseek-v4-flash:cloud`.
- **U3**: puzzle heuristics handle a no-cartoon fallback deterministically
(mockups R-02 / domain-design R-01 resolution). A build-time internet fetch to
enrich content is fine; the shipped page stays self-contained (human ruling).
## Deployment Model
Embedded/static (Q4=A): all three units bundle into single local deliverable
(`newspaper` CLI + templates). No servers, no independent deploys, never
published beyond localhost.
<!-- units-after-confirm -->
@@ -0,0 +1,65 @@
# Units Generation — Questions
> Fill in each `[Answer]:` tag. Options A-E plus X (Other). The file is the
> authoritative record of your unit-decomposition decisions.
## Q1: Unit boundary strategy
How should the build units be drawn from the three components (Generator, AiDraft, Funnies)?
A) One unit per component: `generator`, `ai-draft`, `funnies` — each is a separately buildable piece (recommended; clean mapping to Domain Design)
B) One single unit containing all three components (coarse — simplest, one deliverable)
C) Split finer: e.g. `layout`, `render`, `content-loader`, `puzzle-gen`, `cartoon` sub-units
X) Other (please specify)
[Answer]: A
## Q2: Unit granularity
How fine-grained should the units be at build time?
A) Coarse — a handful of units corresponding to the components (3 units) (recommended for a small local tool)
B) Fine-grained — split each component into smaller buildable pieces for maximum reuse/testing granularity
X) Other (please specify)
[Answer]: A
## Q3: Integration points / contracts
How should the units share data and integrate (the contracts between them)?
A) A shared internal interface/module boundary per component (Generator calls AiDraft and Funnies via defined entry points; the DraftBundle JSON is the review handoff contract) (recommended)
B) Loose — units communicate only through files on disk, no shared code interface
C) A package-manager-style library dependency (one unit imports another as a package)
X) Other (please specify)
[Answer]: A
## Q4: Deployment model
For this fully-local tool, what deployment shape should the units take?
A) Embedded/static — one `newspaper` command + templates, all bundled; single local deliverable (recommended, matches CLI-first file-only posture)
B) Split executables that each run independently (no benefit here, but if preferred)
C) Library + thin CLI wrapper (the generator logic as a reusable library, plus a thin `newspaper` script)
X) Other (please specify)
[Answer]: A
## Consolidated Summary Confirmation
> Summary of your four decomposition answers before the unit artifacts are generated:
>
> - Boundary strategy: **one unit per component** — `generator`, `ai-draft`, `funnies` (Q1=A)
> - Granularity: **coarse** — 3 units corresponding to the components (Q2=A)
> - Integration/contracts: **defined internal interface boundaries**; the DraftBundle JSON is the review handoff contract (Q3=A)
> - Deployment: **embedded/static** — one `newspaper` command + templates, single local deliverable (Q4=A)
>
> Plan approval: the human pre-approved the decomposition plan (answers recorded via the file, self-guided mode).
Does this all look correct before I generate the unit artifacts?
- Looks correct
- Request changes
[Answer]: Looks correct