diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-08-05 00:57:30 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-08-05 00:57:30 +0300 |
| commit | d4f2540cfa9229a5ce24191ddc2c1c9a2138250d (patch) | |
| tree | a3d70c2213b09771f945bd5fb66e472b5c50b7e4 | |
| parent | be1891ec5265e901b897846a6113c46cf330a7dc (diff) | |
fix(ci): make issue gate structural
| -rw-r--r-- | .github/ISSUE_TEMPLATE/bug.yml | 2 | ||||
| -rw-r--r-- | .github/workflows/issue-gate.yml | 88 | ||||
| -rw-r--r-- | CONTRIBUTING.md | 2 |
3 files changed, 53 insertions, 39 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index d8b6e231..ebf054b5 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -10,7 +10,7 @@ body: Contributors who are not maintainers may open focused bug-fix PRs when the title uses `fix: ...` or `fix(scope): ...` and the patch changes no more than 20 files and 1,000 total added or deleted lines. Features and larger changes require maintainer approval first. - Keep this short. If it does not fit on one screen, it is too long. Write in your own voice. + Keep this short. If it does not fit on one screen, it is too long. Reports over 8,000 characters are closed automatically. Write in your own voice. Use only the sections in this template. Do not add root cause, proposed fix, analysis, implementation plan, or similar sections unless a maintainer asks. diff --git a/.github/workflows/issue-gate.yml b/.github/workflows/issue-gate.yml index dd93e02b..b85c437f 100644 --- a/.github/workflows/issue-gate.yml +++ b/.github/workflows/issue-gate.yml @@ -22,6 +22,7 @@ jobs: const author = issue.user.login; const sender = context.payload.sender?.login ?? author; const bugConfirmationPattern = /^\s*-\s*\[[xX]\]\s*I confirm this is a reproducible bug, not a feature request, idea, question, contribution proposal, or direction check\.\s*$/m; + const reproductionConfirmationPattern = /^\s*-\s*\[[xX]\]\s*I reproduced this bug on the version and environment reported below using the exact steps provided\.\s*$/m; const requiredSections = [ '### Is this a reproducible bug?', '### Current behavior', @@ -30,24 +31,8 @@ jobs: '### Impact', '### Environment', ]; - const requiredEnvironmentFields = [ - { - label: 'herdr version', - pattern: /^\s*(?:-\s*)?Herdr version:[^\S\r\n]*\S.*$/im, - }, - { - label: 'update channel', - pattern: /^\s*(?:-\s*)?(?:Update channel(?: \([^)]+\))?|Channel):[^\S\r\n]*\S.*$/im, - }, - { - label: 'operating system', - pattern: /^\s*(?:-\s*)?(?:Operating system|OS):[^\S\r\n]*\S.*$/im, - }, - { - label: 'terminal', - pattern: /^\s*(?:-\s*)?Terminal:[^\S\r\n]*\S.*$/im, - }, - ]; + const maxBodyLength = 8000; + const maxExtraHeadings = 1; function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); @@ -79,6 +64,17 @@ jobs: return typeof section === 'string' && section.trim().length > 0; } + function hasEnvironmentContent(section) { + if (!hasContent(section)) { + return false; + } + + return section.split(/\r?\n/).some((line) => { + const value = line.trim(); + return value.length > 0 && !/^(?:-\s*)?[^:]+:\s*$/.test(value); + }); + } + function bodyWithoutFencedCodeBlocks(value) { let inFence = false; return value @@ -130,12 +126,37 @@ jobs: return; } - const hasBugConfirmation = bugConfirmationPattern.test(body); - const hasBugTemplate = requiredSections.every((section) => body.includes(section)); + const hasBugConfirmation = bugConfirmationPattern.test(body) && reproductionConfirmationPattern.test(body); const allowedHeadings = new Set(requiredSections); const headingBody = bodyWithoutFencedCodeBlocks(body); const headings = [...headingBody.matchAll(/^\s{0,3}#{1,6}\s+(.+?)\s*$/gm)].map((match) => match[0].trim()); + const headingCounts = new Map( + requiredSections.map((section) => [ + section, + headings.filter((heading) => heading === section).length, + ]), + ); + const hasEveryRequiredHeading = requiredSections.every( + (section) => headingCounts.get(section) > 0, + ); + const hasBugTemplate = requiredSections.every( + (section) => headingCounts.get(section) === 1, + ); + const repeatedTemplateHeadings = requiredSections.filter( + (section) => headingCounts.get(section) > 1, + ); const extraHeadings = headings.filter((heading) => !allowedHeadings.has(heading)); + const structuralViolations = []; + if (repeatedTemplateHeadings.length > 0) { + structuralViolations.push('one or more required template headings are repeated'); + } + if (extraHeadings.length > maxExtraHeadings) { + structuralViolations.push(`${extraHeadings.length} extra markdown headings were added`); + } + if (body.length > maxBodyLength) { + structuralViolations.push(`the report is ${body.length} characters; the limit is ${maxBodyLength}`); + } + const currentBehavior = extractSection(body, '### Current behavior'); const expectedBehavior = extractSection(body, '### Expected behavior'); const reproduction = extractSection(body, '### Reproduction'); @@ -147,23 +168,16 @@ jobs: reproduction, impact, ].every(hasContent); - const missingEnvironmentFields = hasContent(environment) - ? requiredEnvironmentFields - .filter((field) => !field.pattern.test(environment)) - .map((field) => field.label) - : requiredEnvironmentFields.map((field) => field.label); - const hasEnvironmentFields = missingEnvironmentFields.length === 0; - if (hasBugConfirmation && hasBugTemplate && extraHeadings.length > 0) { + const hasEnvironment = hasEnvironmentContent(environment); + + if (hasBugConfirmation && hasEveryRequiredHeading && structuralViolations.length > 0) { const message = [ `hi @${author}, thanks for opening this.`, '', - 'this issue uses extra markdown headings outside the bug report template.', + 'this report exceeds the bug report structure limits:', + ...structuralViolations.map((violation) => `- ${violation}`), '', - 'please use the exact template sections only. bug reports should describe observed behavior, exact reproduction steps, impact, and environment. extra root-cause analysis, proposed fixes, implementation plans, or generated diagnosis make reports harder to triage.', - ...(hasEnvironmentFields ? [] : [ - '', - `this report is also missing required environment details: ${missingEnvironmentFields.join(', ')}.`, - ]), + 'please keep the report within the required template, under 8,000 characters, and focused on observed behavior, exact reproduction, impact, and environment.', '', 'closing this so the issue tracker stays limited to concise, actionable bug reports.', ].join('\n'); @@ -185,16 +199,16 @@ jobs: return; } - if (hasBugConfirmation && hasBugTemplate && hasRequiredContent && hasEnvironmentFields) { + if (hasBugConfirmation && hasBugTemplate && hasRequiredContent && hasEnvironment) { console.log(`#${issue.number} matches the bug report template`); return; } - if (hasBugConfirmation && hasBugTemplate && hasRequiredContent && !hasEnvironmentFields) { + if (hasBugConfirmation && hasBugTemplate && hasRequiredContent && !hasEnvironment) { const message = [ `hi @${author}, thanks for opening this.`, '', - `this bug report is missing required environment details: ${missingEnvironmentFields.join(', ')}. please edit the issue and fill in the missing fields.`, + 'this bug report has no filled environment details. please edit the environment section with the Herdr version, update channel, operating system, and terminal.', '', 'shell and relevant config are optional, but they help when they affect the bug.', ].join('\n'); @@ -206,7 +220,7 @@ jobs: body: message, }); - console.log(`#${issue.number} is missing required environment details; leaving issue open for correction`); + console.log(`#${issue.number} has no environment details; leaving issue open for correction`); return; } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 90fdb0a5..1dfa593b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,7 +59,7 @@ Bug reports should answer these questions clearly: If there is no reproduction yet, start a discussion instead. Search open and closed issues before submitting; add evidence to an existing issue instead of opening a duplicate. -Keep bug reports factual, concise, and within the exact template. If the completed report does not fit roughly on one screen, shorten it before submitting. Report only what you or your agent directly observed: what was done, what happened, what was expected, and what environment was used. Do not add root-cause analysis, proposed fixes, implementation plans, or diagnosis dumps unless a maintainer asks. If you use AI to help write the issue, use it to make the report clearer and shorter, not longer. +Keep bug reports factual, concise, and within the exact template. Reports over 8,000 characters are closed automatically; if the completed report does not fit roughly on one screen, shorten it before submitting. Report only what you or your agent directly observed: what was done, what happened, what was expected, and what environment was used. Do not add root-cause analysis, proposed fixes, implementation plans, or diagnosis dumps unless a maintainer asks. If you use AI to help write the issue, use it to make the report clearer and shorter, not longer. If your proposal changes the visual language, interaction model, workflow, persistence, architecture, or product direction, start a discussion instead. |
