@@ -307,3 +307,172 @@ def _create_remediation(self) -> Remediation:
307307 ),
308308 ],
309309 )
310+
311+
312+ class IssuePRTemplatesAssessor (BaseAssessor ):
313+ """Assesses presence of GitHub issue and PR templates.
314+
315+ Tier 3 Important (1.5% weight) - Templates provide structure for AI
316+ when creating issues/PRs and ensure consistent formatting.
317+ """
318+
319+ @property
320+ def attribute_id (self ) -> str :
321+ return "issue_pr_templates"
322+
323+ @property
324+ def tier (self ) -> int :
325+ return 3 # Important
326+
327+ @property
328+ def attribute (self ) -> Attribute :
329+ return Attribute (
330+ id = self .attribute_id ,
331+ name = "Issue & Pull Request Templates" ,
332+ category = "Repository Structure" ,
333+ tier = self .tier ,
334+ description = "Standardized templates for issues and PRs" ,
335+ criteria = "PR template and issue templates in .github/" ,
336+ default_weight = 0.015 ,
337+ )
338+
339+ def assess (self , repository : Repository ) -> Finding :
340+ """Check for GitHub issue and PR templates.
341+
342+ Scoring:
343+ - PR template exists (50%)
344+ - Issue templates exist (50%, requires ≥2 templates)
345+ """
346+ score = 0
347+ evidence = []
348+
349+ # Check for PR template (50%)
350+ pr_template_paths = [
351+ repository .path / ".github" / "PULL_REQUEST_TEMPLATE.md" ,
352+ repository .path / "PULL_REQUEST_TEMPLATE.md" ,
353+ repository .path / ".github" / "pull_request_template.md" ,
354+ ]
355+
356+ pr_template_found = any (p .exists () for p in pr_template_paths )
357+
358+ if pr_template_found :
359+ score += 50
360+ evidence .append ("PR template found" )
361+ else :
362+ evidence .append ("No PR template found" )
363+
364+ # Check for issue templates (50%)
365+ issue_template_dir = repository .path / ".github" / "ISSUE_TEMPLATE"
366+
367+ if issue_template_dir .exists () and issue_template_dir .is_dir ():
368+ try :
369+ # Count .md and .yml files (both formats supported)
370+ md_templates = list (issue_template_dir .glob ("*.md" ))
371+ yml_templates = list (issue_template_dir .glob ("*.yml" )) + list (
372+ issue_template_dir .glob ("*.yaml" )
373+ )
374+ template_count = len (md_templates ) + len (yml_templates )
375+
376+ if template_count >= 2 :
377+ score += 50
378+ evidence .append (
379+ f"Issue templates found: { template_count } templates"
380+ )
381+ elif template_count == 1 :
382+ score += 25
383+ evidence .append (
384+ "Issue template directory exists with 1 template (need ≥2)"
385+ )
386+ else :
387+ evidence .append ("Issue template directory exists but is empty" )
388+ except OSError :
389+ evidence .append ("Could not read issue template directory" )
390+ else :
391+ evidence .append ("No issue template directory found" )
392+
393+ status = "pass" if score >= 75 else "fail"
394+
395+ return Finding (
396+ attribute = self .attribute ,
397+ status = status ,
398+ score = score ,
399+ measured_value = f"PR:{ pr_template_found } , Issues:{ template_count if issue_template_dir .exists () else 0 } " ,
400+ threshold = "PR template + ≥2 issue templates" ,
401+ evidence = evidence ,
402+ remediation = self ._create_remediation () if status == "fail" else None ,
403+ error_message = None ,
404+ )
405+
406+ def _create_remediation (self ) -> Remediation :
407+ """Create remediation guidance for missing templates."""
408+ return Remediation (
409+ summary = "Create GitHub issue and PR templates in .github/ directory" ,
410+ steps = [
411+ "Create .github/ directory if it doesn't exist" ,
412+ "Add PULL_REQUEST_TEMPLATE.md for PRs" ,
413+ "Create .github/ISSUE_TEMPLATE/ directory" ,
414+ "Add bug_report.md for bug reports" ,
415+ "Add feature_request.md for feature requests" ,
416+ "Optionally add config.yml to configure template chooser" ,
417+ ],
418+ tools = ["gh" ],
419+ commands = [
420+ "# Create directories" ,
421+ "mkdir -p .github/ISSUE_TEMPLATE" ,
422+ "" ,
423+ "# Create PR template" ,
424+ "cat > .github/PULL_REQUEST_TEMPLATE.md << 'EOF'" ,
425+ "## Summary" ,
426+ "<!-- Describe the changes in this PR -->" ,
427+ "" ,
428+ "## Related Issues" ,
429+ "Fixes #" ,
430+ "" ,
431+ "## Testing" ,
432+ "- [ ] Tests added/updated" ,
433+ "- [ ] All tests pass" ,
434+ "" ,
435+ "## Checklist" ,
436+ "- [ ] Documentation updated" ,
437+ "- [ ] CHANGELOG.md updated" ,
438+ "EOF" ,
439+ ],
440+ examples = [
441+ """# Bug Report Template (.github/ISSUE_TEMPLATE/bug_report.md)
442+
443+ ```markdown
444+ ---
445+ name: Bug Report
446+ about: Create a report to help us improve
447+ title: '[BUG] '
448+ labels: bug
449+ assignees: ''
450+ ---
451+
452+ **Describe the bug**
453+ A clear description of what the bug is.
454+
455+ **To Reproduce**
456+ Steps to reproduce:
457+ 1. Go to '...'
458+ 2. Click on '....'
459+ 3. See error
460+
461+ **Expected behavior**
462+ What you expected to happen.
463+
464+ **Environment**
465+ - OS: [e.g. macOS 13.0]
466+ - Version: [e.g. 1.0.0]
467+ ```
468+ """ ,
469+ ],
470+ citations = [
471+ Citation (
472+ source = "GitHub Docs" ,
473+ title = "About issue and pull request templates" ,
474+ url = "https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/about-issue-and-pull-request-templates" ,
475+ relevance = "Official GitHub guide for templates" ,
476+ ),
477+ ],
478+ )
0 commit comments