diff --git a/.changeset/widen-changelog-gate-template-paths.md b/.changeset/widen-changelog-gate-template-paths.md new file mode 100644 index 000000000..31563a7e3 --- /dev/null +++ b/.changeset/widen-changelog-gate-template-paths.md @@ -0,0 +1,7 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Widen changelog-gate coverage for template and scaffolding paths + +Previously, the changelog gate only required a changeset for `packages/squad-(sdk|cli)/src/` changes, so template and scaffolding updates under `packages/squad-(sdk|cli)/templates/`, `.squad-templates/`, top-level `templates/`, and agent charters under `.squad/agents/*/charter.md` could reach users with no release-note entry. This widens the gate to those paths so user-facing template and scaffolding changes are no longer silently omitted from release notes. Closes #1156. diff --git a/.github/workflows/squad-ci.yml b/.github/workflows/squad-ci.yml index 569e498cd..2a9b6aa70 100644 --- a/.github/workflows/squad-ci.yml +++ b/.github/workflows/squad-ci.yml @@ -219,12 +219,18 @@ jobs: BASE="${{ github.event.pull_request.base.sha }}" HEAD="${{ github.event.pull_request.head.sha }}" CHANGED=$(git diff --name-only "$BASE"..."$HEAD") - SDK_CLI_CHANGED=$(echo "$CHANGED" | grep -E '^packages/squad-(sdk|cli)/src/' || true) + # Paths whose changes ship behavioral or scaffolding updates and therefore + # require a changeset (or CHANGELOG update). The top-level templates/ tree is + # the primary sync target of .squad-templates/ (see scripts/sync-templates.mjs) — + # an append-only superset whose entire tree is governed squad scaffolding, not + # unrelated content. Agent charters under .squad/agents/ are likewise governed. + SDK_CLI_PATH_REGEX='^packages/squad-(sdk|cli)/src/|^packages/squad-(sdk|cli)/templates/|^\.squad-templates/|^templates/|^\.squad/agents/.+/charter\.md$' + SDK_CLI_CHANGED=$(echo "$CHANGED" | grep -E "$SDK_CLI_PATH_REGEX" || true) if [ -z "$SDK_CLI_CHANGED" ]; then - echo "✅ Not applicable (no SDK/CLI source changes)" >> $GITHUB_STEP_SUMMARY + echo "✅ Not applicable (no SDK/CLI source or template changes)" >> $GITHUB_STEP_SUMMARY exit 0 fi - echo "SDK/CLI source files changed:" + echo "SDK/CLI source or template files changed:" echo "$SDK_CLI_CHANGED" CHANGESET_ADDED=$(git diff --diff-filter=AM --name-only "$BASE"..."$HEAD" | grep -E '^\.changeset/[^/]+\.md$' | grep -vxF '.changeset/README.md' || true) CHANGELOG_CHANGED=$(echo "$CHANGED" | grep -E '^CHANGELOG\.md$' || true) @@ -236,7 +242,7 @@ jobs: echo "✅ CHANGELOG.md updated" >> $GITHUB_STEP_SUMMARY exit 0 fi - echo "::error::No changeset or CHANGELOG.md update found, but SDK/CLI source files were changed." + echo "::error::No changeset or CHANGELOG.md update found, but SDK/CLI source or template files were changed." echo "::error::Run 'npx changeset add' or edit CHANGELOG.md. Escape hatch: add 'skip-changelog' label." echo "❌ No changeset or CHANGELOG.md update" >> $GITHUB_STEP_SUMMARY exit 1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d2c5548c..8c0eae1c2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -187,7 +187,7 @@ External contributors don't have write access, so the review-to-merge flow has a **Your side (contributor):** -1. ✅ All required CI checks are green (build, test, lint; changeset/CHANGELOG gate only applies when `packages/squad-cli/src/` or `packages/squad-sdk/src/` files change) +1. ✅ All required CI checks are green (build, test, lint; the changeset/CHANGELOG gate applies when `packages/squad-(sdk|cli)/src/` **or** governed template/scaffolding paths change — `packages/squad-(sdk|cli)/templates/`, `.squad-templates/`, top-level `templates/`, and `.squad/agents/*/charter.md`) 2. ✅ PR is no longer a draft — mark as **"Ready for review"** 3. ✅ Copilot reviewer bot posts its review automatically 4. ✅ Review Copilot's suggestions and manually apply any you agree with in your fork @@ -214,7 +214,7 @@ An automated readiness check runs on every PR and posts a checklist comment. Add | **Not in draft** | Mark your PR as "Ready for review" when it's done | | **Branch up to date** | Rebase on latest `dev` (`git fetch upstream && git rebase upstream/dev`) | | **Copilot review** | Wait for the Copilot reviewer bot to post its review | -| **Changeset present** | Run `npx changeset add` if you changed files in `packages/squad-sdk/src/` or `packages/squad-cli/src/` | +| **Changeset present** | Run `npx changeset add` if you changed `packages/squad-(sdk|cli)/src/` or governed template/scaffolding paths (`packages/squad-(sdk|cli)/templates/`, `.squad-templates/`, top-level `templates/`, `.squad/agents/*/charter.md`) | | **No merge conflicts** | Resolve any conflicts with the target branch | | **CI passing** | All CI checks (build, test, lint) must be green | @@ -277,7 +277,7 @@ npm unlink -w packages/squad-cli Squad uses [@changesets/cli](https://github.com/changesets/changesets) for independent package versioning. -**When your PR changes SDK or CLI source files** (`packages/squad-sdk/src/` or `packages/squad-cli/src/`), add a changeset file instead of editing `CHANGELOG.md` directly. Changesets prevent merge conflicts when multiple PRs are open simultaneously and are the preferred workflow. +**When your PR changes SDK or CLI source files** (`packages/squad-sdk/src/` or `packages/squad-cli/src/`) **or governed template/scaffolding paths** (`packages/squad-(sdk|cli)/templates/`, `.squad-templates/`, top-level `templates/`, or `.squad/agents/*/charter.md`), add a changeset file instead of editing `CHANGELOG.md` directly. Changesets prevent merge conflicts when multiple PRs are open simultaneously and are the preferred workflow. ### Adding a Changeset @@ -321,7 +321,7 @@ Add streaming support to agent orchestration. Update CLI to display stream progr ### CI Changelog Gate -The `changelog-gate` CI check enforces that PRs touching SDK/CLI source files include either: +The `changelog-gate` CI check enforces that PRs touching SDK/CLI source files — or governed template/scaffolding paths (`packages/squad-(sdk|cli)/templates/`, `.squad-templates/`, top-level `templates/`, `.squad/agents/*/charter.md`) — include either: - A `.changeset/*.md` file (preferred), **or** - A direct `CHANGELOG.md` edit (backward-compatible) diff --git a/test/ci/changelog-gate.test.ts b/test/ci/changelog-gate.test.ts new file mode 100644 index 000000000..59d96e559 --- /dev/null +++ b/test/ci/changelog-gate.test.ts @@ -0,0 +1,63 @@ +/** + * Reads the live SDK_CLI_PATH_REGEX from the changelog gate workflow so this + * suite stays in sync with CI path matching. Guards issue #1156 against + * regressions in governed source/template paths. + */ + +import { describe, it, expect, beforeAll } from 'vitest'; +import { readFileSync } from 'node:fs'; +import path, { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const workflowPath = path.resolve(__dirname, '..', '..', '.github', 'workflows', 'squad-ci.yml'); +const workflow = readFileSync(workflowPath, 'utf-8'); +const patternMatch = workflow.match(/SDK_CLI_PATH_REGEX='([^']+)'/); + +let regex: RegExp; + +beforeAll(() => { + expect( + patternMatch, + 'Expected changelog gate SDK_CLI_PATH_REGEX to be defined in squad-ci.yml', + ).not.toBeNull(); + regex = new RegExp(patternMatch?.[1] ?? ''); +}); + +const governedPaths = [ + 'packages/squad-sdk/src/index.ts', + 'packages/squad-cli/src/cli/core/bootstrap.ts', + 'packages/squad-sdk/templates/skills/example/SKILL.md', + 'packages/squad-cli/templates/scaffold/agent.md', + '.squad-templates/squad.agent.md', + '.squad-templates/scribe-charter.md', + 'templates/skills/release-process/SKILL.md', + 'templates/casting/policy.json', + '.squad/agents/troi/charter.md', +]; + +const unrelatedPaths = [ + 'README.md', + 'docs/guide/intro.md', + '.github/workflows/squad-ci.yml', + 'package.json', + 'packages/squad-cli/package.json', + 'scripts/bump-build.mjs', + 'test/ci/changelog-gate.test.ts', + '.squad/agents/troi/notes.md', + '.squad/routing.md', +]; + +describe('changelog gate path matching', () => { + describe('governed paths require a changeset', () => { + it.each(governedPaths)('%s', filePath => { + expect(regex.test(filePath)).toBe(true); + }); + }); + + describe('unrelated paths are not gated', () => { + it.each(unrelatedPaths)('%s', filePath => { + expect(regex.test(filePath)).toBe(false); + }); + }); +});