diff --git a/.github/workflows/squad-mark-released.yml b/.github/workflows/squad-mark-released.yml new file mode 100644 index 00000000..ce6bea27 --- /dev/null +++ b/.github/workflows/squad-mark-released.yml @@ -0,0 +1,95 @@ +name: Squad Mark Released + +on: + release: + types: [published, released] + +permissions: + repository-projects: write + +env: + PROJECT_ID: PVT_kwHOA5k0b84BVFTy + STATUS_FIELD_ID: PVTSSF_lAHOA5k0b84BVFTyzhQjgPk + DONE_OPTION_ID: "98236657" + RELEASED_OPTION_ID: "8e246b27" + +jobs: + mark-released: + # Skip pre-releases (insider builds) and drafts — only real releases move items + if: ${{ !github.event.release.prerelease && !github.event.release.draft }} + runs-on: ubuntu-latest + + steps: + - name: Move Done → Released on project board + uses: actions/github-script@v9 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const PROJECT_ID = process.env.PROJECT_ID; + const STATUS_FIELD_ID = process.env.STATUS_FIELD_ID; + const DONE_OPTION_ID = process.env.DONE_OPTION_ID; + const RELEASED_OPTION_ID = process.env.RELEASED_OPTION_ID; + + let cursor = null; + let moved = 0; + + do { + const result = await github.graphql(` + query($projectId: ID!, $cursor: String) { + node(id: $projectId) { + ... on ProjectV2 { + items(first: 100, after: $cursor) { + pageInfo { hasNextPage endCursor } + nodes { + id + fieldValues(first: 50) { + nodes { + ... on ProjectV2ItemFieldSingleSelectValue { + optionId + field { + ... on ProjectV2SingleSelectField { id } + } + } + } + } + } + } + } + } + } + `, { projectId: PROJECT_ID, cursor }); + + const items = result.node.items; + cursor = items.pageInfo.hasNextPage ? items.pageInfo.endCursor : null; + + for (const item of items.nodes) { + // Match by field ID (not name) to avoid brittleness on renames + const isDone = item.fieldValues.nodes.some( + fv => fv.field?.id === STATUS_FIELD_ID && fv.optionId === DONE_OPTION_ID + ); + if (!isDone) continue; + + await github.graphql(` + mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { + updateProjectV2ItemFieldValue(input: { + projectId: $projectId + itemId: $itemId + fieldId: $fieldId + value: { singleSelectOptionId: $optionId } + }) { + projectV2Item { id } + } + } + `, { + projectId: PROJECT_ID, + itemId: item.id, + fieldId: STATUS_FIELD_ID, + optionId: RELEASED_OPTION_ID, + }); + + moved++; + core.info(`✅ Item ${item.id} → Released`); + } + } while (cursor); + + core.notice(`🎉 Moved ${moved} item(s) from Done → Released for ${context.payload.release.tag_name}`); diff --git a/.github/workflows/squad-preview.yml b/.github/workflows/squad-preview.yml index 8b4b06ba..0f0e7876 100644 --- a/.github/workflows/squad-preview.yml +++ b/.github/workflows/squad-preview.yml @@ -26,4 +26,7 @@ jobs: run: dotnet build MyBlog.slnx --configuration Release --no-restore -p:TreatWarningsAsErrors=true - name: Run unit tests - run: dotnet test MyBlog.slnx --configuration Release --no-build --verbosity normal + run: | + dotnet test tests/Architecture.Tests --configuration Release --no-build --verbosity normal + dotnet test tests/Web.Tests --configuration Release --no-build --verbosity normal + dotnet test tests/Web.Tests.Integration --configuration Release --no-build --verbosity normal diff --git a/.squad/agents/aragorn/history.md b/.squad/agents/aragorn/history.md index e1000f42..b04c9889 100644 --- a/.squad/agents/aragorn/history.md +++ b/.squad/agents/aragorn/history.md @@ -441,3 +441,4 @@ Triaged Issue #18 ("Branch clean-up" / orphan local-repo changes) against draft **Tag push gate exception:** The pre-push hook blocks direct `dev` branch pushes but cannot distinguish a tag push from a branch push. Tag pushes for releases require `--no-verify` since they target a specific commit SHA (not advancing a branch), making the branch-protection check semantically inapplicable. This is documented here for future release operators. **Release ownership (per Decision #13):** Aragorn validates scope and approves the release contents; Boromir owns operational CI/CD execution. For sprint releases where CI is already confirmed green, Aragorn may proceed directly without a separate Boromir handoff. +