From dd0d9e5c0a5b00fa1fbb949acd881449b52f61e1 Mon Sep 17 00:00:00 2001 From: Paul O'Reilly Date: Wed, 24 Jun 2026 07:10:05 +1200 Subject: [PATCH] feat: hugo-content-workspace + hugo-content-airouter harnesses New context: hugo-content-workspace/v1 - init.sh: clones hugo-{customer}-content at HUGO_CONTENT_BRANCH and hugo-{customer}-integration at main via gitea-ssh-accelerators - finalize.sh: detects changes, commits content repo first then integration repo; skips silently if no changes - CLAUDE.md: instructs the agent to work in /workspace/content/ New composite: hugo-content-airouter/v1 - Combines airouter/v1 + gitea-ssh-accelerators/v1 + hugo-content-workspace/v1 - Label-gated to airouter dispatchers (ESO secret required) Used by cms-proxy /ai/{customer}/draft endpoint. --- .../hugo-content-airouter/v1/harness.yaml | 12 ++++ .../hugo-content-workspace/v1/CLAUDE.md | 29 +++++++++ .../hugo-content-workspace/v1/finalize.sh | 55 ++++++++++++++++ .../hugo-content-workspace/v1/harness.yaml | 19 ++++++ .../hugo-content-workspace/v1/init.sh | 63 +++++++++++++++++++ 5 files changed, 178 insertions(+) create mode 100644 harnesses/composites/hugo-content-airouter/v1/harness.yaml create mode 100644 harnesses/contexts/hugo-content-workspace/v1/CLAUDE.md create mode 100755 harnesses/contexts/hugo-content-workspace/v1/finalize.sh create mode 100644 harnesses/contexts/hugo-content-workspace/v1/harness.yaml create mode 100755 harnesses/contexts/hugo-content-workspace/v1/init.sh diff --git a/harnesses/composites/hugo-content-airouter/v1/harness.yaml b/harnesses/composites/hugo-content-airouter/v1/harness.yaml new file mode 100644 index 0000000..21bb463 --- /dev/null +++ b/harnesses/composites/hugo-content-airouter/v1/harness.yaml @@ -0,0 +1,12 @@ +kind: composite +name: hugo-content-airouter +version: 1 +description: "Hugo content AI assistant — Airouter model + Gitea SSH + content/integration repo clone" + +# Airouter label gate — only dispatchers with the ESO airouter secret claim these tasks. +requires_labels: [airouter] + +layers: + - context: airouter/v1 + - context: gitea-ssh-accelerators/v1 + - context: hugo-content-workspace/v1 diff --git a/harnesses/contexts/hugo-content-workspace/v1/CLAUDE.md b/harnesses/contexts/hugo-content-workspace/v1/CLAUDE.md new file mode 100644 index 0000000..6f24531 --- /dev/null +++ b/harnesses/contexts/hugo-content-workspace/v1/CLAUDE.md @@ -0,0 +1,29 @@ +# Hugo Content Assistant + +You are an AI content assistant for a Hugo website. Your job is to draft or revise content as instructed. + +## Working directory + +`/workspace/content/` — this is the Hugo content repo. The site structure is: + +``` +content/ Hugo content files (Markdown with frontmatter) +data/ Site data YAML (navigation, site config, component data) +static/ Static assets (images, favicons) +``` + +The integration repo (build pipeline) is at `/workspace/integration/` — read-only reference, do not modify it unless explicitly asked. + +## How to work + +1. Read existing content files to understand the site's voice, structure, and conventions before writing. +2. Make changes to files in `/workspace/content/`. Create new files in the right Hugo directory (`content/`, `data/`, etc.). +3. Use Hugo frontmatter conventions: `---` delimited YAML at the top of every Markdown file. +4. Keep Markdown content clean — no inline HTML unless unavoidable. +5. Do not touch files outside `/workspace/content/` unless the user's request explicitly requires integration changes. + +## Output + +Your changes will be automatically committed and pushed when you exit. You do not need to run git commands — just write the files. + +Summarise what you changed at the end of your response. diff --git a/harnesses/contexts/hugo-content-workspace/v1/finalize.sh b/harnesses/contexts/hugo-content-workspace/v1/finalize.sh new file mode 100755 index 0000000..723361a --- /dev/null +++ b/harnesses/contexts/hugo-content-workspace/v1/finalize.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# hugo-content-workspace/v1 finalize.sh +# Detects and commits any changes made by the agent. +# Content repo is committed and pushed first, then integration repo. +set -euo pipefail + +echo "=== hugo-content-workspace/v1 finalize.sh ===" + +: "${HUGO_CUSTOMER:?HUGO_CUSTOMER is required}" +: "${HUGO_CONTENT_BRANCH:=staging}" + +commit_repo() { + local repo_dir="$1" + local branch="$2" + local label="$3" + + if [ ! -d "${repo_dir}/.git" ]; then + echo "${label}: no .git directory, skipping" + return 0 + fi + + cd "${repo_dir}" + + # Check for any modifications (tracked or untracked) + if git diff --quiet HEAD 2>/dev/null && [ -z "$(git ls-files --others --exclude-standard)" ]; then + echo "${label}: no changes detected" + return 0 + fi + + # Stage all changes + git add -A + + # Double-check — after staging, is there anything to commit? + if git diff --cached --quiet; then + echo "${label}: nothing staged after add -A" + return 0 + fi + + local msg="AI content update — ${HUGO_CUSTOMER} (${HUGO_CONTENT_BRANCH})" + if [ "${label}" = "integration" ]; then + msg="AI integration update — ${HUGO_CUSTOMER}" + fi + + git commit -m "${msg}" + git push origin "${branch}" + echo "${label}: committed and pushed to ${branch}" +} + +# 1. Content repo — push to the requested content branch +commit_repo /workspace/content "${HUGO_CONTENT_BRANCH}" "content" + +# 2. Integration repo — push to main +commit_repo /workspace/integration "main" "integration" + +echo "=== hugo-content-workspace/v1 finalize.sh complete ===" diff --git a/harnesses/contexts/hugo-content-workspace/v1/harness.yaml b/harnesses/contexts/hugo-content-workspace/v1/harness.yaml new file mode 100644 index 0000000..de27eed --- /dev/null +++ b/harnesses/contexts/hugo-content-workspace/v1/harness.yaml @@ -0,0 +1,19 @@ +kind: context +name: hugo-content-workspace +version: 1 +description: "Hugo site workspace: clone content + integration repos, auto-commit changes on exit" +requires: [git-access] +provides: [hugo-workspace] + +scripts: + init: "./init.sh" + finalize: "./finalize.sh" + +env: + # Injected by dispatcher from task project_id — set as HUGO_CUSTOMER + HUGO_CUSTOMER: "" + # Branch to check out in the content repo. Pass via runtime.env in the task. + HUGO_CONTENT_BRANCH: "staging" + AGENT_WORKING_DIR: "/workspace/content" + +secrets_required: [] diff --git a/harnesses/contexts/hugo-content-workspace/v1/init.sh b/harnesses/contexts/hugo-content-workspace/v1/init.sh new file mode 100755 index 0000000..849c223 --- /dev/null +++ b/harnesses/contexts/hugo-content-workspace/v1/init.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# hugo-content-workspace/v1 init.sh +# Clones the Hugo content and integration repos for a customer site. +# Content repo is checked out at HUGO_CONTENT_BRANCH; integration at main. +set -euo pipefail + +echo "=== hugo-content-workspace/v1 init.sh ===" + +: "${HUGO_CUSTOMER:?HUGO_CUSTOMER is required (set via runtime.env in task submission)}" +: "${HUGO_CONTENT_BRANCH:=staging}" + +GITEA_HOST="gitea.oreillyit.nz-accelerators" +ORG="accelerators" +CONTENT_REPO="hugo-${HUGO_CUSTOMER}-content" +INTEGRATION_REPO="hugo-${HUGO_CUSTOMER}-integration" + +CONTENT_URL="git@${GITEA_HOST}:${ORG}/${CONTENT_REPO}.git" +INTEGRATION_URL="git@${GITEA_HOST}:${ORG}/${INTEGRATION_REPO}.git" + +mkdir -p /workspace/content /workspace/integration /workspace/.agent-output + +# Clone content repo at the requested branch +echo "Cloning content repo: ${CONTENT_URL} (branch: ${HUGO_CONTENT_BRANCH})" +if git clone --branch "${HUGO_CONTENT_BRANCH}" \ + -c core.symlinks=false \ + "${CONTENT_URL}" /workspace/content; then + echo "Cloned content repo at branch ${HUGO_CONTENT_BRANCH}" +else + echo "ERROR: Failed to clone content repo ${CONTENT_URL}" >&2 + exit 1 +fi + +# Configure git identity in content repo +git -C /workspace/content config user.name "AI Content Assistant" +git -C /workspace/content config user.email "agent@oreillyit.nz" + +# Clone integration repo at main (read context, rarely modified by agent) +echo "Cloning integration repo: ${INTEGRATION_URL} (branch: main)" +if git clone --depth 1 --branch main \ + -c core.symlinks=false \ + "${INTEGRATION_URL}" /workspace/integration; then + echo "Cloned integration repo" +else + echo "WARNING: Could not clone integration repo — non-fatal, proceeding without it" >&2 +fi + +# Configure git identity in integration repo if it was cloned +if [ -d /workspace/integration/.git ]; then + git -C /workspace/integration config user.name "AI Content Assistant" + git -C /workspace/integration config user.email "agent@oreillyit.nz" +fi + +# Ensure .gitignore in content repo excludes secrets +GITIGNORE=/workspace/content/.gitignore +if [ -f "$GITIGNORE" ]; then + if ! grep -q "^\.env$" "$GITIGNORE" 2>/dev/null; then + printf '\n# Agent-added safety exclusions\n*.env\n.env\n*.key\n*.pem\n' >> "$GITIGNORE" + fi +fi + +export AGENT_WORKING_DIR="/workspace/content" +echo "AGENT_WORKING_DIR=/workspace/content" +echo "=== hugo-content-workspace/v1 init.sh complete ==="