diff --git a/.github/agents/reporting.agent.md b/.github/agents/reporting.agent.md index d4466ec311..893d11ab5c 100644 --- a/.github/agents/reporting.agent.md +++ b/.github/agents/reporting.agent.md @@ -2,12 +2,13 @@ name: Reporting Agent title: Reporting description: Interactive agent for creating, organising, and maintaining reports and - progress updates following LightSpeed standards. Guides users through report creation - with proper structure and categorisation. + progress updates following LightSpeed standards. Detects repository type automatically + and selects repository-aware templates for block plugins, block themes, platform, + and control-plane repositories. file_type: agent -version: 'v1.3' +version: 'v2.0' created_date: '2025-11-26' -last_updated: '2026-06-01' +last_updated: '2026-08-29' author: LightSpeed Team mode: conversation model: claude-sonnet @@ -16,6 +17,8 @@ tags: - documentation - automation - interactive +- multi-repo +- context-detection domain: governance stability: stable tools: @@ -62,17 +65,17 @@ permissions: This agent does not create or validate branches. All branches must follow the patterns documented in [instructions/branch-naming.instructions.md](../../instructions/branch-naming.instructions.md) and [BRANCHING_STRATEGY.md](../../docs/BRANCHING_STRATEGY.md). -# Reporting Chatmode +# Reporting Chatmode v2 -Interactive assistant for creating and managing reports in the LightSpeed repository. +Interactive assistant for creating and managing reports across all LightSpeed repositories. ## Purpose -Help users create properly structured reports that follow LightSpeed conventions for organisation, naming, and documentation. +Help users create properly structured reports that follow LightSpeed conventions for organisation, naming, and documentation. In v2, the agent **automatically detects the repository type** and selects a contextually relevant template — no manual template selection required for standard LightSpeed repositories. ## Persona -You are the **Reporting Assistant**, an expert in creating well-organised documentation and reports. You ensure all reports are stored correctly, follow naming conventions, and include proper frontmatter and specifications. +You are the **Reporting Assistant v2**, an expert in creating well-organised, contextually relevant documentation and reports. You detect repository context automatically, select the right template, and ensure all reports are stored correctly, follow naming conventions, and include proper frontmatter and specifications. ## Style @@ -80,16 +83,41 @@ You are the **Reporting Assistant**, an expert in creating well-organised docume - **Precise**: Use exact paths and filenames - **Helpful**: Provide templates and examples - **Vigilant**: Catch and correct convention violations +- **Context-aware**: Adapt sections to the repository type without prompting the user + +## Multi-Repository Support + +Reporting Agent v2 supports four repository types. The agent detects the type automatically; the user only needs to confirm or override if the detected type is `unknown`. + +| Type | Key | Distinctive Report Sections | +|---|---|---| +| GitHub control-plane | `control-plane` | Agent & Automation Health, Workflow Activity, Issue & PR Metrics | +| WordPress block plugin | `block-plugin` | Plugin Health, Block Inventory, Security & Compliance | +| WordPress block theme | `block-theme` | Theme Health, Template & Pattern Inventory, Design System, Accessibility | +| Platform / infrastructure | `platform` | Infrastructure Health, Deployment Activity | +| Unknown (fallback) | `unknown` | Generic sections only | + +### Context Detection + +The agent detects repository type from: + +1. **Repository name** — `.github` → `control-plane` +2. **GitHub topics** — `wordpress-plugin`, `wordpress-theme`, `block-theme`, `control-plane`, `platform`, `gutenberg` +3. **File signals** — `block.json`, `theme.json`, `composer.json`, root `.php` file, `style.css` + `functions.php`, `Dockerfile`, `*.tf`, `Chart.yaml` + +Detection priority: `control-plane` > `block-plugin` > `block-theme` > `platform` > `unknown` ## Capabilities -### 1. Create Reports +### 1. Create Reports (v2: Context-Aware) Guide users through creating new reports: +- **Detect repository type automatically** from name, topics, and file signals - Determine the appropriate category +- Select a repository-aware template (block-plugin, block-theme, control-plane, platform, or generic) - Generate proper frontmatter -- Use the standard report structure +- Use the standard report structure with type-specific sections - Save to the correct location ### 2. Track Progress @@ -133,16 +161,42 @@ Help with report management: ### Initial Greeting ``` -Welcome to the Reporting Assistant! I help you create and manage reports -following LightSpeed standards. +Welcome to the Reporting Assistant v2! I help you create and manage reports +following LightSpeed standards — with automatic repository type detection +so you get contextually relevant sections without any manual setup. What would you like to do? -1. 📝 Create a new report +1. 📝 Create a new report (auto-detected template) 2. 📈 Log a progress update (daily/weekly) 3. 📋 Generate a JSON specification 4. ✅ Validate existing reports 5. 📁 Organise or move reports -6. ❓ Learn about report categories +6. ❓ Learn about report categories or repository types +``` + +### Repository Type Detection Step (v2) + +Before creating a report, detect the repository type: + +``` +Let me detect the repository type first. + +Detected: [block-plugin | block-theme | control-plane | platform | unknown] +Evidence: [list of signals] + +Is this correct? (yes / override with: block-plugin, block-theme, control-plane, platform) +``` + +If `unknown`, prompt: + +``` +I couldn't automatically detect the repository type. +Please choose one: +- block-plugin (WordPress block plugin) +- block-theme (WordPress block theme) +- control-plane (GitHub control-plane / .github repo) +- platform (infrastructure / platform repo) +- generic (no specific type) ``` ### Creating a Report @@ -371,6 +425,10 @@ using the weekly template: - [Reporting Agent](../agents/reporting.agent.md) - [Reporting Prompt](../prompts/reporting.prompt.md) - [File Management Guidelines](../instructions/file-management-guidelines.instructions.md) +- [v2 Specification](../projects/active/reporting-agent-v2-multirepository-2026-08-12/SPECIFICATION.md) +- [v2 Planning](../projects/active/reporting-agent-v2-multirepository-2026-08-12/PLANNING.md) +- [Context Detector](../../agents/metadata-agent/lib/context/repo-context-detector.js) +- [Repository-Aware Templates](../../agents/metadata-agent/lib/templates/repo-templates.js) --- diff --git a/.github/projects/active/reporting-agent-v2-multirepository-2026-08-12/SPECIFICATION.md b/.github/projects/active/reporting-agent-v2-multirepository-2026-08-12/SPECIFICATION.md new file mode 100644 index 0000000000..57200113ee --- /dev/null +++ b/.github/projects/active/reporting-agent-v2-multirepository-2026-08-12/SPECIFICATION.md @@ -0,0 +1,278 @@ +--- +file_type: specification +title: "Reporting Agent v2 — Multi-Repository Support — Specification" +description: "OpenSpec architecture specification for Reporting Agent v2: automatic context detection, repository-aware templates, and org-wide multi-repo support" +version: "v2.0" +created_date: "2026-08-12" +last_updated: "2026-08-29" +status: active +owners: + - LightSpeedWP/maintainers +tags: + - reporting + - multi-repo + - specification + - architecture +--- + +# Reporting Agent v2 — Specification + +**Version:** 2.0 +**Status:** Active +**Supersedes:** Reporting Agent v1.3 + +--- + +## 1. Executive Summary + +Reporting Agent v2 is a single, portable agent that supports heterogeneous repository types within the LightSpeed organisation. It detects the repository type automatically from file structure and GitHub topic metadata, then selects the appropriate Markdown report template with typed, contextually relevant sections. + +**Supported repository types:** + +| Type | Key | Detection Signals | +|---|---|---| +| GitHub control-plane | `control-plane` | `repo === '.github'`, `topic:control-plane`, `agents/` + `workflows/` dirs | +| WordPress block plugin | `block-plugin` | Root `.php` file + `block.json`, `topic:wordpress-plugin` | +| WordPress block theme | `block-theme` | `theme.json`, `style.css` + `functions.php`, `topic:block-theme` | +| Platform / infrastructure | `platform` | `Dockerfile`, `*.tf`, `Chart.yaml`, `topic:platform` | +| Unknown (fallback) | `unknown` | No distinctive signals | + +--- + +## 2. Goals + +1. **Single unified agent** — one `.github/agents/reporting.agent.md` for all repository types. +2. **Automatic context detection** — zero manual configuration for standard LightSpeed repositories. +3. **Repository-aware templates** — each type has sections relevant to its domain. +4. **Org-wide rollout** — 5+ repositories adopt v2 within 2 weeks of publication. + +--- + +## 3. Architecture + +### 3.1 Component Diagram + +```mermaid +flowchart TD + accTitle: Reporting Agent v2 Architecture + accDescr: Shows how the agent detects repo type and selects a template + + A[Agent invoked] --> B[RepoContextDetector] + B --> C{Type detected?} + C -->|block-plugin| D[Block Plugin Template] + C -->|block-theme| E[Block Theme Template] + C -->|control-plane| F[Control Plane Template] + C -->|platform| G[Platform Template] + C -->|unknown| H[Generic Template] + + D --> I[Rendered Report] + E --> I + F --> I + G --> I + H --> I + + classDef detect fill:#dbeafe,color:#1e3a5f,stroke:#1e3a5f; + classDef template fill:#fef3c7,color:#4a2c00,stroke:#b45309; + classDef output fill:#dcfce7,color:#14532d,stroke:#14532d; + classDef decision fill:#f3e8ff,color:#3b0764,stroke:#7e22ce; + + class B detect; + class C decision; + class D,E,F,G,H template; + class I output; +``` + +### 3.2 Module Overview + +``` +agents/metadata-agent/ +├── lib/ +│ ├── context/ +│ │ └── repo-context-detector.js # RepoContextDetector class +│ └── templates/ +│ └── repo-templates.js # Template registry + render function +└── __tests__/ + └── context/ + └── repo-context-detector.test.js +``` + +### 3.3 `RepoContextDetector` + +**Location:** `agents/metadata-agent/lib/context/repo-context-detector.js` + +**Responsibility:** Given a repository name, list of file paths, and optional GitHub repository metadata, return a `RepoContext` object describing the repository type. + +**Interface:** + +```js +const detector = new RepoContextDetector({ owner: 'lightspeedwp' }); +const ctx = detector.detect(repoName, filePaths, repoMeta); +// ctx.type — 'block-plugin' | 'block-theme' | 'control-plane' | 'platform' | 'unknown' +// ctx.templateKey — same as type (used for template selection) +// ctx.signals — array of evidence strings used for detection +// ctx.meta — normalised repository metadata +``` + +**Detection priority (highest to lowest):** + +1. Control-plane +2. Block plugin +3. Block theme +4. Platform +5. Unknown (fallback) + +### 3.4 Template Registry + +**Location:** `agents/metadata-agent/lib/templates/repo-templates.js` + +**Responsibility:** Map repository type keys to Markdown report template functions and render a report from a `TemplateContext`. + +**Interface:** + +```js +const { renderTemplate } = require('./lib/templates/repo-templates'); +const report = renderTemplate(ctx.templateKey, { + repoName: 'my-plugin', + owner: 'lightspeedwp', + date: '2026-08-29', + period: '2026-08', +}); +``` + +--- + +## 4. Detection Specification + +### 4.1 Control-Plane Signals + +| Signal | Condition | +|---|---| +| `repo-name:.github` | `repoName === '.github'` | +| `topic:control-plane` | `repoMeta.topics` includes `'control-plane'` | +| `has:workflows+agents` | Both `.github/workflows` and `agents` directories present in file list | + +### 4.2 Block Plugin Signals + +| Signal | Condition | +|---|---| +| `has:php-root-file` | `.php` file at repository root (non-nested) | +| `has:block.json` | Any `block.json` file in file list | +| `has:composer.json` | `composer.json` at root | +| `topic:wordpress-plugin` | `repoMeta.topics` includes `'wordpress-plugin'` | +| `topic:gutenberg` | `repoMeta.topics` includes `'gutenberg'` | + +**Detection rule:** (`has:php-root-file` AND `has:block.json`) OR (`topic:wordpress-plugin` AND (`has:block.json` OR `has:composer.json`)) + +### 4.3 Block Theme Signals + +| Signal | Condition | +|---|---| +| `has:theme.json` | `theme.json` at root | +| `has:style.css` | `style.css` at root | +| `has:functions.php` | `functions.php` at root | +| `has:templates-directory` | Path starting with `templates/` in file list | +| `topic:wordpress-theme` | `repoMeta.topics` includes `'wordpress-theme'` | +| `topic:block-theme` | `repoMeta.topics` includes `'block-theme'` | + +**Detection rule:** `has:theme.json` OR (`has:style.css` AND `has:functions.php`) OR topic-based detection + +### 4.4 Platform Signals + +| Signal | Condition | +|---|---| +| `has:dockerfile` | `Dockerfile` (case-insensitive) at root | +| `has:terraform` | Any `*.tf` file | +| `has:helm-chart` | `Chart.yaml` or `Chart.yml` | +| `topic:platform` | `repoMeta.topics` includes `'platform'` | +| `topic:infrastructure` | `repoMeta.topics` includes `'infrastructure'` | + +--- + +## 5. Template Specification + +Each template produces a Markdown document with: + +1. **YAML frontmatter** — `title`, `description`, `file_type: report`, `category`, `created_date`, `version`, `repository`, `authors`, `tags` +2. **Header** — Report title, repository link, period, generated date +3. **Summary** — Editable placeholder +4. **Type-specific sections** — Domain sections relevant to the detected repository type (see §5.1–5.5) +5. **Shared sections** — Development Activity (PRs + Issues), Blockers & Risks, Next Steps +6. **Footer** — Reporting Agent v2 attribution link + +### 5.1 Block Plugin Template + +Additional sections: Plugin Health, Block Inventory, Test Coverage, Security & Compliance. + +### 5.2 Block Theme Template + +Additional sections: Theme Health, Template & Pattern Inventory, Design System (theme.json), Accessibility Checks, Performance. + +### 5.3 Control-Plane Template + +Additional sections: Agent & Automation Health, Workflow Activity, Issue & PR Metrics, Test Coverage, Security & Compliance. + +### 5.4 Platform Template + +Additional sections: Infrastructure Health, Deployment Activity, Security. + +### 5.5 Unknown / Generic Template + +Sections: Summary, Development Activity, Blockers & Risks, Next Steps. + +--- + +## 6. Integration with Reporting Agent + +The Reporting Agent (`.github/agents/reporting.agent.md`) surfaces context detection in its conversation flow: + +1. User provides or agent resolves the repository name. +2. Agent calls `RepoContextDetector.detect()` with available file/topic signals. +3. Agent calls `renderTemplate(ctx.templateKey, ctx)` to scaffold the report. +4. Agent presents the scaffold and guides the user through filling in the editable sections. + +If the detected type is `unknown`, the agent prompts the user to confirm or override the type before rendering. + +--- + +## 7. Constraints & Non-Goals + +- **No I/O in detector or templates** — both modules are pure functions; file system reads are performed by the caller (agent or integration script). +- **No API calls in templates** — templates produce static Markdown; live data population is a separate concern for the metadata-agent API layer. +- **No auto-commit** — the rendered report is presented to the user for review before being saved. + +--- + +## 8. Test Strategy + +| Scope | Tool | Target | +|---|---|---| +| `RepoContextDetector` unit | Jest | All detection paths, priority order, edge cases, context object shape | +| Template rendering | Jest | Each template renders without error; required sections present | +| Integration | Jest | Detect + render pipeline for all five repository types | + +**Test location:** `agents/metadata-agent/__tests__/context/repo-context-detector.test.js` + +--- + +## 9. Acceptance Criteria + +- [x] `RepoContextDetector` returns correct type for all five supported repository type scenarios +- [x] All five templates render valid Markdown with required frontmatter fields +- [x] Detection priority is respected (control-plane > block-plugin > block-theme > platform > unknown) +- [x] Agent prompt updated to v2.0 with multi-repo documentation +- [ ] Control-plane validation run produces a structurally valid report +- [ ] Block plugin validation run produces a structurally valid report +- [ ] Block theme validation run produces a structurally valid report + +--- + +## 10. Related Documents + +- [PLANNING.md](./PLANNING.md) — Phase planning and timeline +- [README.md](./README.md) — Project overview +- [Reporting Agent Spec](./../../../.github/agents/reporting.agent.md) — Agent prompt v2.0 +- [GitHub Issue #1898](https://github.com/lightspeedwp/.github/issues/1898) — Master epic + +--- + +*Generated by [Reporting Agent v2](https://github.com/lightspeedwp/.github/blob/develop/.github/agents/reporting.agent.md)* diff --git a/agents/metadata-agent/__tests__/context/repo-context-detector.test.js b/agents/metadata-agent/__tests__/context/repo-context-detector.test.js new file mode 100644 index 0000000000..ce094281ed --- /dev/null +++ b/agents/metadata-agent/__tests__/context/repo-context-detector.test.js @@ -0,0 +1,319 @@ +"use strict"; + +const { + RepoContextDetector, +} = require("../../lib/context/repo-context-detector"); + +describe("RepoContextDetector", () => { + let detector; + + beforeEach(() => { + detector = new RepoContextDetector({ owner: "lightspeedwp" }); + }); + + // --------------------------------------------------------------------------- + // Constructor + // --------------------------------------------------------------------------- + + describe("constructor", () => { + it("stores the owner option", () => { + const d = new RepoContextDetector({ owner: "acme" }); + expect(d.owner).toBe("acme"); + }); + + it("defaults owner to empty string when not provided", () => { + const d = new RepoContextDetector(); + expect(d.owner).toBe(""); + }); + }); + + // --------------------------------------------------------------------------- + // Input validation + // --------------------------------------------------------------------------- + + describe("detect() input validation", () => { + it("throws when repoName is not provided", () => { + expect(() => detector.detect()).toThrow( + "repoName must be a non-empty string", + ); + }); + + it("throws when repoName is an empty string", () => { + expect(() => detector.detect("")).toThrow( + "repoName must be a non-empty string", + ); + }); + + it("throws when repoName is not a string", () => { + expect(() => detector.detect(42)).toThrow( + "repoName must be a non-empty string", + ); + }); + }); + + // --------------------------------------------------------------------------- + // Control-plane detection + // --------------------------------------------------------------------------- + + describe("control-plane detection", () => { + it("detects .github repo by name", () => { + const ctx = detector.detect(".github", []); + expect(ctx.type).toBe("control-plane"); + expect(ctx.signals).toContain("repo-name:.github"); + }); + + it("detects control-plane via topic", () => { + const ctx = detector.detect("my-repo", [], { + topics: ["control-plane"], + }); + expect(ctx.type).toBe("control-plane"); + expect(ctx.signals).toContain("topic:control-plane"); + }); + + it("detects control-plane via workflows + agents directories", () => { + const ctx = detector.detect("my-repo", [ + ".github/workflows/ci.yml", + "agents/reporting.agent.md", + "README.md", + ]); + expect(ctx.type).toBe("control-plane"); + }); + + it("detects control-plane via bare directory names (legacy usage)", () => { + const ctx = detector.detect("my-repo", [ + ".github/workflows", + "agents", + "README.md", + ]); + expect(ctx.type).toBe("control-plane"); + }); + + it("sets templateKey to control-plane", () => { + const ctx = detector.detect(".github", []); + expect(ctx.templateKey).toBe("control-plane"); + }); + }); + + // --------------------------------------------------------------------------- + // Block plugin detection + // --------------------------------------------------------------------------- + + describe("block-plugin detection", () => { + it("detects block plugin via root PHP file + block.json", () => { + const ctx = detector.detect("my-plugin", [ + "my-plugin.php", + "src/block.json", + "package.json", + ]); + expect(ctx.type).toBe("block-plugin"); + expect(ctx.signals).toContain("has:php-root-file"); + expect(ctx.signals).toContain("has:block.json"); + }); + + it("detects block plugin via wordpress-plugin topic + composer.json", () => { + const ctx = detector.detect("my-plugin", ["composer.json"], { + topics: ["wordpress-plugin"], + }); + expect(ctx.type).toBe("block-plugin"); + expect(ctx.signals).toContain("topic:wordpress-plugin"); + }); + + it("sets templateKey to block-plugin", () => { + const ctx = detector.detect("my-plugin", [ + "my-plugin.php", + "block.json", + ]); + expect(ctx.templateKey).toBe("block-plugin"); + }); + + it("does not detect block plugin from PHP file alone", () => { + const ctx = detector.detect("maybe-plugin", ["my-plugin.php"]); + expect(ctx.type).not.toBe("block-plugin"); + }); + }); + + // --------------------------------------------------------------------------- + // Block theme detection + // --------------------------------------------------------------------------- + + describe("block-theme detection", () => { + it("detects block theme via theme.json", () => { + const ctx = detector.detect("my-theme", [ + "theme.json", + "functions.php", + "style.css", + ]); + expect(ctx.type).toBe("block-theme"); + expect(ctx.signals).toContain("has:theme.json"); + }); + + it("detects classic/hybrid theme via style.css + functions.php", () => { + const ctx = detector.detect("my-theme", [ + "style.css", + "functions.php", + "index.php", + ]); + expect(ctx.type).toBe("block-theme"); + }); + + it("detects block theme via block-theme topic", () => { + const ctx = detector.detect("my-theme", [], { + topics: ["block-theme"], + }); + expect(ctx.type).toBe("block-theme"); + expect(ctx.signals).toContain("topic:block-theme"); + }); + + it("sets templateKey to block-theme", () => { + const ctx = detector.detect("my-theme", ["theme.json"]); + expect(ctx.templateKey).toBe("block-theme"); + }); + }); + + // --------------------------------------------------------------------------- + // Platform detection + // --------------------------------------------------------------------------- + + describe("platform detection", () => { + it("detects platform via platform topic", () => { + const ctx = detector.detect("my-platform", [], { + topics: ["platform"], + }); + expect(ctx.type).toBe("platform"); + expect(ctx.signals).toContain("topic:platform"); + }); + + it("detects platform via Dockerfile", () => { + const ctx = detector.detect("my-infra", ["Dockerfile", "README.md"]); + expect(ctx.type).toBe("platform"); + expect(ctx.signals).toContain("has:dockerfile"); + }); + + it("detects platform via Helm chart", () => { + const ctx = detector.detect("my-infra", ["Chart.yaml"]); + expect(ctx.type).toBe("platform"); + expect(ctx.signals).toContain("has:helm-chart"); + }); + + it("detects platform via Terraform files", () => { + const ctx = detector.detect("my-infra", ["main.tf", "variables.tf"]); + expect(ctx.type).toBe("platform"); + expect(ctx.signals).toContain("has:terraform"); + }); + }); + + // --------------------------------------------------------------------------- + // Unknown fallback + // --------------------------------------------------------------------------- + + describe("unknown fallback", () => { + it("returns unknown when no signals match", () => { + const ctx = detector.detect("random-repo", ["README.md", "LICENSE"]); + expect(ctx.type).toBe("unknown"); + expect(ctx.signals).toContain("no-distinctive-signals"); + }); + + it("sets templateKey to unknown", () => { + const ctx = detector.detect("random-repo", []); + expect(ctx.templateKey).toBe("unknown"); + }); + }); + + // --------------------------------------------------------------------------- + // Context object shape + // --------------------------------------------------------------------------- + + describe("context object shape", () => { + it("always returns name matching the input", () => { + const ctx = detector.detect("my-repo", []); + expect(ctx.name).toBe("my-repo"); + }); + + it("includes owner from constructor in meta", () => { + const ctx = detector.detect("my-repo", []); + expect(ctx.meta.owner).toBe("lightspeedwp"); + }); + + it("includes description from repoMeta", () => { + const ctx = detector.detect("my-repo", [], { + description: "A test repo", + }); + expect(ctx.meta.description).toBe("A test repo"); + }); + + it("defaults description to empty string when not provided", () => { + const ctx = detector.detect("my-repo", []); + expect(ctx.meta.description).toBe(""); + }); + + it("normalises topics to empty array when not provided", () => { + const ctx = detector.detect("my-repo", []); + expect(ctx.meta.topics).toEqual([]); + }); + + it("passes through topics from repoMeta", () => { + const ctx = detector.detect("my-repo", [], { + topics: ["foo", "bar"], + }); + expect(ctx.meta.topics).toEqual(["foo", "bar"]); + }); + + it("defaults defaultBranch to main", () => { + const ctx = detector.detect("my-repo", []); + expect(ctx.meta.defaultBranch).toBe("main"); + }); + + it("passes through default_branch from repoMeta", () => { + const ctx = detector.detect("my-repo", [], { + default_branch: "develop", + }); + expect(ctx.meta.defaultBranch).toBe("develop"); + }); + + it("defaults isPrivate to false", () => { + const ctx = detector.detect("my-repo", []); + expect(ctx.meta.isPrivate).toBe(false); + }); + + it("passes through private flag from repoMeta", () => { + const ctx = detector.detect("my-repo", [], { private: true }); + expect(ctx.meta.isPrivate).toBe(true); + }); + + it("signals array is a copy, not a reference", () => { + const ctx = detector.detect(".github", []); + ctx.signals.push("injected"); + const ctx2 = detector.detect(".github", []); + expect(ctx2.signals).not.toContain("injected"); + }); + }); + + // --------------------------------------------------------------------------- + // Priority: control-plane beats theme/plugin when .github repo name + // --------------------------------------------------------------------------- + + describe("detection priority", () => { + it("control-plane takes priority over block-theme signals", () => { + const ctx = detector.detect(".github", [ + "theme.json", + "style.css", + "functions.php", + ]); + expect(ctx.type).toBe("control-plane"); + }); + + it("control-plane takes priority over block-plugin signals", () => { + const ctx = detector.detect(".github", ["my-plugin.php", "block.json"]); + expect(ctx.type).toBe("control-plane"); + }); + + it("block-plugin takes priority over platform when plugin signals present", () => { + const ctx = detector.detect("my-plugin", [ + "my-plugin.php", + "block.json", + "Dockerfile", + ]); + expect(ctx.type).toBe("block-plugin"); + }); + }); +}); diff --git a/agents/metadata-agent/__tests__/context/repo-templates.test.js b/agents/metadata-agent/__tests__/context/repo-templates.test.js new file mode 100644 index 0000000000..17b92ed2f5 --- /dev/null +++ b/agents/metadata-agent/__tests__/context/repo-templates.test.js @@ -0,0 +1,306 @@ +"use strict"; + +const { + renderTemplate, + TEMPLATES, + buildFrontmatter, + blockPluginTemplate, + blockThemeTemplate, + controlPlaneTemplate, + platformTemplate, + unknownTemplate, +} = require("../../lib/templates/repo-templates"); + +const SAMPLE_CTX = { + repoName: "test-repo", + owner: "lightspeedwp", + date: "2026-08-29", + period: "2026-08", +}; + +// --------------------------------------------------------------------------- +// buildFrontmatter +// --------------------------------------------------------------------------- + +describe("buildFrontmatter", () => { + it("produces valid YAML delimiters", () => { + const fm = buildFrontmatter("Title", "Desc", "metrics", "2026-08-29", "my-repo"); + expect(fm.startsWith("---\n")).toBe(true); + expect(fm).toContain("---\n\n"); + }); + + it("includes all required frontmatter fields", () => { + const fm = buildFrontmatter("My Title", "My Desc", "analysis", "2026-08-29", "my-repo"); + expect(fm).toContain('title: "My Title"'); + expect(fm).toContain('description: "My Desc"'); + expect(fm).toContain("file_type: report"); + expect(fm).toContain("category: analysis"); + expect(fm).toContain('created_date: "2026-08-29"'); + expect(fm).toContain('repository: "my-repo"'); + expect(fm).toContain('authors: ["automation"]'); + }); +}); + +// --------------------------------------------------------------------------- +// Template registry +// --------------------------------------------------------------------------- + +describe("TEMPLATES registry", () => { + it("contains all five expected keys", () => { + expect(Object.keys(TEMPLATES).sort()).toEqual( + ["block-plugin", "block-theme", "control-plane", "platform", "unknown"].sort(), + ); + }); + + it("all values are functions", () => { + for (const fn of Object.values(TEMPLATES)) { + expect(typeof fn).toBe("function"); + } + }); +}); + +// --------------------------------------------------------------------------- +// renderTemplate +// --------------------------------------------------------------------------- + +describe("renderTemplate", () => { + it("delegates to the matching template function", () => { + const result = renderTemplate("block-plugin", SAMPLE_CTX); + expect(result).toContain("Plugin Health"); + }); + + it("falls back to unknown template for unrecognised key", () => { + const result = renderTemplate("non-existent-key", SAMPLE_CTX); + expect(result).toContain("Development Activity"); + expect(result).not.toContain("Plugin Health"); + }); +}); + +// --------------------------------------------------------------------------- +// Block Plugin Template +// --------------------------------------------------------------------------- + +describe("blockPluginTemplate", () => { + let report; + + beforeEach(() => { + report = blockPluginTemplate(SAMPLE_CTX); + }); + + it("includes the report title with period", () => { + expect(report).toContain("test-repo — Plugin Report (2026-08)"); + }); + + it("includes repository link", () => { + expect(report).toContain("https://github.com/lightspeedwp/test-repo"); + }); + + it("includes Plugin Health section", () => { + expect(report).toContain("## Plugin Health"); + }); + + it("includes Block Inventory section", () => { + expect(report).toContain("## Block Inventory"); + }); + + it("includes Test Coverage section", () => { + expect(report).toContain("## Test Coverage"); + }); + + it("includes Security & Compliance section", () => { + expect(report).toContain("## Security & Compliance"); + }); + + it("includes Development Activity section", () => { + expect(report).toContain("## Development Activity"); + }); + + it("includes required frontmatter fields", () => { + expect(report).toContain("file_type: report"); + expect(report).toContain('repository: "test-repo"'); + }); + + it("uses period from ctx when provided", () => { + const r = blockPluginTemplate({ ...SAMPLE_CTX, period: "2026-Q3" }); + expect(r).toContain("2026-Q3"); + }); + + it("falls back period to date when not provided", () => { + const { period: _p, ...ctxNoPeriod } = SAMPLE_CTX; + const r = blockPluginTemplate(ctxNoPeriod); + expect(r).toContain("2026-08-29"); + }); +}); + +// --------------------------------------------------------------------------- +// Block Theme Template +// --------------------------------------------------------------------------- + +describe("blockThemeTemplate", () => { + let report; + + beforeEach(() => { + report = blockThemeTemplate(SAMPLE_CTX); + }); + + it("includes the report title with period", () => { + expect(report).toContain("test-repo — Theme Report (2026-08)"); + }); + + it("includes Theme Health section", () => { + expect(report).toContain("## Theme Health"); + }); + + it("includes Template & Pattern Inventory section", () => { + expect(report).toContain("## Template & Pattern Inventory"); + }); + + it("includes Design System section", () => { + expect(report).toContain("## Design System"); + }); + + it("includes Accessibility Checks section", () => { + expect(report).toContain("## Accessibility Checks"); + }); + + it("includes Performance section", () => { + expect(report).toContain("## Performance"); + }); +}); + +// --------------------------------------------------------------------------- +// Control-Plane Template +// --------------------------------------------------------------------------- + +describe("controlPlaneTemplate", () => { + let report; + + beforeEach(() => { + report = controlPlaneTemplate(SAMPLE_CTX); + }); + + it("includes Control-Plane in title", () => { + expect(report).toContain("Control-Plane Report"); + }); + + it("includes Agent & Automation Health section", () => { + expect(report).toContain("## Agent & Automation Health"); + }); + + it("includes Workflow Activity section", () => { + expect(report).toContain("## Workflow Activity"); + }); + + it("includes Issue & PR Metrics section", () => { + expect(report).toContain("## Issue & PR Metrics"); + }); + + it("includes Test Coverage section", () => { + expect(report).toContain("## Test Coverage"); + }); + + it("includes Security & Compliance section", () => { + expect(report).toContain("## Security & Compliance"); + }); +}); + +// --------------------------------------------------------------------------- +// Platform Template +// --------------------------------------------------------------------------- + +describe("platformTemplate", () => { + let report; + + beforeEach(() => { + report = platformTemplate(SAMPLE_CTX); + }); + + it("includes Platform Report in title", () => { + expect(report).toContain("Platform Report"); + }); + + it("includes Infrastructure Health section", () => { + expect(report).toContain("## Infrastructure Health"); + }); + + it("includes Deployment Activity section", () => { + expect(report).toContain("## Deployment Activity"); + }); + + it("includes Security section", () => { + expect(report).toContain("## Security"); + }); +}); + +// --------------------------------------------------------------------------- +// Unknown / Generic Template +// --------------------------------------------------------------------------- + +describe("unknownTemplate", () => { + let report; + + beforeEach(() => { + report = unknownTemplate(SAMPLE_CTX); + }); + + it("includes the report title", () => { + expect(report).toContain("test-repo — Report (2026-08)"); + }); + + it("includes Development Activity section", () => { + expect(report).toContain("## Development Activity"); + }); + + it("includes Next Steps section", () => { + expect(report).toContain("## Next Steps"); + }); + + it("does NOT include Plugin Health section", () => { + expect(report).not.toContain("## Plugin Health"); + }); + + it("does NOT include Theme Health section", () => { + expect(report).not.toContain("## Theme Health"); + }); +}); + +// --------------------------------------------------------------------------- +// Shared template properties +// --------------------------------------------------------------------------- + +describe("all templates share common properties", () => { + const templates = [ + blockPluginTemplate, + blockThemeTemplate, + controlPlaneTemplate, + platformTemplate, + unknownTemplate, + ]; + + for (const tmpl of templates) { + describe(`${tmpl.name}`, () => { + let report; + beforeEach(() => { report = tmpl(SAMPLE_CTX); }); + + it("starts with YAML frontmatter", () => { + expect(report.startsWith("---\n")).toBe(true); + }); + + it("includes Blockers & Risks section", () => { + expect(report).toContain("## Blockers & Risks"); + }); + + it("includes Next Steps section", () => { + expect(report).toContain("## Next Steps"); + }); + + it("includes Reporting Agent v2 attribution", () => { + expect(report).toContain("Reporting Agent v2"); + }); + + it("includes the generated date", () => { + expect(report).toContain(SAMPLE_CTX.date); + }); + }); + } +}); diff --git a/agents/metadata-agent/lib/context/repo-context-detector.js b/agents/metadata-agent/lib/context/repo-context-detector.js new file mode 100644 index 0000000000..1fcba59957 --- /dev/null +++ b/agents/metadata-agent/lib/context/repo-context-detector.js @@ -0,0 +1,244 @@ +/** + * Repository context detector for Reporting Agent v2. + * Automatically detects repository type (WordPress block plugin, block theme, + * platform, or control-plane) and returns the appropriate context descriptor. + */ + +/** @typedef {'block-plugin'|'block-theme'|'platform'|'control-plane'|'unknown'} RepoType */ + +/** + * @typedef {Object} RepoContext + * @property {RepoType} type - Detected repository type + * @property {string} name - Repository name + * @property {string[]} signals - Evidence used for detection + * @property {string} templateKey - Key for selecting the correct report template + * @property {Record} meta - Additional repository metadata + */ + +class RepoContextDetector { + /** + * @param {Object} [options] + * @param {string} [options.owner] - GitHub organisation / user name + */ + constructor(options = {}) { + this.owner = options.owner || ""; + } + + /** + * Detect repository context from file manifest and repository metadata. + * + * @param {string} repoName - Repository name (without owner) + * @param {string[]} filePaths - List of file paths in the repository root + * @param {Record} [repoMeta] - Optional GitHub repo metadata + * @returns {RepoContext} + */ + detect(repoName, filePaths = [], repoMeta = {}) { + if (!repoName || typeof repoName !== "string") { + throw new Error("repoName must be a non-empty string"); + } + + const signals = []; + const files = new Set(filePaths.map((p) => p.toLowerCase())); + + // --- Control-plane detection --- + if (this._isControlPlane(repoName, files, repoMeta, signals)) { + return this._buildContext("control-plane", repoName, signals, repoMeta); + } + + // --- WordPress block plugin detection --- + if (this._isBlockPlugin(files, repoMeta, signals)) { + return this._buildContext("block-plugin", repoName, signals, repoMeta); + } + + // --- WordPress block theme detection --- + if (this._isBlockTheme(files, repoMeta, signals)) { + return this._buildContext("block-theme", repoName, signals, repoMeta); + } + + // --- Platform / generic detection --- + if (this._isPlatform(files, repoMeta, signals)) { + return this._buildContext("platform", repoName, signals, repoMeta); + } + + signals.push("no-distinctive-signals"); + return this._buildContext("unknown", repoName, signals, repoMeta); + } + + // --------------------------------------------------------------------------- + // Private helpers + // --------------------------------------------------------------------------- + + /** + * @param {string} repoName + * @param {Set} files + * @param {Record} meta + * @param {string[]} signals + * @returns {boolean} + */ + _isControlPlane(repoName, files, meta, signals) { + if (repoName === ".github") { + signals.push("repo-name:.github"); + return true; + } + + const topics = Array.isArray(meta.topics) ? meta.topics : []; + if (topics.includes("control-plane")) { + signals.push("topic:control-plane"); + return true; + } + + const hasAgentsDir = [...files].some( + (f) => f === "agents" || f.startsWith("agents/"), + ); + const hasWorkflowsDir = [...files].some( + (f) => f === ".github/workflows" || f.startsWith(".github/workflows/"), + ); + + if (hasAgentsDir) { + signals.push("has:agents-directory"); + } + if (hasAgentsDir && hasWorkflowsDir) { + signals.push("has:workflows+agents"); + return true; + } + + return false; + } + + /** + * @param {Set} files + * @param {Record} meta + * @param {string[]} signals + * @returns {boolean} + */ + _isBlockPlugin(files, meta, signals) { + const hasPluginHeader = [...files].some( + (f) => f.endsWith(".php") && !f.includes("/"), + ); + const hasBlockJson = [...files].some((f) => f.endsWith("block.json")); + const hasComposerJson = files.has("composer.json"); + const hasPackageJson = files.has("package.json"); + + const topics = Array.isArray(meta.topics) ? meta.topics : []; + if (topics.includes("wordpress-plugin")) { + signals.push("topic:wordpress-plugin"); + } + if (topics.includes("gutenberg")) { + signals.push("topic:gutenberg"); + } + + if (hasPluginHeader) signals.push("has:php-root-file"); + if (hasBlockJson) signals.push("has:block.json"); + if (hasComposerJson) signals.push("has:composer.json"); + if (hasPackageJson) signals.push("has:package.json"); + + // Strong signal: root PHP file + block.json = block plugin + if (hasPluginHeader && hasBlockJson) return true; + + // Strong signal: explicit topic + if (topics.includes("wordpress-plugin")) { + if (hasBlockJson || hasComposerJson) return true; + } + + return false; + } + + /** + * @param {Set} files + * @param {Record} meta + * @param {string[]} signals + * @returns {boolean} + */ + _isBlockTheme(files, meta, signals) { + const hasThemeJson = files.has("theme.json"); + const hasFunctionsPhp = files.has("functions.php"); + const hasStyleCss = files.has("style.css"); + const hasTemplatesDir = [...files].some((f) => f.startsWith("templates/")); + + const topics = Array.isArray(meta.topics) ? meta.topics : []; + if (topics.includes("wordpress-theme")) { + signals.push("topic:wordpress-theme"); + } + if (topics.includes("block-theme")) { + signals.push("topic:block-theme"); + } + + if (hasThemeJson) signals.push("has:theme.json"); + if (hasFunctionsPhp) signals.push("has:functions.php"); + if (hasStyleCss) signals.push("has:style.css"); + if (hasTemplatesDir) signals.push("has:templates-directory"); + + // Strong signal: theme.json = block theme + if (hasThemeJson) return true; + + // Strong signal: style.css + functions.php = classic/hybrid theme + if (hasStyleCss && hasFunctionsPhp) return true; + + // Topic-based + if ( + topics.includes("wordpress-theme") || + topics.includes("block-theme") + ) { + return true; + } + + return false; + } + + /** + * @param {Set} files + * @param {Record} meta + * @param {string[]} signals + * @returns {boolean} + */ + _isPlatform(files, meta, signals) { + const hasDockerfile = files.has("dockerfile"); + const hasTerraform = [...files].some((f) => f.endsWith(".tf")); + const hasHelmChart = files.has("chart.yaml") || files.has("chart.yml"); + + const topics = Array.isArray(meta.topics) ? meta.topics : []; + if (topics.includes("platform")) { + signals.push("topic:platform"); + return true; + } + if (topics.includes("infrastructure")) { + signals.push("topic:infrastructure"); + return true; + } + + if (hasDockerfile) signals.push("has:dockerfile"); + if (hasTerraform) signals.push("has:terraform"); + if (hasHelmChart) signals.push("has:helm-chart"); + + if (hasDockerfile || hasTerraform || hasHelmChart) return true; + + return false; + } + + /** + * Build a normalised context object. + * + * @param {RepoType} type + * @param {string} name + * @param {string[]} signals + * @param {Record} meta + * @returns {RepoContext} + */ + _buildContext(type, name, signals, meta) { + return { + type, + name, + signals: [...signals], + templateKey: type, + meta: { + owner: this.owner, + description: meta.description || "", + topics: Array.isArray(meta.topics) ? meta.topics : [], + defaultBranch: meta.default_branch || "main", + isPrivate: meta.private || false, + }, + }; + } +} + +module.exports = { RepoContextDetector }; diff --git a/agents/metadata-agent/lib/templates/repo-templates.js b/agents/metadata-agent/lib/templates/repo-templates.js new file mode 100644 index 0000000000..86501d4a3f --- /dev/null +++ b/agents/metadata-agent/lib/templates/repo-templates.js @@ -0,0 +1,575 @@ +/** + * Repository-aware report templates for Reporting Agent v2. + * Each template is keyed by RepoType and returns a structured Markdown scaffold. + */ + +/** + * @typedef {Object} TemplateContext + * @property {string} repoName - Repository name + * @property {string} owner - Repository owner + * @property {string} date - ISO date string (YYYY-MM-DD) + * @property {string} [description] - Repository description + * @property {string} [period] - Reporting period label (e.g. "2026-08") + */ + +/** + * Generate the YAML frontmatter block for a report. + * + * @param {string} title + * @param {string} description + * @param {string} category + * @param {string} date + * @param {string} repoName + * @returns {string} + */ +function buildFrontmatter(title, description, category, date, repoName) { + return [ + "---", + `title: "${title}"`, + `description: "${description}"`, + `file_type: report`, + `category: ${category}`, + `created_date: "${date}"`, + `version: "v1.0.0"`, + `repository: "${repoName}"`, + `authors: ["automation"]`, + `tags: ["${category}", "automated-report", "${repoName}"]`, + "---", + "", + ].join("\n"); +} + +// --------------------------------------------------------------------------- +// Template: Block Plugin +// --------------------------------------------------------------------------- + +/** + * Report template for WordPress block plugin repositories. + * + * @param {TemplateContext} ctx + * @returns {string} Markdown report scaffold + */ +function blockPluginTemplate(ctx) { + const { repoName, owner, date, period = date } = ctx; + const title = `${repoName} — Plugin Report (${period})`; + + const frontmatter = buildFrontmatter( + title, + `Automated plugin report for ${repoName}`, + "metrics", + date, + repoName, + ); + + return ( + frontmatter + + `# ${title} + +**Repository:** [${owner}/${repoName}](https://github.com/${owner}/${repoName}) +**Period:** ${period} +**Generated:** ${date} + +--- + +## Summary + +> _Replace with a one-paragraph summary of this reporting period._ + +--- + +## Plugin Health + +| Metric | Value | +|---|---| +| Tested up to (WP version) | _TBD_ | +| Requires at least | _TBD_ | +| Requires PHP | _TBD_ | +| Active installs (est.) | _TBD_ | +| Plugin version | _TBD_ | + +--- + +## Block Inventory + +| Block Name | Status | Supports | +|---|---|---| +| _block-name_ | ✅ Stable | color, spacing, typography | + +--- + +## Development Activity + +### Pull Requests + +| PR # | Title | Status | Merged | +|---|---|---|---| +| _#N_ | _Description_ | merged / open | _YYYY-MM-DD_ | + +### Issues + +| Issue # | Title | Status | +|---|---|---| +| _#N_ | _Description_ | open / closed | + +--- + +## Test Coverage + +| Suite | Tests | Pass | Coverage | +|---|---|---|---| +| Unit | _N_ | _N_ | _XX%_ | +| Integration | _N_ | _N_ | _XX%_ | + +--- + +## Security & Compliance + +- [ ] Input sanitisation reviewed +- [ ] Output escaping verified (\`esc_html\`, \`esc_attr\`, \`wp_kses_post\`) +- [ ] Nonces used for privileged actions +- [ ] Capability checks in place +- [ ] Dependencies audited (npm + Composer) + +--- + +## Blockers & Risks + +> _List any current blockers, technical debt items, or risks._ + +--- + +## Next Steps + +1. _Action item 1_ +2. _Action item 2_ + +--- + +*Generated by [Reporting Agent v2](https://github.com/lightspeedwp/.github/blob/develop/.github/agents/reporting.agent.md)* +` + ); +} + +// --------------------------------------------------------------------------- +// Template: Block Theme +// --------------------------------------------------------------------------- + +/** + * Report template for WordPress block theme repositories. + * + * @param {TemplateContext} ctx + * @returns {string} Markdown report scaffold + */ +function blockThemeTemplate(ctx) { + const { repoName, owner, date, period = date } = ctx; + const title = `${repoName} — Theme Report (${period})`; + + const frontmatter = buildFrontmatter( + title, + `Automated theme report for ${repoName}`, + "metrics", + date, + repoName, + ); + + return ( + frontmatter + + `# ${title} + +**Repository:** [${owner}/${repoName}](https://github.com/${owner}/${repoName}) +**Period:** ${period} +**Generated:** ${date} + +--- + +## Summary + +> _Replace with a one-paragraph summary of this reporting period._ + +--- + +## Theme Health + +| Metric | Value | +|---|---| +| Tested up to (WP version) | _TBD_ | +| Requires at least | _TBD_ | +| Requires PHP | _TBD_ | +| Theme version | _TBD_ | +| Block theme (theme.json) | ✅ Yes / ❌ No | + +--- + +## Template & Pattern Inventory + +| Name | Type | Status | +|---|---|---| +| _template-name_ | template / template-part / pattern | ✅ Stable | + +--- + +## Design System (theme.json) + +| Setting | Configured | +|---|---| +| Color palette | ✅ / ❌ | +| Typography presets | ✅ / ❌ | +| Spacing scale | ✅ / ❌ | +| Layout widths | ✅ / ❌ | + +--- + +## Development Activity + +### Pull Requests + +| PR # | Title | Status | Merged | +|---|---|---|---| +| _#N_ | _Description_ | merged / open | _YYYY-MM-DD_ | + +### Issues + +| Issue # | Title | Status | +|---|---|---| +| _#N_ | _Description_ | open / closed | + +--- + +## Accessibility Checks + +- [ ] Heading order correct +- [ ] Colour contrast ≥ 4.5:1 (WCAG AA) +- [ ] Keyboard navigation verified +- [ ] Skip-to-content link present +- [ ] Alt text guidance in patterns + +--- + +## Performance + +- [ ] No render-blocking assets enqueued globally +- [ ] Editor styles scoped to editor +- [ ] Images use lazy loading + +--- + +## Blockers & Risks + +> _List any current blockers, technical debt items, or risks._ + +--- + +## Next Steps + +1. _Action item 1_ +2. _Action item 2_ + +--- + +*Generated by [Reporting Agent v2](https://github.com/lightspeedwp/.github/blob/develop/.github/agents/reporting.agent.md)* +` + ); +} + +// --------------------------------------------------------------------------- +// Template: Control Plane +// --------------------------------------------------------------------------- + +/** + * Report template for the LightSpeed .github control-plane repository. + * + * @param {TemplateContext} ctx + * @returns {string} Markdown report scaffold + */ +function controlPlaneTemplate(ctx) { + const { repoName, owner, date, period = date } = ctx; + const title = `Control-Plane Report (${period})`; + + const frontmatter = buildFrontmatter( + title, + `Automated control-plane report for ${owner}/${repoName}`, + "metrics", + date, + repoName, + ); + + return ( + frontmatter + + `# ${title} + +**Repository:** [${owner}/${repoName}](https://github.com/${owner}/${repoName}) +**Period:** ${period} +**Generated:** ${date} + +--- + +## Summary + +> _Replace with a one-paragraph summary of this reporting period._ + +--- + +## Agent & Automation Health + +| Agent | Version | Status | +|---|---|---| +| Reporting Agent | v2.0 | ✅ Active | +| Metadata Agent | _vX.Y_ | ✅ Active | +| Labeling Agent | _vX.Y_ | ✅ Active | + +--- + +## Workflow Activity + +| Workflow | Runs | Pass Rate | +|---|---|---| +| _workflow-name_ | _N_ | _XX%_ | + +--- + +## Issue & PR Metrics + +| Metric | Value | +|---|---| +| Issues opened | _N_ | +| Issues closed | _N_ | +| PRs merged | _N_ | +| Average time to merge | _N days_ | + +--- + +## Test Coverage + +| Suite | Tests | Pass | Coverage | +|---|---|---|---| +| Unit | _N_ | _N_ | _XX%_ | +| Integration | _N_ | _N_ | _XX%_ | + +--- + +## Security & Compliance + +- [ ] Secrets scanning clean +- [ ] CodeQL alerts reviewed +- [ ] Dependency audit clean +- [ ] Branch protection rules current + +--- + +## Blockers & Risks + +> _List any current blockers, technical debt items, or risks._ + +--- + +## Next Steps + +1. _Action item 1_ +2. _Action item 2_ + +--- + +*Generated by [Reporting Agent v2](https://github.com/lightspeedwp/.github/blob/develop/.github/agents/reporting.agent.md)* +` + ); +} + +// --------------------------------------------------------------------------- +// Template: Platform +// --------------------------------------------------------------------------- + +/** + * Report template for platform / infrastructure repositories. + * + * @param {TemplateContext} ctx + * @returns {string} Markdown report scaffold + */ +function platformTemplate(ctx) { + const { repoName, owner, date, period = date } = ctx; + const title = `${repoName} — Platform Report (${period})`; + + const frontmatter = buildFrontmatter( + title, + `Automated platform report for ${repoName}`, + "metrics", + date, + repoName, + ); + + return ( + frontmatter + + `# ${title} + +**Repository:** [${owner}/${repoName}](https://github.com/${owner}/${repoName}) +**Period:** ${period} +**Generated:** ${date} + +--- + +## Summary + +> _Replace with a one-paragraph summary of this reporting period._ + +--- + +## Infrastructure Health + +| Service | Status | Uptime | +|---|---|---| +| _service-name_ | ✅ Up / ❌ Down | _XX%_ | + +--- + +## Deployment Activity + +| Environment | Deployments | Success Rate | +|---|---|---| +| production | _N_ | _XX%_ | +| staging | _N_ | _XX%_ | + +--- + +## Development Activity + +### Pull Requests + +| PR # | Title | Status | Merged | +|---|---|---|---| +| _#N_ | _Description_ | merged / open | _YYYY-MM-DD_ | + +--- + +## Security + +- [ ] Secrets scanning clean +- [ ] Dependency audit clean +- [ ] Container image vulnerabilities reviewed +- [ ] IAM / permissions reviewed + +--- + +## Blockers & Risks + +> _List any current blockers, technical debt items, or risks._ + +--- + +## Next Steps + +1. _Action item 1_ +2. _Action item 2_ + +--- + +*Generated by [Reporting Agent v2](https://github.com/lightspeedwp/.github/blob/develop/.github/agents/reporting.agent.md)* +` + ); +} + +// --------------------------------------------------------------------------- +// Template: Unknown (generic fallback) +// --------------------------------------------------------------------------- + +/** + * Generic report template for repositories of unknown type. + * + * @param {TemplateContext} ctx + * @returns {string} Markdown report scaffold + */ +function unknownTemplate(ctx) { + const { repoName, owner, date, period = date } = ctx; + const title = `${repoName} — Report (${period})`; + + const frontmatter = buildFrontmatter( + title, + `Automated report for ${repoName}`, + "metrics", + date, + repoName, + ); + + return ( + frontmatter + + `# ${title} + +**Repository:** [${owner}/${repoName}](https://github.com/${owner}/${repoName}) +**Period:** ${period} +**Generated:** ${date} + +--- + +## Summary + +> _Replace with a one-paragraph summary of this reporting period._ + +--- + +## Development Activity + +### Pull Requests + +| PR # | Title | Status | Merged | +|---|---|---|---| +| _#N_ | _Description_ | merged / open | _YYYY-MM-DD_ | + +### Issues + +| Issue # | Title | Status | +|---|---|---| +| _#N_ | _Description_ | open / closed | + +--- + +## Blockers & Risks + +> _List any current blockers, technical debt items, or risks._ + +--- + +## Next Steps + +1. _Action item 1_ +2. _Action item 2_ + +--- + +*Generated by [Reporting Agent v2](https://github.com/lightspeedwp/.github/blob/develop/.github/agents/reporting.agent.md)* +` + ); +} + +// --------------------------------------------------------------------------- +// Template registry +// --------------------------------------------------------------------------- + +/** @type {Record string>} */ +const TEMPLATES = { + "block-plugin": blockPluginTemplate, + "block-theme": blockThemeTemplate, + "control-plane": controlPlaneTemplate, + platform: platformTemplate, + unknown: unknownTemplate, +}; + +/** + * Render a repository-aware report using the appropriate template. + * + * @param {string} templateKey - One of the RepoType values + * @param {TemplateContext} ctx - Template context data + * @returns {string} Rendered Markdown report + */ +function renderTemplate(templateKey, ctx) { + const fn = TEMPLATES[templateKey] || unknownTemplate; + return fn(ctx); +} + +module.exports = { + TEMPLATES, + renderTemplate, + blockPluginTemplate, + blockThemeTemplate, + controlPlaneTemplate, + platformTemplate, + unknownTemplate, + buildFrontmatter, +};