diff --git a/.github/automation/canonical/labels.yml b/.github/automation/canonical/labels.yml new file mode 100644 index 00000000..c4c0af30 --- /dev/null +++ b/.github/automation/canonical/labels.yml @@ -0,0 +1,13 @@ +# Canonical labels (seed). Extend as needed. +- name: area:docs + color: 0E6BA8 + description: Documentation, READMEs, indices +- name: area:workflows + color: FA9F42 + description: GitHub Actions and CI guardrails +- name: area:agents + color: 111827 + description: Repo agents, scripts, and AIOps +- name: v0.2.0 + color: F3F4F6 + description: Target for next promotion cycle diff --git a/.github/automation/canonical/repository-categories.yml b/.github/automation/canonical/repository-categories.yml new file mode 100644 index 00000000..9303a9f8 --- /dev/null +++ b/.github/automation/canonical/repository-categories.yml @@ -0,0 +1,4 @@ +- wordpress +- community +- tooling +- data diff --git a/.github/automation/canonical/repository-names.yml b/.github/automation/canonical/repository-names.yml new file mode 100644 index 00000000..b0779d2f --- /dev/null +++ b/.github/automation/canonical/repository-names.yml @@ -0,0 +1,6 @@ +- .github +- tour-operator +- to-reviews +- to-specials +- to-starter-theme +- to-team diff --git a/.github/automation/schemas/changelog.schema.json b/.github/automation/schemas/changelog.schema.json new file mode 100644 index 00000000..67118922 --- /dev/null +++ b/.github/automation/schemas/changelog.schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Changelog", + "type": "object", + "properties": { + "version": { "type": "string" }, + "date": { "type": "string", "format": "date" }, + "sections": { + "type": "object", + "properties": { + "Added": { "type": "array", "items": { "type": "string" } }, + "Changed": { "type": "array", "items": { "type": "string" } }, + "Fixed": { "type": "array", "items": { "type": "string" } } + }, + "additionalProperties": false + } + }, + "required": ["version", "date"] +} diff --git a/.github/automation/schemas/frontmatter.schema.json b/.github/automation/schemas/frontmatter.schema.json new file mode 100644 index 00000000..01010f24 --- /dev/null +++ b/.github/automation/schemas/frontmatter.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Front Matter", + "type": "object", + "properties": { + "title": { "type": "string", "minLength": 3 }, + "apply_to": { "type": "array", "items": { "type": "string" } }, + "scope": { "enum": ["community", "domain"] }, + "repo_category": { "type": "string" }, + "repo_name": { "type": "array", "items": { "type": "string" } } + }, + "required": ["title", "apply_to"], + "additionalProperties": true +} diff --git a/.github/automation/schemas/version.schema.json b/.github/automation/schemas/version.schema.json new file mode 100644 index 00000000..d8c47086 --- /dev/null +++ b/.github/automation/schemas/version.schema.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Version", + "type": "object", + "properties": { "version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" } }, + "required": ["version"], + "additionalProperties": false +} diff --git a/.github/instructions/wordpress/index.md b/.github/instructions/wordpress/index.md new file mode 100644 index 00000000..76faf421 --- /dev/null +++ b/.github/instructions/wordpress/index.md @@ -0,0 +1,4 @@ +# Instructions (WordPress Domain) +- **Scope:** domain +- **Owners:** Docs +- **Note:** WordPress-only guidance lives here; root stays community-only diff --git a/.github/workflows/collections-indexer.yml b/.github/workflows/collections-indexer.yml new file mode 100644 index 00000000..107c0003 --- /dev/null +++ b/.github/workflows/collections-indexer.yml @@ -0,0 +1,31 @@ +name: Collections Indexer +on: + pull_request: + branches: [ develop ] + paths: + - ".github/collections/**" + - ".github/prompts/**" + - ".github/chatmodes/**" + - ".github/instructions/**" + workflow_dispatch: + +jobs: + index: + runs-on: ubuntu-latest + permissions: + contents: read + concurrency: + group: collections-indexer-${{ github.ref }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install dependencies + run: npm ci + - name: Build index + run: npx ts-node scripts/build-collections-index.ts + - name: Validate references + run: npx ts-node scripts/build-collections-index.ts --validate diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 00000000..f1775997 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,24 @@ +name: Labeler +on: + pull_request: + types: [opened, synchronize, reopened] + branches: [ develop ] + +jobs: + label: + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read + steps: + - uses: actions/checkout@v4 + - name: Load canonical labels + id: labels + run: | + echo "labels=$(yq -o=json .github/automation/canonical/labels.yml)" >> $GITHUB_OUTPUT + - name: Apply labels (dry-run supported) + env: + DRY_RUN: ${{ inputs.dry_run || 'false' }} + run: | + # TODO: implement minimal JS that maps paths/titles to labels; no deletions of human labels + echo "Applied canonical labels (dry-run=$DRY_RUN)" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/quality-gates.yml b/.github/workflows/quality-gates.yml new file mode 100644 index 00000000..e8b85cd6 --- /dev/null +++ b/.github/workflows/quality-gates.yml @@ -0,0 +1,66 @@ +name: Quality Gates +on: + pull_request: + branches: [ develop ] + workflow_dispatch: + +jobs: + gates: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: { node-version: '20' } + + - name: Install tools + run: | + npm install --no-save markdownlint-cli ajv ajv-formats yaml + pipx install yamllint + curl -sSL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash | bash -s -- -b $HOME/bin + echo "$HOME/bin" >> $GITHUB_PATH + + - name: Changed files + id: changed + uses: tj-actions/changed-files@v45 + + - name: Changed Markdown files + id: changed_md + uses: tj-actions/changed-files@v45 + with: + files: '**/*.md' + + - name: yamllint + if: contains(steps.changed.outputs.all_modified_files, '.yml') || contains(steps.changed.outputs.all_modified_files, '.yaml') + run: yamllint . + + - name: markdownlint + if: steps.changed_md.outputs.any_modified == 'true' + run: npx markdownlint '**/*.md' --config .markdownlint.jsonc + + - name: actionlint + run: actionlint + + - name: Build canonicals JSON + run: node scripts/canonical-to-json.js + + - name: Validate front-matter + run: node scripts/validate-frontmatter.js + + - name: Validate version + run: node scripts/validate-version.js + + - name: Validate changelog + run: node scripts/validate-changelog.js + + - name: Summary + if: always() + run: | + cat <<'MD' >> $GITHUB_STEP_SUMMARY + ### Quality Gates — develop + - Lints & schema validators ran on changed files + - Review failures above; fix and re-run + - Owners: Workflows (primary), Docs (front-matter) + MD diff --git a/.github/workflows/release-prep.yml b/.github/workflows/release-prep.yml new file mode 100644 index 00000000..cebcccad --- /dev/null +++ b/.github/workflows/release-prep.yml @@ -0,0 +1,28 @@ +name: Release Prep +on: + workflow_dispatch: + schedule: + - cron: "0 6 * * 1" # weekly, 06:00 UTC Mondays + +jobs: + prep: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install dependencies + run: npm ci + - name: Compute next version & changelog (validated) + run: | + node scripts/validate-version.js + node scripts/validate-changelog.js + # TODO: script to open PR with next version + CHANGELOG block + - name: Summary + run: | + echo "Will open PR with next version and schema-validated changelog" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/weekly-metrics.yml b/.github/workflows/weekly-metrics.yml new file mode 100644 index 00000000..d715a6f5 --- /dev/null +++ b/.github/workflows/weekly-metrics.yml @@ -0,0 +1,28 @@ +name: Weekly Metrics +on: + schedule: [ { cron: "0 7 * * 1" } ] + workflow_dispatch: + +jobs: + metrics: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - name: Aggregate metrics + run: | + # Parse prior runs and build weekly-health.md (seed implementation) + echo "### Weekly Health — develop" > .github/reports/weekly-health.md + echo "- Placeholder metrics; replace with collector outputs" >> .github/reports/weekly-health.md + echo "Wrote seed report" >> $GITHUB_STEP_SUMMARY + - name: Commit report + run: | + git config user.name "lightspeed-bot" + git config user.email "bot@lightspeedwp.agency" + git add .github/reports/weekly-health.md + if git commit -m "chore(reports): update weekly health (seed)"; then + git push + else + echo "No changes" + fi diff --git a/CODEOWNERS b/CODEOWNERS index 94990582..e162f377 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -26,6 +26,7 @@ /.github/agents/ @ashleyshaw /.github/chatmodes/ @ashleyshaw /.github/prompts/ @ashleyshaw +/.github/instructions/ @ashleyshaw /CLAUDE.md @ashleyshaw /GEMINI.md @ashleyshaw /AGENTS.md @ashleyshaw diff --git a/scripts/build-collections-index.ts b/scripts/build-collections-index.ts new file mode 100755 index 00000000..66a059c6 --- /dev/null +++ b/scripts/build-collections-index.ts @@ -0,0 +1,3 @@ +#!/usr/bin/env node +// Build collections index and optionally validate references (placeholder) +console.log('Collections index built (placeholder).'); diff --git a/scripts/canonical-to-json.js b/scripts/canonical-to-json.js new file mode 100755 index 00000000..473b2bb6 --- /dev/null +++ b/scripts/canonical-to-json.js @@ -0,0 +1,21 @@ +#!/usr/bin/env node +const fs = require('fs'); +const path = require('path'); +const YAML = require('yaml'); + +const srcDir = path.join('.github', 'automation', 'canonical'); +const outDir = path.join('.github', 'automation', 'canonical', 'json'); + +fs.mkdirSync(outDir, { recursive: true }); + +for (const file of ['repository-categories.yml','repository-names.yml','labels.yml']) { + try { + const y = fs.readFileSync(path.join(srcDir, file), 'utf8'); + const j = YAML.parse(y); + fs.writeFileSync(path.join(outDir, file.replace(/\.yml$/, '.json')), JSON.stringify(j, null, 2)); + } catch (err) { + console.error(`Failed to process ${file}:`, err.message); + process.exit(1); + } +} +console.log('Canonical JSON generated.'); diff --git a/scripts/validate-changelog.js b/scripts/validate-changelog.js new file mode 100755 index 00000000..d1e45797 --- /dev/null +++ b/scripts/validate-changelog.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node +// Validate changelog fragments against changelog.schema.json (placeholder) +// TODO: Implement changelog validation logic. This script currently does not perform any validation. +// Exiting with code 1 to indicate this is a placeholder and should not be considered a successful validation. +process.exit(1); diff --git a/scripts/validate-frontmatter.js b/scripts/validate-frontmatter.js new file mode 100755 index 00000000..13ad27df --- /dev/null +++ b/scripts/validate-frontmatter.js @@ -0,0 +1,16 @@ +#!/usr/bin/env node +const Ajv = require('ajv'); +const addFormats = require('ajv-formats'); +const fs = require('fs'); +const glob = require('glob'); + +const ajv = new Ajv({ allErrors: true, strict: false }); +addFormats(ajv); + +const schema = JSON.parse(fs.readFileSync('.github/automation/schemas/frontmatter.schema.json', 'utf8')); +// TODO: implement front-matter extraction (YAML header) for .md files +const files = glob.globSync('**/*.md', { ignore: ['node_modules/**'] }); + +let errors = 0; +// TODO: For each file in `files`, extract and validate front-matter here. +if (errors > 0) process.exit(1); diff --git a/scripts/validate-version.js b/scripts/validate-version.js new file mode 100755 index 00000000..4cb3c036 --- /dev/null +++ b/scripts/validate-version.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +// Validate version files against version.schema.json (placeholder)