From d68ac0b5ada188961fc562b17cdb4c9b58459263 Mon Sep 17 00:00:00 2001 From: Sergio Alexander Florez Galeano Date: Wed, 15 Jul 2026 14:09:56 +0000 Subject: [PATCH] feat: auto-refresh vendored agent skills via PR-driven flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turn every upstream release of DailybotHQ/deepworkplan-skill or DailybotHQ/agent-skill into a new release of this website, unattended. - check_vendored_skills.yml (daily 12:00 UTC + workflow_dispatch): resolves the latest tag of each upstream skill via `gh release view`, compares to the vendored SKILL.md version, and if any skill is behind: force-resets feature__vendored_skills_refresh from main, runs `npx skills add @` (asserting the installed SKILL.md version matches the tag), and opens (or updates) a PR against main. - check_and_merge_vendored_skills_pr.yml (daily 17:00 UTC): auto-merges that PR when GitHub reports mergeable_state == "clean". Any other state leaves the PR for a human. Never force-merges. Merging the PR fires release_and_publish.yml normally, so the skill refresh becomes a real website version bump + tag + GitHub Release — skills upstream and website release stay in lockstep. Mirrors the existing check_packages_versions.yml + auto-merge pattern used for npm dependency upgrades. Refactor is behavioural-only; no site content changes. Co-authored-by: Cursor --- .github/docs/WORKFLOWS.md | 117 +++++- .../check_and_merge_vendored_skills_pr.yml | 121 ++++++ .github/workflows/check_vendored_skills.yml | 343 ++++++++++++++++++ AGENTS.md | 14 +- 4 files changed, 587 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/check_and_merge_vendored_skills_pr.yml create mode 100644 .github/workflows/check_vendored_skills.yml diff --git a/.github/docs/WORKFLOWS.md b/.github/docs/WORKFLOWS.md index bbb6fb5..8112158 100644 --- a/.github/docs/WORKFLOWS.md +++ b/.github/docs/WORKFLOWS.md @@ -164,16 +164,119 @@ Dispatches a `cleanup_caches` event via GitHub API. --- +## 6. check_vendored_skills.yml — Detect Vendored-Skill Updates + +| Property | Value | +|----------|-------| +| **Trigger** | Scheduled: **daily at 12:00 UTC** + `workflow_dispatch` | +| **Concurrency** | `check-vendored-skills`, `cancel-in-progress: false` | +| **Branch** | `feature__vendored_skills_refresh` (bot-owned; force-reset from main on every run that finds updates) | + +Detects when either upstream agent-skill repo has cut a release newer than the copy vendored under `.agents/skills/`, and opens a PR that refreshes them: + +- `DailybotHQ/deepworkplan-skill` → `.agents/skills/deepworkplan/` +- `DailybotHQ/agent-skill` → `.agents/skills/dailybot/` + +When that PR merges, `release_and_publish.yml` (Section 5) fires normally — so **a new upstream skill release directly causes a new website release** that ships with the refreshed skills vendored inside. No manual step in between. + +Companion workflow `check_and_merge_vendored_skills_pr.yml` (Section 7) auto-merges the PR once CI is green. + +### Job: `check_vendored_skills` + +| Step | Name | What it does | +|------|------|-------------| +| — | Checkout | `actions/checkout@v4` with `AUTOMATION_GITHUB_TOKEN`, `fetch-depth: 0` | +| — | Setup Node | 24.18.0 | +| 1 | Setup GitHub Config | Git config + `gh auth login` | +| 2 | Resolve upstream tags vs vendored versions | For each skill: `gh release view --repo --json tagName -q .tagName` (unless a `workflow_dispatch` input pins it). Reads current vendored versions from `.agents/skills/*/SKILL.md`. Computes `updates_available`. | +| 3 | Reset feature branch from main | If `updates_available`: deletes the remote branch (if any) and re-branches from main so every run starts on a clean base | +| 4a | Install deepworkplan at resolved tag | `npx --yes skills add DailybotHQ/deepworkplan-skill@ --skill deepworkplan --force -y` + version-match invariant assertion | +| 4b | Install dailybot at resolved tag | Same pattern for `DailybotHQ/agent-skill@` | +| 5 | Stage vendored files and check for actual changes | Stages `.agents/skills/{deepworkplan,dailybot}/` and `skills-lock.json`; short-circuits if `git diff --cached --quiet` (pinned to current) | +| 6 | Commit and push feature branch | Commits `chore: refresh vendored skills to (deepworkplan vX.Y.Z, dailybot vA.B.C)` and force-with-lease pushes to `feature__vendored_skills_refresh` | +| 7 | Open (or update) the pull request | `gh pr create` (or `gh pr edit` if a PR from that branch is already open) with a diff-style body listing the from/to versions and release-notes links | + +### Manual override + +Run from the Actions tab (`workflow_dispatch`) with optional inputs to pin either skill to a specific tag: + +- `deepworkplan_tag` — e.g. `v2.16.3` (blank = latest release) +- `dailybot_tag` — e.g. `v3.10.3` (blank = latest release) + +Useful for previewing a pre-release, or intentionally rolling back to an older tag. + +### Failure modes + +| Failure | Behavior | +|---------|----------| +| Both skills already at latest | Job exits 0 with a summary; no branch, no commit, no PR | +| Upstream repo has no releases / API blip | Warning in job summary; that skill is skipped, the other continues | +| `npx skills add` fails | **Fails the job** — treated as real breakage (the whole point of the smoke test) | +| Vendored `SKILL.md` version does not match the requested tag | **Fails the job** — refuses to open a PR whose title misrepresents its contents | +| No diff after install (rare pin-to-current case) | Skips commit + PR; exits 0 | + +**Secrets:** `AUTOMATION_GITHUB_TOKEN` + +--- + +## 7. check_and_merge_vendored_skills_pr.yml — Auto-Merge Vendored-Skill Refresh PR + +| Property | Value | +|----------|-------| +| **Trigger** | Scheduled: **daily at 17:00 UTC** + `workflow_dispatch` | +| **Timing** | Runs 5 hours after the check workflow to allow CI (`code_check.yml`, `pull_request_check.yml`) to complete | +| **Concurrency** | `check-and-merge-vendored-skills`, `cancel-in-progress: false` | + +Mirrors the pattern of `check_and_merge_packages_upgrades_pr.yml` (Section 4) but targeting the vendored-skills refresh PR. + +### Job: `check_and_merge_vendored_skills_pr` + +| Step | Name | What it does | +|------|------|-------------| +| — | Checkout | `actions/checkout@v4` with `AUTOMATION_GITHUB_TOKEN` | +| — | Setup Node | 24.18.0 | +| 1 | Setup GitHub Config | Git config + `gh auth login` | +| 2 | Locate PR | `gh pr list -B main -s open -H feature__vendored_skills_refresh` — exits 0 if no PR | +| 3 | Read PR title/body | For the run summary only | +| 4 | Check mergeable state and merge if clean | `gh api repos/:owner/:repo/pulls/:n` → if `mergeable_state == "clean"`, `gh pr merge --merge`. Otherwise leaves PR open. | + +**Key behavior:** Only merges when GitHub reports `mergeable_state == "clean"` (all required checks passed AND no conflicts AND branch up to date with base). Any other state (`blocked`, `unstable`, `dirty`, `unknown`, ...) leaves the PR open for a human to review. The workflow never force-merges. + +**Merge triggers a release:** Because `release_and_publish.yml` runs on `pull_request: closed && merged == true`, the merge here directly cuts a new website release (bumps `package.json`, tags `vX.Y.Z`, publishes a GitHub Release). This is intentional — a new upstream skill release IS the reason for a new website release. + +**Opting out of an individual auto-merge:** Close the PR (or push additional commits so it goes non-clean) before 17:00 UTC. The next day's check run will re-open it with the same target tags if they haven't advanced. + +**Secrets:** `AUTOMATION_GITHUB_TOKEN` + +--- + ## Workflow Dependencies ``` -check_pr_size_label - │ - ▼ - release_and_publish - │ - ▼ - cleanup_caches + ┌──────────────────────────────┐ + │ Upstream skill release │ + │ (deepworkplan / agent-skill)│ + └──────────────┬───────────────┘ + │ + 12:00 UTC daily ▶ check_vendored_skills.yml (or manual) + │ + opens/updates PR + │ + ▼ + code_check + pull_request_check (on PR) + │ + 17:00 UTC daily ▶ check_and_merge_vendored_skills_pr.yml + │ + gh pr merge --merge (if clean) + │ + ▼ + release_and_publish.yml (Section 5) + │ + ▼ + check_pr_size_label ──▶ release_and_publish ──▶ cleanup_caches + │ + ▼ + Cloudflare Pages deploy (on push to main) ``` **Note:** Cloudflare Pages deploys independently on push to `main` (configured in Cloudflare dashboard). diff --git a/.github/workflows/check_and_merge_vendored_skills_pr.yml b/.github/workflows/check_and_merge_vendored_skills_pr.yml new file mode 100644 index 0000000..9596840 --- /dev/null +++ b/.github/workflows/check_and_merge_vendored_skills_pr.yml @@ -0,0 +1,121 @@ +name: Check & Merge Vendored Skills PR + +# Auto-merges the PR opened by `check_vendored_skills.yml` once CI is +# green. When the merge lands, the standard `release_and_publish.yml` +# workflow fires (it triggers on `pull_request: closed && merged`) and +# cuts the website's next patch release — so the whole chain from +# "upstream skill v2.16.4 was published" to "deepworkplan.com released +# v1.0.75 with 2.16.4 vendored" runs unattended. +# +# Mirrors `check_and_merge_packages_upgrades_pr.yml`. Only auto-merges +# when GitHub reports `mergeable_state == "clean"` (all required checks +# passed, no conflicts). If not clean, the PR stays open for a human to +# review — this workflow never force-merges. + +on: + workflow_dispatch: + schedule: + # Daily at 17:00 UTC — 5 hours after the check runs at 12:00 UTC. + # Matches the "detect at 15:00 → merge at 20:00" gap used for npm + # dependency upgrades, giving CI enough time to run and, ideally, + # for a maintainer to intervene if a specific upgrade should NOT + # auto-merge (close the PR before 17:00 UTC and this run is a no-op). + - cron: '0 17 * * *' + +env: + GIT_BRANCH_FOR_SKILLS_REFRESH: feature__vendored_skills_refresh + +permissions: + contents: write + pull-requests: write + +concurrency: + group: check-and-merge-vendored-skills + cancel-in-progress: false + +jobs: + check_and_merge_vendored_skills_pr: + name: 'Check & Merge Vendored Skills PR' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.AUTOMATION_GITHUB_TOKEN }} + - uses: actions/setup-node@v4 + with: + node-version: '24.18.0' + + - name: Step 1 - ⚙️ Setup GitHub Config + run: | + git config user.name "🤖 Dailybot" + git config user.email "ops@dailybot.com" + gh auth login --with-token <<< "${{ secrets.AUTOMATION_GITHUB_TOKEN }}" + + - name: Step 2 - 🔍 Locate the open vendored-skills refresh PR + id: locate_pr + run: | + set -euo pipefail + PR_NUMBER=$(gh pr list -B main -s open -L 1 \ + --json number,headRefName \ + -q '.[] | select(.headRefName == "'"$GIT_BRANCH_FOR_SKILLS_REFRESH"'") | .number' \ + || true) + if [ -z "$PR_NUMBER" ]; then + echo "No open PR from ${GIT_BRANCH_FOR_SKILLS_REFRESH} to main — nothing to merge." + echo "**No open PR** — this is expected on days when no upstream skill released." >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + + - if: ${{ steps.locate_pr.outputs.pr_number }} + name: Step 3 - 🧾 Read PR title/body for the run summary + id: read_pr + run: | + set -euo pipefail + PR_NUMBER="${{ steps.locate_pr.outputs.pr_number }}" + PR_TITLE=$(gh pr view "$PR_NUMBER" --json title -q '.title') + echo "pr_title=$PR_TITLE" >> "$GITHUB_OUTPUT" + { + echo "## Vendored-skills refresh PR" + echo "" + echo "**#${PR_NUMBER}** — ${PR_TITLE}" + } >> "$GITHUB_STEP_SUMMARY" + + - if: ${{ steps.locate_pr.outputs.pr_number }} + name: Step 4 - 🚦 Check mergeable state and merge if clean + id: merge_if_clean + run: | + set -euo pipefail + PR_NUMBER="${{ steps.locate_pr.outputs.pr_number }}" + + # Prefer the REST API's `mergeable_state`. `"clean"` means all + # required status checks pass AND there are no conflicts AND the + # branch is up to date with base. Any other value (`"blocked"`, + # `"unstable"`, `"dirty"`, `"unknown"`, ...) means we should NOT + # auto-merge — leave the PR alone for a human to review. + PR_DATA=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}") + PR_MERGEABLE_STATE=$(echo "$PR_DATA" | jq -r '.mergeable_state') + + echo "pr_mergeable_state=$PR_MERGEABLE_STATE" >> "$GITHUB_OUTPUT" + echo "PR #${PR_NUMBER} mergeable_state = ${PR_MERGEABLE_STATE}" + + if [ "$PR_MERGEABLE_STATE" != "clean" ]; then + { + echo "" + echo "**Mergeable state:** \`${PR_MERGEABLE_STATE}\` — not merging automatically. Leaving PR open for review." + } >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + + # `--merge` (not `--squash`) so `release_and_publish.yml`, which + # counts commits since the last release marker, picks up the + # refresh commit as a normal entry in the release notes. + gh pr merge "$PR_NUMBER" --merge + { + echo "" + echo "**Merged automatically** — \`release_and_publish.yml\` will now bump the website version and publish a release with the refreshed skills." + } >> "$GITHUB_STEP_SUMMARY" + + outputs: + pr_number: ${{ steps.locate_pr.outputs.pr_number }} + pr_title: ${{ steps.read_pr.outputs.pr_title }} + pr_mergeable_state: ${{ steps.merge_if_clean.outputs.pr_mergeable_state }} diff --git a/.github/workflows/check_vendored_skills.yml b/.github/workflows/check_vendored_skills.yml new file mode 100644 index 0000000..a1a242d --- /dev/null +++ b/.github/workflows/check_vendored_skills.yml @@ -0,0 +1,343 @@ +name: Check Vendored Skills + +# Detects when either upstream agent-skill repo (deepworkplan-skill or +# agent-skill/dailybot) has cut a release newer than the copy vendored under +# `.agents/skills/`, and opens a PR that refreshes them. Merging that PR +# triggers `release_and_publish.yml` → the website version bumps, a new +# `vX.Y.Z` tag is cut, and a GitHub Release ships with the refreshed skill +# tree inside. In other words: **a new upstream skill release directly +# causes a new website release** — no manual step in between. +# +# Companion workflow `check_and_merge_vendored_skills_pr.yml` auto-merges +# the PR a few hours later once CI is green. Together they mirror the +# `check_packages_versions.yml` + `check_and_merge_packages_upgrades_pr.yml` +# pattern already used for npm dependency upgrades. +# +# Manual runs (`workflow_dispatch`) accept optional per-skill tag pins so a +# maintainer can preview a pre-release or intentionally roll back. + +on: + workflow_dispatch: + inputs: + deepworkplan_tag: + description: 'Pin deepworkplan-skill to a tag (e.g. v2.16.3). Blank = latest release.' + required: false + type: string + dailybot_tag: + description: 'Pin dailybot agent-skill to a tag (e.g. v3.10.3). Blank = latest release.' + required: false + type: string + schedule: + # Daily at 12:00 UTC. Upstream skills release often enough that a + # weekly cadence would leave the site lagging days behind; a daily + # check + a 5-hour delayed auto-merge gets a new upstream release + # into a website release within ~24h without human intervention. + - cron: '0 12 * * *' + +env: + GIT_BRANCH_FOR_SKILLS_REFRESH: feature__vendored_skills_refresh + +permissions: + contents: write + pull-requests: write + +# Never allow two check runs to race for the same feature branch — the +# second run would step on the first's force-push. A queued run waits. +concurrency: + group: check-vendored-skills + cancel-in-progress: false + +jobs: + check_vendored_skills: + name: 'Check Vendored Skills' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.AUTOMATION_GITHUB_TOKEN }} + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: '24.18.0' + registry-url: https://registry.npmjs.org/ + + - name: Step 1 - ⚙️ Setup GitHub Config + run: | + git config user.name "🤖 Dailybot" + git config user.email "ops@dailybot.com" + gh auth login --with-token <<< "${{ secrets.AUTOMATION_GITHUB_TOKEN }}" + + - name: Step 2 - 🔍 Resolve upstream tags vs vendored versions + id: resolve + env: + GH_TOKEN: ${{ secrets.AUTOMATION_GITHUB_TOKEN }} + DWP_INPUT: ${{ github.event.inputs.deepworkplan_tag }} + DB_INPUT: ${{ github.event.inputs.dailybot_tag }} + run: | + set -euo pipefail + + # Resolve one upstream skill to a concrete tag. + # $1 = owner/repo, $2 = workflow_dispatch pin (or ""), $3 = pretty name + # Prints "" on failure so callers can skip cleanly — a transient + # upstream API blip should NOT poison the whole refresh. + # + # CRITICAL: all logging goes to stderr (>&2). Only the tag itself + # touches stdout, because the caller uses `X=$(resolve ...)` — any + # `::notice::`/`::warning::` on stdout would be swallowed into the + # captured variable and the equality checks below would spuriously + # think an update is needed. GitHub Actions still renders workflow + # commands emitted to stderr. + resolve() { + local repo="$1" input="$2" name="$3" + if [ -n "$input" ]; then + echo "::notice::${name}: pinned by workflow input to ${input}" >&2 + printf '%s\n' "$input" + return 0 + fi + local tag + tag=$(gh release view --repo "$repo" --json tagName -q .tagName 2>/dev/null || true) + if [ -z "$tag" ]; then + echo "::warning::${name}: could not resolve latest release for ${repo} — skipping" >&2 + printf '\n' + return 0 + fi + echo "::notice::${name}: latest published release is ${tag}" >&2 + printf '%s\n' "$tag" + } + + DWP_TAG=$(resolve DailybotHQ/deepworkplan-skill "$DWP_INPUT" deepworkplan) + DB_TAG=$(resolve DailybotHQ/agent-skill "$DB_INPUT" dailybot) + + # Currently vendored versions on main (bare number, no leading v). + DWP_CUR=$(sed -nE 's/^version:[[:space:]]*"([^"]+)".*/\1/p' .agents/skills/deepworkplan/SKILL.md | head -n1) + DB_CUR=$(sed -nE 's/^version:[[:space:]]*"([^"]+)".*/\1/p' .agents/skills/dailybot/SKILL.md | head -n1) + + # Per-skill "moved" flag: true iff the resolved tag differs from + # the currently vendored version. Downstream steps use these to + # skip installing skills that would produce no change AND to keep + # the commit subject / PR title accurate ("refresh deepworkplan + # v2.16.3" vs. the misleading "refresh deepworkplan v2.16.3, + # dailybot v3.10.3" when only deepworkplan actually moved). + DWP_MOVED=false + DB_MOVED=false + if [ -n "$DWP_TAG" ] && [ "$DWP_CUR" != "${DWP_TAG#v}" ]; then + DWP_MOVED=true + fi + if [ -n "$DB_TAG" ] && [ "$DB_CUR" != "${DB_TAG#v}" ]; then + DB_MOVED=true + fi + UPDATES_AVAILABLE=false + if [ "$DWP_MOVED" = "true" ] || [ "$DB_MOVED" = "true" ]; then + UPDATES_AVAILABLE=true + fi + + { + echo "dwp_tag=$DWP_TAG" + echo "db_tag=$DB_TAG" + echo "dwp_cur=$DWP_CUR" + echo "db_cur=$DB_CUR" + echo "dwp_moved=$DWP_MOVED" + echo "db_moved=$DB_MOVED" + echo "updates_available=$UPDATES_AVAILABLE" + } >> "$GITHUB_OUTPUT" + + { + echo "## Vendored vs upstream" + echo "" + echo "| Skill | Vendored | Latest upstream | Moved? |" + echo "| --- | --- | --- | --- |" + echo "| deepworkplan | v${DWP_CUR} | ${DWP_TAG:-_(unresolved)_} | ${DWP_MOVED} |" + echo "| dailybot | v${DB_CUR} | ${DB_TAG:-_(unresolved)_} | ${DB_MOVED} |" + echo "" + echo "**Updates available:** \`${UPDATES_AVAILABLE}\`" + } >> "$GITHUB_STEP_SUMMARY" + + - if: ${{ steps.resolve.outputs.updates_available == 'true' }} + name: Step 3 - 🌱 Reset feature branch from main + run: | + set -euo pipefail + # The refresh branch is 100% bot-owned — force-reset from main so + # every run starts from a clean, up-to-date base. This guarantees + # the PR that lands never carries stale conflict material from a + # previous refresh cycle that was never merged. + if [ -n "$(git ls-remote --heads origin $GIT_BRANCH_FOR_SKILLS_REFRESH)" ]; then + git push origin --delete "$GIT_BRANCH_FOR_SKILLS_REFRESH" || true + fi + git checkout -b "$GIT_BRANCH_FOR_SKILLS_REFRESH" + + - if: ${{ steps.resolve.outputs.dwp_moved == 'true' }} + name: Step 4a - 📦 Install deepworkplan at resolved tag + env: + NEW_TAG: ${{ steps.resolve.outputs.dwp_tag }} + run: | + set -euo pipefail + NEW_VERSION="${NEW_TAG#v}" + + # Two `-y` / `--yes` flags below — both required in a non-TTY + # environment: `npx --yes` skips the "install the package?" + # prompt, `skills add ... -y` skips the interactive agent picker. + echo "::group::npx skills add DailybotHQ/deepworkplan-skill@${NEW_TAG}" + npx --yes skills add "DailybotHQ/deepworkplan-skill@${NEW_TAG}" \ + --skill deepworkplan --force -y + echo "::endgroup::" + + # Invariant: what got installed MUST equal what we asked for. + # Otherwise we'd open a PR that misrepresents its title. + VENDORED_VERSION=$(sed -nE 's/^version:[[:space:]]*"([^"]+)".*/\1/p' .agents/skills/deepworkplan/SKILL.md | head -n1) + if [ "$VENDORED_VERSION" != "$NEW_VERSION" ]; then + echo "::error::deepworkplan install produced version '${VENDORED_VERSION}', expected '${NEW_VERSION}' (tag ${NEW_TAG})" + exit 1 + fi + + - if: ${{ steps.resolve.outputs.db_moved == 'true' }} + name: Step 4b - 📦 Install dailybot at resolved tag + env: + NEW_TAG: ${{ steps.resolve.outputs.db_tag }} + run: | + set -euo pipefail + NEW_VERSION="${NEW_TAG#v}" + + echo "::group::npx skills add DailybotHQ/agent-skill@${NEW_TAG}" + npx --yes skills add "DailybotHQ/agent-skill@${NEW_TAG}" \ + --skill dailybot --force -y + echo "::endgroup::" + + VENDORED_VERSION=$(sed -nE 's/^version:[[:space:]]*"([^"]+)".*/\1/p' .agents/skills/dailybot/SKILL.md | head -n1) + if [ "$VENDORED_VERSION" != "$NEW_VERSION" ]; then + echo "::error::dailybot install produced version '${VENDORED_VERSION}', expected '${NEW_VERSION}' (tag ${NEW_TAG})" + exit 1 + fi + + - if: ${{ steps.resolve.outputs.updates_available == 'true' }} + name: Step 5 - 🔍 Stage vendored files and check for actual changes + id: git_changes + run: | + set -euo pipefail + # Stage only the paths the installer is allowed to touch. Anything + # else in the working tree (transient CI artefacts, node_modules, + # etc.) is intentionally excluded from the PR. + git add \ + .agents/skills/deepworkplan \ + .agents/skills/dailybot \ + skills-lock.json 2>/dev/null || true + + if git diff --cached --quiet; then + # This can happen when a workflow_dispatch input pins a tag that + # matches what's already on main — `--force` re-copies identical + # bytes and `git diff` sees no change. + echo "changes_available=false" >> "$GITHUB_OUTPUT" + echo "No file changes staged — vendored copies already match the resolved tags." + { + echo "" + echo "**No PR opened** — the resolved tags produced no file changes." + } >> "$GITHUB_STEP_SUMMARY" + else + echo "changes_available=true" >> "$GITHUB_OUTPUT" + fi + + - if: ${{ steps.git_changes.outputs.changes_available == 'true' }} + name: Step 6 - 📝 Commit and push feature branch + env: + DWP_TAG: ${{ steps.resolve.outputs.dwp_tag }} + DB_TAG: ${{ steps.resolve.outputs.db_tag }} + DWP_MOVED: ${{ steps.resolve.outputs.dwp_moved }} + DB_MOVED: ${{ steps.resolve.outputs.db_moved }} + run: | + set -euo pipefail + + # Build a comma-joined "deepworkplan v2.16.3, dailybot v3.10.4" + # summary listing ONLY the skills that actually moved. `${array[*]}` + # only uses the first character of IFS when joining, so build the + # string by hand to preserve "a, b" (with the space). + parts=() + [ "$DWP_MOVED" = "true" ] && parts+=("deepworkplan ${DWP_TAG}") + [ "$DB_MOVED" = "true" ] && parts+=("dailybot ${DB_TAG}") + MSG="" + for p in "${parts[@]}"; do + if [ -z "$MSG" ]; then MSG="$p"; else MSG="${MSG}, ${p}"; fi + done + + git commit -m "chore: refresh vendored skills to ${MSG}" + # `--force-with-lease` because Step 3 tries to delete the remote + # branch before we branched off main; if that delete failed (rare + # protection edge case), we still need to overwrite the stale + # remote head. Safe because the branch is 100% bot-owned. + git push --force-with-lease -u origin "$GIT_BRANCH_FOR_SKILLS_REFRESH" + + - if: ${{ steps.git_changes.outputs.changes_available == 'true' }} + name: Step 7 - 🚀 Open (or update) the pull request + id: pull_request + env: + DWP_TAG: ${{ steps.resolve.outputs.dwp_tag }} + DB_TAG: ${{ steps.resolve.outputs.db_tag }} + DWP_CUR: ${{ steps.resolve.outputs.dwp_cur }} + DB_CUR: ${{ steps.resolve.outputs.db_cur }} + DWP_MOVED: ${{ steps.resolve.outputs.dwp_moved }} + DB_MOVED: ${{ steps.resolve.outputs.db_moved }} + run: | + set -euo pipefail + + # PR title mirrors the commit subject — only the skills that + # actually moved appear. + parts=() + [ "$DWP_MOVED" = "true" ] && parts+=("deepworkplan ${DWP_TAG}") + [ "$DB_MOVED" = "true" ] && parts+=("dailybot ${DB_TAG}") + TITLE_TAIL="" + for p in "${parts[@]}"; do + if [ -z "$TITLE_TAIL" ]; then TITLE_TAIL="$p"; else TITLE_TAIL="${TITLE_TAIL}, ${p}"; fi + done + PR_TITLE="🤖 Refresh vendored skills to ${TITLE_TAIL}" + + # PR body: a diff-style table (only skills that moved) + a clear + # explanation of what the merge will trigger. Written to a file + # so `gh pr create` gets it verbatim — no shell-escaping traps + # with backticks or newlines. + BODY_FILE="$(mktemp)" + { + echo "Automated refresh of the vendored agent skills that live under \`.agents/skills/\`." + echo "" + echo "| Skill | From | To | Release notes |" + echo "| --- | --- | --- | --- |" + if [ "$DWP_MOVED" = "true" ]; then + echo "| \`deepworkplan\` | \`v${DWP_CUR}\` | \`${DWP_TAG}\` | [notes](https://github.com/DailybotHQ/deepworkplan-skill/releases/tag/${DWP_TAG}) |" + fi + if [ "$DB_MOVED" = "true" ]; then + echo "| \`dailybot\` | \`v${DB_CUR}\` | \`${DB_TAG}\` | [notes](https://github.com/DailybotHQ/agent-skill/releases/tag/${DB_TAG}) |" + fi + echo "" + echo "## How this refresh happens" + echo "" + echo "Skills were installed via \`npx --yes skills add @ --skill --force -y\` — the exact same command any downstream consumer would run. Version invariants (\`SKILL.md\` frontmatter \`version:\` equals the resolved tag) were asserted before the commit was made." + echo "" + echo "## What merging this PR does" + echo "" + echo "Merging this PR triggers \`release_and_publish.yml\`, which bumps \`package.json\`, cuts a new \`vX.Y.Z\` tag, and publishes a GitHub Release — so **a new upstream skill release directly produces a new website release** with the refreshed skills inside." + echo "" + echo "If CI is green, the companion workflow \`check_and_merge_vendored_skills_pr.yml\` will auto-merge this PR a few hours after it opens. Close the PR (or push additional commits) to opt out of that run." + } > "$BODY_FILE" + + # Is there already an open PR from this branch? If yes, edit it + # in place; if not, create a fresh one. + EXISTING_PR=$(gh pr list -B main -s open -H "$GIT_BRANCH_FOR_SKILLS_REFRESH" --json number -q '.[0].number' || true) + if [ -z "$EXISTING_PR" ]; then + PR_LINK=$(gh pr create --title "$PR_TITLE" --body-file "$BODY_FILE" --base main --head "$GIT_BRANCH_FOR_SKILLS_REFRESH") + echo "pr_number=" >> "$GITHUB_OUTPUT" + echo "pr_link=$PR_LINK" >> "$GITHUB_OUTPUT" + echo "Created new PR: $PR_LINK" + else + gh pr edit "$EXISTING_PR" --title "$PR_TITLE" --body-file "$BODY_FILE" + PR_LINK="https://github.com/${GITHUB_REPOSITORY}/pull/${EXISTING_PR}" + echo "pr_number=$EXISTING_PR" >> "$GITHUB_OUTPUT" + echo "pr_link=$PR_LINK" >> "$GITHUB_OUTPUT" + echo "Updated existing PR #${EXISTING_PR}: $PR_LINK" + fi + + { + echo "" + echo "**Opened PR:** ${PR_LINK}" + } >> "$GITHUB_STEP_SUMMARY" + + outputs: + updates_available: ${{ steps.resolve.outputs.updates_available }} + changes_available: ${{ steps.git_changes.outputs.changes_available }} + pr_link: ${{ steps.pull_request.outputs.pr_link }} diff --git a/AGENTS.md b/AGENTS.md index f60262e..02c53f3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -257,10 +257,22 @@ Multiple AI agents collaborate on this codebase. When updating agent guidance, m ### Optional — Dailybot reporting (best-effort, never blocks) -This repo has the DWP **Dailybot addon** wired: the `dailybot` skill is installed alongside `deepworkplan` under `.agents/skills/` (both gitignored installs). When the Dailybot CLI is present and authenticated, DWP work emits standup-style agent updates at four lifecycle points — **kickoff** (a plan is approved: what is being built), **significant task** (a feature/fix ships mid-plan), **blocked** (a run halts; `state.json.blocked` says what it needs), and **completion** (the only **milestone**: what was built) — routed through the dailybot `report` sub-skill, with `--json-data` derived from the plan's `state.json`. If Dailybot is absent, unauthenticated, or unreachable — or `.dailybot/disabled` exists — skip silently and continue: **reporting never blocks any work.** Describe outcomes for the team, never plan IDs, task numbers, file paths, or git stats. +This repo has the DWP **Dailybot addon** wired: the `dailybot` skill is installed alongside `deepworkplan` under `.agents/skills/` (both vendored in-tree and pinned via `skills-lock.json` — see the "Vendored agent skills" section below). When the Dailybot CLI is present and authenticated, DWP work emits standup-style agent updates at four lifecycle points — **kickoff** (a plan is approved: what is being built), **significant task** (a feature/fix ships mid-plan), **blocked** (a run halts; `state.json.blocked` says what it needs), and **completion** (the only **milestone**: what was built) — routed through the dailybot `report` sub-skill, with `--json-data` derived from the plan's `state.json`. If Dailybot is absent, unauthenticated, or unreachable — or `.dailybot/disabled` exists — skip silently and continue: **reporting never blocks any work.** Describe outcomes for the team, never plan IDs, task numbers, file paths, or git stats. **Deterministic hook enforcement (Claude Code):** `.agents/settings.json` wires the Dailybot lifecycle hooks (`dailybot hook session-start | activity | stop`, CLI >= 1.12.0) so the harness itself detects unreported work and reminds the agent at end of turn — no reliance on the model remembering. When a reminder fires: send a report if a meaningful unit of work is done, or run `dailybot hook dismiss` if not — never ignore it silently, and never let reporting block work. The hooks are local-only, always exit 0, and respect `.dailybot/disabled`. +### Vendored agent skills — a new upstream release cuts a new website release + +`.agents/skills/deepworkplan/` and `.agents/skills/dailybot/` are **vendored copies** of the upstream skill repos (`DailybotHQ/deepworkplan-skill` and `DailybotHQ/agent-skill`), tracked in git and pinned via `skills-lock.json`. They are refreshed **automatically** through a PR-driven flow that treats each upstream skill release as a reason to cut a new website release: + +1. **Daily 12:00 UTC** — [`check_vendored_skills.yml`](.github/workflows/check_vendored_skills.yml) resolves the latest tag of each upstream skill via `gh release view`, compares against the currently vendored version, and — if either is behind — force-resets the `feature__vendored_skills_refresh` branch from `main`, runs `npx --yes skills add @ --skill --force -y`, asserts the vendored `SKILL.md` version matches the tag (invariant), commits `chore: refresh vendored skills to (…)`, and opens (or updates) a PR against `main`. +2. **Daily 17:00 UTC** — [`check_and_merge_vendored_skills_pr.yml`](.github/workflows/check_and_merge_vendored_skills_pr.yml) inspects that PR; if GitHub reports `mergeable_state == "clean"` (all required CI green, no conflicts), it auto-merges via `gh pr merge --merge`. Otherwise it leaves the PR open for a human to review. +3. **Merge** — because `release_and_publish.yml` fires on `pull_request: closed && merged == true`, the merge directly bumps `package.json`, cuts a new `vX.Y.Z` tag, and publishes a GitHub Release. **A new upstream skill release becomes a new website release**, unattended. + +Two benefits: **(1)** the vendored copies on `main` never lag more than ~24h behind the upstream latest, and every website release carries an accurate skill snapshot; **(2)** any upstream release that fails to install cleanly through `npx skills` fails **inside the check workflow**, loudly — the PR never opens for a broken release, so downstream consumers never see it either. + +**Do not edit files under `.agents/skills/deepworkplan/` or `.agents/skills/dailybot/` by hand.** Contribute upstream, wait for the next upstream release, and let this flow pull it in. Manual refresh (pinning an older tag, previewing a pre-release) is available via `workflow_dispatch` inputs `deepworkplan_tag` and `dailybot_tag` on the check workflow. Opting out of an individual auto-merge: close the PR (or push extra commits so it goes non-clean) before 17:00 UTC. + ## Quick Commands ```bash