diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d6315a5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,46 @@ +# Changelog + +All notable changes to **ITSulu Blog Publisher** are recorded here. + +This project uses a three-part version number — `MAJOR.MINOR.PATCH` (each part 0–999): + +- **MAJOR** — a major release for sale; significant feature upgrades or a significant change to the software. +- **MINOR** — one or more features added, or a meaningful performance improvement. +- **PATCH** — a single group of commits, or one large commit. + +Release notes are written in plain language so anyone on the team can follow what changed. + +--- + +## v0.4.8 — 2026-05-30 + +The first tagged release. This version gets the whole test suite running green on the +ITSulu Kubernetes cluster and fixes two bugs that affected real blog posts and emails. + +### Fixed + +- **Notification emails now render correctly.** The email template was written in an old + syntax that Odoo 17 no longer understands, so notification emails were going out with raw + code (like `${object.blog_post_id.name}`) instead of the actual post title. The template + was rebuilt using Odoo 17's current format, so subjects and bodies now fill in properly. +- **Generated blog posts are no longer empty.** When a post was created automatically, the + AI-written body was being thrown away before it reached the post. Every auto-generated post + was published blank. The body is now saved to the post's content field. +- Removed a duplicate field definition that produced a warning on every Odoo startup. + +### Testing & Infrastructure + +- The full automated test suite — **69 tests** (48 unit, 15 behaviour, 6 performance) — now + passes end to end on the ITSulu Kubernetes test cluster. +- The CI/CD pipeline was corrected: stages now run in the right order, database credentials + are handled properly, and test reports are generated as build artifacts. +- End-to-end tests now run as ephemeral jobs on the ITSulu cluster (`itsulu-testing` + namespace) instead of an external preview service. +- Brought the test code up to date with Odoo 17 (email rendering, blog post fields, and the + behaviour-test environment setup). + +### Documentation + +- Recorded the Odoo 16 → 17 migration lessons (email templates, post body fields, template + database refresh) in `CLAUDE.md` so they are not rediscovered the hard way. +- Introduced this changelog and the project versioning scheme. diff --git a/CLAUDE.md b/CLAUDE.md index c7762aa..a93b3d3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -797,10 +797,13 @@ When extending core Odoo models (e.g., `blog.post`), be aware of field differenc | Model | Field | Odoo 16 | Odoo 17 | Note | |-------|-------|---------|---------|------| -| `blog.post` | `body_arch` | Yes | **No** | XML layout field; removed in Odoo 17 | -| `blog.post` | `body` | Yes | **No** | HTML body field; use editor interface instead | +| `blog.post` | `body_arch` | Yes | **No** | Removed in Odoo 17 | +| `blog.post` | `body` | Yes | **No** | Removed in Odoo 17 | +| `blog.post` | `content` | — | **Yes** | The HTML body field in Odoo 17 — **writable**; auto-generation MUST write `'content': llm_response.body_html` or posts publish empty | | `blog.post` | `itsulu_social_id` | N/A | Custom | Add via `_inherit` for reverse relationship to custom models | -| `mail.template` | Subject/Body | Mako syntax | Mako syntax | Renders async in send_mail(); use Mako `% for` loops, not method calls in templates | +| `mail.template` | subject / email_from | Mako `${}` | **inline_template `{{ }}`** | Mako `${}` is dead in Odoo 17 — renders literally | +| `mail.template` | body_html | Mako `${}`/`% for` | **qweb `type="html"`** | Use ``, ``, `` as real XML children. NOT `type="qweb"` (invalid RNG type) | +| `mail.template` | render method | `generate_email()` | **`_render_field(field, [ids])`** | Returns `{id: rendered_str}`; `_generate_template` returns UNrendered text | **Pattern for extending blog.post safely:** ```python @@ -817,15 +820,24 @@ class BlogPost(models.Model): **Pattern for creating blog.post test records:** ```python -# Use sudo() to bypass Odoo validation when creating minimal test posts +# Use sudo() to bypass Odoo validation when creating minimal test posts. +# The body field in Odoo 17 is `content` (NOT body / body_arch / body_html). post = self.env['blog.post'].sudo().create({ 'name': 'Test Post', 'blog_id': blog.id, 'is_published': True, - # Do NOT include body, body_arch, or body_html — use website editing UI + 'content': '

HTML body goes here

', # Odoo 17 body field — writable }) ``` +**Pattern for asserting on a rendered mail.template (synchronous):** +```python +template = self.env.ref('module.email_template_xmlid') +rendered = template._render_field('subject', [record.id]) # {id: 'rendered text'} +assert 'Expected' in rendered[record.id] +# body_html: template._render_field('body_html', [record.id]) +``` + --- ## 12. Failure Recovery Quick Reference @@ -915,7 +927,79 @@ assert 800 <= log.tokens_used <= 1200 --- -## 14. Phased Adoption Triggers +## 15. Versioning & Releases + +The project uses a three-part version number, **`MAJOR.MINOR.PATCH`**, each part `0–999`. + +| Part | Bump when… | Examples | +|---|---|---| +| **MAJOR** | A major release for sale: significant feature upgrade, or a significant change to the software. | new product tier, rewrite, breaking redesign | +| **MINOR** | One or more features added, or a meaningful performance improvement. | new LLM provider, new wizard, 2× faster generation | +| **PATCH** | A single group of commits, or one large commit. | bug-fix batch, doc pass, refactor, dependency bump | + +### Sources of truth & where the version lives + +The repo-root **`VERSION`** file is the single source of truth. These are kept in sync: + +| File | Form | Notes | +|---|---|---| +| `VERSION` | `0.4.8` | Source of truth — plain text, one line | +| `addons/itsulu_blog_publisher/__manifest__.py` | `'version': '17.0.0.4.8'` | Odoo manifest = `.` = `17.0` + `0.4.8` | +| `README.md` | `**Version:** 0.4.8` | Header line + status footer | +| `CHANGELOG.md` | `## v0.4.8 — ` | Release notes section per version | +| git tag | `v0.4.8` | Annotated tag; message = the CHANGELOG section | + +**Never hand-edit the version in multiple files.** Use the helper script — it updates all of +them from `VERSION` and derives the Odoo manifest version (`17.0...`). + +### Release process (every version change) + +```bash +# 1. Bump the version (updates VERSION, manifest, README) +scripts/bump-version.sh patch # or: minor | major | set X.Y.Z + +# 2. Write the release notes — add a "## vX.Y.Z — " section to CHANGELOG.md +# in plain, common language (what changed, why it matters), NOT git-speak. +# Group under Fixed / Added / Changed / Testing & Infrastructure / Documentation. + +# 3. Commit the bump + notes together +git add -A && git commit -m "release: vX.Y.Z" + +# 4. Tag the release — the tag message is taken from the CHANGELOG section +scripts/bump-version.sh tag + +# 5. Push branch and tag +git push && git push origin vX.Y.Z +``` + +### Release-notes style (common language) + +Write for a teammate, not a compiler. Each entry says **what changed and why it matters** in +one or two sentences of plain English. Prefer: + +> - **Notification emails now render correctly.** They were going out with raw code in the +> subject instead of the post title; the template was rebuilt for Odoo 17. + +over: + +> - fix: migrate mail.template body_html ${} → qweb t-out + +Keep the technical detail for commit messages and the CLAUDE.md failure table; the CHANGELOG is +the human-readable record. Group entries under **Fixed / Added / Changed / Testing & +Infrastructure / Documentation** as applicable. + +### Rules + +- One tag per version; tags are immutable. If a release is wrong, ship the next PATCH — never + move a tag. +- Tag on the release branch **after** the work is merged/finalised, so the tag points at the + commit that actually ships. +- The Odoo manifest version must always be `17.0.` + the `VERSION` value. The `-u` upgrade path + relies on the manifest version increasing, so bump before deploying schema/data changes. + +--- + +## 16. Phased Adoption Triggers **Add Phase 2 (BDD, factories, coverage gates) when ≥ 2:** - Stakeholders ask "how do we know X works?" diff --git a/README.md b/README.md index 3f7d693..71040dc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # ITSulu Blog Publisher — Odoo 17 Addon +**Version:** 0.4.8 + Automated blog post generation and publishing for Odoo 17 Community Edition, powered by generative AI (Anthropic Claude, OpenAI, Google Gemini, or Ollama). +> **Versioning** — `MAJOR.MINOR.PATCH` (each 0–999). MAJOR = major release for sale / significant change; MINOR = features or performance improvements; PATCH = a single group of commits. See [CHANGELOG.md](CHANGELOG.md) for release notes and [CLAUDE.md](CLAUDE.md) §15 for the full scheme. + ## Features ### ✨ Core Functionality @@ -28,13 +32,14 @@ Automated blog post generation and publishing for Odoo 17 Community Edition, pow ### 📊 Test Coverage -**Phase 2 Complete**: 63 automated tests across 6 test files +**69 automated tests**, all passing on the ITSulu Kubernetes test cluster. | Test Suite | Count | Status | |---|---|---| | Unit Tests (TDD) | 48 | ✅ All Passing | -| Behavior Tests (BDD) | 15 | ✅ All Passing | -| **Total** | **63** | **100% ✅** | +| Behaviour Tests (BDD) | 15 | ✅ All Passing | +| Performance Benchmarks | 6 | ✅ All Passing | +| **Total** | **69** | **100% ✅** | **Coverage Areas**: - Topic queue management (7 tests) @@ -42,7 +47,11 @@ Automated blog post generation and publishing for Odoo 17 Community Edition, pow - Social media copy (16 tests) - Schedule slots (10 tests) - LLM router dispatch (7 tests) -- E2E workflows (15 BDD scenarios) +- E2E / behaviour workflows (15 BDD scenarios) +- Performance SLOs — latency, query count, token usage, concurrency (6 benchmarks) + +Tests run as ephemeral Kubernetes jobs in the `itsulu-testing` namespace against a +primed PostgreSQL template database. See [CLAUDE.md](CLAUDE.md) §8 for the K8s test setup. ## Installation @@ -232,7 +241,8 @@ pre-commit run --all-files ## Documentation -- [CLAUDE.md](CLAUDE.md) — Complete TDD framework, testing patterns, troubleshooting +- [CHANGELOG.md](CHANGELOG.md) — Release notes and version history +- [CLAUDE.md](CLAUDE.md) — Complete TDD framework, testing patterns, versioning scheme, troubleshooting - [ARCHITECTURE.md](ARCHITECTURE.md) — System design, data flow, API contracts - [PHASE2_ROADMAP.md](PHASE2_ROADMAP.md) — Phase 2 implementation status @@ -260,6 +270,6 @@ https://gitlab.com/itsulu-odoo/itsulu-blog-publisher/issues --- -**Current Status**: Phase 2 Complete ✅ (63/63 tests passing) +**Current Status**: v0.4.8 — test suite green (69/69 passing on ITSulu K8s) ✅ **Last Updated**: 2026-05-30 **Maintainer**: Nicholas Riegel (nicholasr@itsulu.com) diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..cb498ab --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.4.8 diff --git a/addons/itsulu_blog_publisher/__manifest__.py b/addons/itsulu_blog_publisher/__manifest__.py index 27cc34b..161ac5e 100644 --- a/addons/itsulu_blog_publisher/__manifest__.py +++ b/addons/itsulu_blog_publisher/__manifest__.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- { 'name': 'ITSulu Blog Publisher', - 'version': '17.0.1.0.0', + # Odoo manifest version = .. Product version + # is tracked in the repo-root VERSION file (currently 0.4.8). See CLAUDE.md §15. + 'version': '17.0.0.4.8', 'summary': 'AI-powered blog post generation with multi-LLM support, scheduling, and social media copy', 'description': """ ITSulu Blog Publisher diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh new file mode 100755 index 0000000..4d23887 --- /dev/null +++ b/scripts/bump-version.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +# +# bump-version.sh — ITSulu Blog Publisher version manager +# +# Versioning scheme: MAJOR.MINOR.PATCH (each part 0–999) +# MAJOR major release for sale; significant feature upgrade / significant change +# MINOR feature(s) added, or a performance improvement +# PATCH a single group of commits, or one large commit +# +# The repo-root VERSION file is the single source of truth. The Odoo manifest +# version is derived as 17.0... (Odoo-series prefix + +# the product version), e.g. product 0.4.8 -> manifest 17.0.0.4.8. +# +# Usage: +# scripts/bump-version.sh patch # 0.4.8 -> 0.4.9 +# scripts/bump-version.sh minor # 0.4.8 -> 0.5.0 +# scripts/bump-version.sh major # 0.4.8 -> 1.0.0 +# scripts/bump-version.sh set 1.2.3 # set an explicit version +# scripts/bump-version.sh tag # create annotated git tag v +# # from the CHANGELOG section +# +# Typical flow for a release: +# 1) scripts/bump-version.sh minor +# 2) edit CHANGELOG.md — fill in the new "## vX.Y.Z" section (plain language) +# 3) git add -A && git commit -m "release: vX.Y.Z" +# 4) scripts/bump-version.sh tag # tags + uses the CHANGELOG notes +# 5) git push && git push --tags +# +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +VERSION_FILE="$ROOT/VERSION" +MANIFEST="$ROOT/addons/itsulu_blog_publisher/__manifest__.py" +README="$ROOT/README.md" +CHANGELOG="$ROOT/CHANGELOG.md" +ODOO_SERIES="17.0" + +die() { echo "error: $*" >&2; exit 1; } + +read_version() { + [ -f "$VERSION_FILE" ] || die "VERSION file not found at $VERSION_FILE" + tr -d '[:space:]' < "$VERSION_FILE" +} + +validate() { + local v="$1" + [[ "$v" =~ ^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$ ]] \ + || die "invalid version '$v' — expected MAJOR.MINOR.PATCH, each part 0–999" + for part in "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}"; do + [ "$part" -le 999 ] || die "version part '$part' exceeds 999" + done +} + +write_everywhere() { + local v="$1" + echo "$v" > "$VERSION_FILE" + # Odoo manifest: 17.0... (series prefix + product version) + sed -i -E "s/('version': ')[0-9.]+(')/\1${ODOO_SERIES}.${v}\2/" "$MANIFEST" + # README version line: **Version:** X.Y.Z + if grep -qE '^\*\*Version:\*\*' "$README"; then + sed -i -E "s/^(\*\*Version:\*\*) .*/\1 ${v}/" "$README" + fi + echo " VERSION -> $v" + echo " manifest -> ${ODOO_SERIES}.${v}" + echo " README -> **Version:** ${v}" +} + +cmd="${1:-}"; arg="${2:-}" +current="$(read_version)" + +case "$cmd" in + major|minor|patch) + IFS='.' read -r MA MI PA <<< "$current" + case "$cmd" in + major) MA=$((MA+1)); MI=0; PA=0 ;; + minor) MI=$((MI+1)); PA=0 ;; + patch) PA=$((PA+1)) ;; + esac + new="${MA}.${MI}.${PA}" + validate "$new" + echo "Bumping ($cmd): $current -> $new" + write_everywhere "$new" + echo + echo "Next: add a '## v${new}' section to CHANGELOG.md, commit, then: scripts/bump-version.sh tag" + ;; + set) + [ -n "$arg" ] || die "usage: bump-version.sh set " + validate "$arg" + echo "Setting version: $current -> $arg" + write_everywhere "$arg" + echo + echo "Next: add a '## v${arg}' section to CHANGELOG.md, commit, then: scripts/bump-version.sh tag" + ;; + tag) + v="$current" + tagname="v${v}" + git -C "$ROOT" rev-parse "$tagname" >/dev/null 2>&1 && die "tag $tagname already exists" + # Extract this version's section from CHANGELOG (from "## vX.Y.Z" to the next "## v" or "---"). + notes="$(awk -v ver="## v${v}" ' + $0 ~ "^"ver" " || $0 == ver {flag=1} + flag && /^---[[:space:]]*$/ {exit} + flag && /^## v/ && $0 !~ ver {exit} + flag {print} + ' "$CHANGELOG")" + [ -n "$notes" ] || die "no CHANGELOG.md section found for ## v${v}" + echo "Creating annotated tag $tagname with release notes:" + echo "----------------------------------------" + echo "$notes" + echo "----------------------------------------" + git -C "$ROOT" tag -a "$tagname" -m "$notes" + echo "Tagged $tagname. Push with: git push origin $tagname" + ;; + ""|-h|--help|help) + grep -E '^#( |$)' "$0" | sed -E 's/^# ?//' + echo "current version: $current" + ;; + *) + die "unknown command '$cmd' (try: major | minor | patch | set | tag | help)" + ;; +esac