---
title: "feat: Add Sources & Inspiration section to generated README"
type: feat
status: completed
date: 2026-03-30
---

# feat: Add Sources & Inspiration section to generated README

## Overview

Add a "Sources & Inspiration" section to every generated CLI's README that credits the ecosystem tools, community projects, and documentation sources that informed the CLI's design. The data already exists in `research.json`, the absorb manifest, and (for sniff-derived CLIs) the `discovery/sniff-report.md` — this work surfaces it in the published README.

## Problem Frame

When Printing Press generates a CLI, the research phase catalogs competing tools, community wrappers, API docs, and ecosystem projects. The absorb manifest explicitly tracks which features were inspired by which sources. But the final published README only shows a one-line "Generated by CLI Printing Press" footer — the ecosystem gets no credit. This is a missed opportunity to:
1. Thank the open source projects that informed the CLI's design
2. Give users pointers to alternative approaches and complementary tools
3. Build goodwill with maintainers of the tools we learned from

## Requirements Trace

- R1. Every generated CLI README includes a "Sources & Inspiration" section crediting researched ecosystem tools
- R2. Credits are auto-generated from existing pipeline data (research.json + absorb manifest), not manually authored
- R3. Each credited source includes: project name, URL, and a one-line description of what it contributed/inspired
- R4. Sources are grouped meaningfully (not a flat dump of every alternative)
- R5. The section appears above the "Generated by" footer, below Troubleshooting
- R6. For sniff-derived CLIs, the discovery provenance (pages visited, sniff method) is credited alongside research sources — acknowledging how the API spec itself was discovered

## Scope Boundaries

- Not adding credits for the OpenAPI/GraphQL spec source itself (that's already implicit)
- Not adding credits to the CLI's `--version` or `--help` output — README only
- Not changing the absorb manifest format
- Not adding a new pipeline phase — this hooks into existing README generation

## Context & Research

### Relevant Code and Patterns

- `internal/generator/templates/readme.md.tmpl` — the Go template for generated READMEs. Credits section goes before the footer (line 126)
- `internal/generator/readme_augment.go` — `AugmentREADME()` injects dogfood evidence into the README post-generation. Source credits follow the same pattern
- `internal/pipeline/research.go` — `ResearchResult` struct has `Alternatives []Alternative` with Name, URL, Language, Stars; `CompetitorInsights` has per-repo analysis
- `internal/pipeline/docs/plans/*/research.json` — persisted research data per API
- Absorb manifest (skill-generated markdown) — has "Ecosystem Tools Found" list and per-feature "Best Source" attribution
- `discovery/sniff-report.md` (from PR #70) — structured sniff provenance with pages visited, endpoints discovered, sniff configuration. Lives in `manuscripts/<api>/<run-id>/discovery/`
- `internal/pipeline/paths.go` — `RunDiscoveryDir()` and `ArchivedDiscoveryDir()` path functions for the discovery directory
- `internal/pipeline/state.go` — `DiscoveryDir()` method on pipeline state

### Existing Patterns

- The README template uses Go template syntax (`{{.Name}}`, `{{range}}`)
- `AugmentREADME` reads evidence files from a directory and injects content via string replacement or append
- The `ResearchResult` struct is already JSON-serializable and loaded via `LoadResearch()`

## Key Technical Decisions

- **Template-based, not augment-based**: Add the sources section directly in `readme.md.tmpl` using template data, rather than as a post-generation augment step. Rationale: the source data is available at generation time (from research.json), and template rendering is cleaner than string manipulation. The augment path is for runtime evidence (dogfood output) that doesn't exist at template time.

- **Data source: research.json only (not absorb manifest)**: The absorb manifest is a skill-generated markdown artifact with no stable machine-readable format. `research.json` already has structured `Alternative` data with name, URL, language, and stars. This is sufficient for credits. Rationale: avoids parsing markdown, keeps the feature entirely in Go pipeline code.

- **Grouping by role, not alphabetical**: Sources are grouped into up to three categories: "Ecosystem Tools" (GitHub repos/CLIs), "Community Documentation" (wikis, API docs projects), and "Discovery" (pages browsed/sniffed to build the spec, from `discovery/sniff-report.md`). Rationale: a flat alphabetical list doesn't communicate what each source contributed. The discovery category only appears for sniff-derived CLIs.

- **Discovery provenance from sniff-report.md**: For sniff-derived CLIs, extract the "Pages Visited" section from `discovery/sniff-report.md` and credit those URLs as the origin of the API spec. This acknowledges the websites whose traffic was observed to reverse-engineer the API. Rationale: PR #70 added structured discovery artifacts specifically for provenance — surfacing them in the README completes the attribution chain.

- **Minimum threshold: skip section if <2 sources total**: If research found fewer than 2 alternatives AND no discovery provenance exists, omit the section entirely. A single-source credits section looks odd. However, if discovery provenance exists, always include the section (even with 0-1 research alternatives) since the discovery origin is important context.

## Open Questions

### Resolved During Planning

- **Should we include stars count?** Yes — it helps users judge which alternatives are actively maintained and popular. Display as a parenthetical.
- **Should we link to specific issues/PRs from competitors?** No — that's too granular and the links could rot. The repo URL is sufficient.

### Deferred to Implementation

- **Exact wording of the section intro**: The implementer should draft something concise and genuine (not corporate). Something like "This CLI was built on the shoulders of these projects" or "We studied these tools and docs while designing this CLI."
- **Whether to add a `<!-- SOURCES_OUTPUT -->` marker**: May be useful for future augmentation. Decide during implementation based on whether the template approach is clean enough.

## Implementation Units

- [ ] **Unit 1: Thread research data into README template context**

  **Goal:** Make `ResearchResult` (specifically the `Alternatives` slice) available to the README template at render time.

  **Requirements:** R2

  **Dependencies:** None

  **Files:**
  - Modify: `internal/generator/generate.go` (or wherever `readme.md.tmpl` is rendered — the template data struct)
  - Modify: `internal/pipeline/research.go` (may need to export a filtered/sorted helper)
  - Test: `internal/generator/generate_test.go`

  **Approach:**
  - Find the struct passed to `readme.md.tmpl` execution
  - Add a `Sources []Alternative` field (or similar) populated from `ResearchResult.Alternatives`
  - Add a `DiscoveryPages []string` field (or similar) for sniff-derived CLIs — extracted from `discovery/sniff-report.md` "Pages Visited" section
  - Filter out alternatives with empty URLs
  - Sort by stars descending so the most notable projects appear first
  - The template data population likely happens in the generate pipeline — thread the `ResearchResult` through
  - For discovery data: check if `discovery/sniff-report.md` exists in the pipeline/run directory, parse the "Pages Visited" section for URLs

  **Patterns to follow:**
  - How `Auth.EnvVars` and `Resources` are already threaded into the template context
  - `LoadResearch()` for reading persisted research data
  - `RunDiscoveryDir()` / `ArchivedDiscoveryDir()` path functions from PR #70

  **Test scenarios:**
  - Happy path: template context includes 5 alternatives from research.json, sorted by stars descending
  - Happy path: template context includes discovery pages from sniff-report.md when present
  - Edge case: research.json has alternatives with empty URLs — these are filtered out
  - Edge case: research.json has no alternatives — Sources slice is empty/nil
  - Edge case: research.json doesn't exist — Sources slice is empty/nil, no error
  - Edge case: discovery/sniff-report.md doesn't exist (non-sniff CLI) — DiscoveryPages is empty/nil

  **Verification:**
  - Template data struct includes Sources field populated from research data when available

- [ ] **Unit 2: Add Sources & Inspiration section to README template**

  **Goal:** Render a credits section in the generated README using the threaded source data, including discovery provenance for sniff-derived CLIs.

  **Requirements:** R1, R3, R4, R5, R6

  **Dependencies:** Unit 1

  **Files:**
  - Modify: `internal/generator/templates/readme.md.tmpl`
  - Test: `internal/generator/generate_test.go` (or a dedicated template rendering test)

  **Approach:**
  - Add a conditional block before the `Generated by` footer — show section if `.Sources` has 2+ entries OR `.DiscoveryPages` is non-empty
  - Section title: `## Sources & Inspiration`
  - Brief intro line (1 sentence, genuine tone)
  - If `.DiscoveryPages` is non-empty, render a "Discovery" subsection crediting the pages that were browsed/sniffed to reverse-engineer the API spec
  - Render each research source as a bullet: `- [**name**](url) — language (stars stars)` or similar
  - Group research sources into "Ecosystem Tools" for GitHub repos with meaningful star counts, and optionally "Community Documentation" for wiki/docs projects if distinguishable from the Alternative data
  - If only one group applies, skip the subheadings and render a flat list
  - Conditionally omit the entire section if fewer than 2 research sources AND no discovery pages

  **Patterns to follow:**
  - The existing `{{range $name, $resource := .Resources}}` pattern in the template
  - The `{{- if .Auth.EnvVars}}` conditional pattern

  **Test scenarios:**
  - Happy path: 5 research sources render as a bulleted list with name, URL, language, and stars
  - Happy path: section appears between Troubleshooting and the "Generated by" footer
  - Happy path: sniff-derived CLI with discovery pages renders a "Discovery" subsection with page URLs
  - Edge case: exactly 1 research source, no discovery — section is omitted entirely
  - Edge case: 0 research sources, no discovery — section is omitted entirely
  - Edge case: 0 research sources but discovery pages exist — section is shown with discovery only
  - Edge case: sources with missing language field — rendered without language parenthetical

  **Verification:**
  - Generated README for an API with 3+ research alternatives contains a "Sources & Inspiration" section with clickable links
  - Generated README for an API with 0-1 alternatives has no Sources section
  - Section appears in the correct position (before footer)

- [ ] **Unit 3: End-to-end verification with existing research data**

  **Goal:** Verify the feature works with real pipeline data (e.g., the ESPN or Notion research.json).

  **Requirements:** R1, R2, R3

  **Dependencies:** Units 1, 2

  **Files:**
  - Test: manual verification or integration test using `internal/pipeline/docs/plans/notion-pipeline/research.json`

  **Approach:**
  - Run the generator against a test fixture that includes research.json with populated alternatives
  - Verify the output README contains the Sources section with correct data from the research
  - Check that the ESPN absorb manifest's 10 ecosystem tools would produce a clean credits section

  **Patterns to follow:**
  - Existing integration tests in `internal/generator/` that render templates against test fixtures

  **Test scenarios:**
  - Integration: Notion research.json (5 alternatives) produces a Sources section with all 5 credited
  - Integration: generated README passes the scorecard README dimension check (no regression)

  **Verification:**
  - A full template render with real research data produces a README with correct, well-formatted Sources section

## System-Wide Impact

- **Scorecard README dimension:** The scorecard evaluates README quality (10 points, Tier 1). Adding a new section should not regress the score — verify `scoreREADME()` in `internal/pipeline/scorecard.go` doesn't penalize extra sections
- **Anti-AI text filter:** `AugmentREADME` runs `CheckText()` on the final README. The Sources section content comes from research.json data (repo names, URLs), so it shouldn't trigger AI-text warnings. But verify the intro line doesn't trip it
- **Unchanged invariants:** The `AugmentREADME` function, dogfood evidence injection, and marker-based replacement are not changed. The new section is purely template-rendered

## Risks & Dependencies

| Risk | Mitigation |
|------|------------|
| Research data has low-quality alternatives (bots, irrelevant repos) | Filter: require non-empty URL, >0 stars if from GitHub search |
| Crediting a tool that the maintainer might object to | Credits are positive ("inspiration") not comparative. No "we're better than X" language |
| Template rendering breaks for APIs with no research phase | Conditional `{{if .Sources}}` ensures graceful omission |

## Sources & References

- ESPN absorb manifest: `printing-press/manuscripts/espn/20260329-135924/research/`
- Notion research.json: `internal/pipeline/docs/plans/notion-pipeline/research.json`
- README template: `internal/generator/templates/readme.md.tmpl`
- README augment: `internal/generator/readme_augment.go`
- Research pipeline: `internal/pipeline/research.go`
- Discovery provenance (PR #70): `internal/pipeline/paths.go` — `RunDiscoveryDir()`, `ArchivedDiscoveryDir()`
- Discovery plan: `docs/plans/2026-03-30-001-feat-discovery-manuscript-provenance-plan.md`
