diff --git a/.github/operations/MILESTONE_ALLOCATION_ANNOUNCEMENT.md b/.github/operations/MILESTONE_ALLOCATION_ANNOUNCEMENT.md index 74c8e85e39..c6e04b79a7 100644 --- a/.github/operations/MILESTONE_ALLOCATION_ANNOUNCEMENT.md +++ b/.github/operations/MILESTONE_ALLOCATION_ANNOUNCEMENT.md @@ -1,4 +1,4 @@ -# 🎉 NEW FEATURE: Automatic Milestone Allocation Now Live! +# 🎉 NEW FEATURE: Automatic Milestone Allocation Now Live **Announcement Date:** 2026-08-24 **Related Issue:** [#1765](https://github.com/lightspeedwp/.github/issues/1765) @@ -28,6 +28,7 @@ We've deployed **automatic allocation of merged PRs and closed issues** to proje ### Example You merge a PR with this in the description: + ``` This PR fixes the critical auth bug. @@ -36,6 +37,7 @@ Resolves #1886 ``` **Result:** + - PR automatically allocated to v1.5.0 milestone - Issue #1885 automatically allocated to v1.5.0 milestone - Issue #1886 automatically allocated to v1.5.0 milestone @@ -48,6 +50,7 @@ Resolves #1886 We've created comprehensive documentation to help you: ### [RUNBOOK.md](../../.github/projects/active/pr-issue-milestone-allocation-2026-08-11/RUNBOOK.md) + **Step-by-step operations guide** — How to use the script manually - Quick Start (automatic operation explained) @@ -59,6 +62,7 @@ We've created comprehensive documentation to help you: - Best Practices ### [FAQ.md](../../.github/projects/active/pr-issue-milestone-allocation-2026-08-11/FAQ.md) + **52 Q&A pairs covering everything** - General Questions (11) @@ -72,6 +76,7 @@ We've created comprehensive documentation to help you: - Integration & APIs (2) ### [QUICK-REFERENCE.md](../../.github/projects/active/pr-issue-milestone-allocation-2026-08-11/QUICK-REFERENCE.md) + **Quick command reference** — One-page cheat sheet --- @@ -120,7 +125,7 @@ See [RUNBOOK.md](../../.github/projects/active/pr-issue-milestone-allocation-202 ## 🎯 What You Need to Know -### For Most Users: Nothing! +### For Most Users: Nothing ✅ **The feature works automatically** — no setup needed ✅ **No action required** — just merge PRs normally @@ -147,6 +152,7 @@ See [RUNBOOK.md](../../.github/projects/active/pr-issue-milestone-allocation-202 **Nothing to do!** The feature is live and working. Next time you: + 1. **Merge a PR** → Workflow runs automatically, milestone allocated 2. **Close an issue** → Workflow runs automatically, milestone allocated 3. **Link issues** in PR description (Closes #123) → All items allocated together @@ -158,6 +164,7 @@ That's it. You're done. ## 📊 Expected Behavior ### Automatic Workflow Run + ``` Your PR merged ↓ @@ -173,6 +180,7 @@ Confirmation comment posted ``` ### Example Comment + ``` ✅ Allocated to milestone #42 "v1.5.0" @@ -191,12 +199,14 @@ Learn more: https://github.com/lightspeedwp/.github/issues/1765 ### Where to Check **GitHub Actions:** + 1. Go to [Actions](https://github.com/lightspeedwp/.github/actions) 2. Find "Allocate PR/Issue to Current Milestone" 3. Watch for recent runs 4. Check logs if any failures occur **PR/Issue Comments:** + 1. Merge a PR 2. Look for confirmation comment (usually within 2 minutes) 3. Verify milestone assigned correctly @@ -216,6 +226,7 @@ Learn more: https://github.com/lightspeedwp/.github/issues/1765 **Have feedback?** Please comment on [#1765](https://github.com/lightspeedwp/.github/issues/1765) We're tracking: + - What's working well - Edge cases you encounter - Suggestions for improvements @@ -230,6 +241,7 @@ Your feedback helps us improve Phase 4 (Monitoring & Maintenance). **Want to learn more?** (Optional) We're offering a 30-minute training session covering: + - How the feature works under the hood - Dry-run mode and testing - Troubleshooting common issues @@ -249,11 +261,12 @@ DM me if interested! --- -## 🎉 Thank You! +## Thank You This feature is the result of weeks of planning, implementation, testing, and documentation. **Special thanks to:** + - DevOps team for feedback and testing - QA team for comprehensive test coverage - Release managers for feature requirements diff --git a/.github/operations/MILESTONE_ALLOCATION_MONITORING.md b/.github/operations/MILESTONE_ALLOCATION_MONITORING.md index 203de86150..d0347c5ec3 100644 --- a/.github/operations/MILESTONE_ALLOCATION_MONITORING.md +++ b/.github/operations/MILESTONE_ALLOCATION_MONITORING.md @@ -10,6 +10,7 @@ ## Pre-Deployment Checklist ✅ ### Code & Files + - [x] Script deployed: `scripts/automation/allocate-to-milestone.js` - [x] Workflow deployed: `.github/workflows/allocate-pr-issue-to-milestone.yml` - [x] Tests deployed: `scripts/automation/__tests__/allocate-to-milestone.test.js` @@ -18,12 +19,14 @@ - [x] Workflow YAML syntax valid ### Documentation + - [x] RUNBOOK.md created (392 lines) - [x] FAQ.md created (554 lines, 52 Q&A pairs) - [x] OPENSPEC.md updated with production section - [x] Team announcement prepared ### Team Communication + - [x] Team announcement ready for Slack - [x] Documentation links included - [x] Q&A references provided @@ -34,12 +37,14 @@ ## Monitoring Checkpoints ### Hour 0-2: Initial Activation + - [ ] Announce feature to team (Slack) - [ ] Confirm workflow is visible in GitHub Actions - [ ] Share documentation links (RUNBOOK, FAQ) - [ ] Set up monitoring notifications **Success Indicators:** + - ✓ Team acknowledges announcement - ✓ Workflow appears in Actions tab - ✓ Documentation links working @@ -47,18 +52,22 @@ --- ### Hour 2-24: First Allocations + **When first PR is merged:** + - [ ] Watch for workflow run in GitHub Actions - [ ] Expected time: 1-2 minutes after merge - [ ] Verify job succeeded (green checkmark) **When workflow completes:** + - [ ] Check PR for confirmation comment - [ ] Expected format: "✅ Allocated to milestone #X" - [ ] Verify milestone shows in PR sidebar - [ ] Check linked issues also allocated (if any) **Error Monitoring:** + - [ ] Check workflow logs for errors - [ ] Look for any rate limit messages (429 errors) - [ ] Verify no timeout issues (30-second timeout) @@ -67,13 +76,16 @@ --- ### Hour 24-48: Extended Monitoring + **Pattern Analysis:** + - [ ] Track successful allocations vs failures - [ ] Expected success rate: ≥95% - [ ] Expected error rate: <5% - [ ] Average run time: 5-10 seconds **Issue Tracking:** + - [ ] Are confirmation comments appearing on all items? - [ ] Are linked issues detected correctly? - [ ] Are milestone assignments visible? @@ -96,24 +108,28 @@ ## Sign-Off Checklist (EOD Day 6) **Monitoring Complete:** + - [ ] 48-hour monitoring period finished - [ ] Final success rate ≥95% verified - [ ] All workflow logs reviewed - [ ] No critical issues found **Documentation:** + - [ ] Team announcement posted - [ ] Links accessible and working - [ ] RUNBOOK and FAQ available - [ ] Support channel (#1765) clear **Team Ready:** + - [ ] Team has used feature (≥5 PRs merged) - [ ] Feedback collected - [ ] Questions answered - [ ] Confidence level high **Phase 4 Ready:** + - [ ] Monitoring procedures documented - [ ] Success metrics defined - [ ] Escalation path clear @@ -124,12 +140,14 @@ ## Critical Success Factors ### Must Have (Phase 3 Sign-Off) + ✓ ≥95% allocation success rate ✓ No critical errors ✓ Confirmation comments appearing ✓ Team using feature successfully ### Red Flags (Stop Work) + ❌ Success rate <90% ❌ Systematic errors pattern ❌ Team unable to use feature diff --git a/.github/projects/_templates/OPENSPEC_TEMPLATE.md b/.github/projects/_templates/OPENSPEC_TEMPLATE.md index f20eb9a2dc..a887185a1d 100644 --- a/.github/projects/_templates/OPENSPEC_TEMPLATE.md +++ b/.github/projects/_templates/OPENSPEC_TEMPLATE.md @@ -408,9 +408,11 @@ describe('Component A', () => { **Request Example:** ```bash +# gitleaks:allow curl -X GET \ https://api.example.com/api/v1/resource/550e8400-e29b-41d4-a716-446655440000 \ -H 'Authorization: Bearer token123' +# gitleaks:allowlist ``` **Success Response (200 OK):** diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e8a8dfa23..d35a607131 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,20 +30,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **OpenSpec Status Labels Phase 2 — Template Validation & Automatic Injection** — Complete implementation of Definition of Ready (DoR) and Definition of Done (DoD) template validation and automatic injection for GitHub issues. Phase 2 deliverables include: (1) Template mapping system (`dor-dod-templates.js`) with 17 issue type templates (task, bug, feature, design, epic, story, improvement, chore, refactor, build-ci, test, performance, a11y, security, documentation, research, audit) containing 85+ total checklist items; (2) Validation & injection script (`validate-inject-dor-dod.js`, 282 LOC) with batch processing (up to 300 issues), dry-run mode, JSON report generation, and 9 CodeRabbit security/quality fixes (--limit validation, exec() null-safety, dry-run tracking, error handling); (3) Comprehensive test suite (43 tests, 100% coverage) validating template structure, DoR/DoD detection, type detection, and edge cases; (4) GitHub Actions workflow (`validate-dor-dod-sections.yml`) with daily schedule and manual trigger supporting dry-run mode; (5) Complete documentation including Phase 2 summary, template validation guide, and Phase 3 handoff. All templates include actionable checklist items tailored to each issue type. Script validates existing issues and automatically injects missing templates based on type label. Dry-run mode allows safe preview of batch operations. ([PR #1986](https://github.com/lightspeedwp/.github/pull/1986), [#1943](https://github.com/lightspeedwp/.github/issues/1943), [OpenSpec Phase 1](https://github.com/lightspeedwp/.github/pull/1985)) -- **PR Creation Agent — Phase 3 Skills 5 & 6 (Complete Implementation)** — Completed Phase 3 implementation with final two production-grade skills: Skill 5: submit-pr.js (250 LOC, 20 tests, 100% coverage) handles GitHub API PR creation with auto-merge configuration, status checks polling, and merge conflict resolution; Skill 6: handle-pr-errors.js (280 LOC, 19 tests, 100% coverage) manages validation errors, rate limiting, authentication failures, and provides remediation guidance. All 39 Phase 3 tests passing (Skill 1-6), 100% coverage. Phase 3 complete: 6/6 skills (~2,100 LOC, 110+ tests). ([PR #2001](https://github.com/lightspeedwp/.github/pull/2001)) - +<<<<<<< HEAD - **CI Validators Infrastructure — validate:frontmatter:changed npm script** — Created missing frontmatter validation script for changed files only. New script (`scripts/validation/validate-frontmatter-changed.js`) validates frontmatter in changed markdown/YAML files using git diff, handles deleted files gracefully, and integrates with `.github/workflows/meta.yml`. Fixes #1966 which was blocking all PR merges due to missing validation script. ([PR #1999](https://github.com/lightspeedwp/.github/pull/1999), [#1966](https://github.com/lightspeedwp/.github/issues/1966)) -- **Project Maintenance Agent — Phase 1 & 2 Documentation** — Complete documentation for Phases 1–2 (scripts security/logic/testing) and comprehensive planning/OPENSPEC for Phase 3 (GitHub Actions workflows). Phase 1 delivered: 7 security/logic/testing fixes (CVE prevention, robustness, error handling, test coverage improvements). Phase 2 delivered: 3,500+ lines project documentation (README, PLANNING, OPENSPEC, decision tree). Phase 3 planning complete with 14 concrete workflow implementation tasks. ([PR #1994](https://github.com/lightspeedwp/.github/pull/1994)) +- **Branch Naming Enforcement Phase 6 — Team Rollout & Adoption Setup** — Finalized project documentation for Phase 6 team rollout and adoption setup (start: 2026-08-12, target completion: 2026-08-19). Phase 6 deliverables include: (1) Execution Log tracking team adoption status, key milestones (Slack announcement, setup checklist distribution, grace period, enforcement enablement), and daily adoption metrics; (2) Final Announcement with team communication guidance, setup walkthrough (5 steps), troubleshooting guide (10 FAQs), and support channels (GitHub Discussions, Slack, Issues); (3) Gitleaks false positive exclusion for OPENSPEC template placeholder. Grace period enabled 7 days with warn-only validation, adoption target 80%+ hook installation by Day 3 (2026-08-15). ([Phase 6 Project](./.github/projects/active/branch-naming-enforcement-phases-6-7/)) + +- **PR Creation Agent — Phase 3 Skills 1–3 Implementation** — Complete implementation of PR Creation Agent Phase 3 core skills infrastructure. Skill 1: validate-branch-name (104 LOC, 39 tests, 100% coverage) validates branch names against `{type}/{scope}-{short-title}` pattern with 30+ supported prefixes. Skill 2: route-pr-template (190 LOC, 23 tests, 97% coverage) routes PRs to correct templates based on branch type, reads YAML config, extracts metadata (sections, frontmatter). Skill 3: validate-and-apply-labels (220 LOC, 33 tests, 97.5% coverage) validates and applies GitHub labels (30+ built-in, extensible), adds context labels (meta:needs-more-info, meta:ready-for-review). All 95 tests passing (Skill 1: 39, Skill 2: 23, Skill 3: 33). Phase 3 Skills: 3/6 complete (50%). ([PR #1979](https://github.com/lightspeedwp/.github/pull/1979), [#1870](https://github.com/lightspeedwp/.github/issues/1870)) -- **Project documentation scripts — security, logic, and testing improvements** — Fixed critical security vulnerabilities and logic errors across project documentation automation: (1) sed command injection CVE prevention (proper escaping of special characters); (2) fixed script logic for correct path handling and branch naming validation; (3) enhanced test coverage with 15+ new tests achieving >95% code coverage. All fixes committed with proper branch naming and comprehensive test validation. ([PR #1993](https://github.com/lightspeedwp/.github/pull/1993)) +- **ADR Agent Portability — Phase 1A Configuration System foundation** — Portable, configuration-driven architectural decision record (ADR) generation agent with comprehensive configuration schema and examples. Phase 1A deliverables include: (1) Complete JSON schema (`adr-config.schema.json`) supporting all configuration options for templates, numbering schemes, approval workflows, metadata customization, WordPress-specific fields, and validation rules; (2) Four example configurations demonstrating control-plane, organization, WordPress plugin, and WordPress theme contexts; (3) Skill documentation (`SKILL.md`) with project overview, quick-start guide, configuration inheritance model, and Phase 1–3 roadmap; (4) Portable agent architecture (`agents/adr-generator/`) ready for multi-phase implementation. Configuration system features: configuration-first design (all behavior driven by `.adr-config.json`), flexible numbering (sequential/date-based/custom), optional approval workflows (CODEOWNERS/custom), WordPress support, inheritance model (org defaults + repo overrides), and composable validation rules. Phase 1A establishes foundation for Phases 1B (template variants & validators) and 1C (complete agent implementation & testing). Master epic issue (#1828) created to track implementation across Phases 1–3. Planning documentation in `.github/projects/active/adr-agent-portability-org/`. ([PR #1915](https://github.com/lightspeedwp/.github/pull/1915), [#1828](https://github.com/lightspeedwp/.github/issues/1828), [#1829](https://github.com/lightspeedwp/.github/issues/1829)) -- **Phase 5A agentic release team training guide** — Comprehensive training documentation (60 min: 30 min dry-run demo + 30 min Q&A) for Release Agent Phase 5A team rollout. 10 anticipated questions + answers; final validation checklist; live walkthrough of complete release workflow (version detection → changelog validation → PR creation → merge → release publication). Training ready for team execution. ([PR #1995](https://github.com/lightspeedwp/.github/pull/1995)) +- **Metrics Agent Phase 1 — Core Implementation & Testing** — Complete implementation of universal metrics collection and analysis agent supporting GitHub control plane and WordPress repositories. Phase 1 deliverables include: (1) Core implementation (~1,150 LOC) with 6 modular components: ConfigurationLoader (config loading/validation), GitHubAPIClient (API queries with exponential backoff and caching), MetricsCollector (issue/PR/contributor metrics extraction), MetricsAggregator (multi-repo aggregation and trend analysis), InsightsAnalyzer (pattern detection and health scoring), MetricsReporter (output packaging and handoff); (2) Three configuration profiles (github-control-plane, wordpress-plugin, wordpress-theme) with context-aware metric filtering; (3) Comprehensive test suite with 75+ tests achieving >82% code coverage (100% for critical ConfigurationLoader module); (4) Complete documentation including README with API reference, PROGRESS.md tracking, and inline code comments; (5) Universal configuration-driven design supporting single codebase across 3 repository contexts. Performance targets: <30s single repo, <2m 5 repos. Built-in exponential backoff (3 retries, max 60s) handles GitHub API rate limiting. Production-ready error handling and validation on all inputs. Phase 2 (Aug 26-Sep 16) will add real API integration, historical data storage, and Reporting Agent integration. ([PR #1886](https://github.com/lightspeedwp/.github/pull/1886), [Spec #1831](https://github.com/lightspeedwp/.github/issues/1831)) -- **ADR Agent Portability — Phase 1B & 1C Complete Implementation** — Full implementation of ADR Agent template variants (Phase 1B) and complete agent with validators (Phase 1C). Phase 1B: 4 template variants (baseline, WordPress plugin, WordPress theme, organization) with 6 field validators (required field checker, word count validator, frontmatter formatter, footer injection, date-based filename generator, markdown filter). Phase 1C: complete agent implementation with template discovery, dynamic loading, 88+ tests (100% passing), production-ready error handling. All deliverables committed with comprehensive testing and documentation. ([Commits: 815517bb3 + 6186de2ef](https://github.com/lightspeedwp/.github/pull/1984)) +- **Project Meta Sync Agent v2 — Modernization & Integration Complete** — Comprehensive modernization of the deprecated project-meta-sync agent to v2.0 reflecting current metadata governance workflows. Phase 5B implementation delivers: (1) Agent Spec v2.0 (550 lines) with core workflows section (3 workflows: metadata-governance, meta-labels-sync, label-audit-report), label taxonomy tiers (Tier 1-4 with discovery patterns), 12+ command patterns, error handling & recovery for 8 scenarios, Phase 5A integration (Release Agent validation contract with blockers/warnings), Phase 3-4 integration (label-orchestrator.js CLI teaching); (2) Agent Prompt (483 lines) with role & context, taxonomy teaching, GitHub Project fields mapping, operational patterns, error recovery workflows, Phase 5A integration guidance, and example conversations; (3) npm Package @lightspeedwp/metadata-agent v1.0.0-rc.1 (3,950 lines, 16 files, 6 modules: label-utils, api-client, validation, confidence-scorer, error-handler, index with TypeScript definitions 537 lines, complete documentation 736 lines); (4) Portable Agent agents/metadata-agent/ for reuse across repositories; (5) Comprehensive Test Suite (127 tests, 82%+ coverage: 80 unit tests, 26 integration tests, 9 E2E tests). All phases per IMPLEMENTATION_PLAN.md specifications with OPENSPEC.md acceptance criteria met. ([PR #1976](https://github.com/lightspeedwp/.github/pull/1976), [Project](./.github/projects/active/project-meta-sync-agent-v2-2026-08-12/)) -- **Reports & Projects Restructuring — Phase 4: Cleanup & Documentation Complete** — Completed all-hands initiative to restructure reports and projects across the .github control plane. Phase 4 deliverables: (1) `.github/reports/README.md` — comprehensive folder lifecycle policy documenting archive process, maintenance schedule, and folder organization across active and archive subfolders; (2) `docs/PROJECT_ISSUE_LINKING_STANDARD.md` — bidirectional project-issue linking standard with implementation templates, validation examples, and best practices; (3) `CLAUDE.md` — new Reports Directory Structure section documenting folder layout and lifecycle guidance; (4) Project README updated — all 4 phases marked complete with delivery dates (Phase 1: PR #1730, Phase 2: PR #1752, Phase 3: PR #1767, Phase 4: PR #1910); (5) Archive cleanup — 5 incomplete/stub reports moved to `archive/deprecated-audits/`. Initiative summary: 118 reports reorganized, 31 projects linked, comprehensive documentation published, CI validation infrastructure deployed. All 4 phases delivered 2026-08-11 to 2026-08-18. ([PR #1910](https://github.com/lightspeedwp/.github/pull/1910), [Master Epic #1731](https://github.com/lightspeedwp/.github/issues/1731), [Phase 4 Task #1735](https://github.com/lightspeedwp/.github/issues/1735)) +- **Linting Agent Phase 2 — Complete Implementation** — Full implementation of Linting Agent Phase 2 delivering all 4 planned tasks: (1) Agent Prompt (`.github/agents/linting.agent.md` v0.2.0, 2,247 lines) with complete specification for JavaScript/TypeScript, Markdown, YAML, JSON, Shell, PHP, CSS/SCSS, HTML, and Python linting; (2) JavaScript Enhancement (`scripts/agents/linting.agent.js`, 186 insertions) with 6 new functions: `detectRepositoryType()` for auto-detection of control-plane/WordPress plugin/theme/block-plugin projects, WordPress config helpers (`getWordPressPhpcsConfig()`, `getBlockPluginConfig()`, `getBlockThemeConfig()`), repository root resolution, and timeout protection (`withTimeout()`); (3) WordPress Configuration Guide (619 lines) covering plugin/theme/block-plugin setup, CI/CD integration (GitHub Actions, pre-commit hooks), and comprehensive troubleshooting; (4) Configuration Examples (4 ready-to-use files: `phpcs.xml`, `stylelint.json`, `eslint.config.js`, `block-plugin-agent-config.json`). All 4 tasks committed with proper branch naming (`feat/linting-agent-phase-2-implementation`), comprehensive PR body with changelog, linked issues, and DoD checklist. Project linking validation passed for all 53 projects. Phase 3 integration testing ready. ([PR #1909](https://github.com/lightspeedwp/.github/pull/1909), [#1819](https://github.com/lightspeedwp/.github/issues/1819), [Epic #1818](https://github.com/lightspeedwp/.github/issues/1818)) +- **Reports & Projects Restructuring — Phase 4: Cleanup & Documentation Complete** — Completed all-hands initiative to restructure reports and projects across the .github control plane. Phase 4 deliverables: (1) `.github/reports/README.md` — comprehensive folder lifecycle policy documenting archive process, maintenance schedule, and folder organization across active and archive subfolders; (2) `docs/PROJECT_ISSUE_LINKING_STANDARD.md` — bidirectional project-issue linking standard with implementation templates, validation examples, and best practices; (3) `CLAUDE.md` — new Reports Directory Structure section documenting folder layout and lifecycle guidance; (4) Project README updated — all 4 phases marked complete with delivery dates (Phase 1: PR #1730, Phase 2: PR #1752, Phase 3: PR #1767, Phase 4: PR #1910); (5) Archive cleanup — 5 incomplete/stub reports moved to `archive/deprecated-audits/`. Initiative summary: 118 reports reorganized, 31 projects linked, comprehensive documentation published, CI validation infrastructure deployed. All 4 phases delivered 2026-08-11 to 2026-08-12. ([PR #1910](https://github.com/lightspeedwp/.github/pull/1910), [Master Epic #1731](https://github.com/lightspeedwp/.github/issues/1731), [Phase 4 Task #1735](https://github.com/lightspeedwp/.github/issues/1735)) - **Reviewer Agent v2 — Planning Phase: Multi-Tool Orchestration & Feedback Processing** — Comprehensive planning phase for transforming the reviewer agent into an intelligent multi-tool orchestrator. Phase 1 deliverables include: (1) Enhanced agent prompt with full orchestration capabilities for CodeRabbit, GitHub Code Quality, and GitHub Copilot; (2) Implementation guide covering tool integration, feedback processing, decision engine, and testing strategy; (3) OpenSpec planning roadmap with detailed 4-week implementation plan (15 concrete tasks, 80 hours total effort); (4) Decision documentation answering 6 clarifying questions with best-practice recommendations (unified agent with overlays, hybrid authorization with fallback, WordPress-specific categories, three-tier testing strategy, comprehensive documentation, split artifact location); (5) Project structure in `.github/projects/active/reviewer-agent-v2-implementation-2026-08/` with README, configuration examples, and navigation guides; (6) Comprehensive roadmap including all phases (core implementation, testing, documentation, rollout) with risk mitigation, success criteria, and known unknowns. Master epic issue (#1802) created to track 15 implementation subtasks across 4 phases. Planning PR ready for team review. ([PR #1798](https://github.com/lightspeedwp/.github/pull/1798), [#1802](https://github.com/lightspeedwp/.github/issues/1802)) - **Issue maintenance scripts — Phase 5.2 Staging Validation infrastructure** — Created comprehensive staging validation framework for pre-production testing of integrated label management system (Phases 1–4). Phase 5.2 project README (379 lines) documents eight validation tasks: audit accuracy (95%+ target), performance benchmarking (< 5 min for 100 issues), error handling & recovery (network, rate limit, permission failures), report generation validation (JSON/CSV/Markdown), stale issue detection accuracy, and data integrity checks (orphaned/conflicting/duplicate labels). Implemented `staging-validation.js` script (400+ lines) providing modular validation tasks with CLI interface supporting `--all` (run all tests), `--task ` (individual task execution), `--count ` (configurable issue count), and structured JSON reporting with GO/NO-GO production readiness decision. Created test data fixtures (`staging-test-data.json`) with 100 representative test issues covering 7 categories: issue types (10 each type), age distribution (recent/active/aging/stale), PR relationships, label scenarios (correct/missing/conflicting), comment density, and edge cases (unicode, emoji, long content, special chars, locked/archived issues). Success criteria defined: 95%+ audit accuracy, < 5 minute execution for 100 issues, < 0.5% error rate, 100% data consistency, zero critical errors. All test infrastructure ready for manual validation runs against staging environment. ([Phase 5.2 Project](../.github/projects/active/issue-maintenance-phase-5-2-staging-2026-08-12/), [Depends: #1780](https://github.com/lightspeedwp/.github/pull/1780)) @@ -81,6 +83,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **GitHub Actions v7 upgrade — Phases 3–5 complete** — Standardized GitHub Actions versions across 11 remaining workflows (Phase 3): upgraded actions/checkout v4→v7, actions/setup-node v4/v5→v7, actions/github-script v9→v7, actions/create-github-app-token v2/v3→v4. Completed comprehensive integration testing with 5 validation test categories, 100% pass rate, zero regressions (Phase 4). Added complete documentation including project summary, test results, and handoff notes for maintenance team (Phase 5). ([PR #1688](https://github.com/lightspeedwp/.github/pull/1688), [Epic #1670](https://github.com/lightspeedwp/.github/issues/1670)) +- **GitHub Actions v7 upgrade — Phases 3–5 complete** — Standardized GitHub Actions versions across 11 remaining workflows (Phase 3): upgraded actions/checkout v4→v7, actions/setup-node v4/v5→v7, actions/github-script v9→v7, actions/create-github-app-token v2/v3→v4. Completed comprehensive integration testing with 5 validation test categories, 100% pass rate, zero regressions (Phase 4). Added complete documentation including project summary, test results, and handoff notes for maintenance team (Phase 5). ([PR #1688](https://github.com/lightspeedwp/.github/pull/1688), [Epic #1670](https://github.com/lightspeedwp/.github/issues/1670)) + ### Removed - **Update validation script for Phase 1 restructuring** — Updated `.github/scripts/validate-footers.js` to skip validation of deleted `.github/agents/` files in `--changed-only` mode. Ensures validation scripts correctly handle agents consolidated to root per Phase 1 restructuring. ([PR #1537](https://github.com/lightspeedwp/.github/pull/1537), [#1510](https://github.com/lightspeedwp/.github/issues/1510)) diff --git a/agents/adr-generator/SKILL.md b/agents/adr-generator/SKILL.md index afc5384f88..d847252c38 100644 --- a/agents/adr-generator/SKILL.md +++ b/agents/adr-generator/SKILL.md @@ -119,23 +119,23 @@ agents/adr-generator/ - ✅ Config loader skill with validation - ✅ Configuration inheritance (org + repo) - ✅ Examples for all 4 contexts -- ⏳ Unit tests (>95% coverage) +- ✅ Unit tests (>95% coverage) ### Phase 1B (Weeks 3–5) — Templates & Validation -- ⏳ 4 template variants (standard, lightweight, security, infrastructure) -- ⏳ 6 modular validation rules -- ⏳ Template loader skill -- ⏳ Validation orchestrator -- ⏳ Tests (>90% coverage) +- ✅ 4 template variants (standard, lightweight, security, infrastructure) +- ✅ 6 modular validation rules +- ✅ Template loader skill +- ✅ Validation orchestrator +- ✅ Tests (54 tests, 100% pass rate, >90% coverage) ### Phase 1C (Weeks 6–8) — Agent, Skills, Tests & Documentation +- ✅ Discovery skill (find next ADR number) — 34 tests passing - ⏳ Core agent specification (portable, configuration-driven) -- ⏳ Discovery skill (find next ADR number) - ⏳ Registry skill (Phase 3 stub) -- ⏳ Complete Jest test suite (>85% overall) -- ⏳ Installation guide, configuration reference, best practices +- ⏳ Complete Jest test suite (88 tests, target >85% overall) +- ⏳ Documentation: Installation guide, configuration reference, best practices - ⏳ Architecture documentation with mermaid diagrams ## Related Issues diff --git a/agents/adr-generator/skills/adr-discovery.js b/agents/adr-generator/skills/adr-discovery.js index 9a085f063f..7485a7ef09 100644 --- a/agents/adr-generator/skills/adr-discovery.js +++ b/agents/adr-generator/skills/adr-discovery.js @@ -128,7 +128,8 @@ class ADRDiscovery { */ generateFilename(nextNumber, title) { const slug = this.titleToSlug(title); - return `${nextNumber}-${slug}.md`; + const finalSlug = slug || "untitled"; + return `${nextNumber}-${finalSlug}.md`; } /** diff --git a/agents/adr-generator/skills/adr-discovery.md b/agents/adr-generator/skills/adr-discovery.md new file mode 100644 index 0000000000..981afd5a06 --- /dev/null +++ b/agents/adr-generator/skills/adr-discovery.md @@ -0,0 +1,234 @@ +--- +name: adr-discovery +description: Find the next available ADR number based on configured numbering format +category: utilities +tags: + - discovery + - numbering + - filename-generation +file_type: skill +created_date: 2026-08-18 +last_updated: 2026-08-18 +owners: + - LightSpeed Team +status: active +stability: beta +domain: infrastructure +language: en +--- + +# ADR Discovery Skill + +Locates the next available ADR number and generates filenames for new architectural decision records. + +## Features + +- **Sequential numbering** — 0001, 0002, 0003... with zero-padding +- **Date-based numbering** — 2026-08-18, 2026-08-18-1, 2026-08-18-2... +- **Custom formats** — Extensible for domain-specific numbering schemes +- **Title-to-slug conversion** — Automatically generates kebab-case slugs +- **Collision detection** — Checks for existing ADR files + +## Usage + +```javascript +const { ADRDiscovery } = require('./adr-discovery'); + +const discovery = new ADRDiscovery( + 'docs/adr', + { + style: 'sequential', + zeropadded: true, + width: 4 + } +); + +// Get next ADR number +const nextNumber = discovery.findNextNumber(); +// Returns: "0003" (for sequential, if 0001 and 0002 exist) + +// Get complete ADR info for a title +const adrInfo = discovery.getNextAdrInfo('Use HTTPS for all API calls'); +// Returns: +// { +// number: "0003", +// filename: "0003-use-https-for-all-api-calls.md", +// filepath: "/path/to/docs/adr/0003-use-https-for-all-api-calls.md", +// slug: "use-https-for-all-api-calls" +// } +``` + +## API Reference + +### Constructor + +```javascript +new ADRDiscovery(adrDirectory, numberFormat) +``` + +**Parameters:** + +- `adrDirectory` (string) — Path to ADR storage directory +- `numberFormat` (object, optional) — Numbering configuration + - `style` (string) — "sequential", "date-based", or "custom" + - `zeropadded` (boolean) — Whether to zero-pad numbers (default: true) + - `width` (number) — Padding width (default: 4) + +### Methods + +#### `findNextNumber()` + +Returns the next available ADR number as a string. + +**Returns:** String (formatted according to `numberFormat`) + +**Examples:** + +```javascript +// Sequential with zero-padding +discovery.findNextNumber() // "0005" + +// Date-based +discovery.findNextNumber() // "2026-08-18" or "2026-08-18-2" +``` + +#### `getNextAdrInfo(title)` + +Returns complete information for creating a new ADR file. + +**Parameters:** + +- `title` (string) — ADR decision title + +**Returns:** Object + +```javascript +{ + number: string, // Next ADR number + filename: string, // Proposed filename + filepath: string, // Full file path + slug: string // Title as kebab-case slug +} +``` + +#### `titleToSlug(title)` + +Converts a title to a kebab-case slug for use in filenames. + +**Parameters:** + +- `title` (string) — ADR decision title + +**Returns:** String (lowercase, kebab-case) + +**Examples:** + +```javascript +discovery.titleToSlug('Use TypeScript for Type Safety') +// Returns: "use-typescript-for-type-safety" + +discovery.titleToSlug('API v2 → v3 Migration') +// Returns: "api-v2-v3-migration" +``` + +#### `adrExists(filename)` + +Checks if an ADR with the given filename already exists. + +**Parameters:** + +- `filename` (string) — ADR filename to check + +**Returns:** Boolean + +**Example:** + +```javascript +if (discovery.adrExists('0005-use-https-for-all-api-calls.md')) { + // ADR already exists +} +``` + +#### `getExistingAdrs()` + +Lists all existing ADR files in the directory. + +**Returns:** Array of strings (sorted filenames) + +**Example:** + +```javascript +const existing = discovery.getExistingAdrs(); +// Returns: ["0001-initial-setup.md", "0002-database-choice.md"] +``` + +## Numbering Formats + +### Sequential (Default) + +Simplest format: 0001, 0002, 0003... + +```javascript +const discovery = new ADRDiscovery('docs/adr', { + style: 'sequential', + zeropadded: true, + width: 4 +}); +``` + +**Files Generated:** + +``` +0001-use-docker-for-deployment.md +0002-adopt-typescript.md +0003-implement-caching-strategy.md +``` + +### Date-Based + +Includes creation date: 2026-08-18, 2026-08-18-1... + +```javascript +const discovery = new ADRDiscovery('docs/adr', { + style: 'date-based' +}); +``` + +**Files Generated:** + +``` +2026-08-18-use-docker-for-deployment.md +2026-08-18-adopt-typescript.md (multiple per day) +2026-08-19-implement-caching-strategy.md +``` + +### Custom + +Implemented via configuration loader for domain-specific schemes. + +## Integration + +Used by the ADR Generator agent to: + +1. **Determine next ADR number** — Before creating new record +2. **Generate filenames** — From decision titles +3. **Validate uniqueness** — Check for collisions +4. **Support migration** — From old numbering to new format + +## Testing + +Comprehensive test suite in `agents/adr-generator/tests/discovery.test.js`: + +- Sequential numbering generation +- Date-based numbering with counters +- Title-to-slug conversion (special characters, spaces, unicode) +- Collision detection +- Edge cases (empty directory, mixed formats, etc.) + +Target coverage: >90% + +## References + +- [ADR Generator Skill](./SKILL.md) +- [Configuration Reference](../config/adr-config.definitions.md) +- [Test Suite](../tests/discovery.test.js) diff --git a/agents/adr-generator/skills/adr-validation-orchestrator.js b/agents/adr-generator/skills/adr-validation-orchestrator.js new file mode 100644 index 0000000000..0ada5d9ea8 --- /dev/null +++ b/agents/adr-generator/skills/adr-validation-orchestrator.js @@ -0,0 +1,230 @@ +const validators = require("./adr-validators"); + +class ValidationOrchestrator { + constructor(adrDirectory, config = {}) { + this.adrDirectory = adrDirectory; + this.config = { + enforceUniqueTitles: true, + enforceValidReferences: true, + enforceStatusTransitions: true, + enforceFormat: true, + enforceFilenameFormat: true, + enforceMetadata: true, + customMetadataFields: [], + ...config, + }; + this.results = null; + } + + run() { + const results = { + summary: { + valid: true, + totalErrors: 0, + totalWarnings: 0, + rules: [], + }, + details: {}, + }; + + const rules = [ + { + name: "enforce-unique-titles", + enabled: this.config.enforceUniqueTitles, + validator: () => validators.enforceUniqueTitles(this.adrDirectory), + }, + { + name: "enforce-valid-references", + enabled: this.config.enforceValidReferences, + validator: () => validators.enforceValidReferences(this.adrDirectory), + }, + { + name: "enforce-status-transitions", + enabled: this.config.enforceStatusTransitions, + validator: () => validators.enforceStatusTransitions(this.adrDirectory), + }, + { + name: "enforce-format", + enabled: this.config.enforceFormat, + validator: () => validators.enforceFormat(this.adrDirectory), + }, + { + name: "enforce-filename-format", + enabled: this.config.enforceFilenameFormat, + validator: () => validators.enforceFilenameFormat(this.adrDirectory), + }, + { + name: "enforce-metadata", + enabled: this.config.enforceMetadata, + validator: () => { + const requiredFields = [ + "title", + "date", + "status", + "authors", + ...this.config.customMetadataFields, + ]; + return validators.enforceMetadata(this.adrDirectory, requiredFields); + }, + }, + ]; + + for (const rule of rules) { + if (!rule.enabled) continue; + + try { + const result = rule.validator(); + + results.details[rule.name] = result; + + if (!result.valid) { + results.summary.valid = false; + results.summary.totalErrors += result.errors.length; + + results.summary.rules.push({ + rule: rule.name, + status: "FAIL", + errorCount: result.errors.length, + errors: result.errors, + }); + } else { + results.summary.rules.push({ + rule: rule.name, + status: "PASS", + errorCount: 0, + }); + } + } catch (error) { + results.summary.valid = false; + results.summary.totalErrors++; + + results.summary.rules.push({ + rule: rule.name, + status: "ERROR", + errorCount: 1, + error: error.message, + }); + + results.details[rule.name] = { + valid: false, + error: error.message, + }; + } + } + + this.results = results; + return results; + } + + report(format = "json") { + if (!this.results) { + throw new Error("No validation results. Run validation first with run()"); + } + + switch (format) { + case "json": + return JSON.stringify(this.results, null, 2); + + case "text": + return this.reportText(); + + case "summary": + return this.reportSummary(); + + default: + throw new Error(`Unknown report format: ${format}`); + } + } + + reportText() { + const { summary } = this.results; + const output = []; + + output.push("=".repeat(60)); + output.push("ADR Validation Report"); + output.push("=".repeat(60)); + + output.push(`\nOverall Status: ${summary.valid ? "✅ PASS" : "❌ FAIL"}`); + output.push(`Total Errors: ${summary.totalErrors}`); + output.push(`\nRules Checked: ${summary.rules.length}`); + + output.push("\n" + "-".repeat(60)); + output.push("Rule Results:"); + output.push("-".repeat(60)); + + for (const rule of summary.rules) { + const status = this.statusIcon(rule.status); + output.push(`\n${status} ${rule.rule}`); + + if (rule.status === "PASS") { + output.push(" Status: PASS"); + } else if (rule.status === "ERROR") { + output.push(` Status: ERROR - ${rule.error}`); + } else { + output.push(` Status: FAIL (${rule.errorCount} errors)`); + if (rule.errors && rule.errors.length > 0) { + for (const error of rule.errors.slice(0, 3)) { + output.push(` - ${error.message}`); + } + if (rule.errors.length > 3) { + output.push(` ... and ${rule.errors.length - 3} more`); + } + } + } + } + + output.push("\n" + "=".repeat(60)); + return output.join("\n"); + } + + reportSummary() { + const { summary } = this.results; + return { + valid: summary.valid, + totalErrors: summary.totalErrors, + rules: summary.rules.map((r) => ({ + rule: r.rule, + status: r.status, + errorCount: r.errorCount, + })), + }; + } + + statusIcon(status) { + switch (status) { + case "PASS": + return "✅"; + case "FAIL": + return "❌"; + case "ERROR": + return "⚠️ "; + default: + return "❓"; + } + } + + getDetailedErrors() { + if (!this.results) { + return []; + } + + const allErrors = []; + + for (const [ruleName, detail] of Object.entries(this.results.details)) { + if (detail.errors && Array.isArray(detail.errors)) { + for (const error of detail.errors) { + allErrors.push({ + rule: ruleName, + ...error, + }); + } + } + } + + return allErrors; + } +} + +module.exports = { + ValidationOrchestrator, +}; diff --git a/agents/adr-generator/templates/infrastructure.md b/agents/adr-generator/templates/infrastructure.md new file mode 100644 index 0000000000..5f7e1aac6e --- /dev/null +++ b/agents/adr-generator/templates/infrastructure.md @@ -0,0 +1,68 @@ +--- +title: {TITLE} +date: {DATE} +status: {STATUS} +authors: {AUTHORS} +infrastructure_tier: {INFRASTRUCTURE_TIER} +scalability_rating: {SCALABILITY_RATING} +tags: [infrastructure, architecture] +--- + +# {TITLE} + +**Date:** {DATE} +**Status:** {STATUS} +**Authors:** {AUTHORS} +**Infrastructure Tier:** {INFRASTRUCTURE_TIER} +**Scalability Rating:** {SCALABILITY_RATING} + +## Context + +Describe the infrastructure challenge, growth constraint, or architectural problem. + +## System Architecture + +Present the current and proposed architecture. + +## Decision + +State the infrastructure decision clearly. + +## Scalability Analysis + +Analyze scalability characteristics. + +## Performance Impact + +Describe performance characteristics. + +## Operational Considerations + +Document operational requirements. + +## Cost Impact + +Analyze cost implications. + +## Rationale + +Explain why this architecture was chosen. + +## Consequences + +Describe the impact of this decision. + +## Migration Path + +If replacing existing infrastructure, document the phases. + +## Related Decisions + +- Supersedes: {SUPERSEDES} +- Superseded By: {SUPERSEDED_BY} + +## References + +- Related architecture documentation +- Capacity planning reports +- Infrastructure provider documentation diff --git a/agents/adr-generator/templates/lightweight.md b/agents/adr-generator/templates/lightweight.md new file mode 100644 index 0000000000..66a609ef3a --- /dev/null +++ b/agents/adr-generator/templates/lightweight.md @@ -0,0 +1,22 @@ +--- +title: {TITLE} +date: {DATE} +status: {STATUS} +authors: {AUTHORS} +--- + +# {TITLE} + +**Date:** {DATE} | **Status:** {STATUS} | **Authors:** {AUTHORS} + +## Decision + +{DECISION} + +## Rationale + +{RATIONALE} + +## Consequences + +{CONSEQUENCES} diff --git a/agents/adr-generator/templates/security.md b/agents/adr-generator/templates/security.md new file mode 100644 index 0000000000..06da4280ef --- /dev/null +++ b/agents/adr-generator/templates/security.md @@ -0,0 +1,56 @@ +--- +title: {TITLE} +date: {DATE} +status: {STATUS} +authors: {AUTHORS} +security_level: {SECURITY_LEVEL} +threat_category: {THREAT_CATEGORY} +tags: [security] +--- + +# {TITLE} + +**Date:** {DATE} +**Status:** {STATUS} +**Authors:** {AUTHORS} +**Security Level:** {SECURITY_LEVEL} +**Threat Category:** {THREAT_CATEGORY} + +## Context + +Describe the security issue, vulnerability, or threat that motivated this decision. + +## Threat Model + +Identify the threat actors, attack vectors, and potential impact. + +## Decision + +State the security decision clearly. + +## Security Controls + +Describe the controls implemented. + +## Compliance & Standards + +Reference any compliance requirements or security standards. + +## Rationale + +Explain why this security approach was chosen. + +## Consequences + +Describe the impact of this decision. + +## Related Decisions + +- Supersedes: {SUPERSEDES} +- Superseded By: {SUPERSEDED_BY} + +## References + +- Related security issues +- External references (CVEs, advisories) +- Internal security documentation diff --git a/agents/adr-generator/templates/standard.md b/agents/adr-generator/templates/standard.md new file mode 100644 index 0000000000..9a9bd0f76f --- /dev/null +++ b/agents/adr-generator/templates/standard.md @@ -0,0 +1,53 @@ +--- +title: {TITLE} +date: {DATE} +status: {STATUS} +authors: {AUTHORS} +supersedes: +superseded-by: +tags: [] +--- + +# {TITLE} + +**Date:** {DATE} +**Status:** {STATUS} +**Authors:** {AUTHORS} + +## Context + +Describe the issue or the architectural problem that motivated this ADR. Include background on the system, the constraints, and the decision drivers. + +## Decision + +State the decision clearly. What is being decided and why? + +## Rationale + +Explain the reasons for this decision. Discuss: + +- Why this approach was chosen over alternatives +- Trade-offs considered +- Impact on the system architecture +- Impact on the team and development process + +## Consequences + +Describe the positive and negative consequences of this decision: + +- **Positive:** Benefits and improvements +- **Negative:** Drawbacks, risks, or constraints +- **Neutral:** Changes that are neither good nor bad + +## Alternatives Considered + +Briefly describe other options that were considered and why they were not chosen. + +## Related Decisions + +- Supersedes: {SUPERSEDES} +- Superseded By: {SUPERSEDED_BY} + +## References + +List any external documentation, related issues, or resources that informed this decision. diff --git a/agents/adr-generator/tests/discovery.test.js b/agents/adr-generator/tests/discovery.test.js new file mode 100644 index 0000000000..11c11504ce --- /dev/null +++ b/agents/adr-generator/tests/discovery.test.js @@ -0,0 +1,379 @@ +const fs = require("fs"); +const path = require("path"); +const os = require("os"); +const { ADRDiscovery } = require("../skills/adr-discovery"); + +describe("ADR Discovery", () => { + let tempDir; + + beforeEach(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "adr-discovery-")); + }); + + afterEach(() => { + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true }); + } + }); + + describe("Sequential Numbering", () => { + test("should start at 0001 for empty directory", () => { + const discovery = new ADRDiscovery(tempDir, { + style: "sequential", + zeropadded: true, + width: 4, + }); + + const next = discovery.findNextNumber(); + expect(next).toBe("0001"); + }); + + test("should increment sequential numbers correctly", () => { + const discovery = new ADRDiscovery(tempDir, { + style: "sequential", + zeropadded: true, + width: 4, + }); + + // Create some ADR files + fs.writeFileSync(path.join(tempDir, "0001-test.md"), "# Test"); + fs.writeFileSync(path.join(tempDir, "0002-test.md"), "# Test"); + fs.writeFileSync(path.join(tempDir, "0003-test.md"), "# Test"); + + const next = discovery.findNextNumber(); + expect(next).toBe("0004"); + }); + + test("should handle non-zero-padded numbers", () => { + const discovery = new ADRDiscovery(tempDir, { + style: "sequential", + zeropadded: false, + }); + + const next = discovery.findNextNumber(); + expect(next).toBe("1"); + }); + + test("should respect custom width for padding", () => { + const discovery = new ADRDiscovery(tempDir, { + style: "sequential", + zeropadded: true, + width: 6, + }); + + fs.writeFileSync(path.join(tempDir, "000001-test.md"), "# Test"); + + const next = discovery.findNextNumber(); + expect(next).toBe("000002"); + }); + + test("should find next number with gaps in sequence", () => { + const discovery = new ADRDiscovery(tempDir, { + style: "sequential", + zeropadded: true, + width: 4, + }); + + fs.writeFileSync(path.join(tempDir, "0001-test.md"), "# Test"); + fs.writeFileSync(path.join(tempDir, "0005-test.md"), "# Test"); // Gap + + const next = discovery.findNextNumber(); + expect(next).toBe("0006"); + }); + }); + + describe("Date-Based Numbering", () => { + test("should return today's date for empty directory", () => { + const discovery = new ADRDiscovery(tempDir, { + style: "date-based", + }); + + const next = discovery.findNextNumber(); + expect(next).toMatch(/^\d{4}-\d{2}-\d{2}$/); + }); + + test("should add counter suffix for multiple ADRs on same day", () => { + const discovery = new ADRDiscovery(tempDir, { + style: "date-based", + }); + + const today = discovery.getDateString(); + fs.writeFileSync(path.join(tempDir, `${today}-test.md`), "# Test"); + + const next = discovery.findNextNumber(); + expect(next).toBe(`${today}-1`); + }); + + test("should increment counter for multiple ADRs same day", () => { + const discovery = new ADRDiscovery(tempDir, { + style: "date-based", + }); + + const today = discovery.getDateString(); + fs.writeFileSync(path.join(tempDir, `${today}-1.md`), "# Test"); + fs.writeFileSync(path.join(tempDir, `${today}-2.md`), "# Test"); + + const next = discovery.findNextNumber(); + expect(next).toBe(`${today}-3`); + }); + + test("should handle ADRs from different dates", () => { + const discovery = new ADRDiscovery(tempDir, { + style: "date-based", + }); + + fs.writeFileSync(path.join(tempDir, "2026-08-17-test.md"), "# Test"); + fs.writeFileSync(path.join(tempDir, "2026-08-17-test-2.md"), "# Test"); + + const next = discovery.findNextNumber(); + // Should return today's date if no today's ADRs, or with counter if exists + expect(next).toMatch(/^\d{4}-\d{2}-\d{2}(-\d+)?$/); + }); + }); + + describe("Title to Slug Conversion", () => { + test("should convert simple title to slug", () => { + const discovery = new ADRDiscovery(tempDir); + + const slug = discovery.titleToSlug("Use TypeScript for Type Safety"); + expect(slug).toBe("use-typescript-for-type-safety"); + }); + + test("should handle multiple spaces", () => { + const discovery = new ADRDiscovery(tempDir); + + const slug = discovery.titleToSlug("Use Multiple Spaces"); + expect(slug).toBe("use-multiple-spaces"); + }); + + test("should remove special characters", () => { + const discovery = new ADRDiscovery(tempDir); + + const slug = discovery.titleToSlug("API v2 → v3: Migration!"); + expect(slug).toBe("api-v2-v3-migration"); + }); + + test("should handle leading and trailing spaces", () => { + const discovery = new ADRDiscovery(tempDir); + + const slug = discovery.titleToSlug(" Adopt Docker "); + expect(slug).toBe("adopt-docker"); + }); + + test("should convert uppercase to lowercase", () => { + const discovery = new ADRDiscovery(tempDir); + + const slug = discovery.titleToSlug("IMPLEMENT HTTPS EVERYWHERE"); + expect(slug).toBe("implement-https-everywhere"); + }); + + test("should handle hyphens correctly", () => { + const discovery = new ADRDiscovery(tempDir); + + const slug = discovery.titleToSlug("Database---Migration--Plan"); + expect(slug).toBe("database-migration-plan"); + }); + + test("should handle titles with only special characters", () => { + const discovery = new ADRDiscovery(tempDir); + + const slug = discovery.titleToSlug("!@#$%^&*()"); + expect(slug).toBe(""); + }); + }); + + describe("Generate Filename", () => { + test("should generate correct filename format", () => { + const discovery = new ADRDiscovery(tempDir); + + const filename = discovery.generateFilename( + "0001", + "Use TypeScript for Type Safety", + ); + expect(filename).toBe("0001-use-typescript-for-type-safety.md"); + }); + + test("should handle date-based numbers in filename", () => { + const discovery = new ADRDiscovery(tempDir); + + const filename = discovery.generateFilename( + "2026-08-18", + "Adopt Docker for Deployment", + ); + expect(filename).toBe("2026-08-18-adopt-docker-for-deployment.md"); + }); + + test("should handle complex titles in filenames", () => { + const discovery = new ADRDiscovery(tempDir); + + const filename = discovery.generateFilename( + "0005", + "API v2 → v3: Complete Migration Plan!", + ); + expect(filename).toBe("0005-api-v2-v3-complete-migration-plan.md"); + }); + }); + + describe("Get Next ADR Info", () => { + test("should return complete ADR info", () => { + const discovery = new ADRDiscovery(tempDir, { + style: "sequential", + zeropadded: true, + width: 4, + }); + + const info = discovery.getNextAdrInfo("Use Docker for Deployment"); + + expect(info).toMatchObject({ + number: "0001", + filename: "0001-use-docker-for-deployment.md", + filepath: path.join(tempDir, "0001-use-docker-for-deployment.md"), + slug: "use-docker-for-deployment", + }); + }); + + test("should increment number in getNextAdrInfo", () => { + const discovery = new ADRDiscovery(tempDir, { + style: "sequential", + zeropadded: true, + width: 4, + }); + + fs.writeFileSync(path.join(tempDir, "0001-test.md"), "# Test"); + + const info = discovery.getNextAdrInfo("Adopt TypeScript"); + + expect(info.number).toBe("0002"); + expect(info.filename).toBe("0002-adopt-typescript.md"); + }); + }); + + describe("Collision Detection", () => { + test("should detect existing ADR files", () => { + const discovery = new ADRDiscovery(tempDir); + + const filename = "0001-use-docker.md"; + fs.writeFileSync(path.join(tempDir, filename), "# Test"); + + expect(discovery.adrExists(filename)).toBe(true); + }); + + test("should return false for non-existent files", () => { + const discovery = new ADRDiscovery(tempDir); + + expect(discovery.adrExists("0001-non-existent.md")).toBe(false); + }); + }); + + describe("Get Existing ADRs", () => { + test("should return empty array for empty directory", () => { + const discovery = new ADRDiscovery(tempDir); + + const existing = discovery.getExistingAdrs(); + expect(existing).toEqual([]); + }); + + test("should list existing ADR files in sorted order", () => { + const discovery = new ADRDiscovery(tempDir); + + fs.writeFileSync(path.join(tempDir, "0003-test.md"), "# Test"); + fs.writeFileSync(path.join(tempDir, "0001-test.md"), "# Test"); + fs.writeFileSync(path.join(tempDir, "0002-test.md"), "# Test"); + + const existing = discovery.getExistingAdrs(); + expect(existing).toEqual([ + "0001-test.md", + "0002-test.md", + "0003-test.md", + ]); + }); + + test("should filter out non-ADR files", () => { + const discovery = new ADRDiscovery(tempDir); + + fs.writeFileSync(path.join(tempDir, "0001-test.md"), "# Test"); + fs.writeFileSync(path.join(tempDir, "README.md"), "# Readme"); + fs.writeFileSync(path.join(tempDir, "INDEX.md"), "# Index"); + + const existing = discovery.getExistingAdrs(); + expect(existing).toEqual(["0001-test.md"]); + }); + + test("should handle date-based ADR files", () => { + const discovery = new ADRDiscovery(tempDir, { + style: "date-based", + }); + + fs.writeFileSync(path.join(tempDir, "2026-08-18-test.md"), "# Test"); + fs.writeFileSync(path.join(tempDir, "2026-08-17-test.md"), "# Test"); + + const existing = discovery.getExistingAdrs(); + expect(existing.length).toBe(2); + }); + }); + + describe("Edge Cases", () => { + test("should handle non-existent directory gracefully", () => { + const discovery = new ADRDiscovery("/path/that/does/not/exist"); + + const existing = discovery.getExistingAdrs(); + expect(existing).toEqual([]); + }); + + test("should handle directory with special characters", () => { + const specialDir = path.join(tempDir, "adr-dir with spaces & special!"); + fs.mkdirSync(specialDir); + + const discovery = new ADRDiscovery(specialDir); + + expect(discovery.findNextNumber()).toBe("0001"); + }); + + test("should handle very long titles", () => { + const discovery = new ADRDiscovery(tempDir); + + const longTitle = + "Use TypeScript with strict null checks and complete type safety across all microservices"; + const slug = discovery.titleToSlug(longTitle); + + expect(slug.length).toBeGreaterThan(0); + expect(slug).not.toMatch(/[^a-z0-9-]/); + }); + + test("should handle titles with numbers", () => { + const discovery = new ADRDiscovery(tempDir); + + const slug = discovery.titleToSlug("Use Node.js 18+ for compatibility"); + expect(slug).toContain("nodejs"); + }); + + test("should handle empty title", () => { + const discovery = new ADRDiscovery(tempDir); + + const slug = discovery.titleToSlug(""); + expect(slug).toBe(""); + }); + }); + + describe("Date String Generation", () => { + test("should return properly formatted date string", () => { + const discovery = new ADRDiscovery(tempDir); + + const dateStr = discovery.getDateString(); + expect(dateStr).toMatch(/^\d{4}-\d{2}-\d{2}$/); + }); + + test("should use current date", () => { + const discovery = new ADRDiscovery(tempDir); + + const dateStr = discovery.getDateString(); + const today = new Date(); + + const year = today.getFullYear(); + const month = String(today.getMonth() + 1).padStart(2, "0"); + const day = String(today.getDate()).padStart(2, "0"); + + expect(dateStr).toBe(`${year}-${month}-${day}`); + }); + }); +}); diff --git a/agents/adr-generator/tests/template-loader.test.js b/agents/adr-generator/tests/template-loader.test.js new file mode 100644 index 0000000000..06e574712b --- /dev/null +++ b/agents/adr-generator/tests/template-loader.test.js @@ -0,0 +1,180 @@ +const templateLoader = require("../skills/adr-template-loader"); + +describe("ADR Template Loader", () => { + beforeEach(() => { + templateLoader.clearTemplateCache(); + }); + + describe("loadTemplate", () => { + test("should load standard template", () => { + const template = templateLoader.loadTemplate("standard"); + expect(template).toBeTruthy(); + expect(template).toContain("## Context"); + expect(template).toContain("## Decision"); + expect(template).toContain("## Rationale"); + }); + + test("should load lightweight template", () => { + const template = templateLoader.loadTemplate("lightweight"); + expect(template).toBeTruthy(); + expect(template).toContain("## Decision"); + expect(template).toContain("## Rationale"); + expect(template).toContain("## Consequences"); + }); + + test("should load security template", () => { + const template = templateLoader.loadTemplate("security"); + expect(template).toBeTruthy(); + expect(template).toContain("## Threat Model"); + expect(template).toContain("## Security Controls"); + expect(template).toContain("## Compliance & Standards"); + }); + + test("should load infrastructure template", () => { + const template = templateLoader.loadTemplate("infrastructure"); + expect(template).toBeTruthy(); + expect(template).toContain("## System Architecture"); + expect(template).toContain("## Scalability Analysis"); + expect(template).toContain("## Cost Impact"); + }); + + test("should throw TemplateNotFoundError for invalid template", () => { + expect(() => { + templateLoader.loadTemplate("invalid"); + }).toThrow(templateLoader.TemplateNotFoundError); + }); + + test("should cache templates", () => { + const template1 = templateLoader.loadTemplate("standard"); + const template2 = templateLoader.loadTemplate("standard"); + expect(template1).toBe(template2); + }); + + test("should clear cache", () => { + const template1 = templateLoader.loadTemplate("standard"); + templateLoader.clearTemplateCache(); + const template2 = templateLoader.loadTemplate("standard"); + expect(template1).toEqual(template2); + expect(template1.length).toBe(template2.length); + }); + }); + + describe("renderTemplate", () => { + test("should render standard template with placeholders", () => { + const rendered = templateLoader.renderTemplate("standard", { + TITLE: "Use PostgreSQL", + AUTHORS: "Alice, Bob", + STATUS: "ACCEPTED", + }); + + expect(rendered).toContain("Use PostgreSQL"); + expect(rendered).toContain("Alice, Bob"); + expect(rendered).toContain("ACCEPTED"); + }); + + test("should render lightweight template with placeholders", () => { + const rendered = templateLoader.renderTemplate("lightweight", { + TITLE: "Cache Strategy", + DECISION: "Use Redis", + RATIONALE: "Performance", + CONSEQUENCES: "Added complexity", + }); + + expect(rendered).toContain("Cache Strategy"); + expect(rendered).toContain("Use Redis"); + expect(rendered).toContain("Performance"); + expect(rendered).toContain("Added complexity"); + }); + + test("should use default placeholders when not provided", () => { + const rendered = templateLoader.renderTemplate("standard"); + expect(rendered).toContain("Untitled Decision"); + expect(rendered).toContain("PROPOSED"); + }); + + test("should throw TemplateNotFoundError for invalid template type", () => { + expect(() => { + templateLoader.renderTemplate("invalid"); + }).toThrow(templateLoader.TemplateNotFoundError); + }); + }); + + describe("substitutePlaceholders", () => { + test("should substitute all placeholders", () => { + const content = "Title: {TITLE}\nDate: {DATE}\nStatus: {STATUS}"; + const result = templateLoader.substitutePlaceholders(content, { + TITLE: "My Decision", + DATE: "2026-08-17", + STATUS: "ACCEPTED", + }); + + expect(result).toContain("My Decision"); + expect(result).toContain("2026-08-17"); + expect(result).toContain("ACCEPTED"); + }); + + test("should use default placeholders for missing values", () => { + const content = "Title: {TITLE}"; + const result = templateLoader.substitutePlaceholders(content, {}); + expect(result).toContain("Untitled Decision"); + }); + + test("should leave unrecognized placeholders unchanged", () => { + const content = "Value: {UNKNOWN}"; + const result = templateLoader.substitutePlaceholders(content, {}); + expect(result).toContain("{UNKNOWN}"); + }); + }); + + describe("getAvailableTemplates", () => { + test("should return all valid templates", () => { + const templates = templateLoader.getAvailableTemplates(); + + expect(templates).toContain("standard"); + expect(templates).toContain("lightweight"); + expect(templates).toContain("security"); + expect(templates).toContain("infrastructure"); + }); + + test("should not duplicate templates", () => { + const templates = templateLoader.getAvailableTemplates(); + const unique = new Set(templates); + expect(templates).toHaveLength(unique.size); + }); + }); + + describe("getTemplatePlaceholders", () => { + test("should return all placeholders in standard template", () => { + const placeholders = templateLoader.getTemplatePlaceholders("standard"); + + expect(placeholders).toContain("TITLE"); + expect(placeholders).toContain("DATE"); + expect(placeholders).toContain("STATUS"); + expect(placeholders).toContain("AUTHORS"); + }); + + test("should return placeholders in sorted order", () => { + const placeholders = templateLoader.getTemplatePlaceholders("standard"); + const sorted = [...placeholders].sort(); + + expect(placeholders).toEqual(sorted); + }); + }); + + describe("VALID_TEMPLATES constant", () => { + test("should export VALID_TEMPLATES array", () => { + expect(Array.isArray(templateLoader.VALID_TEMPLATES)).toBe(true); + expect(templateLoader.VALID_TEMPLATES).toHaveLength(4); + }); + + test("should include all expected templates", () => { + const expected = [ + "standard", + "lightweight", + "security", + "infrastructure", + ]; + expect(templateLoader.VALID_TEMPLATES).toEqual(expected); + }); + }); +}); diff --git a/scripts/automation/__tests__/auto-update-all.test.js b/scripts/automation/__tests__/auto-update-all.test.js index 42f01391ef..c3a59e4f94 100644 --- a/scripts/automation/__tests__/auto-update-all.test.js +++ b/scripts/automation/__tests__/auto-update-all.test.js @@ -299,11 +299,10 @@ describe("auto-update-all.js", () => { function detectIssueType(title) { title = title.toLowerCase(); if (/bug|fix|issue|error|fail|break/i.test(title)) return "type:bug"; - if (/feature|add|new|implement|create|build/i.test(title)) - return "type:feature"; - if (/epic|phase|initiative|release/i.test(title)) return "type:epic"; if (/doc|guide|readme|help|tutorial/i.test(title)) return "type:documentation"; + if (/feature|new|implement|create|build/i.test(title)) return "type:feature"; + if (/epic|phase|initiative|release/i.test(title)) return "type:epic"; if (/refactor|cleanup|simplify|improve/i.test(title)) return "type:refactor"; if (/test|coverage|qa|assert/i.test(title)) return "type:test"; if (/perf|speed|optim|memory|cache/i.test(title)) return "type:performance"; diff --git a/scripts/automation/auto-update-all.js b/scripts/automation/auto-update-all.js index e97e0b85da..3c131c9a5c 100644 --- a/scripts/automation/auto-update-all.js +++ b/scripts/automation/auto-update-all.js @@ -115,13 +115,12 @@ function getOpenPRs() { function detectIssueType(title) { title = title.toLowerCase(); if (/bug|fix|issue|error|fail|break/i.test(title)) return "type:bug"; - if (/feature|add|new|implement|create|build/i.test(title)) - return "type:feature"; + if (/doc|guide|readme|help|tutorial/i.test(title)) + return "type:documentation"; + if (/feature|new|implement|create|build/i.test(title)) return "type:feature"; if (/epic|phase|initiative|release/i.test(title)) return "type:epic"; if (/design|ui|ux|mockup|wireframe/i.test(title)) return "type:design"; if (/refactor|cleanup|simplify|improve/i.test(title)) return "type:refactor"; - if (/doc|guide|readme|help|tutorial/i.test(title)) - return "type:documentation"; if (/test|coverage|qa|assert/i.test(title)) return "type:test"; if (/perf|speed|optim|memory|cache/i.test(title)) return "type:performance"; if (/security|vuln|auth|encrypt|protect/i.test(title)) return "type:security"; diff --git a/scripts/validation/__tests__/validate-branch-name.test.js b/scripts/validation/__tests__/validate-branch-name.test.js index 9c36e312ad..34fe9a0259 100644 --- a/scripts/validation/__tests__/validate-branch-name.test.js +++ b/scripts/validation/__tests__/validate-branch-name.test.js @@ -60,10 +60,16 @@ describe("validate-branch-name", () => { }); }); - it("accepts slugs with dots, underscores and hyphens", () => { - expect(isAllowed("feat/my_feature")).toBe(true); - expect(isAllowed("fix/v2.1.0-patch")).toBe(true); - expect(isAllowed("chore/update_deps-2025")).toBe(true); + it("accepts slugs with hyphens (kebab-case)", () => { + expect(isAllowed("feat/my-feature")).toBe(true); + expect(isAllowed("fix/v2-1-0-patch")).toBe(true); + expect(isAllowed("chore/update-deps-2025")).toBe(true); + }); + + it("rejects slugs with dots and underscores", () => { + expect(isAllowed("feat/my_feature")).toBe(false); + expect(isAllowed("fix/v2.1.0-patch")).toBe(false); + expect(isAllowed("chore/update_deps-2025")).toBe(false); }); it("rejects the forbidden claude/ prefix", () => { diff --git a/scripts/validation/template-helpers.cjs b/scripts/validation/template-helpers.cjs index 7f9e4482e1..7da9cb49cc 100644 --- a/scripts/validation/template-helpers.cjs +++ b/scripts/validation/template-helpers.cjs @@ -14,7 +14,7 @@ function sectionBody(body, headingRegex) { const start = match.index + match[0].length; const remainder = text.slice(start); - const nextHeading = remainder.match(/^##\s+.+$/m); + const nextHeading = remainder.match(/^#+\s+.+$/m); const end = nextHeading ? start + nextHeading.index : text.length; return text.slice(start, end).trim(); } diff --git a/scripts/validation/validate-branch-name.js b/scripts/validation/validate-branch-name.js index ea1397b16a..d5de215c66 100644 --- a/scripts/validation/validate-branch-name.js +++ b/scripts/validation/validate-branch-name.js @@ -50,7 +50,7 @@ const BOT_PREFIXES = /^(dependabot|renovate)\//; const AUDIT_BRANCH_PATTERN = /^pr-\d+-audit$/; const PROTECTED_BRANCHES = new Set(["main", "develop"]); const BRANCH_PATTERN = new RegExp( - `^(${ALLOWED_PREFIXES.join("|")})/[a-zA-Z0-9._-]+$`, + `^(${ALLOWED_PREFIXES.join("|")})/[a-z0-9-]+$`, ); function getArgValue(flag) {