diff --git a/.github/workflows/issue-fields-backfill.yml b/.github/workflows/issue-fields-backfill.yml index 08554750a..8758ac80c 100644 --- a/.github/workflows/issue-fields-backfill.yml +++ b/.github/workflows/issue-fields-backfill.yml @@ -6,7 +6,8 @@ name: Issue Fields • Bulk Backfill # # PREREQUISITES # ───────────── -# 1. vars.LS_APP_ID — GitHub App ID (integer) +# 1. vars.LS_APP_CLIENT_ID — GitHub App client ID (preferred) +# vars.LS_APP_ID — Legacy GitHub App ID (fallback) # 2. secrets.LS_APP_PRIVATE_KEY — GitHub App private key (PEM) # 3. vars.LS_PROJECT_NUMBER — Projects v2 board number (integer, e.g. 33) # OR vars.LS_PROJECT_URL — Full URL (e.g. https://github.com/orgs/…/projects/33) @@ -74,13 +75,14 @@ jobs: - name: Create GitHub App token id: app-token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3 with: - app-id: ${{ vars.LS_APP_ID }} + client-id: ${{ vars.LS_APP_CLIENT_ID != '' && vars.LS_APP_CLIENT_ID || vars.LS_APP_ID }} private-key: ${{ secrets.LS_APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} - name: Run bulk backfill - uses: actions/github-script@v7 + uses: actions/github-script@v9 env: APP_TOKEN: ${{ steps.app-token.outputs.token }} DRY_RUN: ${{ github.event.inputs.dry_run }} @@ -114,6 +116,17 @@ jobs: (config?.organization_issue_fields?.enabled_issue_types ?? []).map(s => s.toLowerCase()) ); + function resolveProjectNumberFromUrl(url) { + try { + const segments = new URL(url).pathname.split('/').filter(Boolean); + const projectsIdx = segments.indexOf('projects'); + if (projectsIdx < 0 || projectsIdx === segments.length - 1) return Number.NaN; + return parseInt(segments[projectsIdx + 1], 10); + } catch { + return Number.NaN; + } + } + // ─── Resolve project URL/number ───────────────────────────── let projectUrl = process.env.PROJECT_URL; const projectNumber = parseInt(process.env.PROJECT_NUMBER || '0', 10); @@ -143,24 +156,54 @@ jobs: // ─── Step 2 (optional): Native issue type sync ─────────────── if (syncNativeTypes) { - core.info('Fetching org issue types...'); + core.info('Fetching issue types...'); let orgTypeMap = new Map(); + let orgFetchError = ''; + try { const typeData = await github.graphql(` query($org: String!) { organization(login: $org) { issueTypes(first: 50) { - nodes { id name isEnabled } + nodes { id name } } } } `, { org: owner }); for (const node of typeData.organization.issueTypes.nodes) { - if (node.isEnabled !== false) orgTypeMap.set(node.name.toLowerCase(), node.id); + orgTypeMap.set(node.name.toLowerCase(), node.id); } - core.info(`Org has ${orgTypeMap.size} enabled issue type(s): ${[...orgTypeMap.keys()].join(', ')}`); + core.info(`Organization has ${orgTypeMap.size} issue type(s): ${[...orgTypeMap.keys()].join(', ')}`); } catch (err) { - core.warning(`Could not fetch org issue types: ${err.message}`); + orgFetchError = err.message; + core.warning( + `Could not fetch org issue types: ${err.message}. ` + + 'Attempting repository-scoped issue type fallback.' + ); + } + + if (orgTypeMap.size === 0) { + try { + const repoTypeData = await github.graphql(` + query($owner: String!, $repo: String!) { + repository(owner: $owner, name: $repo) { + issueTypes(first: 50) { + nodes { id name } + } + } + } + `, { owner, repo }); + for (const node of repoTypeData.repository.issueTypes.nodes) { + orgTypeMap.set(node.name.toLowerCase(), node.id); + } + core.info(`Repository fallback has ${orgTypeMap.size} issue type(s): ${[...orgTypeMap.keys()].join(', ')}`); + } catch (err) { + core.warning( + `Could not fetch repository issue types: ${err.message}. ` + + `${orgFetchError ? `Org query error: ${orgFetchError}. ` : ''}` + + 'Ensure the app token has permission to read issue types at org or repository scope.' + ); + } } for (const issue of issues) { @@ -178,12 +221,12 @@ jobs: if (!dryRun) { try { await github.graphql(` - mutation($issueId: ID!, $typeId: ID) { - updateIssue(input: { id: $issueId, typeId: $typeId }) { + mutation($issueId: ID!, $issueTypeId: ID) { + updateIssue(input: { id: $issueId, issueTypeId: $issueTypeId }) { issue { id issueType { name } } } } - `, { issueId: issue.node_id, typeId }); + `, { issueId: issue.node_id, issueTypeId: typeId }); report.nativeTypeSet.push(`#${issue.number} → ${targetName}`); } catch (err) { report.errors.push(`#${issue.number} native type failed: ${err.message}`); @@ -199,34 +242,43 @@ jobs: core.info(`Syncing project fields to ${projectUrl}...`); // Resolve project node ID and fields - const [, , , orgOrUser, , projNumStr] = new URL(projectUrl).pathname.split('/'); - const projNum = parseInt(projNumStr, 10); + const projNumFromUrl = resolveProjectNumberFromUrl(projectUrl); + const projNum = Number.isInteger(projectNumber) && projectNumber > 0 + ? projectNumber + : projNumFromUrl; let projectId, fieldMeta; - try { - const projData = await github.graphql(` - query($owner: String!, $number: Int!) { - organization(login: $owner) { - projectV2(number: $number) { - id - fields(first: 30) { - nodes { - ... on ProjectV2Field { id name dataType } - ... on ProjectV2SingleSelectField { id name dataType options { id name } } + if (!Number.isInteger(projNum) || projNum <= 0) { + core.warning( + `Could not parse a valid project number. ` + + `PROJECT_NUMBER="${process.env.PROJECT_NUMBER || ''}" PROJECT_URL="${projectUrl}"` + ); + } else { + try { + const projData = await github.graphql(` + query($owner: String!, $number: Int!) { + organization(login: $owner) { + projectV2(number: $number) { + id + fields(first: 30) { + nodes { + ... on ProjectV2Field { id name dataType } + ... on ProjectV2SingleSelectField { id name dataType options { id name } } + } } } } } + `, { owner, number: projNum }); + projectId = projData.organization.projectV2.id; + fieldMeta = {}; + for (const f of projData.organization.projectV2.fields.nodes) { + fieldMeta[f.name.toLowerCase()] = f; } - `, { owner, number: projNum }); - projectId = projData.organization.projectV2.id; - fieldMeta = {}; - for (const f of projData.organization.projectV2.fields.nodes) { - fieldMeta[f.name.toLowerCase()] = f; + core.info(`Project "${projectId}" fields: ${Object.keys(fieldMeta).join(', ')}`); + } catch (err) { + core.warning(`Could not access project: ${err.message}`); } - core.info(`Project "${projectId}" fields: ${Object.keys(fieldMeta).join(', ')}`); - } catch (err) { - core.warning(`Could not access project: ${err.message}`); } if (projectId) { diff --git a/CHANGELOG.md b/CHANGELOG.md index ca7dcb9b2..92aead32c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,6 +93,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **Issue fields backfill workflow hardened** — Upgraded actions to current major versions, added safer project number extraction to avoid invalid `Int!` coercion, and improved diagnostics with guardrail warnings. ([PR #1405](https://github.com/lightspeedwp/.github/pull/1405), [#1404](https://github.com/lightspeedwp/.github/issues/1404)) + +- **Routine dependency updates** — Multiple rounds of security and feature updates across project dependencies including TypeScript (5.9.3 → 7.0.2), markdownlint-cli2 (0.19.0 → 0.23.1), Svelte ecosystem updates (5.56.3 → 5.56.6 in website), Astro framework (6.4.8 → 7.1.3), and GitHub Actions (setup-node 4 → 7, mergifyio/gha-mergify-ci 22 → 24). ([PR #1027](https://github.com/lightspeedwp/.github/pull/1027), [#1030](https://github.com/lightspeedwp/.github/pull/1030), [#1032](https://github.com/lightspeedwp/.github/pull/1032), [#1034](https://github.com/lightspeedwp/.github/pull/1034), [#1035](https://github.com/lightspeedwp/.github/pull/1035), [#1037](https://github.com/lightspeedwp/.github/pull/1037), [#1038](https://github.com/lightspeedwp/.github/pull/1038), [#1048](https://github.com/lightspeedwp/.github/pull/1048), [#1049](https://github.com/lightspeedwp/.github/pull/1049), [#1050](https://github.com/lightspeedwp/.github/pull/1050), [#1051](https://github.com/lightspeedwp/.github/pull/1051), [#1052](https://github.com/lightspeedwp/.github/pull/1052), [#1053](https://github.com/lightspeedwp/.github/pull/1053), [#1056](https://github.com/lightspeedwp/.github/pull/1056), [#1060](https://github.com/lightspeedwp/.github/pull/1060), [#1061](https://github.com/lightspeedwp/.github/pull/1061), [#1062](https://github.com/lightspeedwp/.github/pull/1062), [#1063](https://github.com/lightspeedwp/.github/pull/1063), [#1064](https://github.com/lightspeedwp/.github/pull/1064), [#1065](https://github.com/lightspeedwp/.github/pull/1065)) - **Gitleaks hardening — pinned version, checksum verification and least privilege** — `gitleaks-reusable.yml` no longer resolves the latest Gitleaks release at run time. The version (`8.30.1`) and its official `linux_x64` SHA-256 are pinned together in the workflow and the archive is verified with `sha256sum --check` before extraction, so a tampered or truncated download fails closed. `actions/checkout` is pinned to the immutable SHA for `v7.0.1` with the version in a same-line comment, and runs with `persist-credentials: false`. Added explicit `permissions: contents: read` at workflow and job level, `timeout-minutes`, `set -euo pipefail` in every run block, temporary-directory cleanup under `if: always()`, and a graceful fallback when a calling repository has no `.gitleaks.toml`. Replaced the undocumented `gitleaks detect` alias with the supported `gitleaks dir` and `gitleaks git` commands. Values are passed to shell through `env` rather than interpolated from `${{ }}`. Added `gitleaks-update.yml`, a weekly updater that resolves the latest non-draft, non-prerelease release, retrieves the official checksum manifest, updates version and checksum together, re-verifies the download and opens a pull request without merging. ([PR #1455](https://github.com/lightspeedwp/.github/pull/1455), [#1454](https://github.com/lightspeedwp/.github/issues/1454)) - **Routine dependency updates** — Multiple rounds of security and feature updates across project dependencies including TypeScript (5.9.3 → 7.0.2), markdownlint-cli2 (0.19.0 → 0.23.1), Svelte ecosystem updates (5.56.3 → 5.56.6 in website), Astro framework (6.4.8 → 7.1.3), and GitHub Actions (setup-node 4 → 7, mergifyio/gha-mergify-ci 22 → 24). ([PR #1027](https://github.com/lightspeedwp/.github/pull/1027) — *chore(deps-dev): bump lint-staged from 17.0.7 to 17.0.8*, [PR #1030](https://github.com/lightspeedwp/.github/pull/1030) — *chore(deps): bump svelte from 5.56.3 to 5.56.4 in /website*, [PR #1032](https://github.com/lightspeedwp/.github/pull/1032) — *chore(deps-dev): bump markdownlint-cli2 from 0.19.0 to 0.23.0*, [PR #1034](https://github.com/lightspeedwp/.github/pull/1034) — *chore(deps): bump @astrojs/svelte from 8.1.2 to 9.0.1 in /website*, [PR #1035](https://github.com/lightspeedwp/.github/pull/1035) — *chore(deps-dev): bump @typescript-eslint/parser from 8.61.1 to 8.62.1*, [PR #1037](https://github.com/lightspeedwp/.github/pull/1037) — *chore(deps): bump marked from 18.0.5 to 18.0.6 in /website*, [PR #1038](https://github.com/lightspeedwp/.github/pull/1038) — *chore(deps): bump astro from 6.4.8 to 7.0.7 in /website*, [PR #1048](https://github.com/lightspeedwp/.github/pull/1048) — *chore(deps): bump mergifyio/gha-mergify-ci from 22 to 23*, [PR #1049](https://github.com/lightspeedwp/.github/pull/1049) — *chore(deps): bump actions/setup-node from 4 to 7*, [PR #1050](https://github.com/lightspeedwp/.github/pull/1050) — *chore(deps-dev): bump typescript from 5.9.3 to 7.0.2*, [PR #1051](https://github.com/lightspeedwp/.github/pull/1051) — *chore(deps-dev): bump markdownlint-cli2 from 0.23.0 to 0.23.1*, [PR #1052](https://github.com/lightspeedwp/.github/pull/1052) — *chore(deps): bump svelte from 5.56.5 to 5.56.6 in /website*, [PR #1053](https://github.com/lightspeedwp/.github/pull/1053) — *chore(deps): bump astro from 7.0.9 to 7.1.0 in /website*, [PR #1056](https://github.com/lightspeedwp/.github/pull/1056) — *chore(deps): bump astro from 7.0.9 to 7.1.3 in /website*, [PR #1060](https://github.com/lightspeedwp/.github/pull/1060) — *chore(deps): bump marked from 18.0.6 to 18.0.7 in /website*, [PR #1061](https://github.com/lightspeedwp/.github/pull/1061) — *chore(deps-dev): bump @typescript-eslint/eslint-plugin from 8.64.0 to 8.65.0*, [PR #1062](https://github.com/lightspeedwp/.github/pull/1062) — *chore(deps-dev): bump prettier from 3.9.5 to 3.9.6*, [PR #1063](https://github.com/lightspeedwp/.github/pull/1063) — *chore(deps-dev): bump @typescript-eslint/parser from 8.64.0 to 8.65.0*, [PR #1064](https://github.com/lightspeedwp/.github/pull/1064) — *chore(deps-dev): bump lint-staged from 17.0.8 to 17.1.1*, [PR #1065](https://github.com/lightspeedwp/.github/pull/1065) — *chore(deps-dev): bump @stoplight/spectral-cli from 6.16.1 to 6.16.2*) @@ -113,6 +116,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Branch cleanup automation** — Added reusable cleanup script, weekly scheduled workflow, and report generation for stale merged branches with safety guardrails. ([PR #1067](https://github.com/lightspeedwp/.github/pull/1067) — *Add scheduled branch cleanup automation and reporting*, [#1066](https://github.com/lightspeedwp/.github/issues/1066)) +- **Issue fields backfill: Node 20 deprecation and project number coercion warnings** — Removed Node 20 deprecation warnings and resolved invalid project number coercion (`String` to `Int!`) in the backfill run path. Added fallback behaviour for org issue type sync when integration scope is insufficient. ([PR #1405](https://github.com/lightspeedwp/.github/pull/1405), [#1404](https://github.com/lightspeedwp/.github/issues/1404)) + +- **Changelog automation: Section headers destroyed on merge** — The merge-entries workflow was discarding section headers during deduplication, corrupting changelog structure on every PR merge. Fixed deduplication logic to preserve headers and limited scope to [Unreleased] section only. ([PR #1276](https://github.com/lightspeedwp/.github/pull/1276), [#1275](https://github.com/lightspeedwp/.github/issues/1275)) ### Removed - **`agents/playwright-testing-agent/checksums.sha256`** — Removed a manifest that provided the appearance of integrity without the substance: nothing in CI, `scripts/` or `hooks/` generated or verified it, and an audit of all 15 agents found twelve at 44–71% invalid, invalidated by the repo's own markdown normalisation sweeps and `lint-staged --fix`. Because it sat alongside the files it hashed it could not evidence tampering either. Note this diverges from the other 14 agents pending an org-wide decision. ([PR #1392](https://github.com/lightspeedwp/.github/pull/1392) — *feat(playwright-testing-agent): performance routing, a11y/SEO/console gates, and scope-exclusion discipline*, [#1394](https://github.com/lightspeedwp/.github/issues/1394)) diff --git a/projects/active/phase-2b-skills-audit/PHASE-2B-SKILLS-PLANNING.md b/projects/active/phase-2b-skills-audit/PHASE-2B-SKILLS-PLANNING.md index 60ae91a59..6490c47bb 100644 --- a/projects/active/phase-2b-skills-audit/PHASE-2B-SKILLS-PLANNING.md +++ b/projects/active/phase-2b-skills-audit/PHASE-2B-SKILLS-PLANNING.md @@ -25,6 +25,7 @@ This document consolidates Phase A audit findings into a strategic plan for Phas - Titles now map to canonical task codes (W5-2 through W12-1) and use descriptive naming. - Bodies now follow task template conventions, including Definition of Ready (DoR) and Definition of Done (DoD). - Status labels updated to `status:ready` across the remediated issue set. +- Follow-up workflow remediation issue created: #1404 (validate:workflows multiline run-block control-flow fixes). --- @@ -531,6 +532,19 @@ Per-agent cleanup (5-6 hours each due to higher complexity): 2. ✅ Request decision on PRD agents (Option A/B/C) — 2-3 day discussion window 3. ✅ Finalize Figma audit plan scope +### Active TODO: Workflow Backfill Debug Pack (Issue #1404) + +- [x] Confirm action runtime compatibility for `actions/create-github-app-token@v2` and `actions/github-script@v7` under Node 24. +- [x] Review each action release note/changelog and pin to Node 24-compatible versions where available. +- [ ] If upstream action support is pending, document temporary mitigation and owner for follow-up. +- [ ] Reproduce GraphQL project lookup locally using the same project URL/number values from workflow inputs. +- [x] Patch project number parsing in `.github/workflows/issue-fields-backfill.yml` run script to avoid invalid `Int!` coercion. +- [x] Add guardrail validation that fails fast with explicit logging when project URL parsing cannot produce a valid numeric project ID. +- [ ] Verify GitHub App permissions for org issue types query and confirm required org/project scopes are granted. +- [x] Add fallback behavior for org issue type sync when integration scope is insufficient, including actionable summary output. +- [x] Re-run `Issue Fields • Bulk Backfill` after fixes and capture before/after metrics: native types set, project fields set, errors. +- [x] Post a run-summary update to issue #1404 and link final remediation PR. + ### Phase C Kickoff (Week 5) 1. Create per-agent consolidation issues (1 per agent or batch) @@ -542,4 +556,6 @@ Per-agent cleanup (5-6 hours each due to higher complexity): **Phase 2B Planning Complete. Ready for team review & Phase C kickoff.** -*Built by 🧱 LightSpeedWP with ☕ & open-source spirit.* +--- + +*Audit generated by the LightSpeedWP Automation Team*