Skip to content

Commit b1be0ee

Browse files
authored
Merge pull request #7 from webmaxru/webmaxru-content-versioning
Add content versioning and GitHub Releases for the living book
2 parents d7103aa + cd74dbd commit b1be0ee

29 files changed

Lines changed: 858 additions & 42 deletions
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
description: Cut a new versioned release of the book's CONTENT — bump the version, update the changelog, verify, and publish the GitHub Release with the PDF attached.
3+
---
4+
5+
# Release Content
6+
7+
Publish a new **content version** of the GitHub Agentic Workflows interactive book. Versioning
8+
applies to the **prose under `content/` only** — never the site generator, PDF tooling, analytics,
9+
or any other part of the repo.
10+
11+
**What changed:** <one-line summary of the content edits, or "read the diff since the last release">
12+
**Version bump:** <major | minor | patch, or an explicit `x.y` — decide from the rules below if unset>
13+
14+
## Versioning model (read first)
15+
- **Source of truth:** `content/VERSION` — a single line, e.g. `1.1`.
16+
- **History / release notes:** `content/CHANGELOG.md` — Keep-a-Changelog style; the release notes
17+
are generated from this file.
18+
- **Shared helper:** `scripts/content_version.py` (`version` / `tag` / `notes`) — the stdlib-only
19+
parser used by the site generator, the PDF builder, and the release workflow.
20+
- **Tag & Release:** each version ships as a GitHub Release tagged `content-vX.Y` with the matching
21+
single-file PDF (`gh-aw-book-vX.Y.pdf`) attached, so every past state stays reproducible.
22+
- **Automation:** `.github/workflows/release-content.yml` cuts the release automatically when
23+
`content/VERSION` changes on `main`. It is idempotent (skips if the release exists) and
24+
self-healing (re-attaches the PDF if it went missing). You normally just prepare the bump — the
25+
workflow publishes.
26+
27+
**SemVer for prose** — pick the bump:
28+
- **MAJOR** — structural rewrite or reordering of the book.
29+
- **MINOR** — new chapters, sections, or material.
30+
- **PATCH** — corrections and clarifications only.
31+
32+
## Steps
33+
1. **Confirm the scope.** Show what content changed since the last release:
34+
`git diff $(python scripts/content_version.py tag)..HEAD -- content/`. Summarize it for the
35+
reader. If nothing under `content/` changed, **stop** — there is nothing to release.
36+
2. **Pick the new version** `x.y` from the bump rules. The current version is
37+
`python scripts/content_version.py version`.
38+
3. **Update `content/CHANGELOG.md`.** Add a new section at the very top (keep older entries intact):
39+
```
40+
## [x.y] - YYYY-MM-DD (today's date)
41+
One-line summary of this release.
42+
43+
### Added / ### Changed / ### Fixed
44+
- **Bold lead:** what changed and why it matters to the reader.
45+
```
46+
4. **Bump `content/VERSION`** to `x.y` (single line, nothing else).
47+
5. **Verify locally — all must pass:**
48+
- `python scripts/content_version.py version``x.y`; `python scripts/content_version.py notes x.y`
49+
prints your new notes.
50+
- `python site/generate.py` → clean build; the header version pill and `site/versions.html` show
51+
`vx.y`.
52+
- `python scripts/build_pdf.py` → the PDF running footer reads `vx.y`.
53+
6. **Commit** the bump plus the regenerated site output: `content/VERSION`, `content/CHANGELOG.md`,
54+
and the changed `site/**` files. Message: `Release content vx.y`. Include the trailer
55+
`Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>`.
56+
7. **Publish:**
57+
- **Preferred — merge to `main`.** Open a PR and merge it. The deploy workflow republishes the
58+
site (now showing `vx.y`) and `release-content.yml` builds the versioned PDF, pulls the notes
59+
from the changelog, and creates `content-vx.y` as Latest. Re-runs are safe (idempotent).
60+
- **Manual fallback** (only if you must publish without merging). Write the notes to a file
61+
**from Python** (capturing `python` stdout into a PowerShell variable mangles em-dashes on
62+
Windows), then:
63+
```
64+
python scripts/content_version.py notes x.y # write output to notes.md
65+
gh release create content-vx.y \
66+
"gh-aw-book-vx.y.pdf#GitHub Agentic Workflows — vx.y (PDF)" \
67+
--title "Content vx.y" --notes-file notes.md --target <content-commit-sha>
68+
```
69+
8. **Confirm.** `gh release list` shows `content-vx.y` as **Latest** with the PDF asset attached,
70+
and the online **Version history** page (`versions.html`) lists it.
71+
72+
**Guardrails:** never bump the version for non-content changes; never attach a PDF that predates the
73+
content it claims to represent (that's why the first release, `content-v1.0`, ships notes-only —
74+
the PDF feature postdated it).
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
name: Release book content
2+
3+
# Publishes a GitHub Release for the book's CONTENT edition whenever content/VERSION changes.
4+
#
5+
# The book is a living document: its prose (content/) is versioned independently of the site
6+
# generator and tooling. content/VERSION is the source of truth, content/CHANGELOG.md is the
7+
# history, and each version is shipped here as a Release tagged `content-vX.Y` with the matching
8+
# single-file PDF attached — so every published state stays reproducible and downloadable.
9+
#
10+
# This job is CONTENT-only: it is not triggered by site/tooling changes, and it is idempotent —
11+
# if a release for the current version already exists, it does nothing.
12+
13+
on:
14+
push:
15+
branches: [main]
16+
paths:
17+
- "content/VERSION"
18+
# CHANGELOG is included so a notes-only fix can retrigger a release that
19+
# failed on a previous push (the idempotency guard prevents duplicates).
20+
- "content/CHANGELOG.md"
21+
# Manual runs (re)cut the release for whatever ref they are dispatched against:
22+
# the version, PDF, and notes always come from that ref's content/VERSION and
23+
# content/CHANGELOG.md, so a dispatched release can never be mislabeled.
24+
workflow_dispatch:
25+
26+
# Creating a release and pushing the tag needs write access to repository contents.
27+
permissions:
28+
contents: write
29+
30+
concurrency:
31+
group: release-content
32+
cancel-in-progress: false
33+
34+
jobs:
35+
release:
36+
runs-on: ubuntu-latest
37+
steps:
38+
- name: Checkout
39+
uses: actions/checkout@v4
40+
with:
41+
fetch-depth: 0
42+
43+
- name: Set up Python
44+
uses: actions/setup-python@v5
45+
with:
46+
python-version: "3.12"
47+
48+
- name: Resolve content version, tag, and asset name
49+
id: meta
50+
run: |
51+
# Always derive from the checked-out content/VERSION so the tag, PDF,
52+
# and notes describe exactly this ref — no override can desync them.
53+
VERSION="$(python scripts/content_version.py version)"
54+
TAG="$(python scripts/content_version.py tag "$VERSION")"
55+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
56+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
57+
echo "asset=gh-aw-book-v$VERSION.pdf" >> "$GITHUB_OUTPUT"
58+
echo "Resolved content version $VERSION -> tag $TAG"
59+
60+
- name: Decide whether to create, repair, or skip this release
61+
id: guard
62+
env:
63+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
64+
run: |
65+
TAG="${{ steps.meta.outputs.tag }}"
66+
ASSET="${{ steps.meta.outputs.asset }}"
67+
if gh release view "$TAG" >/dev/null 2>&1; then
68+
if gh release view "$TAG" --json assets --jq '.assets[].name' | grep -Fxq "$ASSET"; then
69+
echo "Release $TAG already exists with asset $ASSET — nothing to do."
70+
echo "action=skip" >> "$GITHUB_OUTPUT"
71+
else
72+
echo "Release $TAG exists but PDF asset $ASSET is missing — will (re)upload it."
73+
echo "action=upload" >> "$GITHUB_OUTPUT"
74+
fi
75+
else
76+
echo "No release for $TAG yet — will create it."
77+
echo "action=create" >> "$GITHUB_OUTPUT"
78+
fi
79+
80+
- name: Write release notes from the changelog
81+
if: steps.guard.outputs.action == 'create'
82+
run: |
83+
{
84+
echo "## GitHub Agentic Workflows — content ${{ steps.meta.outputs.tag }}"
85+
echo
86+
python scripts/content_version.py notes "${{ steps.meta.outputs.version }}"
87+
echo
88+
echo "---"
89+
echo
90+
echo "📖 Read online: https://aw.isainative.dev/ · [Version history](https://aw.isainative.dev/versions.html)"
91+
echo
92+
echo "📄 The single-file PDF for this version is attached below."
93+
} > release-notes.md
94+
cat release-notes.md
95+
96+
- name: Build the versioned PDF edition
97+
if: steps.guard.outputs.action != 'skip'
98+
run: |
99+
python -m pip install --upgrade pip
100+
python -m pip install pyyaml
101+
python site/generate.py
102+
python -m pip install -r scripts/requirements-pdf.txt
103+
python -m playwright install --with-deps chromium
104+
python scripts/build_pdf.py
105+
cp site/gh-aw-book.pdf "${{ steps.meta.outputs.asset }}"
106+
107+
- name: Create the GitHub Release
108+
if: steps.guard.outputs.action == 'create'
109+
env:
110+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
111+
run: |
112+
gh release create "${{ steps.meta.outputs.tag }}" \
113+
"${{ steps.meta.outputs.asset }}#GitHub Agentic Workflows — v${{ steps.meta.outputs.version }} (PDF)" \
114+
--title "Content v${{ steps.meta.outputs.version }}" \
115+
--notes-file release-notes.md \
116+
--target "${GITHUB_SHA}"
117+
118+
- name: Attach the missing PDF to the existing release
119+
if: steps.guard.outputs.action == 'upload'
120+
env:
121+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
122+
run: |
123+
gh release upload "${{ steps.meta.outputs.tag }}" \
124+
"${{ steps.meta.outputs.asset }}#GitHub Agentic Workflows — v${{ steps.meta.outputs.version }} (PDF)" \
125+
--clobber

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,13 +158,16 @@ content/
158158
playbook-brief.md # scope / intent
159159
toc.yml # chapter spec (source of truth)
160160
outline.md # architect's working outline
161+
VERSION # content edition version (source of truth), e.g. 1.1
162+
CHANGELOG.md # per-version content history (release-notes source)
161163
research/ # per-chapter theory + capability notes (generated by the fleet)
162164
examples/ # compile-verified .md workflow examples (generated by the fleet)
163165
site/
164166
generate.py # scaffolds the site from content/toc.yml
165167
index.html # generated
166168
chapters/*.html # generated: one page per chapter in toc.yml
167169
book.html # generated: single-page edition (source for the PDF)
170+
versions.html # generated: content version history, linked to GitHub Releases
168171
gh-aw-book.pdf # build artifact (gitignored): the downloadable PDF
169172
assets/ # style.css + app.js + analytics.js (built beacon)
170173
analytics/
@@ -174,6 +177,7 @@ azure/
174177
package.json # analytics build tooling (esbuild bundle + report command)
175178
scripts/
176179
build_pdf.py # renders site/book.html → site/gh-aw-book.pdf (Playwright)
180+
content_version.py # content version + changelog parser (site, PDF, release workflow)
177181
requirements-pdf.txt # Python deps for the PDF build
178182
run-fleet.ps1 # convenience launcher
179183
report.ps1 # engagement report (PowerShell)
@@ -241,6 +245,52 @@ current book.
241245

242246
---
243247

248+
## Content versioning & releases
249+
250+
This is a **living book**, so its **content** carries its own version — independent of the site
251+
generator, PDF renderer, analytics beacon, and other tooling in this repo. Only the prose under
252+
[`content/`](content/) is versioned; changing the build scripts or styling does **not** bump the
253+
book's version.
254+
255+
- **Source of truth:** [`content/VERSION`](content/VERSION) — a single line, e.g. `1.1`.
256+
- **History:** [`content/CHANGELOG.md`](content/CHANGELOG.md) — human-readable notes per version,
257+
following [Semantic Versioning](https://semver.org/) applied to prose (MAJOR = structural rewrite
258+
or reordering; MINOR = new chapters/sections/material; PATCH = corrections and clarifications).
259+
- **Helper:** [`scripts/content_version.py`](scripts/content_version.py) is the shared parser used by
260+
the site generator, the PDF builder, and the release workflow:
261+
```powershell
262+
python scripts/content_version.py version # -> 1.1
263+
python scripts/content_version.py tag # -> content-v1.1
264+
python scripts/content_version.py notes # release notes for the current version (from the changelog)
265+
```
266+
267+
**Where the version shows up.** `site/generate.py` surfaces `v<version>` across the online edition
268+
(a pill in the header, the cover "Edition" line, the chapter breadcrumbs and footers, and the
269+
single-page/PDF cover), and generates a dedicated **[Version history](https://aw.isainative.dev/versions.html)**
270+
page (`site/versions.html`) rendered from the changelog and linked to each GitHub Release. The PDF's
271+
running footer is stamped with the same version.
272+
273+
**GitHub Releases.** Each content version is published as a GitHub Release tagged
274+
`content-v<version>` with the matching single-file PDF attached, so every past state of the book
275+
stays reproducible and downloadable. This is automated by
276+
[`.github/workflows/release-content.yml`](.github/workflows/release-content.yml): when a push to
277+
`main` changes `content/VERSION`, it builds the versioned PDF, pulls the release notes from the
278+
changelog, and cuts the release. The job is **idempotent** — if a release for the current version
279+
already exists it does nothing, and if that release is somehow missing its PDF it re-attaches it. It
280+
can also be run on demand via **workflow_dispatch**, which (re)cuts the release for whatever ref it
281+
is dispatched against — the version, PDF, and notes always come from that ref's `content/VERSION`,
282+
so a release can never be mislabeled.
283+
284+
**To publish a new version:**
285+
286+
1. Edit the chapters under `content/chapters/`.
287+
2. Add a `## [x.y] - YYYY-MM-DD` entry to [`content/CHANGELOG.md`](content/CHANGELOG.md).
288+
3. Bump [`content/VERSION`](content/VERSION) to `x.y`.
289+
4. Merge to `main` — the deploy workflow republishes the site (now showing the new version) and the
290+
release workflow cuts `content-vx.y` with the PDF attached.
291+
292+
---
293+
244294
## Privacy-friendly analytics (cookieless RUM)
245295

246296
The published site is instrumented with **cookieless Real User Monitoring** via Azure Application

content/CHANGELOG.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Changelog — Book Content
2+
3+
All notable changes to the **book content** — the chapters and their source under
4+
[`content/`](./) — are recorded here. This log tracks the *content edition* only. The site
5+
generator, PDF renderer, analytics beacon, and other tooling evolve with the repository's commit
6+
history and are deliberately **not** part of this version line.
7+
8+
The current content version lives in [`content/VERSION`](./VERSION). Each version is published as a
9+
GitHub Release tagged `content-vMAJOR.MINOR` with the matching single-file PDF edition attached, so
10+
every published state of this living book stays reproducible and downloadable.
11+
12+
Versioning follows [Semantic Versioning](https://semver.org/) applied to prose:
13+
14+
- **MAJOR** — a structural rewrite, or reordering of parts/chapters.
15+
- **MINOR** — new chapters, sections, or substantive new material.
16+
- **PATCH** — corrections, clarifications, and small edits that add no new material.
17+
18+
## [1.1] - 2026-07-08
19+
20+
Added Agent Package Manager (APM) coverage so the fleet chapters explain how shared agentic
21+
components are distributed and governed as supply-chain dependencies.
22+
23+
### Added
24+
25+
- **Chapter 11 — Reuse & Memory:** APM dependencies via `shared/apm.md`, showing how imported shared
26+
components are declared and resolved like packaged dependencies.
27+
- **Chapter 13 — Governance & FinOps:** APM supply-chain governance with `apm-policy.yml`, covering
28+
provenance and policy for third-party agentic components.
29+
30+
## [1.0] - 2026-07-03
31+
32+
Initial release of the complete book: fourteen chapters across three parts, each anchored to a
33+
concept and carrying a verified, compilable example workflow, following the Repo Assistant from a
34+
single triage workflow to a governed multi-repo fleet.
35+
36+
### Added
37+
38+
- **Part I — The Individual:** Chapters 1–5 — what agentic workflows are, your first workflow,
39+
anatomy & the compile model, triggers, and engines.
40+
- **Part II — The Team:** Chapters 6–10 — safe outputs, defense in depth, tools & MCP, continuous
41+
triage & docs, and continuous review & testing.
42+
- **Part III — The Organization:** Chapters 11–14 — reuse & memory, observability & debugging,
43+
governance & FinOps, and fleets & adoption.

content/VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.1

scripts/build_pdf.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,20 @@
3232
BOOK_PAGE = "book.html"
3333
PDF_PATH = SITE / "gh-aw-book.pdf"
3434

35-
# Running footer: book title on the left, "Page N / M" on the right. The
35+
# Content version stamped into the PDF footer (source of truth: content/VERSION).
36+
sys.path.insert(0, str(ROOT / "scripts"))
37+
import content_version # noqa: E402 (path set up above)
38+
39+
CONTENT_VERSION = content_version.read_version()
40+
41+
# Running footer: book title + content version on the left, "Page N / M" on the right. The
3642
# pageNumber / totalPages spans are filled in by Chromium.
3743
FOOTER_TEMPLATE = (
3844
'<div style="width:100%;font-family:\'Hanken Grotesk\',Arial,sans-serif;'
3945
'font-size:8px;color:#8a93a6;padding:0 14mm;display:flex;'
4046
'justify-content:space-between;align-items:center;">'
41-
'<span>GitHub Agentic Workflows \u2014 An Interactive Book</span>'
47+
'<span>GitHub Agentic Workflows \u2014 An Interactive Book '
48+
f'\u00b7 v{CONTENT_VERSION}</span>'
4249
'<span>Page <span class="pageNumber"></span> / <span class="totalPages"></span></span>'
4350
"</div>"
4451
)

0 commit comments

Comments
 (0)