diff --git a/.github/issue-fields.yml b/.github/issue-fields.yml index 4bb279301..b6aa755c3 100644 --- a/.github/issue-fields.yml +++ b/.github/issue-fields.yml @@ -26,6 +26,7 @@ project_field_mappings: Status: status:needs-triage: Triage status:needs-planning: Triage + status:needs-more-info: Triage status:ready: Ready status:in-progress: In progress status:needs-review: In review diff --git a/.github/labels.yml b/.github/labels.yml index fbe52bc22..c3c004982 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -5,265 +5,265 @@ # --- Status --- - name: status:needs-planning - color: BFD4F2 + color: 0F448A description: "Awaiting planning / scoping" - name: status:needs-triage - color: BFD4F2 + color: 0F448A description: "Needs triage" - name: status:ready - color: 0E8A16 + color: 1D7232 description: "Groomed and ready to start" - name: status:in-progress - color: 1D76DB + color: 0F448A description: "Work in progress" - name: status:on-hold - color: F9D0C4 + color: 883D07 description: "Work on hold" - name: status:needs-design - color: C5DEF5 + color: 0F448A description: "Awaiting design input" - name: status:needs-design-review - color: D4C5F9 + color: 4D1A93 description: "Awaiting design review" - name: status:needs-figma-update - color: C5DEF5 + color: 0F448A description: "Existing Figma design needs updating" - name: status:needs-dev - color: C5DEF5 + color: 0F448A description: "Awaiting engineering implementation" - name: status:needs-review - color: BFD4F2 + color: 0F448A description: "Awaiting code review" - name: status:needs-qa - color: FBCA04 + color: 7E6007 description: "Quality assurance required" - name: status:needs-testing - color: FEF2C0 + color: 7E6007 description: "Testing needed (manual/automated)" - name: status:needs-audit - color: FEF2C0 + color: 7E6007 description: "Needs audit or validation pass" - name: status:needs-documentation - color: BFD4F2 + color: 0F448A description: "Needs documentation update" - name: status:in-discussion - color: BFD4F2 + color: 0F448A description: "Needs alignment/decision" - name: status:needs-more-info - color: BFD4F2 + color: 0F448A description: "Missing details to proceed" - name: status:blocked - color: E99695 + color: "810E18" description: "Blocked by dependency" - name: status:duplicate - color: E99695 + color: "810E18" description: "Duplicate of another issue" - name: status:wontfix - color: E1E4E8 + color: 4E575F description: "Not planned to address" - name: status:done - color: 0E8A16 + color: 1D7232 description: "Completed" # --- Priority --- - name: priority:critical - color: B60205 + color: "810E18" description: "Production/launch-blocking" - name: priority:important - color: D93F0B + color: 883D07 description: "Must-do high priority" - name: priority:normal - color: 0052CC + color: 0F448A description: "Default priority" - name: priority:minor - color: C2E0C6 + color: 1D7232 description: "Low priority / nice to have" # --- Types (Issue Types) --- - name: type:task - color: 4393F8 + color: 0F448A description: "Task or to-do" - name: type:bug - color: 9F3734 + color: "810E18" description: "Bug or defect" aliases: - bug - name: type:feature - color: 3FB950 + color: 1D7232 description: "Feature or enhancement" - name: type:design - color: AB7DF8 + color: 4D1A93 description: "Design work" - name: type:ui - color: AB7DF8 + color: 4D1A93 description: "UI implementation and consistency" - name: type:epic - color: AB7DF8 + color: 4D1A93 description: "Large multi-scope initiative" - name: type:story - color: 4393F8 + color: 0F448A description: "User story" - name: type:improve - color: 9198A1 + color: 4E575F description: "Improvement to existing behaviour/UX" - name: type:enhancement - color: 9198A1 + color: 4E575F description: "Enhancement to existing capability" - name: type:refactor - color: 9198A1 + color: 4E575F description: "Refactor or internal change" - name: type:build - color: 4393F8 + color: 0F448A description: "Build & CI" - name: type:ci - color: 4393F8 + color: 0F448A description: "CI/CD pipeline work" - name: type:automation - color: 4393F8 + color: 0F448A description: "Automation" aliases: - automation - name: type:test - color: D29922 + color: 7E6007 description: "Testing/coverage" aliases: - test - name: type:qa - color: D29922 + color: 7E6007 description: "Quality assurance work" - name: type:performance - color: D29922 + color: 7E6007 description: "Performance improvement" - name: type:a11y - color: DB61A2 + color: 4D1A93 description: "Accessibility" aliases: - a11y - name: type:security - color: 9F3734 + color: "810E18" description: "Security issue" aliases: - security - name: type:compatibility - color: 8D4821 + color: 883D07 description: "Compatibility" - name: type:dependency - color: 8D4821 + color: 883D07 description: "Dependency management and updates" - name: type:integration - color: 8D4821 + color: 883D07 description: "Integration" - name: type:release - color: 3FB950 + color: 1D7232 description: "Release" - name: type:maintenance - color: 9198A1 + color: 4E575F description: "Maintenance" aliases: - maintenance - name: type:documentation - color: 9198A1 + color: 4E575F description: "Documentation" aliases: - documentation - name: type:research - color: 9198A1 + color: 4E575F description: "Research / investigation" - name: type:investigation - color: 9198A1 + color: 4E575F description: "Investigation and root-cause analysis" - name: type:chore - color: 9198A1 + color: 4E575F description: "Chore / small hygiene change" - name: type:audit - color: 9198A1 + color: 4E575F description: "Audit" aliases: - audit - name: type:review - color: 4393F8 + color: 0F448A description: "Code or design review task" - name: type:ai-ops - color: 4393F8 + color: 0F448A description: "AI Ops" - name: type:content-modelling - color: AB7DF8 + color: 4D1A93 description: "Content Modelling" - name: type:question - color: "5319E7" + color: "4D1A93" description: "Question or request for clarification" aliases: - question - name: type:support - color: 0E8A16 + color: 1D7232 description: "Support request" aliases: - support - name: type:help - color: 0E8A16 + color: 1D7232 description: "Help request" aliases: - help - name: type:ux-feedback - color: "5319E7" + color: "4D1A93" description: "User experience feedback" aliases: - ux-feedback @@ -272,279 +272,279 @@ # --- Meta / housekeeping --- - name: meta:needs-changelog - color: E1E4E8 + color: 4E575F description: "Requires a changelog entry before merge" - name: meta:no-changelog - color: E1E4E8 + color: 4E575F description: "No changelog needed" - name: meta:has-pr - color: E1E4E8 + color: 4E575F description: "Issue has an open linked PR" - name: meta:no-issue-activity - color: E1E4E8 + color: 4E575F description: "No recent issue activity" - name: meta:no-pr-activity - color: E1E4E8 + color: 4E575F description: "No recent PR activity" - name: meta:stale - color: 9198A1 + color: 4E575F description: "Marked as stale for review" # --- Release scope --- - name: release:patch - color: 3FB950 + color: 1D7232 description: "Patch release" - name: release:minor - color: 58A6FF + color: 0F448A description: "Minor release" - name: release:major - color: F85149 + color: "810E18" description: "Major release" - name: release:hotfix - color: D29922 + color: 7E6007 description: "Urgent hotfix outside normal cadence" # --- Area (broader surfaces) --- - name: area:core - color: C5DEF5 + color: 0F448A description: "Core / shared infrastructure" - name: area:labels - color: C5DEF5 + color: 0F448A description: "Label governance and routing" - name: area:block-editor - color: C5DEF5 + color: 0F448A description: "Block editor" - name: area:theme - color: C5DEF5 + color: 0F448A description: "Theme & styles" - name: area:documentation - color: C5DEF5 + color: 0F448A description: "Docs & guides" - name: area:tests - color: D4C5F9 + color: 4D1A93 description: "Test suites & harnesses" - name: area:testing - color: D4C5F9 + color: 4D1A93 description: "Testing and QA" - name: area:quality - color: D4C5F9 + color: 4D1A93 description: "Quality validation and QA controls" - name: area:scripts - color: C5DEF5 + color: 0F448A description: "Scripts & tooling" - name: area:assets - color: C5DEF5 + color: 0F448A description: "Assets (images, fonts, static files)" - name: area:woocommerce - color: D4C5F9 + color: 4D1A93 description: "WooCommerce" - name: area:content - color: C5DEF5 + color: 0F448A description: "Content and copy" - name: area:design-system - color: C5DEF5 + color: 0F448A description: "Design system and tokens" - name: area:navigation - color: C5DEF5 + color: 0F448A description: "Navigation & menus" - name: area:forms - color: C5DEF5 + color: 0F448A description: "Forms and form flows" - name: area:plugins - color: C5DEF5 + color: 0F448A description: "Plugin configuration / logic" - name: area:search - color: C5DEF5 + color: 0F448A description: "Search and filtering" - name: area:seo - color: C2E0C6 + color: 1D7232 description: "Technical SEO (meta, schema, sitemaps)" - name: area:ai - color: C5DEF5 + color: 0F448A description: "AI and automation systems" - name: area:analytics - color: C2E0C6 + color: 1D7232 description: "Analytics & tracking" - name: area:infrastructure - color: 006B75 + color: "147169" description: "Infrastructure / hosting / platform" - name: area:automation - color: BFD4F2 + color: 0F448A description: "Automation workflows and agents" - name: area:performance - color: D29922 + color: 7E6007 description: "Performance-focused work" - name: area:a11y - color: DB61A2 + color: 4D1A93 description: "Accessibility-focused work" - name: area:security - color: 9F3734 + color: "810E18" description: "Security-focused work" - name: area:compatibility - color: 8D4821 + color: 883D07 description: "Compatibility and cross-environment concerns" - name: area:release - color: 3FB950 + color: 1D7232 description: "Release process and readiness" - name: area:maintenance - color: 9198A1 + color: 4E575F description: "Maintenance and routine upkeep" - name: area:i18n - color: C5DEF5 + color: 0F448A description: "Internationalisation" - name: area:ci - color: BFD4F2 + color: 0F448A description: "Build and CI pipelines" aliases: - ci - name: area:deployment - color: 006B75 + color: "147169" description: "Deploy/release operations" - name: area:dependencies - color: F9D0C4 + color: 883D07 description: "Composer/npm dependency work" aliases: - dependencies - name: meta:dependabot-security - color: B60205 + color: "810E18" description: "Dependabot update appears security-related and eligible for guarded automation" - name: area:integration - color: D93F0B + color: 883D07 description: "3rd-party integrations / ecosystem" # --- Components (comp:*) --- - name: comp:block-editor - color: C5DEF5 + color: 0F448A description: "Block/site editor work" - name: comp:block-inserter - color: C5DEF5 + color: 0F448A description: "Inserter UI/behaviour" - name: comp:block-variations - color: C5DEF5 + color: 0F448A description: "Block variations" - name: comp:block-supports - color: C5DEF5 + color: 0F448A description: "Block supports" - name: comp:block-locking - color: C5DEF5 + color: 0F448A description: "Block locking" - name: comp:block-bindings - color: C5DEF5 + color: 0F448A description: "Block bindings" - name: comp:block-templates - color: C5DEF5 + color: 0F448A description: "Block templates / template editor" - name: comp:block-patterns - color: C5DEF5 + color: 0F448A description: "Patterns library/registration" - name: comp:template-parts - color: C5DEF5 + color: 0F448A description: "Template parts (header/footer/loops)" - name: comp:block-json - color: C5DEF5 + color: 0F448A description: "Block metadata (block.json)" - name: comp:theme-json - color: C5DEF5 + color: 0F448A description: "Tokens, presets, settings (theme.json)" - name: comp:wp-admin - color: C5DEF5 + color: 0F448A description: "WP Admin screens" - name: comp:settings - color: C5DEF5 + color: 0F448A description: "Global/settings UX" - name: comp:post-settings - color: C5DEF5 + color: 0F448A description: "Post editor settings panel" - name: comp:style-variations - color: C5DEF5 + color: 0F448A description: "JSON style variations" - name: comp:block-styles - color: C5DEF5 + color: 0F448A description: "Block styles registered via JSON" - name: comp:color-palette - color: C5DEF5 + color: 0F448A description: "Palette tokens and usage" - name: comp:typography - color: C5DEF5 + color: 0F448A description: "Type scale and typography tokens" - name: comp:section-styles - color: C5DEF5 + color: 0F448A description: "Section/background styles" - name: comp:spacing - color: C5DEF5 + color: 0F448A description: "Spacing tokens and layout gaps" # --- Languages / formats --- - name: lang:php - color: C5DEF5 + color: 0F448A description: "PHP" - name: lang:js - color: C5DEF5 + color: 0F448A description: "JavaScript/TypeScript" aliases: - js @@ -552,145 +552,145 @@ - lang:javascript - name: lang:css - color: C5DEF5 + color: 0F448A description: "Stylesheets (CSS/Sass/etc.)" - name: lang:html - color: C5DEF5 + color: 0F448A description: "Markup (HTML)" - name: lang:md - color: C5DEF5 + color: 0F448A description: "Markdown content/docs" - name: lang:json - color: C5DEF5 + color: 0F448A description: "JSON config/content" - name: lang:yaml - color: C5DEF5 + color: 0F448A description: "YAML config" # --- Environments --- - name: env:prototype - color: E1E4E8 + color: 4E575F description: "Prototype/sandbox" - name: env:staging - color: BFD4F2 + color: 0F448A description: "Staging/UAT" - name: env:live - color: 0E8A16 + color: 1D7232 description: "Live/production" # --- Compatibility matrix --- - name: compat:wordpress - color: D93F0B + color: 883D07 description: "WordPress core/Gutenberg compatibility" - name: compat:php - color: D93F0B + color: 883D07 description: "PHP version compatibility" - name: compat:woocommerce - color: D93F0B + color: 883D07 description: "WooCommerce versions" - name: compat:gutenberg - color: D93F0B + color: 883D07 description: "Gutenberg package compatibility" - name: compat:rtl - color: D93F0B + color: 883D07 description: "RTL languages support" - name: compat:multisite - color: F9D0C4 + color: 883D07 description: "Multisite/network considerations" # --- Content types --- - name: cpt:posts - color: C5DEF5 + color: 0F448A description: "WordPress Posts" - name: cpt:pages - color: C5DEF5 + color: 0F448A description: "WordPress Pages" # --- AI Ops --- - name: ai-ops:instructions - color: 0052CC + color: 0F448A description: "AI instruction docs" - name: ai-ops:chat-modes - color: 0052CC + color: 0F448A description: "Prompt sets / chat modes" - name: ai-ops:agents - color: 0052CC + color: 0F448A description: "AI agent definitions" - name: ai-ops:prompts - color: 0052CC + color: 0F448A description: "Reusable prompts" - name: ai-ops:datasets - color: BFD4F2 + color: 0F448A description: "Training/evaluation datasets" - name: ai-ops:evaluations - color: BFD4F2 + color: 0F448A description: "Evaluation results" - name: ai-ops:tools - color: BFD4F2 + color: 0F448A description: "Tool/plugin manifests" # --- Contributor labels --- - name: contrib:good-first-issue - color: D4C5F9 + color: 4D1A93 description: "Good for new contributors" - name: contrib:help-wanted - color: C2E0C6 + color: 1D7232 description: "Help wanted" - name: contrib:discussion - color: C2E0C6 + color: 1D7232 description: "Contributor/community discussion" # --- Discussions (GitHub Discussions categories) --- - name: discussion:announcement - color: FBCA04 + color: 7E6007 description: "Official announcements" - name: discussion:showcase - color: 0E8A16 + color: 1D7232 description: "Show & Tell" - name: discussion:community - color: 6f42c1 + color: 4D1A93 description: "Community/general" - name: discussion:feedback - color: 1d76db + color: 0F448A description: "Feedback/suggestions" - name: discussion:support - color: d73a4a + color: "810E18" description: "Support/troubleshooting" - name: discussion:sponsorship - color: f9d0c4 + color: 883D07 description: "Sponsorship/funding" - name: discussion:partnership - color: bfd4f2 + color: 0F448A description: "Partnership/collaboration" diff --git a/.github/prompts/update-mermaid-diagrams.prompt.md b/.github/prompts/update-mermaid-diagrams.prompt.md index acc6ac96c..669599a65 100644 --- a/.github/prompts/update-mermaid-diagrams.prompt.md +++ b/.github/prompts/update-mermaid-diagrams.prompt.md @@ -4,7 +4,7 @@ title: "Update Mermaid Diagrams" description: "Refresh Mermaid diagrams across the repository or targeted paths with WCAG 2.2 AA colour contrast, updated content, and current standards." mode: "agent" tools: ["read", "edit", "search", "shell"] -tags: ["mermaid", "documentation", "a11y", "wcag", "colour-contrast", "readme"] +tags: ["mermaid", "documentation", "a11y", "wcag", "colour-contrast", "readme", "automation"] last_updated: "2026-06-18" --- diff --git a/.github/reports/mermaid-validation-report.md b/.github/reports/mermaid-validation-report.md index ff93d48d1..6ddc40dc2 100644 --- a/.github/reports/mermaid-validation-report.md +++ b/.github/reports/mermaid-validation-report.md @@ -1,4 +1,3 @@ ---- title: Mermaid Diagram Syntax Validation Report description: Mermaid diagram syntax validation results for repository README files version: "1.1.0" @@ -11,18 +10,21 @@ status: active stability: stable --- -# Mermaid Diagram Syntax Validation Report +Mermaid Diagram Syntax Validation Report +======================================== **Generated**: 2026-06-18T12:07:27.486Z -## Summary +Summary +------- - **Total diagrams**: 17 - **Valid diagrams**: 17 - **Error diagrams**: 0 - **Success rate**: 100.0% -## Files Analyzed +Files Analyzed +-------------- - README.md - agents/README.md @@ -58,10 +60,12 @@ stability: stable - workflows/README.md - workflows/memory/README.md -## Detailed Results +Detailed Results +---------------- ✅ All diagrams are syntactically valid! -## Recommendations +Recommendations +--------------- ✅ All Mermaid diagrams pass syntax validation. Proceed to accessibility compliance audit (#669). diff --git a/.github/workflows/README.md b/.github/workflows/README.md index f6b39f2b6..3ce138cb2 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -2,7 +2,7 @@ file_type: "documentation" title: ".github Workflows Directory" description: "GitHub Actions workflows for CI, automation, labeling, metrics, and community health across the LightSpeed organisation" -version: "v2.0" +version: "v2.0.1" last_updated: "2026-06-18" maintainer: "LightSpeed Team" tags: ["workflows", "github-actions", "automation", "ci-cd"] @@ -45,6 +45,7 @@ This directory contains the GitHub Actions workflows that power CI/CD, automatio | Workflow | File | Trigger | |---|---|---| | Meta Agent (Frontmatter, Badges, Metrics) | `meta.yml` | PR/push → develop (md/yml paths), schedule (Mon 03:00) | +| Metadata Governance | `metadata-governance.yml` | issues, pull_request_target | | Planner Agent | `planner.yml` | issues (opened), workflow_dispatch | | Reviewer Agent | `reviewer.yml` | pull_request | | Project Meta Sync | `project-meta-sync.yml` | issues, pull_request | @@ -80,10 +81,10 @@ This directory contains the GitHub Actions workflows that power CI/CD, automatio | Trigger type | Workflows | |---|---| -| `pull_request` / `pull_request_target` | checks, linting, testing, validate-pr-template, labeling, reviewer, dependabot-security-label, readme-regen, changelog-validate | +| `pull_request` / `pull_request_target` | checks, linting, testing, validate-pr-template, labeling, reviewer, dependabot-security-label, readme-regen, changelog-validate, metadata-governance | | `push → develop` | checks, linting, testing, meta, labeling, readme-regen, readme-update, template-enforcement | | `push → main` | main-branch-guard, release, awesome-github-site | -| `issues` | labeling, planner, issues, template-enforcement, issue-close-label-hygiene, project-meta-sync, checklist-finalisation | +| `issues` | labeling, planner, issues, template-enforcement, issue-close-label-hygiene, project-meta-sync, metadata-governance, checklist-finalisation | | `schedule` | meta (Mon 03:00), metrics (Mon 06:00), metrics-summary (Mon 09:00), reporting, readme-audit, project-archival | | `workflow_dispatch` | most workflows (manual trigger) | diff --git a/.github/workflows/labeling.yml b/.github/workflows/labeling.yml index 09a673c34..e1206ab32 100644 --- a/.github/workflows/labeling.yml +++ b/.github/workflows/labeling.yml @@ -90,7 +90,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} DRY_RUN: ${{ inputs.dry_run || 'false' }} - run: node scripts/agents/labeling.agent.js + run: node scripts/agents/run-labeling-agent.cjs - name: Generate report id: report diff --git a/.github/workflows/metadata-governance.yml b/.github/workflows/metadata-governance.yml new file mode 100644 index 000000000..d23113d0b --- /dev/null +++ b/.github/workflows/metadata-governance.yml @@ -0,0 +1,61 @@ +name: Metadata • Issues & PRs + +on: + issues: + types: [opened, reopened, edited, labeled, unlabeled] + pull_request_target: + types: [opened, reopened, edited, synchronize, ready_for_review, labeled, unlabeled] + +permissions: + contents: read + issues: write + pull-requests: write + +concurrency: + group: metadata-governance-${{ github.event_name }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }} + cancel-in-progress: false + +jobs: + sync-metadata: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22.22.1" + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: Sync issue and PR metadata + id: metadata + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_FIELDS_CONFIG: .github/issue-fields.yml + run: node scripts/agents/includes/issue-pr-metadata.cjs + + - name: Summarise metadata sync + env: + METADATA_ASSIGNEE: ${{ steps.metadata.outputs.metadata_assignee }} + METADATA_MILESTONE: ${{ steps.metadata.outputs.metadata_milestone }} + METADATA_LINKED_REFS: ${{ steps.metadata.outputs.metadata_linked_refs }} + METADATA_PARENT_REFS: ${{ steps.metadata.outputs.metadata_parent_refs }} + METADATA_CHILD_REFS: ${{ steps.metadata.outputs.metadata_child_refs }} + METADATA_BLOCKS_REFS: ${{ steps.metadata.outputs.metadata_blocks_refs }} + METADATA_BLOCKED_BY_REFS: ${{ steps.metadata.outputs.metadata_blocked_by_refs }} + METADATA_SECURITY_REFS: ${{ steps.metadata.outputs.metadata_security_refs }} + run: | + { + echo "### Metadata governance" + echo "- Assignee: ${METADATA_ASSIGNEE:-none}" + echo "- Milestone: ${METADATA_MILESTONE:-none}" + echo "- Linked refs: ${METADATA_LINKED_REFS:-none}" + echo "- Parent refs: ${METADATA_PARENT_REFS:-none}" + echo "- Child refs: ${METADATA_CHILD_REFS:-none}" + echo "- Blocks refs: ${METADATA_BLOCKS_REFS:-none}" + echo "- Blocked-by refs: ${METADATA_BLOCKED_BY_REFS:-none}" + echo "- Security refs: ${METADATA_SECURITY_REFS:-none}" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/project-meta-sync.yml b/.github/workflows/project-meta-sync.yml index 3f5ed090d..087a5d374 100644 --- a/.github/workflows/project-meta-sync.yml +++ b/.github/workflows/project-meta-sync.yml @@ -118,6 +118,7 @@ jobs: EVENT_ACTION: ${{ github.event.action }} PR_MERGED: ${{ github.event.pull_request.merged }} ITEM_CREATED_AT: ${{ github.event.issue.created_at || github.event.pull_request.created_at }} + ITEM_MILESTONE_DUE_ON: ${{ github.event.issue.milestone.due_on || github.event.pull_request.milestone.due_on }} ISSUE_FIELDS_CONFIG: .github/issue-fields.yml - name: Update project fields @@ -127,8 +128,18 @@ jobs: project-url: ${{ env.PROJECT_URL }} github-token: ${{ steps.app-token.outputs.token }} item-id: ${{ steps.addp.outputs.itemId }} - field-keys: Status,Priority,Type,Effort,Start date - field-values: ${{ steps.derive.outputs.status }},${{ steps.derive.outputs.priority }},${{ steps.derive.outputs.type }},${{ steps.derive.outputs.effort }},${{ steps.derive.outputs.start_date }} + field-keys: Status,Priority,Type,Effort + field-values: ${{ steps.derive.outputs.status }},${{ steps.derive.outputs.priority }},${{ steps.derive.outputs.type }},${{ steps.derive.outputs.effort }} + + - name: Update kickoff dates + if: steps.preflight.outputs.enabled == 'true' && steps.addp.outputs.itemId != '' && (steps.derive.outputs.start_date != '' || steps.derive.outputs.target_date != '') + uses: titoportas/update-project-fields@v0.1.0 + with: + project-url: ${{ env.PROJECT_URL }} + github-token: ${{ steps.app-token.outputs.token }} + item-id: ${{ steps.addp.outputs.itemId }} + field-keys: Start date,Target date + field-values: ${{ steps.derive.outputs.start_date }},${{ steps.derive.outputs.target_date }} - name: Add or update aging and SLA annotation if: steps.preflight.outputs.enabled == 'true' && github.event.action != 'closed' diff --git a/.github/workflows/template-enforcement.yml b/.github/workflows/template-enforcement.yml index 137cf4e94..b94701fdf 100644 --- a/.github/workflows/template-enforcement.yml +++ b/.github/workflows/template-enforcement.yml @@ -56,26 +56,39 @@ jobs: }); } + if ((issue.labels || []).some((label) => label.name === 'status:needs-more-info')) { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + name: 'status:needs-more-info', + }).catch((error) => { + core.info(`Unable to remove status:needs-more-info from #${issue.number}: ${error.message}`); + }); + } + return; } const message = isGithubActions ? [ marker, - '🚫 This issue was opened by automation without the required template sections.', + '⚠️ This issue was opened by automation without the required template sections.', '', `Missing required section(s): ${missing.join(', ')}`, '', + 'The issue will remain open and flagged until the body is corrected.', 'Update the automation to render the canonical template before creating the issue.', '- Recommended workflow: `.github/workflows/issue-create-from-template.yml`', ].join('\n') : [ marker, - '🚫 This issue does not follow a required issue template and has been closed automatically.', + '⚠️ This issue does not follow a required issue template.', '', `Missing required section(s): ${missing.join(', ')}`, '', - 'Please reopen with one of the official templates:', + 'The issue will remain open and flagged until the body is corrected.', + 'Please update the issue with one of the official templates:', '- https://github.com/lightspeedwp/.github/issues/new/choose', '', 'If this was opened by automation, update the automation to preserve the template sections before creating the issue.', @@ -97,23 +110,44 @@ jobs: }); } - if (isGithubActions) { - core.setFailed( - `Issue #${issue.number} was opened by github-actions[bot] and is missing required template sections: ${missing.join(', ')}`, - ); - return; + const labelsToRemove = (issue.labels || []) + .map((label) => label.name) + .filter((label) => label.startsWith('status:') && label !== 'status:needs-more-info'); + + if (labelsToRemove.length > 0) { + for (const labelName of labelsToRemove) { + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + name: labelName, + }); + } catch (error) { + core.info(`Status label cleanup skipped for ${labelName}: ${error.message}`); + } + } } - if (issue.state !== 'closed') { - await github.rest.issues.update({ + const labelsToAdd = ['status:needs-more-info']; + const currentLabels = new Set((issue.labels || []).map((label) => label.name)); + const missingLabels = labelsToAdd.filter((label) => !currentLabels.has(label)); + if (missingLabels.length > 0) { + await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, - state: 'closed', - state_reason: 'not_planned' + labels: missingLabels, }); } + if (isGithubActions) { + core.setFailed( + `Issue #${issue.number} was opened by github-actions[bot] and is missing required template sections: ${missing.join(', ')}`, + ); + return; + } + core.setFailed(`Issue #${issue.number} is missing required template sections: ${missing.join(', ')}`); validate-push-guardrail: diff --git a/CHANGELOG.md b/CHANGELOG.md index 6745ba396..b692a9119 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- **Metadata governance automation for issues and pull requests** — Added and hardened the GitHub automation that assigns project items, milestones, assignees, issue/PR relationships, project field values, and labelling behaviour for new issues and pull requests. Also updated the related docs, workflow guards, and test coverage to match the current codebase. ([#974](https://github.com/lightspeedwp/.github/pull/974)) + - **Community health audit — PR templates, governance docs, and README alignment** — Completed a comprehensive audit of all community health files: updated WCAG version references from 2.1 to 2.2 AA in `pr_bug.md`, `pr_chore.md`, `pr_ci.md`, and `pr_dep_update.md`; added 15 missing branch-prefix rows to the default `pull_request_template.md` quick-selector table to align with `PULL_REQUEST_TEMPLATE/config.yml`; expanded `AGENTS.md` issue template list from 10 to 23 entries and added Saved Replies section; expanded `CLAUDE.md` issue template list to match; fixed template count, range, and parity note in `.github/custom-instructions.md`; completely rewrote `.github/workflows/README.md` with an accurate inventory of all 27 real workflows (removed 4 phantom workflow references); updated `.github/README.md` version date; added 20 missing files to `.github/SAVED_REPLIES/README.md`; added template index table to `.github/ISSUE_TEMPLATE/README.md`; replaced generic category list with the 9 actual YAML file inventory in `.github/DISCUSSION_TEMPLATE/README.md`; updated `docs/ISSUE_CREATION_GUIDE.md` to 25-template parity and corrected label values. ([#966](https://github.com/lightspeedwp/.github/pull/966)) ### Fixed diff --git a/agents/agent.md b/agents/agent.md index 059db07d4..9c21acba4 100644 --- a/agents/agent.md +++ b/agents/agent.md @@ -1,7 +1,7 @@ --- title: "Main Agent Index" -version: 'v2.1' -last_updated: '2026-06-01' +version: 'v2.1.1' +last_updated: '2026-06-18' author: "LightSpeed" maintainer: "Ash Shaw" description: "Directory index referencing all agents, instructions, PR templates, and cross-references." @@ -59,7 +59,7 @@ The following instruction files provide detailed standards for agent development | [LABELING.md](../docs/LABELING.md) | Comprehensive labeling documentation and automation rules | labeling.agent.md | | [metrics.instructions.md](../instructions/metrics.instructions.md) | Metrics collection, aggregation, and reporting standards | metrics.agent.md | | [planner.instructions.md](../instructions/planner.instructions.md) | PR planning, checklist generation, and merge readiness validation | planner.agent.md (to be created) | -| [project-meta-sync.instructions.md](../instructions/project-meta-sync.instructions.md) | GitHub Project board field synchronisation from labels | project-meta-sync.agent.md | +| [project-meta-sync.instructions.md](../instructions/project-meta-sync.instructions.md) | Deprecated compatibility guidance for project metadata sync | project-meta-sync.agent.md | | [release.instructions.md](../instructions/release.instructions.md) | Release management, semantic versioning, and changelog compliance | release.agent.md | | [workflows.instructions.md](../instructions/workflows.instructions.md) | GitHub Actions workflow standards and patterns | All workflow files | | [reporting.instructions.md](../instructions/reporting.instructions.md) | Report generation, storage, and formatting standards | All agents that generate reports | diff --git a/agents/project-meta-sync.agent.md b/agents/project-meta-sync.agent.md index dbb30b0b6..4272bc2e7 100644 --- a/agents/project-meta-sync.agent.md +++ b/agents/project-meta-sync.agent.md @@ -1,7 +1,8 @@ --- title: Project Meta Sync -description: Syncs GitHub Project board meta fields (Status, Priority, Type) from - issue/PR labels and branch names, automating project management and triage workflows. +description: Deprecated compatibility spec for the legacy project meta sync entrypoint. + The active automation now lives in the project-meta-sync and metadata-governance + workflows plus their helper scripts. target: github-copilot handoffs: - label: Update Project Fields @@ -66,7 +67,6 @@ permissions: - github:repo - github:issues metadata: - guardrails: Only update fields based on canonical label mappings. Notify maintainers - on mapping conflicts. Support rollback and audit logging. Never remove items from - project without warning. + guardrails: Compatibility only. Do not treat this spec as the active contract; + use the workflow and helper scripts referenced above. --- diff --git a/docs/AUTOMATION.md b/docs/AUTOMATION.md index 5c15cd606..df7f38481 100644 --- a/docs/AUTOMATION.md +++ b/docs/AUTOMATION.md @@ -2,7 +2,7 @@ file_type: "documentation" title: "Automation & Workflows" description: "Strategy, governance, and workflow documentation for GitHub automation in LightSpeed repositories." -version: "v1.0.3" +version: "v1.0.4" last_updated: "2026-06-18" owners: ["LightSpeedWP Team"] tags: ["automation", "workflows", "governance", "agents"] @@ -72,9 +72,10 @@ If your project allows hotfixes directly to `main`, ensure validation workflows | --- | --- | --- | --- | | **labeling.yml** | develop | Unified labelling, status/priority, and type automation | labeling.agent.js | | **changelog-validate.yml** | develop | Enforce changelog requirements and PR labelling standards | changelog validation | +| **metadata-governance.yml** | issues / pull_request_target | Apply assignee, milestone, and relationship metadata | issue-pr-metadata.cjs | | **planner.yml** | develop | Post merge-readiness checklists and exit criteria to PRs | planner.agent.js | | **reviewer.yml** | develop | Automated PR review and quality feedback | reviewer.agent.js | -| **project-meta-sync.yml** | develop | Sync project board with PR/issue labels, status, priority, type, and supported project fields | project-meta-sync.agent.js | +| **project-meta-sync.yml** | push / issues / pull_request | Sync project board fields from labels and kickoff metadata | derive-project-fields.cjs | | **checklist-finalisation.yml** | issues.closed / pull_request_target.closed | Final checklist sync for completed issues and merged PRs | workflow backstop | | **release.yml** | main | Versioning, changelog generation, tagging, and release notes | release.agent.js | | **reporting.yml** | develop | Generate metrics and activity reports | reporting.agent.js | @@ -152,6 +153,13 @@ Issue types are defined once in `.github/issue-types.yml` and used by both: **Enforcement:** One type per issue (one-hot principle); issue type field mirrors `type:*` label for consistency. +### Metadata Governance + +- Issues and PRs are assigned to the repository project automatically on create. +- New issues and PRs should receive an assignee, milestone, and relationship metadata where relevant. +- `Start date` and `Target date` remain empty until the item is explicitly marked `status:ready` or `status:in-progress`. +- Template enforcement must flag incomplete issues, apply `status:needs-more-info`, and keep the item open. + --- ## Workflow & Agent Governance diff --git a/docs/GITHUB_PROJECT_OPERATIONS_SPEC.md b/docs/GITHUB_PROJECT_OPERATIONS_SPEC.md index 188ffb487..feb465917 100644 --- a/docs/GITHUB_PROJECT_OPERATIONS_SPEC.md +++ b/docs/GITHUB_PROJECT_OPERATIONS_SPEC.md @@ -13,6 +13,7 @@ Keep contributor docs lean and current by describing operational rules while tre - `.github/issue-types.yml` (issue-type mappings) - `.github/issue-fields.yml` (project field mappings and defaults) - `.github/workflows/labeling.yml` (label automation) +- `.github/workflows/metadata-governance.yml` (assignee, milestone, and relationship metadata) - `.github/workflows/project-meta-sync.yml` (project field sync) ## Unified Project Template Model @@ -80,13 +81,14 @@ Template labels must remain canonical and pass: - `Priority` - `Type` - `Effort` -- `Start date` +- `Start date` and `Target date` only when `status:ready` or `status:in-progress` is present Derivation source notes: - `Status`, `Priority`, and `Type` are mapped from labels via `.github/issue-fields.yml` mappings. - `Effort` uses the configured default from canonical issue-fields configuration. -- `Start date` is derived from item creation date for opened/reopened items that are not done. +- `Start date` and `Target date` stay empty at creation time and are only populated after kickoff-ready metadata is present (`status:ready` or `status:in-progress`). +- Metadata governance for assignees, milestones, and relationships is handled separately by `.github/workflows/metadata-governance.yml`. Current preflight conditions must be satisfied before sync runs: @@ -96,8 +98,7 @@ Current preflight conditions must be satisfied before sync runs: Safe automation boundary: -- Active write path is limited to the five fields above. -- `Target date` remains defined in canonical config but is not currently written by the active workflow path. +- Active write path is limited to the five core derived fields plus kickoff-aware date handling. - Additional direct issue-field writes are out of scope until a dedicated follow-up verification approves extension. Verification reference: `.github/reports/audits/2026-06-07-private-project-issue-field-write-verification-879.md`. diff --git a/docs/ISSUE_CREATION_GUIDE.md b/docs/ISSUE_CREATION_GUIDE.md index b2abac7e9..c41c183bd 100644 --- a/docs/ISSUE_CREATION_GUIDE.md +++ b/docs/ISSUE_CREATION_GUIDE.md @@ -2,7 +2,7 @@ title: GitHub Issue Creation Guide description: How to create well-formed issues, select templates, and trigger automation file_type: documentation -version: "1.0.4" +version: "1.0.5" created_date: "2026-05-31" last_updated: "2026-06-18" author: Claude Code @@ -99,7 +99,7 @@ Review these checkboxes to ensure they align with your scope. ### 4. Add Labels and Metadata -Issue templates currently do not pre-populate labels. Add labels manually: +Issue templates do not pre-populate labels in the form, but automation adds the canonical set after creation. Add labels manually only when the workflow cannot infer them: - Add the appropriate `type:*` label (e.g., `type:bug`, `type:feature`) - Add exactly one `status:*` label @@ -121,11 +121,14 @@ Click **Submit new issue**. Your issue is now visible to the team and ready for - `labeling.yml` runs on issue events (`opened`, `edited`, `reopened`, `labeled`, `unlabeled`, `transferred`) - Unified labeling agent applies canonicalization, one-hot enforcement, defaults, and content-based type detection - PR automation remains stronger due to branch/file signals available in PR context +- `metadata-governance.yml` automatically adds new issues and PRs to the configured project, assigns the requester when possible, attaches or creates an appropriate milestone, and records relationships when they are present in the body +- `project-meta-sync.yml` keeps the project fields in sync and leaves `Start date` and `Target date` empty until work is explicitly marked `status:ready` or `status:in-progress` ### ⚠️ Practical Implication -- Issue outcomes are not yet fully deterministic from template choice alone. -- Include clear issue text and apply canonical labels explicitly for reliable triage. +- Issue outcomes are still driven by the body content and canonical labels, so keep the template complete and specific. +- Incomplete templates are flagged and labelled for correction rather than closed. +- Metadata governance now handles the project item, assignee, milestone, and relationship metadata automatically when it can infer them safely. ### AI / Automation Issue Creation diff --git a/docs/ISSUE_FIELDS.md b/docs/ISSUE_FIELDS.md index 7245c7909..411aabf4a 100644 --- a/docs/ISSUE_FIELDS.md +++ b/docs/ISSUE_FIELDS.md @@ -2,9 +2,9 @@ title: Issue Fields Specification description: Canonical specification for GitHub organization issue fields, type mappings, and project automation configuration file_type: documentation -version: v1.0.4 +version: v1.0.5 created_date: '2026-05-31' -last_updated: '2026-06-07' +last_updated: '2026-06-18' authors: - Claude Code - LightSpeed Team @@ -454,6 +454,7 @@ Status mappings: - In QA: status:needs-qa - Blocked: status:blocked - On hold: status:on-hold +- Needs more info: status:needs-more-info - Closed: status:done Priority mappings: @@ -466,7 +467,7 @@ Priority mappings: Default priority: priority:normal - Default type: type:task -- Status workflow values: status:needs-triage, status:needs-planning +- Status workflow values: status:needs-triage, status:needs-planning, status:needs-more-info --- diff --git a/docs/LABELING.md b/docs/LABELING.md index 645e0edb2..392206aff 100644 --- a/docs/LABELING.md +++ b/docs/LABELING.md @@ -2,7 +2,8 @@ title: "Labeling Strategy & Governance" description: "Label taxonomy, automation rules, and governance for LightSpeed repositories." file_type: "documentation" -version: 'v1.0.1' +version: 'v1.0.2' +last_updated: '2026-06-18' author: "LightSpeed Team" maintainer: "LightSpeed Team" owners: ["lightspeedwp"] @@ -60,10 +61,10 @@ Indicate the current progress or state of an issue or PR: Indicate urgency and scheduling priority: -- `priority:urgent` — Security issue, critical bug, or blocker -- `priority:high` — High-impact, affecting multiple users +- `priority:critical` — Production or launch-blocking work +- `priority:important` — High-impact work that should be prioritised soon - `priority:normal` — Standard feature or improvement (default) -- `priority:low` — Nice-to-have, deferred work +- `priority:minor` — Nice-to-have, deferred work **Rule:** Each issue and PR has exactly one `priority:*` label. @@ -75,7 +76,7 @@ Classify the nature of the work: - `type:feature` — New functionality - `type:improvement` — Enhancement to existing functionality - `type:chore` — Maintenance, cleanup, tooling, or refactoring -- `type:docs` — Documentation improvements +- `type:documentation` — Documentation improvements - `type:test` — Test suite additions or fixes - `type:refactor` — Code quality improvements, no behaviour change - `type:performance` — Performance optimisation @@ -208,7 +209,7 @@ PR branch names automatically assign `type:*` labels: - `hotfix/` → `type:bug` + `release:hotfix` - `refactor/` → `type:refactor` - `perf/` → `type:performance` -- `docs/` → `type:docs` +- `docs/` → `type:documentation` - `test/` → `type:test` - `chore/` → `type:chore` - `ci/` → `type:chore` + `area:ci` diff --git a/docs/LABEL_COLOR_STRATEGY.md b/docs/LABEL_COLOR_STRATEGY.md index 60fdc1bf6..4361e94dc 100644 --- a/docs/LABEL_COLOR_STRATEGY.md +++ b/docs/LABEL_COLOR_STRATEGY.md @@ -1,10 +1,10 @@ --- title: Label Color Strategy Specification -description: Comprehensive color strategy for the 150 canonical labels based on semantic meaning and workflow state +description: Accessible colour strategy for the canonical label set in .github/labels.yml file_type: documentation -version: v1.0.0 +version: v1.1.0 created_date: '2026-05-31' -last_updated: '2026-06-01' +last_updated: '2026-06-18' authors: - Claude Code - LightSpeed Team @@ -23,447 +23,79 @@ stability: stable # Label Color Strategy Specification -**Version**: v1.0.0 +**Version**: v1.1.0 **Created**: 2026-05-31 **Owner**: LightSpeed Team **Status**: Active - ---- +**Coverage**: 158 canonical labels ## Executive Summary -This document defines a comprehensive color strategy for the 150 canonical labels in the LightSpeed `.github` repository. The strategy organises labels into semantic families with consistent color assignments, improving visual navigation, workflow clarity, and maintainability of the label taxonomy. - -**Key Improvements**: - -- Reduces color fragmentation (31 unique colors → 8 primary families) -- Establishes clear semantic associations between color and label purpose -- Provides explicit assignment rules for new labels -- Improves accessibility with sufficient contrast -- Creates a maintainable, scalable foundation for label expansion - ---- - -## 1. Color Families & Semantic Mapping - -### 1.1 Primary Color Families (8 Families) - -| Family | Hex Codes | Semantic Meaning | Label Categories | Count | -| --- | --- | --- | --- | --- | -| **Green (Ready/Done)** | `#0DBA3D`, `#34B71C`, `#6BB71C` | Positive completion, resolution, readiness | status:done, status:ready, type:enhancement (✓merged), design:approved | 18 | -| **Blue (Planning/Review)** | `#0969DA`, `#4986E8`, `#C5DEF5` | Discussion, collaboration, needs input | type:documentation, type:discussion, status:needs-review, lang:* | 52 | -| **Yellow (Testing/Audit)** | `#D29922`, `#FCE2B7`, `#F2D06D` | Validation, testing, audit workflows | status:testing, type:bug (when testing), type:audit, priority:medium | 24 | -| **Red (Blocked/Impediment)** | `#EF3B39`, `#F85149`, `#FCE2E2` | Blockers, duplicates, critical issues | status:blocked, type:duplicate, priority:urgent, type:security | 18 | -| **Orange (On-Hold/Deferred)** | `#FB8500`, `#D5A87B`, `#FDBF7C` | Delayed, deferred, rejected, wontfix | status:wontfix, status:on-hold, type:epic (when deferred), priority:low | 16 | -| **Purple (Design Workflows)** | `#8957E5`, `#D89AF6`, `#B4A7E8` | Design, UX, accessibility | type:design, type:ui, type:accessibility, design:* | 14 | -| **Gray (Meta/Infrastructure)** | `#57606A`, `#B1BAC4`, `#D0D7DE` | Process, meta, automation, infrastructure | meta:*, area:ci-cd, type:internal, domain:* | 12 | -| **Teal (Integration/External)** | `#2DA39D`, `#2DBFA3`, `#9FE1E3` | External systems, integrations, dependencies | area:integration, area:external, type:dependency, platform:* | 16 | - -**Total Coverage**: 150 labels across 8 families - ---- - -## 2. Detailed Family Specifications - -### 2.1 Green Family: Ready & Done (Positive Completion) - -**Purpose**: Signal positive completion, readiness for next phase, or approved state - -**Hex Codes** (in order of preference): - -- `#0DBA3D` — Primary (strong, high-saturation green) -- `#34B71C` — Secondary (slightly darker) -- `#6BB71C` — Tertiary (olive-green for subtle emphasis) - -**Assignment Rules**: - -- All `status:done*` labels → `#0DBA3D` -- All `status:ready*` labels → `#0DBA3D` -- Approved/accepted labels (design:approved, reviewed:accepted) → `#0DBA3D` -- Enhancement/feature labels when merged → `#34B71C` - -**Label Examples**: - -- status:done (merged) -- status:ready-for-review -- status:ready-for-merge -- design:approved -- reviewed:approved - -**Accessibility**: Strong contrast against white/light backgrounds; readable for green-blind users with supporting label text. - ---- - -### 2.2 Blue Family: Planning & Review (Collaborative Input) - -**Purpose**: Indicate discussion, planning, code review, or work awaiting input - -**Hex Codes** (in order of preference): - -- `#0969DA` — Primary (strong, readable blue) -- `#4986E8` — Secondary (lighter, softer tone) -- `#C5DEF5` — Tertiary (very light blue for subtle/secondary labels) - -**Assignment Rules**: - -- All `status:needs-*` labels → `#0969DA` -- All `type:documentation` labels → `#4986E8` -- All `type:discussion` labels → `#4986E8` -- All `lang:*` (language) labels → `#C5DEF5` -- Code review related (needs-review, awaiting-feedback) → `#0969DA` -- Discussion/question labels → `#4986E8` - -**Label Examples**: - -- status:needs-review -- status:needs-feedback -- type:discussion -- type:documentation -- lang:php -- lang:javascript - -**Accessibility**: Primary and secondary high contrast; tertiary use sparingly for less critical labels. - ---- - -### 2.3 Yellow Family: Testing & Audit (Validation States) - -**Purpose**: Signal validation workflows, testing phases, and audit activities - -**Hex Codes** (in order of preference): - -- `#D29922` — Primary (gold, good contrast) -- `#FCE2B7` — Secondary (light yellow for supporting labels) -- `#F2D06D` — Tertiary (medium yellow for medium-emphasis) - -**Assignment Rules**: - -- All `status:testing*` labels → `#D29922` -- All `type:audit*` labels → `#D29922` -- Performance/testing related → `#F2D06D` -- QA/validation labels → `#FCE2B7` - -**Label Examples**: - -- status:testing -- type:audit -- type:performance -- priority:medium (when used for testing triage) - -**Accessibility**: Gold primary maintains legibility; lighter variants should include descriptive text. - ---- - -### 2.4 Red Family: Blocked & Impediments (Critical Issues) - -**Purpose**: Highlight blockers, duplicates, critical bugs, and impediments to progress - -**Hex Codes** (in order of preference): - -- `#EF3B39` — Primary (bright red, high-alert) -- `#F85149` — Secondary (slightly darker red) -- `#FCE2E2` — Tertiary (very light red for subtle issues) - -**Assignment Rules**: - -- All `status:blocked*` labels → `#EF3B39` -- All `type:duplicate` labels → `#EF3B39` -- All `priority:urgent` labels → `#EF3B39` -- All `type:security*` labels → `#EF3B39` -- Bug labels with critical impact → `#F85149` - -**Label Examples**: - -- status:blocked -- type:duplicate -- priority:urgent -- type:security -- type:bug (critical variants) - -**Accessibility**: High contrast for urgent visibility; use with supporting label text for clarity. - ---- - -### 2.5 Orange Family: On-Hold & Deferred (Delayed/Rejected) - -**Purpose**: Indicate deferred decisions, rejected features, or items on hold - -**Hex Codes** (in order of preference): - -- `#FB8500` — Primary (bright orange) -- `#FDBF7C` — Secondary (light orange) -- `#D5A87B` — Tertiary (muted orange for subtle issues) - -**Assignment Rules**: - -- All `status:wontfix` labels → `#FB8500` -- All `status:on-hold*` labels → `#FB8500` -- Deferred/postponed items → `#FDBF7C` -- Epic/larger-scope labels when on hold → `#D5A87B` - -**Label Examples**: - -- status:wontfix -- status:on-hold -- status:postponed - -**Accessibility**: Bright orange visible but lower urgency than red; sufficient contrast for visibility. - ---- - -### 2.6 Purple Family: Design Workflows (UX/Design) - -**Purpose**: Identify design, UX, accessibility, and user-experience-focused work - -**Hex Codes** (in order of preference): - -- `#8957E5` — Primary (vibrant purple) -- `#B4A7E8` — Secondary (lighter purple) -- `#D89AF6` — Tertiary (soft purple for supporting labels) +This document defines the accessible colour palette used by the canonical label set in [`.github/labels.yml`](../.github/labels.yml). The palette is intentionally small so that workflow meaning stays consistent, automation stays predictable, and the repo remains WCAG 2.2 AA compliant. -**Assignment Rules**: +Wave 5.2 remediation is complete. The colour audit identified the old palette as too fragmented and too light in several places; the live config now uses an AA-compliant palette and the supporting docs have been updated to match. -- All `type:design*` labels → `#8957E5` -- All `type:ui` labels → `#8957E5` -- All `type:accessibility*` labels → `#8957E5` -- All `design:*` state labels → `#B4A7E8` -- UX-related, user feedback → `#D89AF6` +## 1. Canonical Palette -**Label Examples**: +The current label set uses eight accessible colour families: -- type:design -- type:ui -- type:accessibility -- design:approved -- design:pending-review - -**Accessibility**: Vibrant purple maintains contrast; use lighter variants for secondary emphasis only. - ---- - -### 2.7 Gray Family: Meta & Infrastructure (Process) - -**Purpose**: Mark infrastructure, meta-workflow, automation, and process-related items - -**Hex Codes** (in order of preference): - -- `#57606A` — Primary (dark gray) -- `#B1BAC4` — Secondary (medium gray) -- `#D0D7DE` — Tertiary (light gray for subtle meta) - -**Assignment Rules**: - -- All `meta:*` labels → `#57606A` -- All `area:ci-cd` labels → `#57606A` -- All `type:internal` labels → `#57606A` -- Domain/organisation labels → `#B1BAC4` -- Infrastructure/automation supporting labels → `#D0D7DE` - -**Label Examples**: - -- meta:needs-changelog -- meta:needs-review -- area:ci-cd -- type:internal -- domain:governance - -**Accessibility**: Dark gray primary provides contrast; use B1BAC4 and D0D7DE sparingly with clear labels. - ---- - -### 2.8 Teal Family: Integration & External (Dependencies) - -**Purpose**: Indicate external dependencies, integrations, and platform-specific work - -**Hex Codes** (in order of preference): - -- `#2DA39D` — Primary (teal) -- `#2DBFA3` — Secondary (bright teal) -- `#9FE1E3` — Tertiary (light teal for supporting labels) - -**Assignment Rules**: - -- All `area:integration*` labels → `#2DA39D` -- All `area:external*` labels → `#2DA39D` -- All `type:dependency*` labels → `#2DA39D` -- Platform/vendor-specific labels → `#2DBFA3` -- External system coordination → `#9FE1E3` - -**Label Examples**: - -- area:integration -- area:external -- type:dependency -- platform:github -- platform:github-actions - -**Accessibility**: Teal primary readable on light backgrounds; lighter variants should be accompanied by clear labels. - ---- - -## 3. Label Assignment Rules & Examples - -### 3.1 Multi-Category Labels - -Some labels span multiple categories. Use these rules when a label fits multiple families: - -| Scenario | Resolution | Example | +| Family | Hex | Typical Use | | --- | --- | --- | -| Bug found during testing | Use Yellow (testing context takes precedence) | type:bug + status:testing → Yellow | -| Documentation needs review | Use Blue (review/discussion takes precedence) | type:documentation + status:needs-review → Blue | -| Security vulnerability | Use Red (urgency/severity takes precedence) | type:security + type:bug → Red | -| Accessibility issue | Use Purple (type takes precedence over urgency) | type:accessibility + priority:urgent → Purple | - -### 3.2 Creating New Labels - -When creating new labels, follow this hierarchy: - -1. **Determine primary purpose** (type, status, area, etc.) -2. **Map to appropriate family** using Table 1 (Section 1.1) -3. **Choose hex code** based on emphasis level: - - Primary color (first in family) — for high-visibility labels - - Secondary color (second) — for standard labels - - Tertiary color (third) — for supporting/secondary labels -4. **Verify contrast** against light and dark backgrounds -5. **Document** the rationale in label frontmatter - ---- - -## 4. Migration Path (Canonical Config Files) - -### 4.1 Phase 1: Documentation & Strategy (Current) +| Blue | `#0F448A` | Planning, review, general work, structural labels | +| Green | `#1D7232` | Ready, done, feature, release, live state | +| Yellow | `#7E6007` | QA, testing, audit, validation, performance | +| Red | `#810E18` | Blockers, bugs, security, critical risk | +| Orange | `#883D07` | On-hold, hotfix, compatibility, cautionary work | +| Purple | `#4D1A93` | Design, accessibility, clarification, feedback | +| Gray | `#4E575F` | Meta, maintenance, hygiene, stale/housekeeping | +| Teal | `#147169` | Infrastructure, automation, integration, external systems | -- ✅ This specification document (v1.0.0) -- Provides explicit color assignment rules -- Establishes semantic color families +## 2. Assignment Rules -### 4.2 Phase 2: Update labels.yml (Issue #683) +1. Keep one primary colour family per label family. +2. Use the same family for related labels so the taxonomy is easy to scan. +3. Prefer the canonical palette above when creating or updating labels. +4. Do not introduce ad hoc colours for one-off labels unless a new governance decision is logged. +5. If a new family is genuinely required, document the rationale and add it through the normal label governance flow. -- Reassign all 150 labels to new color families -- Group labels by family for easier maintenance -- Add comments documenting family assignment +### 2.1 Family Guidance -### 4.3 Phase 3: Validate & Document (Issue #685) +- `status:*` labels use Blue, Green, Yellow, Red, Orange, Purple, or Gray depending on lifecycle state. +- `priority:*` labels use Red, Orange, Blue, and Gray for descending urgency. +- `type:*` labels use the family that best matches the work category and review flow. +- `area:*`, `comp:*`, `lang:*`, `env:*`, `compat:*`, `cpt:*`, `ai-ops:*`, `contrib:*`, and `discussion:*` follow the same canonical palette so the colour system remains readable at scale. -- Update LABELING.md with visual reference -- Create label colour palette reference -- Document best practices for label selection +## 3. Accessibility ---- - -## 5. Accessibility & Contrast Standards - -All colors in this strategy meet **WCAG AA contrast requirements** (minimum 4.5:1 ratio) against both white and dark backgrounds. - -**Contrast Verification** (vs. white #FFFFFF): - -| Color | Hex | Contrast Ratio | WCAG Level | Status | -| --- | --- | --- | --- | --- | -| `#0DBA3D` (Green primary) | #0DBA3D | ~2.6:1 | ❌ Fails AA | ⚠️ Needs review | -| `#0969DA` (Blue primary) | #0969DA | ~5.2:1 | ✅ AA | ❌ Fails AAA | -| `#D29922` (Yellow primary) | #D29922 | ~4.8:1 | ✅ AA | ❌ Fails AAA | -| `#EF3B39` (Red primary) | #EF3B39 | ~3.9:1 | ❌ Fails AA | ⚠️ Needs review | -| `#FB8500` (Orange primary) | #FB8500 | ~2.8:1 | ❌ Fails AA | ⚠️ Needs review | -| `#8957E5` (Purple primary) | #8957E5 | ~4.6:1 | ✅ AA | ❌ Fails AAA | -| `#57606A` (Gray primary) | #57606A | ~7.1:1 | ✅ AAA | ✅ Compliant | -| `#2DA39D` (Teal primary) | #2DA39D | ~3.1:1 | ❌ Fails AA | ⚠️ Needs review | - -**⚠️ Accessibility Notice**: Several primary colors do not meet WCAG AA contrast standards against white backgrounds. Secondary and tertiary colors also require verification. This specification needs a colour accessibility audit and potential colour adjustments to ensure full WCAG AA compliance. See Issue #686 (Wave 5.2 Canonical Config Files Audit) for remediation tracking. - ---- - -## 6. Visual Palette Reference - -``` -Green Family (Ready/Done): -████ #0DBA3D ████ #34B71C ████ #6BB71C - -Blue Family (Planning/Review): -████ #0969DA ████ #4986E8 ████ #C5DEF5 - -Yellow Family (Testing/Audit): -████ #D29922 ████ #F2D06D ████ #FCE2B7 - -Red Family (Blocked/Impediment): -████ #EF3B39 ████ #F85149 ████ #FCE2E2 - -Orange Family (On-Hold/Deferred): -████ #FB8500 ████ #FDBF7C ████ #D5A87B - -Purple Family (Design): -████ #8957E5 ████ #B4A7E8 ████ #D89AF6 - -Gray Family (Meta/Infrastructure): -████ #57606A ████ #B1BAC4 ████ #D0D7DE - -Teal Family (Integration/External): -████ #2DA39D ████ #2DBFA3 ████ #9FE1E3 -``` - ---- - -## 7. Implementation Checklist - -- [x] Define color families and semantic mapping -- [x] Document assignment rules and rationale -- [x] Verify WCAG AA contrast compliance -- [x] Create visual palette reference -- [ ] Update labels.yml with colour assignments (Issue #683) -- [ ] Update LABELING.md with strategy reference (Issue #685) -- [ ] Create label selection guidance for contributors - ---- - -## 8. Maintenance & Evolution - -### 8.1 Adding New Colours - -New colors should only be added if: - -1. A new semantic family is needed (not existing 8 families) -2. Current families cannot accommodate the label -3. Accessibility standards can be maintained -4. Owner team approves the addition +All colours currently assigned in `.github/labels.yml` meet WCAG 2.2 AA contrast against white backgrounds. -### 8.2 Deprecating Colours +- Colour is never the only workflow signal. +- Labels are always paired with descriptive text and automation context. +- The palette is built from dark enough tones to remain readable in GitHub's label chips. -Deprecated colours (not in this spec) will be gradually migrated to this strategy through: +## 4. Migration Status -1. Issue #683: Reassignment of existing labels -2. Gradual phase-out in new label creation -3. Documentation of old→new mapping for auditability +- [x] Update `labels.yml` with colour assignments (Issue #683) +- [x] Update supporting documentation (Issue #685) +- [x] Close out the Wave 5.2 colour audit note and remediation tracking (PR #686) -### 8.3 Review Cycle +## 5. Related Documentation -This specification should be reviewed annually or when: - -- New label categories are introduced -- Accessibility standards change -- GitHub's colour palette capabilities expand -- User feedback indicates confusion - ---- - -## 9. Related Documentation - -- [LABELING.md](./LABELING.md) — Comprehensive labeling guide -- [ISSUE_FIELDS.md](./ISSUE_FIELDS.md) — Issue field mapping -- `.github/labels.yml` — Canonical label configuration (to be updated) +- [`docs/LABEL_STRATEGY.md`](./LABEL_STRATEGY.md) +- [`docs/LABEL_INVENTORY.md`](./LABEL_INVENTORY.md) +- [`docs/LABELING.md`](./LABELING.md) +- [`.github/labels.yml`](../.github/labels.yml) - Issue #650: Wave 5.2 Audit (parent) - Issue #683: Type Mapping Reconciliation (dependent) - Issue #685: Supporting Documentation Updates (dependent) ---- - -## 10. Changelog +## 6. Changelog | Date | Change | Author | | --- | --- | --- | -| 2026-05-31 | Initial specification v1.0.0 — 8 color families, 150 label mapping | Claude Code | - ---- +| 2026-06-18 | Replaced the stale palette with the current WCAG AA-safe canonical palette and marked Wave 5.2 remediation complete | LightSpeed Team | +| 2026-05-31 | Initial specification v1.0.0 | Claude Code | **Document Status**: ✅ Active -**Last Updated**: 2026-05-31 -**Next Review**: 2027-05-31 (annual) +**Last Updated**: 2026-06-18 **Owner**: LightSpeed Team diff --git a/docs/LABEL_INVENTORY.md b/docs/LABEL_INVENTORY.md index e11bd8d36..54ca86823 100644 --- a/docs/LABEL_INVENTORY.md +++ b/docs/LABEL_INVENTORY.md @@ -1,10 +1,10 @@ --- title: Label Inventory — Complete Reference -description: Complete inventory of all 150+ canonical labels organized by semantic family, with color codes, descriptions, and usage guidance +description: Complete inventory of all 158 canonical labels organized by semantic family, with color codes, descriptions, and usage guidance file_type: documentation -version: v1.1.1 +version: v1.1.2 created_date: '2026-05-31' -last_updated: '2026-06-03' +last_updated: '2026-06-18' authors: - Claude Code - LightSpeed Team @@ -24,10 +24,10 @@ stability: stable # Label Inventory — Complete Reference -**Version**: v1.1.1 +**Version**: v1.1.2 **Created**: 2026-05-31 -**Last Updated**: 2026-06-03 -**Total Labels**: 150+ +**Last Updated**: 2026-06-18 +**Total Labels**: 158 This document provides a complete inventory of all canonical labels organized by semantic family. Use this as a reference when: @@ -44,26 +44,26 @@ Labels tracking the workflow state and lifecycle of work items. | Label | Color | Description | Use When | | --- | --- | --- | --- | -| `status:needs-planning` | BFD4F2 | Awaiting planning / scoping | Issue requires analysis/breakdown before work starts | -| `status:needs-triage` | BFD4F2 | Needs triage | Issue needs investigation/categorisation | -| `status:ready` | 0E8A16 | Groomed and ready to start | Issue is scoped, detailed, ready for work | -| `status:in-progress` | 1D76DB | Work in progress | Someone is actively working | -| `status:on-hold` | F9D0C4 | Work on hold | Work paused; waiting for decision | -| `status:needs-design` | C5DEF5 | Awaiting design input | Needs design review before implementation | -| `status:needs-design-review` | D4C5F9 | Awaiting design review | Design needs review before approval | -| `status:needs-figma-update` | C5DEF5 | Existing Figma design needs updating | Figma files need refresh | -| `status:needs-dev` | C5DEF5 | Awaiting engineering implementation | Ready for engineering to begin | -| `status:needs-review` | BFD4F2 | Awaiting code review | PR/code needs peer review | -| `status:needs-qa` | FBCA04 | Quality assurance required | Needs QA/testing | -| `status:needs-testing` | FEF2C0 | Testing needed (manual/automated) | Awaiting testing pass | -| `status:needs-audit` | FEF2C0 | Needs audit or validation pass | Needs security/compliance audit | -| `status:needs-documentation` | BFD4F2 | Needs documentation update | Documentation/guides need updating | -| `status:in-discussion` | BFD4F2 | Needs alignment/decision | Awaiting discussion/decision | -| `status:needs-more-info` | BFD4F2 | Missing details to proceed | Awaiting more information from reporter | -| `status:blocked` | E99695 | Blocked by dependency | Blocked by external factor/dependency | -| `status:duplicate` | E99695 | Duplicate of another issue | Duplicate of existing issue | -| `status:wontfix` | E1E4E8 | Not planned to address | Won't be fixed/addressed | -| `status:done` | 0E8A16 | Completed | Work complete | +| `status:needs-planning` | 0F448A | Awaiting planning / scoping | Issue requires analysis/breakdown before work starts | +| `status:needs-triage` | 0F448A | Needs triage | Issue needs investigation/categorisation | +| `status:ready` | 1D7232 | Groomed and ready to start | Issue is scoped, detailed, ready for work | +| `status:in-progress` | 0F448A | Work in progress | Someone is actively working | +| `status:on-hold` | 883D07 | Work on hold | Work paused; waiting for decision | +| `status:needs-design` | 0F448A | Awaiting design input | Needs design review before implementation | +| `status:needs-design-review` | 4D1A93 | Awaiting design review | Design needs review before approval | +| `status:needs-figma-update` | 0F448A | Existing Figma design needs updating | Figma files need refresh | +| `status:needs-dev` | 0F448A | Awaiting engineering implementation | Ready for engineering to begin | +| `status:needs-review` | 0F448A | Awaiting code review | PR/code needs peer review | +| `status:needs-qa` | 7E6007 | Quality assurance required | Needs QA/testing | +| `status:needs-testing` | 7E6007 | Testing needed (manual/automated) | Awaiting testing pass | +| `status:needs-audit` | 7E6007 | Needs audit or validation pass | Needs security/compliance audit | +| `status:needs-documentation` | 0F448A | Needs documentation update | Documentation/guides need updating | +| `status:in-discussion` | 0F448A | Needs alignment/decision | Awaiting discussion/decision | +| `status:needs-more-info` | 0F448A | Missing details to proceed | Awaiting more information from reporter | +| `status:blocked` | 810E18 | Blocked by dependency | Blocked by external factor/dependency | +| `status:duplicate` | 810E18 | Duplicate of another issue | Duplicate of existing issue | +| `status:wontfix` | 4E575F | Not planned to address | Won't be fixed/addressed | +| `status:done` | 1D7232 | Completed | Work complete | --- @@ -73,10 +73,10 @@ Labels signalling urgency and business impact. | Label | Color | Description | Use When | | --- | --- | --- | --- | -| `priority:critical` | B60205 | Production/launch-blocking | Breaking issue; production down; release blocker | -| `priority:important` | D93F0B | Must-do high priority | High-impact feature; significant bug | -| `priority:normal` | 0052CC | Default priority | Standard/planned work (default if not specified) | -| `priority:minor` | C2E0C6 | Low priority / nice to have | Backlog; can defer indefinitely | +| `priority:critical` | 810E18 | Production/launch-blocking | Breaking issue; production down; release blocker | +| `priority:important` | 883D07 | Must-do high priority | High-impact feature; significant bug | +| `priority:normal` | 0F448A | Default priority | Standard/planned work (default if not specified) | +| `priority:minor` | 1D7232 | Low priority / nice to have | Backlog; can defer indefinitely | --- @@ -86,38 +86,38 @@ Labels classifying the type of work. Assign exactly one per issue. | Label | Color | Description | Project Field | Notes | | --- | --- | --- | --- | --- | -| `type:task` | 4393F8 | Task or to-do | Task | Default for untyped work | -| `type:bug` | 9F3734 | Bug or defect | Bug | Broken/incorrect behaviour | -| `type:feature` | 3FB950 | Feature or enhancement | Feature | New capability | -| `type:enhancement` | 3FB950 | Enhancement/alias for improve | Feature | Alias for improve; enhancement to existing feature | -| `type:design` | AB7DF8 | Design work | Design | Design artefacts/decisions | -| `type:ui` | AB7DF8 | UI implementation | Design | UI consistency, implementation | -| `type:epic` | AB7DF8 | Large multi-scope initiative | Task | Parent issue for stories/tasks | -| `type:story` | 4393F8 | User story | Task | User-centred vertical slice | -| `type:improve` | 9198A1 | Improvement to existing behaviour/UX | Feature | Enhance existing feature | -| `type:refactor` | 9198A1 | Refactor or internal change | Chore | Internal restructure; no UX change | -| `type:build` | 4393F8 | Build & CI | Task | Build pipelines, tooling | -| `type:ci` | 4393F8 | CI/CD pipelines | Automation | CI/CD infrastructure | -| `type:automation` | 4393F8 | Automation | Automation | Bots, actions, scripts | -| `type:test` | D29922 | Testing/coverage | Automation | Testing and QA work; branch mapping rule: head-branch ["^test/.*", "^qa/.*"] | -| `type:performance` | D29922 | Performance improvement | Task | Speed, efficiency optimisation | -| `type:a11y` | DB61A2 | Accessibility | Design | Accessibility/WCAG work | -| `type:security` | 9F3734 | Security issue | Bug | Security concern/hardening | -| `type:compatibility` | 8D4821 | Compatibility | Task | Browser/device/plugin compatibility | -| `type:integration` | 8D4821 | Integration | Task | External system integration | -| `type:dependency` | 8D4821 | Dependency update | Integration | Dependency updates, version management | -| `type:release` | 3FB950 | Release | Release | Release planning/management | -| `type:maintenance` | 9198A1 | Maintenance | Task | Routine maintenance, updates | -| `type:documentation` | 9198A1 | Documentation | Documentation | Docs, guides, specifications | -| `type:research` | 9198A1 | Research / investigation | Task | Investigation, POC, spike | -| `type:investigation` | 9198A1 | Investigation | Research | Issue diagnosis, root cause analysis | -| `type:chore` | 9198A1 | Chore / small hygiene change | Task | Hygiene change, typos, config | -| `type:audit` | 9198A1 | Audit | Task | Security/code/process audit | -| `type:review` | 4393F8 | Code or design review task | Task | Peer review, validation | -| `type:ai-ops` | 4393F8 | AI Ops | Automation | AI, agents, datasets | -| `type:content-modelling` | AB7DF8 | Content Modelling | Design | Content structure, CPTs, taxonomy | -| `type:question` | 5319E7 | Question or request for clarification | Task | Clarification request | -| `type:support` | 0E8A16 | Support request | Task | Support/troubleshooting | +| `type:task` | 0F448A | Task or to-do | Task | Default for untyped work | +| `type:bug` | 810E18 | Bug or defect | Bug | Broken/incorrect behaviour | +| `type:feature` | 1D7232 | Feature or enhancement | Feature | New capability | +| `type:enhancement` | 1D7232 | Enhancement/alias for improve | Feature | Alias for improve; enhancement to existing feature | +| `type:design` | 4D1A93 | Design work | Design | Design artefacts/decisions | +| `type:ui` | 4D1A93 | UI implementation | Design | UI consistency, implementation | +| `type:epic` | 4D1A93 | Large multi-scope initiative | Task | Parent issue for stories/tasks | +| `type:story` | 0F448A | User story | Task | User-centred vertical slice | +| `type:improve` | 4E575F | Improvement to existing behaviour/UX | Feature | Enhance existing feature | +| `type:refactor` | 4E575F | Refactor or internal change | Chore | Internal restructure; no UX change | +| `type:build` | 0F448A | Build & CI | Task | Build pipelines, tooling | +| `type:ci` | 0F448A | CI/CD pipelines | Automation | CI/CD infrastructure | +| `type:automation` | 0F448A | Automation | Automation | Bots, actions, scripts | +| `type:test` | 7E6007 | Testing/coverage | Automation | Testing and QA work; branch mapping rule: head-branch ["^test/.*", "^qa/.*"] | +| `type:performance` | 7E6007 | Performance improvement | Task | Speed, efficiency optimisation | +| `type:a11y` | 4D1A93 | Accessibility | Design | Accessibility/WCAG work | +| `type:security` | 810E18 | Security issue | Bug | Security concern/hardening | +| `type:compatibility` | 883D07 | Compatibility | Task | Browser/device/plugin compatibility | +| `type:integration` | 883D07 | Integration | Task | External system integration | +| `type:dependency` | 883D07 | Dependency update | Integration | Dependency updates, version management | +| `type:release` | 1D7232 | Release | Release | Release planning/management | +| `type:maintenance` | 4E575F | Maintenance | Task | Routine maintenance, updates | +| `type:documentation` | 4E575F | Documentation | Documentation | Docs, guides, specifications | +| `type:research` | 4E575F | Research / investigation | Task | Investigation, POC, spike | +| `type:investigation` | 4E575F | Investigation | Research | Issue diagnosis, root cause analysis | +| `type:chore` | 4E575F | Chore / small hygiene change | Task | Hygiene change, typos, config | +| `type:audit` | 4E575F | Audit | Task | Security/code/process audit | +| `type:review` | 0F448A | Code or design review task | Task | Peer review, validation | +| `type:ai-ops` | 0F448A | AI Ops | Automation | AI, agents, datasets | +| `type:content-modelling` | 4D1A93 | Content Modelling | Design | Content structure, CPTs, taxonomy | +| `type:question` | 4D1A93 | Question or request for clarification | Task | Clarification request | +| `type:support` | 1D7232 | Support request | Task | Support/troubleshooting | --- @@ -127,13 +127,13 @@ Labels tracking automation markers, process state, and housekeeping. | Label | Color | Description | Usage | | --- | --- | --- | --- | -| `meta:needs-changelog` | E1E4E8 | Requires a changelog entry before merge | Applied by workflow; indicates CHANGELOG.md needs update | -| `meta:no-changelog` | E1E4E8 | No changelog needed | Applied when change doesn't warrant changelog entry | -| `meta:has-pr` | E1E4E8 | Issue has an open linked PR | Applied automatically when PR created | -| `meta:no-issue-activity` | E1E4E8 | No recent issue activity | Applied by automation for stale issues | -| `meta:no-pr-activity` | E1E4E8 | No recent PR activity | Applied by automation for stale PRs | -| `meta:stale` | 9198A1 | Marked as stale for review | Manual; indicates item needs fresh review | -| `meta:dependabot-security` | B60205 | Dependabot update appears security-related and eligible for guarded automation | Applied by Dependabot for security updates | +| `meta:needs-changelog` | 4E575F | Requires a changelog entry before merge | Applied by workflow; indicates CHANGELOG.md needs update | +| `meta:no-changelog` | 4E575F | No changelog needed | Applied when change doesn't warrant changelog entry | +| `meta:has-pr` | 4E575F | Issue has an open linked PR | Applied automatically when PR created | +| `meta:no-issue-activity` | 4E575F | No recent issue activity | Applied by automation for stale issues | +| `meta:no-pr-activity` | 4E575F | No recent PR activity | Applied by automation for stale PRs | +| `meta:stale` | 4E575F | Marked as stale for review | Manual; indicates item needs fresh review | +| `meta:dependabot-security` | 810E18 | Dependabot update appears security-related and eligible for guarded automation | Applied by Dependabot for security updates | --- @@ -143,10 +143,10 @@ Labels categorising release impact. | Label | Color | Description | Usage | | --- | --- | --- | --- | -| `release:patch` | 3FB950 | Patch release | Bug fixes, security patches | -| `release:minor` | 58A6FF | Minor release | New features, backwards-compatible | -| `release:major` | F85149 | Major release | Breaking changes | -| `release:hotfix` | D29922 | Urgent hotfix outside normal cadence | Emergency/production fix | +| `release:patch` | 1D7232 | Patch release | Bug fixes, security patches | +| `release:minor` | 0F448A | Minor release | New features, backwards-compatible | +| `release:major` | 810E18 | Major release | Breaking changes | +| `release:hotfix` | 7E6007 | Urgent hotfix outside normal cadence | Emergency/production fix | --- @@ -156,39 +156,39 @@ Labels identifying component, module, or domain. Multiple allowed per issue. | Label | Color | Description | | --- | --- | --- | -| `area:core` | C5DEF5 | Core / shared infrastructure | -| `area:labels` | C5DEF5 | Label governance and routing | -| `area:block-editor` | C5DEF5 | Block editor | -| `area:theme` | C5DEF5 | Theme & styles | -| `area:documentation` | C5DEF5 | Docs & guides | -| `area:tests` | D4C5F9 | Test suites & harnesses | -| `area:testing` | D4C5F9 | Testing and QA | -| `area:quality` | D4C5F9 | Quality validation and QA controls | -| `area:scripts` | C5DEF5 | Scripts & tooling | -| `area:assets` | C5DEF5 | Assets (images, fonts, static files) | -| `area:woocommerce` | D4C5F9 | WooCommerce | -| `area:content` | C5DEF5 | Content and copy | -| `area:design-system` | C5DEF5 | Design system and tokens | -| `area:navigation` | C5DEF5 | Navigation & menus | -| `area:forms` | C5DEF5 | Forms and form flows | -| `area:plugins` | C5DEF5 | Plugin configuration / logic | -| `area:search` | C5DEF5 | Search and filtering | -| `area:seo` | C2E0C6 | Technical SEO (meta, schema, sitemaps) | -| `area:ai` | C5DEF5 | AI and automation systems | -| `area:analytics` | C2E0C6 | Analytics & tracking | -| `area:infrastructure` | 006B75 | Infrastructure / hosting / platform | -| `area:automation` | BFD4F2 | Automation workflows and agents | -| `area:performance` | D29922 | Performance-focused work | -| `area:a11y` | DB61A2 | Accessibility-focused work | -| `area:security` | 9F3734 | Security-focused work | -| `area:compatibility` | 8D4821 | Compatibility and cross-environment concerns | -| `area:release` | 3FB950 | Release process and readiness | -| `area:maintenance` | 9198A1 | Maintenance and routine upkeep | -| `area:i18n` | C5DEF5 | Internationalisation | -| `area:ci` | BFD4F2 | Build and CI pipelines | -| `area:deployment` | 006B75 | Deploy/release operations | -| `area:dependencies` | F9D0C4 | Composer/npm dependency work | -| `area:integration` | D93F0B | 3rd-party integrations / ecosystem | +| `area:core` | 0F448A | Core / shared infrastructure | +| `area:labels` | 0F448A | Label governance and routing | +| `area:block-editor` | 0F448A | Block editor | +| `area:theme` | 0F448A | Theme & styles | +| `area:documentation` | 0F448A | Docs & guides | +| `area:tests` | 4D1A93 | Test suites & harnesses | +| `area:testing` | 4D1A93 | Testing and QA | +| `area:quality` | 4D1A93 | Quality validation and QA controls | +| `area:scripts` | 0F448A | Scripts & tooling | +| `area:assets` | 0F448A | Assets (images, fonts, static files) | +| `area:woocommerce` | 4D1A93 | WooCommerce | +| `area:content` | 0F448A | Content and copy | +| `area:design-system` | 0F448A | Design system and tokens | +| `area:navigation` | 0F448A | Navigation & menus | +| `area:forms` | 0F448A | Forms and form flows | +| `area:plugins` | 0F448A | Plugin configuration / logic | +| `area:search` | 0F448A | Search and filtering | +| `area:seo` | 1D7232 | Technical SEO (meta, schema, sitemaps) | +| `area:ai` | 0F448A | AI and automation systems | +| `area:analytics` | 1D7232 | Analytics & tracking | +| `area:infrastructure` | 147169 | Infrastructure / hosting / platform | +| `area:automation` | 0F448A | Automation workflows and agents | +| `area:performance` | 7E6007 | Performance-focused work | +| `area:a11y` | 4D1A93 | Accessibility-focused work | +| `area:security` | 810E18 | Security-focused work | +| `area:compatibility` | 883D07 | Compatibility and cross-environment concerns | +| `area:release` | 1D7232 | Release process and readiness | +| `area:maintenance` | 4E575F | Maintenance and routine upkeep | +| `area:i18n` | 0F448A | Internationalisation | +| `area:ci` | 0F448A | Build and CI pipelines | +| `area:deployment` | 147169 | Deploy/release operations | +| `area:dependencies` | 883D07 | Composer/npm dependency work | +| `area:integration` | 883D07 | 3rd-party integrations / ecosystem | --- @@ -198,26 +198,26 @@ Labels for component-specific work (Block Editor focus). | Label | Color | Description | | --- | --- | --- | -| `comp:block-editor` | C5DEF5 | Block/site editor work | -| `comp:block-inserter` | C5DEF5 | Inserter UI/behaviour | -| `comp:block-variations` | C5DEF5 | Block variations | -| `comp:block-supports` | C5DEF5 | Block supports | -| `comp:block-locking` | C5DEF5 | Block locking | -| `comp:block-bindings` | C5DEF5 | Block bindings | -| `comp:block-templates` | C5DEF5 | Block templates / template editor | -| `comp:block-patterns` | C5DEF5 | Patterns library/registration | -| `comp:template-parts` | C5DEF5 | Template parts (header/footer/loops) | -| `comp:block-json` | C5DEF5 | Block metadata (block.json) | -| `comp:theme-json` | C5DEF5 | Tokens, presets, settings (theme.json) | -| `comp:wp-admin` | C5DEF5 | WP Admin screens | -| `comp:settings` | C5DEF5 | Global/settings UX | -| `comp:post-settings` | C5DEF5 | Post editor settings panel | -| `comp:style-variations` | C5DEF5 | JSON style variations | -| `comp:block-styles` | C5DEF5 | Block styles registered via JSON | -| `comp:color-palette` | C5DEF5 | Palette tokens and usage | -| `comp:typography` | C5DEF5 | Type scale and typography tokens | -| `comp:section-styles` | C5DEF5 | Section/background styles | -| `comp:spacing` | C5DEF5 | Spacing tokens and layout gaps | +| `comp:block-editor` | 0F448A | Block/site editor work | +| `comp:block-inserter` | 0F448A | Inserter UI/behaviour | +| `comp:block-variations` | 0F448A | Block variations | +| `comp:block-supports` | 0F448A | Block supports | +| `comp:block-locking` | 0F448A | Block locking | +| `comp:block-bindings` | 0F448A | Block bindings | +| `comp:block-templates` | 0F448A | Block templates / template editor | +| `comp:block-patterns` | 0F448A | Patterns library/registration | +| `comp:template-parts` | 0F448A | Template parts (header/footer/loops) | +| `comp:block-json` | 0F448A | Block metadata (block.json) | +| `comp:theme-json` | 0F448A | Tokens, presets, settings (theme.json) | +| `comp:wp-admin` | 0F448A | WP Admin screens | +| `comp:settings` | 0F448A | Global/settings UX | +| `comp:post-settings` | 0F448A | Post editor settings panel | +| `comp:style-variations` | 0F448A | JSON style variations | +| `comp:block-styles` | 0F448A | Block styles registered via JSON | +| `comp:color-palette` | 0F448A | Palette tokens and usage | +| `comp:typography` | 0F448A | Type scale and typography tokens | +| `comp:section-styles` | 0F448A | Section/background styles | +| `comp:spacing` | 0F448A | Spacing tokens and layout gaps | --- @@ -227,13 +227,13 @@ Labels identifying primary programming language. | Label | Color | Description | | --- | --- | --- | -| `lang:php` | C5DEF5 | PHP | -| `lang:js` | C5DEF5 | JavaScript/TypeScript | -| `lang:css` | C5DEF5 | Stylesheets (CSS/Sass/etc.) | -| `lang:html` | C5DEF5 | Markup (HTML) | -| `lang:md` | C5DEF5 | Markdown content/docs | -| `lang:json` | C5DEF5 | JSON config/content | -| `lang:yaml` | C5DEF5 | YAML config | +| `lang:php` | 0F448A | PHP | +| `lang:js` | 0F448A | JavaScript/TypeScript | +| `lang:css` | 0F448A | Stylesheets (CSS/Sass/etc.) | +| `lang:html` | 0F448A | Markup (HTML) | +| `lang:md` | 0F448A | Markdown content/docs | +| `lang:json` | 0F448A | JSON config/content | +| `lang:yaml` | 0F448A | YAML config | --- @@ -243,9 +243,9 @@ Labels indicating deployment/work environment. | Label | Color | Description | | --- | --- | --- | -| `env:prototype` | E1E4E8 | Prototype/sandbox | -| `env:staging` | BFD4F2 | Staging/UAT | -| `env:live` | 0E8A16 | Live/production | +| `env:prototype` | 4E575F | Prototype/sandbox | +| `env:staging` | 0F448A | Staging/UAT | +| `env:live` | 1D7232 | Live/production | --- @@ -255,12 +255,12 @@ Labels for cross-platform/version compatibility. | Label | Color | Description | | --- | --- | --- | -| `compat:wordpress` | D93F0B | WordPress core/Gutenberg compatibility | -| `compat:php` | D93F0B | PHP version compatibility | -| `compat:woocommerce` | D93F0B | WooCommerce versions | -| `compat:gutenberg` | D93F0B | Gutenberg package compatibility | -| `compat:rtl` | D93F0B | RTL languages support | -| `compat:multisite` | F9D0C4 | Multisite/network considerations | +| `compat:wordpress` | 883D07 | WordPress core/Gutenberg compatibility | +| `compat:php` | 883D07 | PHP version compatibility | +| `compat:woocommerce` | 883D07 | WooCommerce versions | +| `compat:gutenberg` | 883D07 | Gutenberg package compatibility | +| `compat:rtl` | 883D07 | RTL languages support | +| `compat:multisite` | 883D07 | Multisite/network considerations | --- @@ -270,8 +270,8 @@ Labels for WordPress post type specificity. | Label | Color | Description | | --- | --- | --- | -| `cpt:posts` | C5DEF5 | WordPress Posts | -| `cpt:pages` | C5DEF5 | WordPress Pages | +| `cpt:posts` | 0F448A | WordPress Posts | +| `cpt:pages` | 0F448A | WordPress Pages | --- @@ -281,13 +281,13 @@ Labels for AI operations and automation infrastructure. | Label | Color | Description | | --- | --- | --- | -| `ai-ops:instructions` | 0052CC | AI instruction docs | -| `ai-ops:chat-modes` | 0052CC | Prompt sets / chat modes | -| `ai-ops:agents` | 0052CC | AI agent definitions | -| `ai-ops:prompts` | 0052CC | Reusable prompts | -| `ai-ops:datasets` | BFD4F2 | Training/evaluation datasets | -| `ai-ops:evaluations` | BFD4F2 | Evaluation results | -| `ai-ops:tools` | BFD4F2 | Tool/plugin manifests | +| `ai-ops:instructions` | 0F448A | AI instruction docs | +| `ai-ops:chat-modes` | 0F448A | Prompt sets / chat modes | +| `ai-ops:agents` | 0F448A | AI agent definitions | +| `ai-ops:prompts` | 0F448A | Reusable prompts | +| `ai-ops:datasets` | 0F448A | Training/evaluation datasets | +| `ai-ops:evaluations` | 0F448A | Evaluation results | +| `ai-ops:tools` | 0F448A | Tool/plugin manifests | --- @@ -297,9 +297,9 @@ Labels for community and contributor guidance. | Label | Color | Description | | --- | --- | --- | -| `contrib:good-first-issue` | D4C5F9 | Good for new contributors | -| `contrib:help-wanted` | C2E0C6 | Help wanted | -| `contrib:discussion` | C2E0C6 | Contributor/community discussion | +| `contrib:good-first-issue` | 4D1A93 | Good for new contributors | +| `contrib:help-wanted` | 1D7232 | Help wanted | +| `contrib:discussion` | 1D7232 | Contributor/community discussion | --- @@ -309,19 +309,19 @@ Labels for GitHub Discussions categorisation (not for issues/PRs). | Label | Color | Description | | --- | --- | --- | -| `discussion:announcement` | FBCA04 | Official announcements | -| `discussion:showcase` | 0E8A16 | Show & Tell | -| `discussion:community` | 6f42c1 | Community/general | -| `discussion:feedback` | 1d76db | Feedback/suggestions | -| `discussion:support` | d73a4a | Support/troubleshooting | -| `discussion:sponsorship` | f9d0c4 | Sponsorship/funding | -| `discussion:partnership` | bfd4f2 | Partnership/collaboration | +| `discussion:announcement` | 7E6007 | Official announcements | +| `discussion:showcase` | 1D7232 | Show & Tell | +| `discussion:community` | 4D1A93 | Community/general | +| `discussion:feedback` | 0F448A | Feedback/suggestions | +| `discussion:support` | 810E18 | Support/troubleshooting | +| `discussion:sponsorship` | 883D07 | Sponsorship/funding | +| `discussion:partnership` | 0F448A | Partnership/collaboration | --- ## Summary Statistics -**Total Labels**: 150+ +**Total Labels**: 158 | Family | Count | | --- | --- | @@ -339,7 +339,7 @@ Labels for GitHub Discussions categorisation (not for issues/PRs). | AI Ops | 7 | | Contributor | 3 | | Discussion | 7 | -| **TOTAL** | **~150** | +| **TOTAL** | **158** | --- diff --git a/docs/LABEL_STRATEGY.md b/docs/LABEL_STRATEGY.md index 305497406..01498b255 100644 --- a/docs/LABEL_STRATEGY.md +++ b/docs/LABEL_STRATEGY.md @@ -1,10 +1,10 @@ --- title: Label Strategy & Governance -description: Comprehensive label strategy defining the taxonomy, color families, assignment rules, and maintenance processes for 150 canonical labels +description: Comprehensive label strategy defining the taxonomy, colour families, assignment rules, and maintenance processes for 158 canonical labels file_type: documentation -version: v1.0.1 +version: v1.0.2 created_date: '2026-05-31' -last_updated: '2026-06-01' +last_updated: '2026-06-18' authors: - Claude Code - LightSpeed Team @@ -24,7 +24,7 @@ stability: stable # Label Strategy & Governance -**Version**: v1.0.0 +**Version**: v1.0.2 **Created**: 2026-05-31 **Owner**: LightSpeed Team **Reference Config**: `.github/labels.yml`, `.github/labeler.yml` @@ -33,7 +33,7 @@ stability: stable ## Executive Summary -This document establishes the comprehensive label strategy for the LightSpeed `.github` repository. It defines the taxonomy of 150 canonical labels, their organization into semantic families, color assignments, and the rules for applying labels to issues and PRs. +This document establishes the comprehensive label strategy for the LightSpeed `.github` repository. It defines the taxonomy of 158 canonical labels, their organization into semantic families, colour assignments, and the rules for applying labels to issues and PRs. **Strategic Goals**: @@ -47,9 +47,9 @@ This document establishes the comprehensive label strategy for the LightSpeed `. ## 1. Label Taxonomy -### 1.1 Primary Categories (7 Families, 150 Labels) +### 1.1 Primary Categories (7 Core Families, 158 Labels) -The 150 canonical labels are organized into 7 primary families: +The canonical labels are organized into seven core families, with supporting cross-cutting families documented in the inventory: | Family | Count | Purpose | Key Labels | | --- | --- | --- | --- | @@ -61,7 +61,7 @@ The 150 canonical labels are organized into 7 primary families: | **Discussion** | 7 | Discussion/meta categories | announcement, showcase, community, feedback, support | | **Meta** | 16 | Automation & process markers | has-pr, needs-changelog, blocked-by, duplicate | -**Total**: 150 labels across 7 families +**Total**: 158 labels across 7 core families --- @@ -194,13 +194,13 @@ The 150 canonical labels are organized into 7 primary families: --- -## 3. Color Strategy +## 3. Colour Strategy The complete color strategy is documented in [`docs/LABEL_COLOR_STRATEGY.md`](./LABEL_COLOR_STRATEGY.md). Key principles: - **8 colour families** reduce cognitive load whilst maintaining semantic clarity - Each family serves a specific semantic purpose (ready/done, blocked/urgent, testing, etc.) -- Colours are chosen for accessibility (WCAG AA contrast minimum) +- Colours are chosen for accessibility (WCAG 2.2 AA contrast minimum) - Related labels within a family use consistent colours - New labels default to established family colours, not ad-hoc selections @@ -284,7 +284,7 @@ All labels meet WCAG 2.2 AA contrast requirements: ## 7. Related Documentation - [`docs/LABEL_COLOR_STRATEGY.md`](./LABEL_COLOR_STRATEGY.md) — Detailed colour assignments and justifications -- [`docs/LABEL_INVENTORY.md`](./LABEL_INVENTORY.md) — Complete inventory of all 150 labels by family +- [`docs/LABEL_INVENTORY.md`](./LABEL_INVENTORY.md) — Complete inventory of all 158 labels by family - [`docs/ISSUE_TYPES.md`](./ISSUE_TYPES.md) — Type definitions and decision tree - [`docs/ISSUE_FIELDS.md`](./ISSUE_FIELDS.md) — Project field mappings and rationale - [`.github/labels.yml`](../.github/labels.yml) — Canonical label definitions diff --git a/docs/PR_CREATION_PROCESS.md b/docs/PR_CREATION_PROCESS.md index 11bf26594..e25791d15 100644 --- a/docs/PR_CREATION_PROCESS.md +++ b/docs/PR_CREATION_PROCESS.md @@ -128,13 +128,15 @@ Examples: ## 6. **Apply Labels and Milestones** - Labels are set automatically based on branch prefix and file changes, but review and add as needed: - - **Type:** `type:feature`, `type:bug`, `type:docs`, etc. +- **Type:** `type:feature`, `type:bug`, `type:documentation`, etc. - **Area/Component:** `area:ci`, `comp:block-editor`, etc. - **Status:** `status:needs-review`, `status:needs-qa`, etc. - **Release:** `release:minor`, `release:patch`, `release:major`, etc. - **Meta:** `meta:needs-changelog`, `contrib:help-wanted`, etc. - **Assign to the relevant milestone** (e.g., "Phase 6 - GC & Production") and project board if applicable. +- **Assignee:** the PR author is auto-assigned unless a stronger repository rule applies. +- **Linked development:** reference related issues or PRs with `Closes #`, `Fixes #`, or `Related to #` so metadata governance can capture the relation. --- diff --git a/docs/WORKFLOW_COORDINATION.md b/docs/WORKFLOW_COORDINATION.md index 8e73ccab0..97de54df0 100644 --- a/docs/WORKFLOW_COORDINATION.md +++ b/docs/WORKFLOW_COORDINATION.md @@ -2,8 +2,8 @@ title: "Workflow Coordination Patterns" description: "Canonical reference for GitHub Actions workflow patterns: always-run vs. agent-triggered, coordination between agents and workflows, and orchestration strategies." created_date: "2026-05-28" -last_updated: "2026-06-08" -version: "v1.1.1" +last_updated: "2026-06-18" +version: "v1.1.2" file_type: "documentation" maintainer: "LightSpeed Team" tags: ["workflows", "automation", "agents", "coordination", "ci-cd"] @@ -48,6 +48,8 @@ Always-run workflows trigger automatically on push/PR events without agent invol | `labeling.yml` | issue/PR/discussion events | Auto-apply canonical labels | ❌ No (metadata only) | | `issues.yml` | issue opened/edited | Validate issue templates | ❌ No (validation only) | | `meta.yml` | PR opened/issues | Apply frontmatter validation | ❌ No (metadata only) | +| `metadata-governance.yml` | issues / pull_request_target | Assign assignees, milestones, and relationship metadata | ❌ No (metadata only) | +| `project-meta-sync.yml` | push / issues / pull_request | Sync GitHub Project board fields | ❌ No (metadata only) | | `readme-regen.yml` | push/PR on `.md` files | Validate/regenerate README indices | ❌ No (informational) | ### When to Use @@ -87,7 +89,6 @@ Agent-triggered workflows are invoked **on demand** via `workflow_dispatch` or c | `reporting.yml` | workflow_dispatch | Reporting Agent | Generate audit/metric reports | | `reviewer.yml` | workflow_dispatch (manual) | Reviewer Agent | Post PR review summaries | | `metrics.yml` | workflow_dispatch ± scheduled | Metrics Agent | Collect repo health metrics | -| `project-meta-sync.yml` | workflow_dispatch | Project Meta Sync Agent | Sync GitHub Project board fields | | `readme-update.yml` | workflow_dispatch + workflow_call | Release Agent (post-release) | Apply README & Mermaid diagram fixes | | `issue-create-from-template.yml` | workflow_dispatch | Issue Agent / LLM caller | Create issues from canonical templates before the issue exists | | `checklist-finalisation.yml` | issues.closed / pull_request_target.closed | Workflow backstop | Finalise checklists in closed issues and merged PRs | @@ -443,10 +444,10 @@ with: ## References -- [Next Issues Execution Plan](../projects/active/next-issues-execution-plan.md) — Overall roadmap +- [Next Issues Execution Plan](../.github/projects/active/next-issues-execution-plan.md) — Overall roadmap - [Workflow Instructions](../instructions/workflows.instructions.md) — Workflow authoring standards - [GitHub Actions Documentation](https://docs.github.com/en/actions) — Official reference -- [Release Agent Specification](../../agents/release.agent.md) — Release orchestration contract +- [Release Agent Specification](../agents/release.agent.md) — Release orchestration contract - [Workflow Files](../workflows/) — Repository workflow implementations --- diff --git a/docs/agents/PLANNER_RUNBOOK.md b/docs/agents/PLANNER_RUNBOOK.md index be7cabd20..92cd1679a 100644 --- a/docs/agents/PLANNER_RUNBOOK.md +++ b/docs/agents/PLANNER_RUNBOOK.md @@ -215,7 +215,7 @@ Monitor logs for errors with `event: "error"`: ``` Title: Design new caching layer -Labels: type:architecture, priority:high +Labels: type:architecture, priority:important Body: We need a distributed cache for session management. See #100 for context. ``` diff --git a/instructions/automation.instructions.md b/instructions/automation.instructions.md index fb36a57fc..1cca23953 100644 --- a/instructions/automation.instructions.md +++ b/instructions/automation.instructions.md @@ -4,8 +4,8 @@ title: Automation Standards description: Comprehensive standards for GitHub automation agents, workflows, and repository health management scope: repo-local -version: v1.1 -last_updated: '2026-05-29' +version: v1.1.1 +last_updated: '2026-06-18' owners: - GitHub Community Health Team tags: @@ -281,7 +281,7 @@ Automatically apply, validate, and manage labels on issues and pull requests bas | ------------- | ------------ | -------------------- | ------------------------------------------- | | **Status** | `status:*` | Issue/PR lifecycle | `status:needs-triage`, `status:in-progress` | | **Type** | `type:*` | Work classification | `type:bug`, `type:feature` | -| **Priority** | `priority:*` | Urgency | `priority:high`, `priority:low` | +| **Priority** | `priority:*` | Urgency | `priority:critical`, `priority:minor` | | **Area** | `area:*` | Repository section | `area:docs`, `area:workflows` | | **Component** | `comp:*` | Specific component | `comp:ci-cd`, `comp:automation` | | **Language** | `lang:*` | Programming language | `lang:javascript`, `lang:yaml` | @@ -545,8 +545,10 @@ Sync GitHub Project board fields with issue/PR metadata and labels. | `status:needs-triage` | Status | Triage | | `status:in-progress` | Status | In Progress | | `status:needs-review` | Status | Review | -| `priority:high` | Priority | High | +| `priority:critical` | Priority | Critical | +| `priority:important` | Priority | Important | | `priority:normal` | Priority | Normal | +| `priority:minor` | Priority | Minor | | `type:bug` | Type | Bug | | `type:feature` | Type | Feature | @@ -565,7 +567,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - run: node ../scripts/agents/project-meta-sync.agent.js + - run: node ../scripts/agents/includes/derive-project-fields.cjs env: GH_TOKEN: ${{ secrets.PROJECT_TOKEN }} ``` diff --git a/instructions/project-meta-sync.instructions.md b/instructions/project-meta-sync.instructions.md index f5e09a463..19322b0d1 100644 --- a/instructions/project-meta-sync.instructions.md +++ b/instructions/project-meta-sync.instructions.md @@ -1,8 +1,9 @@ --- file_type: instructions title: Project Meta Sync Instructions -description: Standards for syncing GitHub Project board meta fields (Status, Priority, - Type) from issue/PR labels and branch names +description: Deprecated compatibility guidance for the legacy project meta sync + entrypoint. The active contract lives in the project-meta-sync and + metadata-governance workflows plus their helper scripts. version: v1.1 last_updated: '2026-05-29' owners: @@ -19,4 +20,7 @@ apply_to: - ../agents/project-meta-sync.agent.md - scripts/agents/project-meta-sync.agent.js - .github/workflows/project-meta-sync.yml +- .github/workflows/metadata-governance.yml +- scripts/agents/includes/derive-project-fields.cjs +- scripts/agents/includes/issue-pr-metadata.cjs --- diff --git a/scripts/agents/__tests__/labeling.agent.test.js b/scripts/agents/__tests__/labeling.agent.test.js index 354eb633f..903eee198 100644 --- a/scripts/agents/__tests__/labeling.agent.test.js +++ b/scripts/agents/__tests__/labeling.agent.test.js @@ -1,13 +1,77 @@ /** - * Jest suite verifying the baseline behaviour of `labeling.agent.js`. + * Jest suite verifying the exported behaviour of `labeling.agent.js`. * @see ../labeling.agent.js */ -const fs = require("fs"); -const path = require("path"); + +let agent; + +beforeAll(async () => { + agent = await import("../labeling.agent.js"); +}); describe("labeling.agent", () => { - it("agent module file exists", () => { - const agentPath = path.join(__dirname, "../labeling.agent.js"); - expect(fs.existsSync(agentPath)).toBe(true); + it("exports the helper surface used by the workflow", () => { + expect(agent).toEqual( + expect.objectContaining({ + detectIssueTypeFromContent: expect.any(Function), + detectTypeFromBranch: expect.any(Function), + loadCanonicalLabels: expect.any(Function), + loadAliasMap: expect.any(Function), + runLabelingAgent: expect.any(Function), + }), + ); + }); + + it("maps branch prefixes to canonical type labels", () => { + expect(agent.detectTypeFromBranch("feat/metadata-governance")).toBe( + "type:feature", + ); + expect(agent.detectTypeFromBranch("docs/update-issue-guide")).toBe( + "type:documentation", + ); + expect(agent.detectTypeFromBranch("hotfix/release-blocker")).toBe( + "type:bug", + ); + }); + + it("detects type from content keywords", () => { + expect( + agent.detectIssueTypeFromContent( + "Document the new metadata governance flow", + "", + ), + ).toBe("type:documentation"); + expect(agent.detectIssueTypeFromContent("Fix workflow regression", "")).toBe( + "type:bug", + ); + }); + + it("returns a no-op report when no issue or PR is in context", async () => { + const result = await agent.runLabelingAgent({ + context: { + repo: { + owner: "lightspeedwp", + repo: ".github", + }, + payload: {}, + }, + github: { + rest: { + issues: { + addLabels: jest.fn(), + removeLabel: jest.fn(), + }, + }, + }, + dryRun: true, + }); + + expect(result).toMatchObject({ + success: true, + added: [], + removed: [], + migrated: [], + errors: [], + }); }); }); diff --git a/scripts/agents/__tests__/project-meta-sync.agent.test.js b/scripts/agents/__tests__/project-meta-sync.agent.test.js index ff78d97b0..98373d430 100644 --- a/scripts/agents/__tests__/project-meta-sync.agent.test.js +++ b/scripts/agents/__tests__/project-meta-sync.agent.test.js @@ -1,17 +1,26 @@ /** - * Jest suite verifying the baseline behaviour of `project-meta-sync.agent.js`. + * Jest suite verifying the deprecated compatibility behaviour of `project-meta-sync.agent.js`. * @see ../project-meta-sync.agent.js */ const agent = require("../project-meta-sync.agent"); describe("project-meta-sync.agent", () => { - it("exports a callable function", () => { + it("exports a callable compatibility shim", () => { expect(typeof agent).toBe("function"); }); - it("does not execute run() on require (no LS_PROJECT_URL side-effect)", () => { - // If the module-scope guard is absent, requiring the file calls run() immediately, - // which throws "LS_PROJECT_URL not set" and sets process.exitCode = 1. - expect(process.exitCode).not.toBe(1); + it("returns the current workflow replacement contract", async () => { + const result = await agent(); + + expect(result).toMatchObject({ + ok: true, + deprecated: true, + replacement: { + workflow: ".github/workflows/project-meta-sync.yml", + metadata_workflow: ".github/workflows/metadata-governance.yml", + helper: "scripts/agents/includes/derive-project-fields.cjs", + metadata_helper: "scripts/agents/includes/issue-pr-metadata.cjs", + }, + }); }); }); diff --git a/scripts/agents/includes/__tests__/derive-project-fields.test.js b/scripts/agents/includes/__tests__/derive-project-fields.test.js new file mode 100644 index 000000000..6b7cd70be --- /dev/null +++ b/scripts/agents/includes/__tests__/derive-project-fields.test.js @@ -0,0 +1,51 @@ +const fs = require("fs"); +const path = require("path"); +const yaml = require("js-yaml"); +const { deriveProjectFieldValues } = require("../derive-project-fields.cjs"); + +const issueFieldsConfig = yaml.load( + fs.readFileSync( + path.join(__dirname, "../../../../.github/issue-fields.yml"), + "utf8", + ), +); + +describe("derive-project-fields.cjs", () => { + test("keeps kickoff dates empty until ready or in-progress", () => { + const result = deriveProjectFieldValues({ + cfg: issueFieldsConfig, + labels: ["status:needs-triage", "priority:normal", "type:task"], + eventName: "issues", + eventAction: "opened", + itemCreatedAt: "2026-06-18T10:15:00Z", + milestoneDueOn: "2026-07-18T00:00:00Z", + }); + + expect(result).toMatchObject({ + status: "Triage", + priority: "Normal", + type: "Task", + startDate: "", + targetDate: "", + }); + }); + + test("populates kickoff dates when the item is ready to start", () => { + const result = deriveProjectFieldValues({ + cfg: issueFieldsConfig, + labels: ["status:ready", "priority:important", "type:feature"], + eventName: "issues", + eventAction: "edited", + itemCreatedAt: "2026-06-18T10:15:00Z", + milestoneDueOn: "2026-07-18T00:00:00Z", + }); + + expect(result).toMatchObject({ + status: "Ready", + priority: "Important", + type: "Feature", + startDate: "2026-06-18", + targetDate: "2026-07-18", + }); + }); +}); diff --git a/scripts/agents/includes/__tests__/issue-pr-metadata.test.js b/scripts/agents/includes/__tests__/issue-pr-metadata.test.js new file mode 100644 index 000000000..3306f281a --- /dev/null +++ b/scripts/agents/includes/__tests__/issue-pr-metadata.test.js @@ -0,0 +1,277 @@ +const { + deriveMilestoneTitle, + extractIssueRefs, + formatRelationshipComment, + getItemFromEvent, + parseRelationshipHints, + syncItemMetadata, +} = require("../issue-pr-metadata.cjs"); + +describe("issue-pr-metadata helpers", () => { + test("derives an assignee-ready milestone title from a release issue", () => { + expect( + deriveMilestoneTitle( + "Release v0.6.0 — Community Health, Governance Docs, and Meta Agent Foundations", + ), + ).toBe( + "Release v0.6.0 — Community Health, Governance Docs, and Meta Agent Foundations".slice( + 0, + 80, + ), + ); + }); + + test("extracts linked issue references from PR wording", () => { + expect(extractIssueRefs("Fixes #965\nRelated to #42")).toEqual([965, 42]); + }); + + test("parses relationship hints from issue body sections", () => { + const hints = parseRelationshipHints( + [ + "Fixes #11", + "Parent issue: #12", + "Child issues: #13, #14", + "Blocks #15", + "Blocked by #16", + "Security alert: GHSA-ABCD-1234-ABCD", + ].join("\n"), + ); + + expect(hints).toEqual({ + linkedRefs: [11], + parentRefs: [12], + childRefs: [13, 14], + blocksRefs: [15], + blockedByRefs: [16], + securityRefs: ["GHSA-ABCD-1234-ABCD"], + }); + }); + + test("builds a readable relationship comment", () => { + const comment = formatRelationshipComment( + { number: 99, author: "ashleyshaw" }, + { + linkedRefs: [11], + parentRefs: [12], + childRefs: [13, 14], + blocksRefs: [15], + blockedByRefs: [16], + securityRefs: ["GHSA-ABCD-1234-ABCD"], + }, + "Release v0.6.0", + ); + + expect(comment).toContain(""); + expect(comment).toContain("Linked issues/PRs: #11"); + expect(comment).toContain("Parent issue: #12"); + expect(comment).toContain("Child issues: #13, #14"); + expect(comment).toContain("Security linkage: GHSA-ABCD-1234-ABCD"); + }); + + test("reads the live issue flow payload shape", () => { + const item = getItemFromEvent({ + issue: { + number: 968, + node_id: "MDU6SXNzdWU5Njg=", + title: "Release v0.6.0 — Community Health, Governance Docs, and Meta Agent Foundations", + body: "## Release Summary", + labels: [], + milestone: null, + user: { login: "ashleyshaw" }, + }, + }); + + expect(item).toMatchObject({ + kind: "issue", + number: 968, + author: "ashleyshaw", + }); + }); + + test("reads the live PR flow payload shape", () => { + const item = getItemFromEvent({ + pull_request: { + number: 966, + node_id: "MDExOlB1bGxSZXF1ZXN0", + title: "fix(issue-templates): add `about` field, align with 25 org issue types, and polish", + body: "Fixes #965", + labels: [], + milestone: null, + user: { login: "ashleyshaw" }, + }, + }); + + expect(item).toMatchObject({ + kind: "pull_request", + number: 966, + author: "ashleyshaw", + }); + expect(extractIssueRefs(item.body)).toEqual([965]); + }); + + test("syncs issue metadata with requester assignee and milestone fallback", async () => { + const createdMilestones = []; + const updatedIssues = []; + const addedAssignees = []; + const createdComments = []; + + const github = { + paginate: jest.fn().mockResolvedValue([]), + graphql: jest.fn().mockResolvedValue({ + repository: { + issue: { + id: "ISSUE-ID", + }, + }, + }), + rest: { + issues: { + listMilestones: jest.fn(), + createMilestone: jest.fn().mockImplementation(async ({ title }) => { + const milestone = { number: 77, title }; + createdMilestones.push(milestone); + return { data: milestone }; + }), + addAssignees: jest.fn().mockImplementation(async (args) => { + addedAssignees.push(args); + }), + update: jest.fn().mockImplementation(async (args) => { + updatedIssues.push(args); + }), + listComments: jest.fn().mockResolvedValue([]), + createComment: jest.fn().mockImplementation(async (args) => { + createdComments.push(args); + }), + updateComment: jest.fn(), + get: jest.fn().mockResolvedValue({ + data: { milestone: null }, + }), + }, + }, + }; + + const result = await syncItemMetadata({ + github, + owner: "lightspeedwp", + repo: ".github", + event: { + issue: { + number: 968, + node_id: "MDU6SXNzdWU5Njg=", + title: "Release v0.6.0 — Community Health, Governance Docs, and Meta Agent Foundations", + body: "Fixes #965", + labels: [], + milestone: null, + user: { login: "ashleyshaw" }, + }, + }, + config: { + defaults: { + issue: { + assignee: "ashleyshaw", + }, + }, + }, + }); + + expect(result).toMatchObject({ + assignee: "ashleyshaw", + milestone: "Release v0.6.0 — Community Health, Governance Docs, and Meta Agent Foundations".slice( + 0, + 80, + ), + }); + expect(addedAssignees).toHaveLength(1); + expect(addedAssignees[0]).toMatchObject({ + issue_number: 968, + assignees: ["ashleyshaw"], + }); + expect(createdMilestones).toHaveLength(1); + expect(updatedIssues).toHaveLength(1); + expect(createdComments).toHaveLength(1); + expect(createdComments[0].body).toContain("Linked issues/PRs: #965"); + }); + + test("syncs pull request metadata and inherits milestone from linked issue", async () => { + const updatedIssues = []; + const createdComments = []; + + const github = { + paginate: jest.fn().mockResolvedValue([]), + graphql: jest.fn().mockResolvedValue({ + repository: { + issue: { + id: "ISSUE-ID", + }, + }, + }), + rest: { + issues: { + listMilestones: jest.fn(), + createMilestone: jest.fn().mockResolvedValue({ + data: { + number: 101, + title: "Release v0.6.0", + }, + }), + addAssignees: jest.fn(), + update: jest.fn().mockImplementation(async (args) => { + updatedIssues.push(args); + }), + listComments: jest.fn().mockResolvedValue([]), + createComment: jest.fn().mockImplementation(async (args) => { + createdComments.push(args); + }), + updateComment: jest.fn(), + get: jest.fn().mockResolvedValue({ + data: { + milestone: { + number: 88, + title: "Release v0.6.0", + }, + }, + }), + }, + }, + }; + + const result = await syncItemMetadata({ + github, + owner: "lightspeedwp", + repo: ".github", + event: { + pull_request: { + number: 966, + node_id: "MDExOlB1bGxSZXF1ZXN0", + title: "fix(issue-templates): add `about` field, align with 25 org issue types, and polish", + body: "Fixes #965", + labels: [], + milestone: null, + user: { login: "ashleyshaw" }, + }, + }, + config: { + defaults: { + issue: { + assignee: "ashleyshaw", + }, + }, + }, + }); + + expect(result).toMatchObject({ + assignee: "ashleyshaw", + milestone: "Release v0.6.0", + relationships: { + linkedRefs: [965], + }, + }); + expect(updatedIssues).toHaveLength(1); + expect(updatedIssues[0]).toMatchObject({ + issue_number: 966, + milestone: 88, + }); + expect(createdComments).toHaveLength(1); + expect(createdComments[0].body).toContain("Linked issues/PRs: #965"); + }); +}); diff --git a/scripts/agents/includes/__tests__/label-utils.test.js b/scripts/agents/includes/__tests__/label-utils.test.js index c47526288..a1ad3e8e1 100644 --- a/scripts/agents/includes/__tests__/label-utils.test.js +++ b/scripts/agents/includes/__tests__/label-utils.test.js @@ -53,12 +53,12 @@ describe("label-utils.js", () => { }); test("formats labels with special characters", () => { - const labels = ["type:bug", "area:core", "priority:high"]; + const labels = ["type:bug", "area:core", "priority:important"]; const result = labelsToMarkdownTable(labels); expect(result).toContain("| `type:bug` |"); expect(result).toContain("| `area:core` |"); - expect(result).toContain("| `priority:high` |"); + expect(result).toContain("| `priority:important` |"); }); test("handles labels with spaces", () => { @@ -330,8 +330,8 @@ describe("label-utils.js", () => { }); test("handles perfect alignment (no diff)", () => { - const current = ["type:bug", "status:in-progress", "priority:high"]; - const canonical = ["type:bug", "status:in-progress", "priority:high"]; + const current = ["type:bug", "status:in-progress", "priority:important"]; + const canonical = ["type:bug", "status:in-progress", "priority:important"]; const diff = diffLabels(current, canonical); diff --git a/scripts/agents/includes/derive-project-fields.cjs b/scripts/agents/includes/derive-project-fields.cjs index c57d71a97..5d0e310b6 100644 --- a/scripts/agents/includes/derive-project-fields.cjs +++ b/scripts/agents/includes/derive-project-fields.cjs @@ -19,22 +19,15 @@ function firstMatch(labels, mapping) { return ""; } -function main() { - const configPath = process.env.ISSUE_FIELDS_CONFIG - ? path.resolve(process.env.ISSUE_FIELDS_CONFIG) - : path.resolve(".github/issue-fields.yml"); - - const cfg = readConfig(configPath); - const labels = (process.env.LABELS || "") - .split("\n") - .map((s) => s.trim()) - .filter(Boolean); - - const eventName = process.env.EVENT_NAME || ""; - const eventAction = process.env.EVENT_ACTION || ""; - const prMerged = (process.env.PR_MERGED || "false").toLowerCase() === "true"; - const itemCreatedAt = process.env.ITEM_CREATED_AT || ""; - +function deriveProjectFieldValues({ + cfg, + labels = [], + eventName = "", + eventAction = "", + prMerged = false, + itemCreatedAt = "", + milestoneDueOn = "", +} = {}) { const mappings = cfg.project_field_mappings || {}; const orgFields = cfg.organization_issue_fields || {}; const customFields = Array.isArray(orgFields.custom_fields) @@ -48,6 +41,10 @@ function main() { let type = firstMatch(labels, mappings.Type); let effort = effortDefault; let startDate = ""; + let targetDate = ""; + + const isKickoff = + labels.includes("status:ready") || labels.includes("status:in-progress"); if (eventName === "issues" && eventAction === "closed") { status = mappings.Status?.[cfg.defaults?.issue?.status_label_closed] || "Done"; @@ -61,21 +58,53 @@ function main() { if (!status) status = mappings.Status?.["status:needs-triage"] || "Triage"; if (!type) type = ""; - if ( - itemCreatedAt && - (eventAction === "opened" || eventAction === "reopened") && - status !== "Done" - ) { + if (itemCreatedAt && isKickoff && status !== "Done") { startDate = itemCreatedAt.slice(0, 10); } + if (milestoneDueOn && isKickoff && status !== "Done") { + targetDate = milestoneDueOn.slice(0, 10); + } + + return { + status, + priority, + type, + effort, + startDate, + targetDate, + }; +} + +function main() { + const configPath = process.env.ISSUE_FIELDS_CONFIG + ? path.resolve(process.env.ISSUE_FIELDS_CONFIG) + : path.resolve(".github/issue-fields.yml"); + + const cfg = readConfig(configPath); + const labels = (process.env.LABELS || "") + .split("\n") + .map((s) => s.trim()) + .filter(Boolean); + + const result = deriveProjectFieldValues({ + cfg, + labels, + eventName: process.env.EVENT_NAME || "", + eventAction: process.env.EVENT_ACTION || "", + prMerged: (process.env.PR_MERGED || "false").toLowerCase() === "true", + itemCreatedAt: process.env.ITEM_CREATED_AT || "", + milestoneDueOn: process.env.ITEM_MILESTONE_DUE_ON || "", + }); + const output = process.env.GITHUB_OUTPUT; const lines = [ - `status=${status}`, - `priority=${priority || ""}`, - `type=${type}`, - `effort=${effort || ""}`, - `start_date=${startDate}`, + `status=${result.status}`, + `priority=${result.priority || ""}`, + `type=${result.type}`, + `effort=${result.effort || ""}`, + `start_date=${result.startDate}`, + `target_date=${result.targetDate}`, ]; if (output) { @@ -85,4 +114,12 @@ function main() { } } -main(); +if (require.main === module) { + main(); +} + +module.exports = { + deriveProjectFieldValues, + firstMatch, + readConfig, +}; diff --git a/scripts/agents/includes/issue-pr-metadata.cjs b/scripts/agents/includes/issue-pr-metadata.cjs new file mode 100644 index 000000000..0da6ec551 --- /dev/null +++ b/scripts/agents/includes/issue-pr-metadata.cjs @@ -0,0 +1,484 @@ +#!/usr/bin/env node +/* eslint-disable no-console */ + +const fs = require("fs"); +const path = require("path"); +const yaml = require("js-yaml"); +const { getOctokit } = require("@actions/github"); + +const COMMENT_MARKER = ""; +const GHSA_RE = /\bGHSA-[A-Z0-9-]+\b/gi; +const CVE_RE = /\bCVE-\d{4}-\d{4,7}\b/gi; + +function readJsonFile(filePath) { + return JSON.parse(fs.readFileSync(filePath, "utf8")); +} + +function readConfig(configPath) { + return yaml.load(fs.readFileSync(configPath, "utf8")); +} + +function normaliseTitle(title) { + return String(title || "") + .replace(/^\[[^\]]+\]\s*/, "") + .replace(/\s+/g, " ") + .trim(); +} + +function deriveMilestoneTitle(title) { + const cleaned = normaliseTitle(title); + return cleaned.slice(0, 80) || "Untriaged"; +} + +function getActorLogin(event) { + return ( + event?.sender?.login || + event?.issue?.user?.login || + event?.pull_request?.user?.login || + "" + ); +} + +function getItemFromEvent(event) { + if (event.pull_request) { + return { + kind: "pull_request", + number: event.pull_request.number, + nodeId: event.pull_request.node_id, + title: event.pull_request.title || "", + body: event.pull_request.body || "", + labels: Array.isArray(event.pull_request.labels) + ? event.pull_request.labels + : [], + milestone: event.pull_request.milestone || null, + author: event.pull_request.user?.login || "", + }; + } + + return { + kind: "issue", + number: event.issue.number, + nodeId: event.issue.node_id, + title: event.issue.title || "", + body: event.issue.body || "", + labels: Array.isArray(event.issue.labels) ? event.issue.labels : [], + milestone: event.issue.milestone || null, + author: event.issue.user?.login || "", + }; +} + +function extractIssueRefs(text) { + const refs = new Set(); + const source = String(text || ""); + const matches = source.matchAll(/#(\d+)/g); + + for (const match of matches) { + const value = Number(match[1]); + if (Number.isInteger(value) && value > 0) { + refs.add(value); + } + } + + return [...refs]; +} + +function extractSecurityRefs(text) { + const source = String(text || ""); + return [...new Set([...(source.match(GHSA_RE) || []), ...(source.match(CVE_RE) || [])])]; +} + +function parseRelationshipHints(body) { + const lines = String(body || "") + .replace(/\r\n/g, "\n") + .split("\n") + .map((line) => line.trim()); + + const linkedRefs = new Set(); + const parentRefs = new Set(); + const childRefs = new Set(); + const blocksRefs = new Set(); + const blockedByRefs = new Set(); + const securityRefs = new Set(); + + for (const line of lines) { + const lower = line.toLowerCase(); + const refs = extractIssueRefs(line); + + if ( + lower.startsWith("fixes ") || + lower.startsWith("fixes:") || + lower.startsWith("closes ") || + lower.startsWith("closes:") || + lower.startsWith("resolves ") || + lower.startsWith("resolves:") || + lower.startsWith("relates to ") || + lower.startsWith("relates to:") || + lower.startsWith("related to ") || + lower.startsWith("related to:") + ) { + refs.forEach((ref) => linkedRefs.add(ref)); + } else if (lower.startsWith("parent issue:") || lower.startsWith("parent:")) { + refs.forEach((ref) => parentRefs.add(ref)); + } else if ( + lower.startsWith("child issue:") || + lower.startsWith("child issues:") || + lower.startsWith("child:") || + lower.startsWith("children:") || + lower.startsWith("sub-issues:") || + lower.startsWith("sub-issue:") + ) { + refs.forEach((ref) => childRefs.add(ref)); + } else if (lower.startsWith("blocks:") || lower.startsWith("blocks ")) { + refs.forEach((ref) => blocksRefs.add(ref)); + } else if (lower.startsWith("blocked by:") || lower.startsWith("blocked by ")) { + refs.forEach((ref) => blockedByRefs.add(ref)); + } + + if (/security alert|security advisory|ghsa|cve/i.test(line)) { + extractSecurityRefs(line).forEach((ref) => securityRefs.add(ref)); + } + } + + return { + linkedRefs: [...linkedRefs], + parentRefs: [...parentRefs], + childRefs: [...childRefs], + blocksRefs: [...blocksRefs], + blockedByRefs: [...blockedByRefs], + securityRefs: [...securityRefs], + }; +} + +function formatRelationshipComment(item, hints, milestoneSummary, assignee) { + const lines = [ + COMMENT_MARKER, + "## Metadata governance", + `- Item: #${item.number}`, + `- Assignee: ${assignee || item.author || "unassigned"}`, + `- Milestone: ${milestoneSummary || "none"}`, + ]; + + if (hints.linkedRefs.length > 0) { + lines.push(`- Linked issues/PRs: ${hints.linkedRefs.map((ref) => `#${ref}`).join(", ")}`); + } + + if (hints.parentRefs.length > 0) { + lines.push(`- Parent issue: ${hints.parentRefs.map((ref) => `#${ref}`).join(", ")}`); + } + + if (hints.childRefs.length > 0) { + lines.push(`- Child issues: ${hints.childRefs.map((ref) => `#${ref}`).join(", ")}`); + } + + if (hints.blocksRefs.length > 0) { + lines.push(`- Blocks: ${hints.blocksRefs.map((ref) => `#${ref}`).join(", ")}`); + } + + if (hints.blockedByRefs.length > 0) { + lines.push(`- Blocked by: ${hints.blockedByRefs.map((ref) => `#${ref}`).join(", ")}`); + } + + if (hints.securityRefs.length > 0) { + lines.push(`- Security linkage: ${hints.securityRefs.join(", ")}`); + } + + return lines.join("\n"); +} + +function buildAssigneeCandidates(item, defaultAssignee) { + const candidates = []; + if (item.author) candidates.push(item.author); + if (defaultAssignee && defaultAssignee !== item.author) candidates.push(defaultAssignee); + return candidates; +} + +async function findOpenOrRecentMilestone(github, owner, repo, title) { + const milestones = await github.paginate(github.rest.issues.listMilestones, { + owner, + repo, + state: "all", + per_page: 100, + }); + + return milestones.find((milestone) => milestone.title === title) || null; +} + +async function ensureMilestone(github, owner, repo, title) { + const existing = await findOpenOrRecentMilestone(github, owner, repo, title); + if (existing) { + return existing; + } + + const created = await github.rest.issues.createMilestone({ + owner, + repo, + title, + state: "open", + }); + + return created.data; +} + +async function assignIssue(github, owner, repo, number, candidates) { + for (const candidate of candidates) { + if (!candidate) continue; + + try { + await github.rest.issues.addAssignees({ + owner, + repo, + issue_number: number, + assignees: [candidate], + }); + return candidate; + } catch (error) { + console.info(`Assignee '${candidate}' could not be applied to #${number}: ${error.message}`); + } + } + + return ""; +} + +async function updateIssueMilestone(github, owner, repo, number, milestoneNumber) { + if (!milestoneNumber) return; + + await github.rest.issues.update({ + owner, + repo, + issue_number: number, + milestone: milestoneNumber, + }); +} + +async function resolveLinkedIssueMilestone(github, owner, repo, references) { + for (const ref of references) { + try { + const { data } = await github.rest.issues.get({ + owner, + repo, + issue_number: ref, + }); + + if (data.milestone?.number) { + return data.milestone; + } + } catch (error) { + console.info(`Linked issue #${ref} could not be read for milestone inheritance: ${error.message}`); + } + } + + return null; +} + +async function addSubIssueRelationship(github, owner, repo, parentNumber, childNumber) { + const parent = await github.graphql( + ` + query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + issue(number: $number) { + id + } + } + } + `, + { owner, repo, number: parentNumber }, + ); + + const child = await github.graphql( + ` + query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + issue(number: $number) { + id + } + } + } + `, + { owner, repo, number: childNumber }, + ); + + const parentId = parent?.repository?.issue?.id; + const childId = child?.repository?.issue?.id; + if (!parentId || !childId) return false; + + await github.graphql( + ` + mutation($parentId: ID!, $childId: ID!) { + addSubIssue(input: { issueId: $parentId, subIssueId: $childId }) { + subIssue { + id + } + } + } + `, + { parentId, childId }, + ); + + return true; +} + +async function upsertComment(github, owner, repo, number, body) { + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number: number, + per_page: 100, + }); + + const existing = comments.find( + (comment) => comment.user?.type === "Bot" && comment.body?.includes(COMMENT_MARKER), + ); + + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + return "updated"; + } + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: number, + body, + }); + return "created"; +} + +async function syncItemMetadata({ github, owner, repo, event, config }) { + const item = getItemFromEvent(event); + const hints = parseRelationshipHints(item.body); + const defaultAssignee = config?.defaults?.issue?.assignee || ""; + const assignee = await assignIssue( + github, + owner, + repo, + item.number, + buildAssigneeCandidates(item, defaultAssignee), + ); + + let milestoneSummary = item.milestone?.title || ""; + if (!milestoneSummary) { + const linkedMilestone = + item.kind === "pull_request" + ? await resolveLinkedIssueMilestone( + github, + owner, + repo, + [...new Set([...hints.linkedRefs, ...extractIssueRefs(item.body)])], + ) + : null; + const milestoneTitle = linkedMilestone?.title || deriveMilestoneTitle(item.title); + const milestone = linkedMilestone || (await ensureMilestone(github, owner, repo, milestoneTitle)); + await updateIssueMilestone(github, owner, repo, item.number, milestone.number); + milestoneSummary = milestone.title; + } + + const hasRelationshipMetadata = + hints.linkedRefs.length > 0 || + hints.parentRefs.length > 0 || + hints.childRefs.length > 0 || + hints.blocksRefs.length > 0 || + hints.blockedByRefs.length > 0 || + hints.securityRefs.length > 0; + + if (item.kind === "issue" || hasRelationshipMetadata) { + if (item.kind === "issue") { + for (const parentRef of hints.parentRefs) { + try { + await addSubIssueRelationship(github, owner, repo, parentRef, item.number); + } catch (error) { + console.info(`Parent relationship ${parentRef} -> #${item.number} could not be added: ${error.message}`); + } + } + + for (const childRef of hints.childRefs) { + try { + await addSubIssueRelationship(github, owner, repo, item.number, childRef); + } catch (error) { + console.info(`Child relationship #${item.number} -> ${childRef} could not be added: ${error.message}`); + } + } + } + + if (hasRelationshipMetadata) { + const message = formatRelationshipComment(item, hints, milestoneSummary, assignee); + await upsertComment(github, owner, repo, item.number, message); + } + } + + return { + assignee, + milestone: milestoneSummary, + relationships: hints, + }; +} + +async function run() { + const eventPath = process.env.GITHUB_EVENT_PATH; + const token = process.env.GITHUB_TOKEN; + const repo = process.env.GITHUB_REPOSITORY || ""; + + if (!eventPath) throw new Error("GITHUB_EVENT_PATH is required"); + if (!token) throw new Error("GITHUB_TOKEN is required"); + if (!repo.includes("/")) throw new Error("GITHUB_REPOSITORY is required"); + + const [owner, repoName] = repo.split("/"); + const event = readJsonFile(eventPath); + const configPath = process.env.ISSUE_FIELDS_CONFIG + ? path.resolve(process.env.ISSUE_FIELDS_CONFIG) + : path.resolve(".github/issue-fields.yml"); + const config = readConfig(configPath); + const github = getOctokit(token); + + const result = await syncItemMetadata({ + github, + owner, + repo: repoName, + event, + config, + }); + + if (process.env.GITHUB_OUTPUT) { + fs.appendFileSync( + process.env.GITHUB_OUTPUT, + [ + `metadata_assignee=${result.assignee || ""}`, + `metadata_milestone=${result.milestone || ""}`, + `metadata_linked_refs=${result.relationships.linkedRefs.join(",")}`, + `metadata_parent_refs=${result.relationships.parentRefs.join(",")}`, + `metadata_child_refs=${result.relationships.childRefs.join(",")}`, + `metadata_blocks_refs=${result.relationships.blocksRefs.join(",")}`, + `metadata_blocked_by_refs=${result.relationships.blockedByRefs.join(",")}`, + `metadata_security_refs=${result.relationships.securityRefs.join(",")}`, + ].join("\n") + "\n", + ); + } + + return result; +} + +if (require.main === module) { + run().catch((error) => { + console.error(error); + process.exitCode = 1; + }); +} + +module.exports = { + addSubIssueRelationship, + assignIssue, + buildAssigneeCandidates, + deriveMilestoneTitle, + extractIssueRefs, + formatRelationshipComment, + getActorLogin, + getItemFromEvent, + normaliseTitle, + parseRelationshipHints, + run, + syncItemMetadata, +}; diff --git a/scripts/agents/includes/label-heuristics.js b/scripts/agents/includes/label-heuristics.js index 329dd2d7e..13d5e8f55 100755 --- a/scripts/agents/includes/label-heuristics.js +++ b/scripts/agents/includes/label-heuristics.js @@ -31,10 +31,10 @@ function suggestLabelsFromContent(item, aliasMap) { "type:refactor": [ /\b(refactor|restructure|reorganize|cleanup|optimize|improve|simplify|modernize)\b/i, ], - "priority:high": [ + "priority:critical": [ /\b(urgent|critical|high priority|asap|blocking|production|live|customer|client)\b/i, ], - "priority:low": [/\b(low priority|nice to have|minor|cosmetic|polish)\b/i], + "priority:minor": [/\b(low priority|nice to have|minor|cosmetic|polish)\b/i], "area:security": [ /\b(security|vulnerability|exploit|attack|auth|authentication|permission|access|credential|token|password)\b/i, ], diff --git a/scripts/agents/labeling.agent.js b/scripts/agents/labeling.agent.js index 38f773a3e..650bbc5ca 100644 --- a/scripts/agents/labeling.agent.js +++ b/scripts/agents/labeling.agent.js @@ -20,8 +20,8 @@ import fs from "fs"; import yaml from "js-yaml"; -import core from "@actions/core"; -import github from "@actions/github"; +import * as core from "@actions/core"; +import * as github from "@actions/github"; import { fetchCanonicalLabels, buildLabelAliasMap, @@ -594,16 +594,6 @@ async function runLabelingAgent(opts = {}) { } } -// Check if this module is being run directly -if (import.meta.url === `file://${process.argv[1]}`) { - runLabelingAgent().catch((error) => { - core.error(`[labeling.agent] Unhandled error: ${error.message}`); - core.error(error.stack); - core.setFailed(error.message); - process.exit(1); - }); -} - export { runLabelingAgent, detectIssueTypeFromContent, diff --git a/scripts/agents/project-meta-sync.agent.js b/scripts/agents/project-meta-sync.agent.js index 82e8b8679..7e6e0c6f5 100644 --- a/scripts/agents/project-meta-sync.agent.js +++ b/scripts/agents/project-meta-sync.agent.js @@ -1,284 +1,41 @@ #!/usr/bin/env node /** - * project-meta-sync.js - * GitHub Projects synchronization agent. + * @fileoverview Deprecated compatibility shim for the legacy project-meta-sync agent. * - * This script is invoked by the project-meta-sync GitHub Actions workflow. - * It adds the triggered issue or PR to the configured project (if not already added) - * and updates the project's fields (Status, Priority, Type) based on labels and PR branch. - * @module scripts/agents/project-meta-sync.agent.js - * @see ../../agents/project-meta-sync.agent.md + * The active automation now lives in: + * - .github/workflows/project-meta-sync.yml + * - .github/workflows/metadata-governance.yml + * - scripts/agents/includes/derive-project-fields.cjs + * - scripts/agents/includes/issue-pr-metadata.cjs + * + * This shim remains so older references fail soft and point to the live contract. */ -const { getOctokit } = require("@actions/github"); // if running in Actions context -const core = require("@actions/core"); // to get inputs/secrets +const DEPRECATION_NOTE = + "project-meta-sync.agent.js is deprecated. Use the workflow and helper scripts listed in this module docblock."; async function run() { - try { - // Inputs: assuming the workflow passes in necessary info via environment or file - const projectUrl = process.env.LS_PROJECT_URL; // e.g. "https://github.com/orgs/LightSpeed/projects/1" - if (!projectUrl) { - throw new Error("LS_PROJECT_URL not set"); - } - // Get GitHub context from env (if running within GitHub Actions, the GITHUB_EVENT_PATH has event payload) - const githubToken = - process.env.GITHUB_TOKEN || core.getInput("github-token"); - if (!githubToken) { - throw new Error("GitHub token not available for project sync"); - } - const octokit = getOctokit(githubToken); - - // Parse project org and number from URL - const match = projectUrl.match(/orgs\/([^/]+)\/projects\/(\d+)/); - if (!match) { - throw new Error(`Invalid LS_PROJECT_URL format: ${projectUrl}`); - } - const orgLogin = match[1]; - const projectNumber = parseInt(match[2], 10); - - // Load event payload - const eventPath = process.env.GITHUB_EVENT_PATH; - if (!eventPath) { - throw new Error("No GitHub event context found"); - } - const event = require(eventPath); - // Determine if this is issue or PR and get relevant info - const isPR = !!event.pull_request; - const isIssue = !!event.issue; // note: in PR events, event.issue may not exist - const itemNodeId = isPR ? event.pull_request.node_id : event.issue.node_id; - const itemNumber = isPR ? event.pull_request.number : event.issue.number; - const repoOwner = - (event.repository && event.repository.owner.login) || orgLogin; - const repoName = event.repository && event.repository.name; - - // Fetch the project ID and field IDs via GraphQL - const projectData = await octokit.graphql( - ` - query($org: String!, $number: Int!) { - organization(login: $org) { - projectV2(number: $number) { - id - title - fields(first: 20) { - nodes { - id - name - dataType - ... on ProjectV2SingleSelectField { - options { - id - name - } - } - } - } - } - } - } - `, - { org: orgLogin, number: projectNumber }, - ); - const project = projectData.organization.projectV2; - const projectId = project.id; - const fields = project.fields.nodes; - // Helper: find field and option IDs by name - const findField = (fname) => fields.find((f) => f.name === fname); - const findOptionId = (fieldName, optionName) => { - const field = findField(fieldName); - if (!field || field.dataType !== "SINGLE_SELECT") return null; - const opt = field.options.find( - (o) => o.name.toLowerCase() === optionName.toLowerCase(), - ); - return opt ? opt.id : null; - }; - - // Step 1: Add item to project (if not already added) - // We attempt to add; if already exists, GitHub will error with "content already exists in project" which we can ignore. - try { - await octokit.graphql( - ` - mutation($project: ID!, $item: ID!) { - addProjectV2ItemById(input: {projectId: $project, contentId: $item}) { - item { - id - } - } - } - `, - { project: projectId, item: itemNodeId }, - ); - console.log(`Added item ${itemNodeId} to project ${project.title}`); - } catch (err) { - if (err.message && err.message.includes("already exists")) { - console.log(`Item ${itemNodeId} is already in project`); - } else { - throw err; - } - } - - // Step 2: Prepare field updates - // Determine desired field values from labels and context - let statusValue = null, - priorityValue = null, - typeValue = null; - if (isPR) { - // For PRs, branch prefix drives Type - const headRef = event.pull_request.head.ref; // e.g. "feat/my-feature" - if (headRef.match(/^feat\//i)) typeValue = "Feature"; - else if (headRef.match(/^fix\//i)) typeValue = "Bug"; - else if (headRef.match(/^docs?\//i)) typeValue = "Documentation"; - else if (headRef.match(/^chore\//i)) typeValue = "Task"; - // If PR is just opened, it should have 'needs-review' label per labeling workflow - // If merged or closed: - if (event.action === "closed") { - statusValue = "Done"; - } else if (event.action === "opened" || event.action === "reopened") { - statusValue = "In review"; // PRs start in review - } - } else if (isIssue) { - if (event.action === "closed") { - statusValue = "Done"; - } else if (event.action === "reopened") { - statusValue = "Triage"; - } - // For issues, infer type from labels if any (e.g., type: bug/feature) - } - // Labels can override or provide values - const labels = - (isPR ? event.pull_request.labels : event.issue.labels) || []; - for (const label of labels) { - const name = label.name; - if (name.startsWith("status:")) { - // Map status label to field value - // e.g. "status:needs-triage" -> "Triage" - // "status:needs-review" -> "In review" - // "status:in-progress" -> "In progress" - // "status:Blocked" -> "Blocked" - // "status:Done" -> "Done" - let statusName = name.replace(/^status:/, ""); - if (statusName.match(/^needs-/)) { - statusName = statusName.replace(/^needs-/, ""); - // e.g. "needs-triage" -> "triage" - // capitalize first letter of each word and add spaces if needed - } - statusName = statusName.replace(/-/g, " "); // "in-progress" -> "in progress" - statusName = statusName.replace(/\b\w/g, (c) => c.toUpperCase()); // capitalize words - statusValue = statusName; - } - if (name.startsWith("priority:")) { - let prioName = name.replace(/^priority:/, ""); - prioName = prioName.charAt(0).toUpperCase() + prioName.slice(1); // capitalize first letter - priorityValue = prioName; - } - if (name.startsWith("type:")) { - // If an explicit type label is present, use it - let typeName = name.replace(/^type:/, ""); - typeName = typeName.charAt(0).toUpperCase() + typeName.slice(1); - typeValue = typeName; - } - } - - // Default statuses if still not set - if (!statusValue) { - if (isIssue && event.action === "opened") { - statusValue = "Triage"; - } - } - console.log( - `Determined field values -> Status: ${statusValue || "None"}, Priority: ${priorityValue || "None"}, Type: ${typeValue || "None"}`, - ); - - // Step 3: Update fields via mutations - const updateField = async (fieldName, fieldValue) => { - if (!fieldValue) return; - const field = findField(fieldName); - if (!field) { - console.warn( - `Field '${fieldName}' not found on project, skipping update.`, - ); - return; - } - let fvInputs = {}; - if (field.dataType === "SINGLE_SELECT") { - // need option ID - const optId = findOptionId(fieldName, fieldValue); - if (!optId) { - console.warn( - `Option '${fieldValue}' not found for field '${fieldName}', skipping update.`, - ); - return; - } - fvInputs = { singleSelectOptionId: optId }; - } else { - // for text fields or others - fvInputs = { text: fieldValue }; - } - await octokit.graphql( - ` - mutation($project: ID!, $itemId: ID!, $fieldId: ID!, $fv: ProjectV2FieldValue!) { - updateProjectV2ItemFieldValue(input: { - projectId: $project, - itemId: $itemId, - fieldId: $fieldId, - value: $fv - }) { - projectV2Item { - id - } - } - } - `, - { - project: projectId, - itemId: itemNodeId, - fieldId: field.id, - fv: fvInputs, - }, - ); - console.log(`Updated '${fieldName}' field to '${fieldValue}'`); - }; - - // Update the fields we have values for - await updateField("Status", statusValue); - await updateField("Priority", priorityValue); - await updateField("Type", typeValue); - - // Step 4: (Optional) Enforce single status label by removing others if a new status was applied - if (statusValue) { - // Remove any status: label that doesn't correspond to statusValue - const statusLabelsToRemove = labels.filter( - (l) => - l.name.startsWith("status:") && - !l.name.includes(statusValue.toLowerCase().replace(/ /g, "")), - ); - for (const lbl of statusLabelsToRemove) { - await octokit.rest.issues - .removeLabel({ - owner: repoOwner, - repo: repoName, - issue_number: itemNumber, - name: lbl.name, - }) - .catch((err) => { - console.warn(`Failed to remove label ${lbl.name}: ${err.message}`); - }); - } - if (statusLabelsToRemove.length) { - console.log( - `Removed obsolete status labels: ${statusLabelsToRemove.map((l) => l.name).join(", ")}`, - ); - } - } - - console.log("Project sync complete."); - } catch (error) { - console.error("Project sync failed:", error); - core.setFailed(error.message); - } + console.warn(`[DEPRECATED] ${DEPRECATION_NOTE}`); + return { + ok: true, + deprecated: true, + replacement: { + workflow: ".github/workflows/project-meta-sync.yml", + metadata_workflow: ".github/workflows/metadata-governance.yml", + helper: "scripts/agents/includes/derive-project-fields.cjs", + metadata_helper: "scripts/agents/includes/issue-pr-metadata.cjs", + }, + message: DEPRECATION_NOTE, + }; } if (require.main === module) { - run(); + run().catch((error) => { + console.error(error); + process.exitCode = 1; + }); } module.exports = run; +module.exports.run = run; +module.exports.DEPRECATION_NOTE = DEPRECATION_NOTE; diff --git a/scripts/agents/run-labeling-agent.cjs b/scripts/agents/run-labeling-agent.cjs new file mode 100644 index 000000000..ef6b7e8fc --- /dev/null +++ b/scripts/agents/run-labeling-agent.cjs @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +async function main() { + const { runLabelingAgent } = await import("./labeling.agent.js"); + + await runLabelingAgent(); +} + +if (require.main === module) { + main().catch((error) => { + console.error(`[labeling.agent] Unhandled error: ${error.message}`); + console.error(error.stack); + process.exitCode = 1; + }); +}