diff --git a/.github/prompts/create-github-issue-feature-from-specification.prompt.md b/.github/prompts/create-github-issue-feature-from-specification.prompt.md index ab7fe8838..58b5cc93d 100644 --- a/.github/prompts/create-github-issue-feature-from-specification.prompt.md +++ b/.github/prompts/create-github-issue-feature-from-specification.prompt.md @@ -10,3 +10,27 @@ tools: "search_issues", ] --- + +# Create GitHub Issue from Specification + +Create GitHub Issue for the specification at `${file}`. + +## Process + +1. Analyse specification file to extract requirements +2. Check existing issues using `search_issues` +3. Create or update the issue using `issue_write` +4. Use `feature_request.yml` template (fallback to default) + +## Requirements + +- Single issue for the complete specification +- Clear title identifying the specification +- Include only changes required by the specification +- Verify against existing issues before creation + +## Issue Content + +- Title: Feature name from specification +- Description: Problem statement, proposed solution, and context +- Labels: feature, enhancement (as appropriate) diff --git a/.github/prompts/create-github-issues-feature-from-implementation-plan.prompt.md b/.github/prompts/create-github-issues-feature-from-implementation-plan.prompt.md index 6e891a8b5..3db76c134 100644 --- a/.github/prompts/create-github-issues-feature-from-implementation-plan.prompt.md +++ b/.github/prompts/create-github-issues-feature-from-implementation-plan.prompt.md @@ -10,3 +10,27 @@ tools: "search_issues", ] --- + +# Create GitHub Issue from Implementation Plan + +Create GitHub Issues for the implementation plan at `${file}`. + +## Process + +1. Analyse plan file to identify phases +2. Check existing issues using `search_issues` +3. Create or update one issue per phase using `issue_write` +4. Use `feature_request.yml` or `chore_request.yml` templates (fallback to default) + +## Requirements + +- One issue per implementation phase +- Clear, structured titles and descriptions +- Include only changes required by the plan +- Verify against existing issues before creation + +## Issue Content + +- Title: Phase name from implementation plan +- Description: Phase details, requirements, and context +- Labels: Appropriate for issue type (feature/chore) diff --git a/.github/prompts/create-github-issues-for-unmet-specification-requirements.prompt.md b/.github/prompts/create-github-issues-for-unmet-specification-requirements.prompt.md index 000a62f8b..c0531a2a5 100644 --- a/.github/prompts/create-github-issues-for-unmet-specification-requirements.prompt.md +++ b/.github/prompts/create-github-issues-for-unmet-specification-requirements.prompt.md @@ -10,3 +10,34 @@ tools: "search_issues", ] --- + +# Create GitHub Issues for Unmet Specification Requirements + +Create GitHub Issues for unimplemented requirements in the specification at `${file}`. + +## Process + +1. Analyse specification file to extract all requirements +2. Check codebase implementation status for each requirement +3. Search existing issues using `search_issues` to avoid duplicates +4. Create a new issue per unimplemented requirement using `issue_write` +5. Use `feature_request.yml` template (fallback to default) + +## Requirements + +- One issue per unimplemented requirement from specification +- Clear requirement ID and description mapping +- Include implementation guidance and acceptance criteria +- Verify against existing issues before creation + +## Issue Content + +- Title: Requirement ID and brief description +- Description: Detailed requirement, implementation method, and context +- Labels: feature, enhancement (as appropriate) + +## Implementation Check + +- Search codebase for related code patterns +- Check related specification files in `/spec/` directory +- Verify requirement isn't partially implemented diff --git a/.github/workflows/metrics-summary.yml b/.github/workflows/metrics-summary.yml index 4b5bc6658..f6e425786 100644 --- a/.github/workflows/metrics-summary.yml +++ b/.github/workflows/metrics-summary.yml @@ -105,7 +105,6 @@ jobs: github.event.inputs.report_channel == 'discussions' ) runs-on: ubuntu-latest - steps: - name: Checkout repository uses: actions/checkout@v4 @@ -136,11 +135,8 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const title = `Weekly Metrics Summary — ${new Date().toISOString().split('T')[0]}`; - const body = `${{ steps.read_report.outputs.content }} - ---- - -*Generated by metrics-summary workflow. [View full reports](${{ github.server_url }}/${{ github.repository }}/tree/develop/.github/reports/metrics/)*`; + const reportUrl = `${{ github.server_url }}/${{ github.repository }}/tree/develop/.github/reports/metrics/`; + const body = `${{ steps.read_report.outputs.content }}\n\n---\n\n*Generated by metrics-summary workflow. [View full reports](${reportUrl})*`; try { const { data } = await github.rest.discussions.createDiscussion({ @@ -148,7 +144,7 @@ jobs: repo: context.repo.repo, title, body, - category_id: 'general', // Adjust based on your category ID + category_id: 'general', }); console.log(`Posted discussion: ${data.html_url}`); } catch (err) { diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 8837d949f..8326b01fc 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -7,10 +7,19 @@ on: jobs: check: runs-on: ubuntu-latest + env: + HUSKY: '0' steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' + - run: node --version && npm --version - run: npm ci - - run: npm run check + - run: npm run lint:js + - run: npm run lint:yaml + - run: npm run lint:pkg-json + - run: npm run lint:workflows + - run: npm run lint:md + - run: npm run lint:json + - run: npm run test diff --git a/.nvmrc b/.nvmrc index a45fd52cc..2bd5a0a98 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24 +22 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 83e9c4c39..98475ca50 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ tags: ["contributing", "guidelines", "workflow", "standards", "pull-requests"] ## 🚀 Quick Start (TL;DR) 1. **Fork & Clone:** Fork this repo and clone it locally. -2. **Install dependencies:** `npm ci` (runs `prepare` and installs Husky hooks) +2. **Install dependencies:** `npm ci` 3. **Create a branch:** Use the correct prefix (e.g. `feat/`, `fix/`, `docs/`). 4. **Write code & tests:** Follow [Coding Standards](instructions/coding-standards.instructions.md) and add/expand tests. 5. **Lint & test:** Run `npm run lint:all` and `npm test` before committing. On @@ -149,3 +149,12 @@ Refer to `.vscode/extensions.json` and `.vscode/settings.json` for the authorita - [Languages & Linting](instructions/languages.instructions.md) --- + +## Licence + +By contributing to this project, you agree that your contributions will be licensed under the GNU General Public License v3.0. See the [LICENSE](LICENSE) file for details. + +Thank you for helping us make LightSpeed better! + +*Maintained with ❤️ by the 🚀 LightSpeedWP Automation Team* +[Org Profile](https://github.com/lightspeedwp/.github/tree/main/profile) diff --git a/agents/mode-prd.agent.md b/agents/mode-prd.agent.md index 371c2e7a6..bed9bad38 100644 --- a/agents/mode-prd.agent.md +++ b/agents/mode-prd.agent.md @@ -1,33 +1,23 @@ --- name: "Product Requirements Document Generator" description: "Generate a comprehensive Product Requirements Document (PRD) in Markdown, detailing user stories, acceptance criteria, technical considerations, and metrics. Optionally create GitHub issues upon user confirmation." -version: "v1.1" -last_updated: "2026-05-29" -owners: ["LightSpeedWP Engineering"] -tags: ["agent", "mode", "prd", "product-management", "requirements"] -file_type: "agent" -status: "active" -domain: "planning" -stability: "stable" tools: -- codebase -- edit/editFiles -- fetch -- findTestFiles -- list_issues -- githubRepo -- search -- add_issue_comment -- issue_write -- issue_read -- search_issues -permissions: -- read -- write -- github:issues + [ + "codebase", + "edit/editFiles", + "fetch", + "findTestFiles", + "list_issues", + "githubRepo", + "search", + "add_issue_comment", + "issue_write", + "issue_read", + "search_issues", + ] metadata: - guardrails: Ask clarifying questions first, confirm scope before drafting any PRD, - only create docs with user approval, and never write issues without explicit consent. + guardrails: "Ask clarifying questions first, confirm scope before drafting any PRD, only create docs with user approval, and never write issues without explicit consent." + --- # Create PRD Agent @@ -214,3 +204,5 @@ Concise paragraph describing the user's journey and benefits. - Bullet list of criteria. --- + +After generating the PRD, I will ask if you want to proceed with creating GitHub issues for the user stories. If you agree, I will create them using `issue_write` and provide you with the links. diff --git a/scripts/agents/__tests__/project-meta-sync.agent.test.js b/scripts/agents/__tests__/project-meta-sync.agent.test.js index fb75a3ee4..b22aaed43 100644 --- a/scripts/agents/__tests__/project-meta-sync.agent.test.js +++ b/scripts/agents/__tests__/project-meta-sync.agent.test.js @@ -2,11 +2,16 @@ * Jest suite verifying the baseline behaviour of `project-meta-sync.agent.js`. * @see ../project-meta-sync.agent.js */ -// Basic smoke test for project-meta-sync.agent.js -const agent = require("../project-meta-sync.agent"); +const agent = require('../project-meta-sync.agent'); -describe("project-meta-sync.agent", () => { - it("should be defined", () => { - expect(agent).toBeDefined(); +describe('project-meta-sync.agent', () => { + it('exports a callable function', () => { + expect(typeof agent).toBe('function'); + }); + + it('does not execute run() on require (no LS_PROJECT_URL side-effect)', () => { + // If the module-scope guard is absent, requiring the file calls run() immediately, + // which throws "LS_PROJECT_URL not set" and sets process.exitCode = 1. + expect(process.exitCode).not.toBe(1); }); }); diff --git a/scripts/agents/planner.agent.js b/scripts/agents/planner.agent.js index 85917d3ba..a82156e54 100644 --- a/scripts/agents/planner.agent.js +++ b/scripts/agents/planner.agent.js @@ -50,6 +50,57 @@ function determinePlanType(title, labels, description) { return "task"; } +function deriveProjectFromLabels(labels) { + if (!Array.isArray(labels)) { + return { + project: null, + confidence: "low", + reason: "No clear project mapping; manual review recommended", + }; + } + + // Map area labels to project names + const areaToProject = { + "area:workflows": "workflows-automation", + "area:documentation": "documentation", + "area:testing": "testing-coverage", + "area:security": "security-hardening", + }; + + for (const label of labels) { + if (areaToProject[label]) { + return { + project: areaToProject[label], + confidence: "high", + reason: `Derived from label '${label}'`, + }; + } + } + + // Check for type labels to suggest generic projects + if (labels.includes("type:feature") || labels.includes("type:enhancement")) { + return { + project: "feature-development", + confidence: "medium", + reason: "Feature/enhancement type; recommend feature-development project", + }; + } + + if (labels.includes("type:bug")) { + return { + project: "bug-fixes", + confidence: "medium", + reason: "Bug type; recommend bug-fixes project", + }; + } + + return { + project: null, + confidence: "low", + reason: "No clear project mapping; manual review recommended", + }; +} + function generateArchitecturePlan(issue) { return `## 📐 Architecture Plan for #${issue.number} @@ -178,18 +229,43 @@ async function analyzeContext(octokit, context) { labels, linkedIssues, type: determinePlanType(title, labels, description), + projectAssignment: deriveProjectFromLabels(labels), }; } function generatePlan(context) { + const baseIssue = { number: context.number }; + let plan; + switch (context.type) { case "architecture": - return generateArchitecturePlan({ number: context.number }); + plan = generateArchitecturePlan(baseIssue); + break; case "implementation": - return generateImplementationPlan({ number: context.number }); + plan = generateImplementationPlan(baseIssue); + break; default: - return generateTaskPlan({ number: context.number }); + plan = generateTaskPlan(baseIssue); + } + + // Add project assignment recommendation if available + if (context.projectAssignment?.project) { + const projectSection = ` +### 📋 Project Assignment + +**Assigned Project:** \`${context.projectAssignment.project}\` +**Confidence:** ${context.projectAssignment.confidence} +**Reason:** ${context.projectAssignment.reason} +`; + return plan.replace( + /---\r?\n\*\*Generated by Planner Agent\*\*.*/, + `${projectSection.trimStart()} +--- +**Generated by Planner Agent** `, + ); } + + return plan; } async function run(context = github.context, options = {}) { @@ -239,6 +315,11 @@ async function run(context = github.context, options = {}) { if (dryRun) { core.info(`DRY-RUN: Would post plan:\n${plan}`); logger.info("Dry-run mode: plan not posted", { event: "dry-run" }); + if (analysisContext.projectAssignment?.project) { + core.info( + `DRY-RUN: Would assign to project: ${analysisContext.projectAssignment.project} (${analysisContext.projectAssignment.confidence})`, + ); + } } else { try { const prComments = await octokit.rest.issues.listComments({ @@ -277,6 +358,12 @@ async function run(context = github.context, options = {}) { issueNumber: issue.number, }); } + + if (analysisContext.projectAssignment?.project) { + core.info( + `Project assignment: ${analysisContext.projectAssignment.project} (${analysisContext.projectAssignment.confidence})`, + ); + } } catch (error) { throw new Error( `Failed to post plan on #${issue.number}: ${error.message}`,