Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR overhauls the project by replacing the previous gitlab.com SDK–based implementation with a custom REST client, introducing a new scanning/service layer, output formatters, CLI redesign, workflows, and comprehensive documentation/templates. Key areas include a new internal architecture (api/services/ui/models), revamped CLI flags and behavior, and CI/release workflow updates plus updated licensing and docs.
- Replaces prior gitlab client usage with custom REST API client (internal/api) and concurrent scanner (internal/services)
- Adds multi-format output utilities and progress reporting (internal/ui) plus new CLI command structure
- Updates workflows and documentation, but some code/documentation mismatches exist (flags, formats, versioning)
Reviewed Changes
Copilot reviewed 28 out of 30 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| main.go | Removes legacy header comment. |
| internal/ui/formatter.go | Adds formatters (CSV/JSON/YAML) and progress reporters. |
| internal/services/scanner.go | Adds concurrent scanning logic and project/stat conversion. |
| internal/models/types.go | Introduces data models for repository stats and scan results. |
| internal/api/types.go | Defines REST data structures replacing external SDK types. |
| internal/api/rest_client.go | Implements direct REST client with counting/stat aggregation. |
| cmd/root.go | Rebuilds CLI with new flags, execution flow, and output handling. |
| go.mod | Updates Go version and dependencies (adds cobra/viper, yaml); introduces a likely incorrect module path. |
| README.md | Rewrites documentation (features, usage, architecture) but diverges from implemented flags/output. |
| LICENSE | Updates copyright year to 2025. |
| .github/workflows/*.yml | Adds build/release-drafter and updates release workflow; removes auto-tag workflow. |
| .github/ISSUE_TEMPLATE/* | Adds standardized issue templates and config. |
| .github/copilot-instructions.md | Adds extensive development best practices. |
| .github/CODEOWNERS | Sets default code owners. |
| Removed legacy internal/api/* SDK wrappers | Cleans out old SDK-based logic now superseded. |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
go.mod
Outdated
| github.com/spf13/cast v1.10.0 // indirect | ||
| github.com/spf13/pflag v1.0.10 // indirect | ||
| github.com/subosito/gotenv v1.6.0 // indirect | ||
| go.yaml.in/yaml/v3 v3.0.4 // indirect |
There was a problem hiding this comment.
The module path 'go.yaml.in/yaml/v3' appears to be a misspelling; the correct import path is 'gopkg.in/yaml.v3'. Update the go.mod entry and run 'go mod tidy' to remove the invalid line.
| go.yaml.in/yaml/v3 v3.0.4 // indirect |
| go 1.23.0 | ||
|
|
||
| toolchain go1.24.1 | ||
| go 1.25.1 |
There was a problem hiding this comment.
The module go directive is 1.25.1, but the build workflow uses Go 1.21 (build.yml line 26). This mismatch can cause unexpected behavior if 1.25 features are used. Align the workflow to use 'go-version-file: go.mod' or downgrade the directive to a version available in CI.
| go 1.25.1 | |
| go 1.21 |
| - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b #v4.1.4 | ||
| - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 #v5.0.1 | ||
| with: | ||
| go-version: 1.21 |
There was a problem hiding this comment.
Workflow pins Go 1.21 while go.mod specifies 1.25.1, creating an environment mismatch. Switch to 'go-version-file: go.mod' or update this to the same version to ensure consistent builds.
| go-version: 1.21 | |
| go-version-file: go.mod |
| | `Project` | String | Project name | API: `name` | | ||
| | `Is_Empty` | Boolean | Whether repository is empty | API: `empty_repo` | | ||
| | `isFork` | Boolean | Whether project is a fork | API: `forked_from_project` | | ||
| | `isArchive` | Boolean | Whether project is archived | API: `archived` | |
There was a problem hiding this comment.
Column names here diverge from struct tags (e.g., 'Project' vs 'Repo_Name', 'isArchive' vs 'IsArchive' semantics). Align documentation with actual CSV headers produced in formatter.go or adjust formatter for consistency.
| | `isArchive` | Boolean | Whether project is archived | API: `archived` | | |
| | `IsArchive` | Boolean | Whether project is archived | API: `archived` | |
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 28 out of 30 changed files in this pull request and generated 20 comments.
Comments suppressed due to low confidence (1)
internal/api/rest_client.go:1
- [nitpick] Returning raw response bodies (possibly containing sensitive details) directly in formatted error strings can leak data to logs. Consider truncating or sanitizing the body content (e.g. limit length) before embedding in errors.
package api
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b #v4.1.4 | ||
| - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 #v5.0.1 | ||
| with: | ||
| go-version: 1.21 |
There was a problem hiding this comment.
The build workflow pins Go 1.21 while go.mod specifies 'go 1.25.1'. This mismatch can cause build or feature incompatibility issues; update to use the version from go.mod (e.g. use 'go-version-file: go.mod' or align the explicit version).
| go-version: 1.21 | |
| go-version-file: go.mod |
| func getCSVHeaders() []string { | ||
| return []string{ | ||
| "Namespace", | ||
| "Project", | ||
| "Is_Empty", | ||
| "isFork", | ||
| "isArchive", | ||
| "Project_Size(mb)", | ||
| "LFS_Size(mb)", | ||
| "Collaborator_Count", | ||
| "Protected_Branch_Count", | ||
| "MR_Review_Count", | ||
| "Milestone_Count", | ||
| "Issue_Count", | ||
| "MR_Count", | ||
| "MR_Review_Comment_Count", | ||
| "Commit_Count", | ||
| "Issue_Comment_Count", | ||
| "Release_Count", | ||
| "Branch_Count", | ||
| "Tag_Count", | ||
| "Has_Wiki", | ||
| "Full_URL", | ||
| "Created", | ||
| "Last_Push", | ||
| "Last_Update", | ||
| } | ||
| } |
There was a problem hiding this comment.
CSV headers use 'Project', 'Project_Size(mb)', and 'MR_' while the model tags in internal/models/types.go define 'Repo_Name', 'Repo_Size(mb)', and 'PR_'. This inconsistency creates an unstable external CSV schema and may break downstream tooling that relies on consistent field names. Align headers and struct tags (choose one convention: MR vs PR and Project vs Repo) and update both header builder and struct tags accordingly.
| writer := csv.NewWriter(file) | ||
| defer writer.Flush() |
There was a problem hiding this comment.
Errors occurring during csv.Writer.Flush() are silently ignored; a write error (e.g. disk full) would be lost. Capture and return writer.Error() after flushing: defer func(){ writer.Flush(); if err := writer.Error(); err != nil { /* handle */ } }() or perform an explicit check before returning.
| } | ||
|
|
||
| return nil |
There was a problem hiding this comment.
Errors occurring during csv.Writer.Flush() are silently ignored; a write error (e.g. disk full) would be lost. Capture and return writer.Error() after flushing: defer func(){ writer.Flush(); if err := writer.Error(); err != nil { /* handle */ } }() or perform an explicit check before returning.
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 28 out of 30 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (1)
README.md:1
- Documentation states MR_Review_Count uses upvotes + approved_by, but implementation (getMergeRequestReviewCount) only counts entries in approved_by (approvals). Update the description to match actual logic or adjust code to include upvotes if intended.
# GitLab Repository Statistics
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b #v4.1.4 | ||
| - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 #v5.0.1 | ||
| with: | ||
| go-version: 1.21 |
There was a problem hiding this comment.
CI config sets go-version: 1.21 while go.mod requires go 1.25.1; this mismatch can cause build/tooling failures. Align the workflow Go version with the go.mod directive (or lower the directive) for consistent builds.
| go-version: 1.21 | |
| go-version: 1.25.1 |
cmd/root.go
Outdated
| result, err := scanner.ScanRepositories(ctx, scanOptions, progressReporter) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("scan failed for namespace %s: %w", ns, err) | ||
| } | ||
|
|
||
| // Filter results by namespace | ||
| for _, stat := range result.RepositoryStats { | ||
| if ns == "" || stat.Namespace == ns { | ||
| allStats = append(allStats, stat) | ||
| } | ||
| } |
There was a problem hiding this comment.
Namespace filtering happens after scanning all accessible projects, causing unnecessary API calls and processing for users specifying --namespace. Implement server-side filtering (e.g., resolve group ID then restrict ListProjects) to avoid full-instance scans when a namespace is provided.
See below for a potential fix:
// Create a copy of scanOptions for each namespace to avoid side effects
nsScanOptions := *scanOptions
nsScanOptions.Namespace = ns
result, err := scanner.ScanRepositories(ctx, &nsScanOptions, progressReporter)
if err != nil {
return nil, fmt.Errorf("scan failed for namespace %s: %w", ns, err)
}
allStats = append(allStats, result.RepositoryStats...)
| project, err := client.GetProject(ctx, repoPath) | ||
| if err != nil { | ||
| fmt.Printf("Warning: Failed to get project %s: %v\n", repoPath, err) | ||
| continue | ||
| } |
There was a problem hiding this comment.
repoPath (namespace/project) is passed unescaped to /projects/%v; GitLab's API expects a URL-encoded path (e.g., url.PathEscape) or numeric ID. Without encoding, the request path segments will be misinterpreted and may 404. Apply url.PathEscape(repoPath) before formatting.
cmd/root.go
Outdated
| scanOptions := &models.ScanOptions{ | ||
| GitLabURL: gitlabURL, | ||
| Token: token, | ||
| OutputFormat: output, // Already normalized to lowercase | ||
| Verbose: debug, | ||
| IncludeArchived: true, | ||
| MaxProjects: 0, | ||
| } |
There was a problem hiding this comment.
IncludeArchived is hard-coded to true and there is no flag to control it, making the option misleading. Either expose a CLI flag (e.g., --include-archived) or remove the field; otherwise users cannot exclude archived projects.
| You can set the GitLab token via environment variable: | ||
|
|
||
| ```bash | ||
| export GITLAB_TOKEN="your-token-here" | ||
| ./gh-gitlab-stats --hostname gitlab.com | ||
| ``` |
There was a problem hiding this comment.
README suggests GITLAB_TOKEN environment variable is supported, but the code only reads the --token/-t flag (no viper binding or env lookup for token). Either implement environment variable support (e.g., via viper.BindEnv) or remove this section to avoid confusion.
| You can set the GitLab token via environment variable: | |
| ```bash | |
| export GITLAB_TOKEN="your-token-here" | |
| ./gh-gitlab-stats --hostname gitlab.com | |
| ``` |
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 28 out of 30 changed files in this pull request and generated 8 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| project, err := client.GetProject(ctx, repoPath) | ||
| if err != nil { | ||
| fmt.Printf("Warning: Failed to get project %s: %v\n", repoPath, err) | ||
| continue |
There was a problem hiding this comment.
repoPath contains namespace/project (with a slash), but GetProject builds /projects/%v without URL‑encoding the path; GitLab requires the path to be URL-escaped (e.g. group%2Fproject) or a numeric ID. This will fail for textual IDs. Encode with url.PathEscape(repoPath) before passing, or accept only numeric IDs.
internal/api/rest_client.go
Outdated
| params := url.Values{} | ||
| params.Set("statistics", "true") | ||
|
|
||
| path := fmt.Sprintf("/projects/%v", projectID) |
There was a problem hiding this comment.
Project lookup with a string identifier containing slashes (namespace/project) must be URL-encoded (GitLab expects /projects/:id where :id is URL-escaped). Wrap projectID when it's a string path: path := fmt.Sprintf("/projects/%s", url.PathEscape(projectID.(string))).
| path := fmt.Sprintf("/projects/%v", projectID) | |
| var path string | |
| switch v := projectID.(type) { | |
| case string: | |
| path = fmt.Sprintf("/projects/%s", url.PathEscape(v)) | |
| default: | |
| path = fmt.Sprintf("/projects/%v", v) | |
| } |
| func (c *RestClient) getMergeRequestReviewCount(ctx context.Context, projectID interface{}) (int, error) { | ||
| // In GitLab, reviews are tracked as "approvals" on merge requests | ||
| // We'll count MRs that have at least one approval | ||
| totalReviews := 0 | ||
| mrParams := url.Values{} | ||
| mrParams.Set("scope", "all") | ||
| mrParams.Set("per_page", "100") | ||
|
|
||
| for page := 1; page <= 10; page++ { // Limit to first 1000 MRs | ||
| mrParams.Set("page", strconv.Itoa(page)) | ||
| mrPath := fmt.Sprintf("/projects/%v/merge_requests", projectID) | ||
| mrBody, _, err := c.doRequest(ctx, "GET", mrPath, mrParams) | ||
| if err != nil { | ||
| break | ||
| } | ||
|
|
||
| var pageMRs []map[string]interface{} | ||
| if err := json.Unmarshal(mrBody, &pageMRs); err != nil { | ||
| break | ||
| } | ||
|
|
||
| if len(pageMRs) == 0 { | ||
| break | ||
| } | ||
|
|
||
| for _, mr := range pageMRs { | ||
| // Only count actual approvals from approved_by, not upvotes | ||
| // Upvotes are just "thumbs up" reactions, not actual code reviews | ||
| if approvers, ok := mr["approved_by"].([]interface{}); ok { | ||
| totalReviews += len(approvers) | ||
| } | ||
| } |
There was a problem hiding this comment.
The List Merge Requests API does not reliably include full approval details (approved_by often omitted without extra parameters or separate approvals endpoint), so this may undercount reviews. Use the MR approvals endpoint (/projects/:id/merge_requests/:iid/approvals) or explicitly request approvals-related fields to ensure accuracy; consider making this optional due to cost.
| func (c *RestClient) getMergeRequestCommentCount(ctx context.Context, projectID interface{}) (int, error) { | ||
| // In GitLab, MR comments are called "notes" and include both regular comments and code review comments | ||
| // We need to get notes from the merge_requests endpoint |
There was a problem hiding this comment.
[nitpick] Fetching up to 1000 full MR records to sum user_notes_count is expensive (large JSON payloads). Consider an early exit when totalNotes surpasses a configured threshold, making this metric optional, or parallelizing requests; alternatively expose a flag to skip comment aggregation to reduce API load.
| for _, mr := range pageMRs { | ||
| if userNotesCount, ok := mr["user_notes_count"].(float64); ok { | ||
| totalNotes += int(userNotesCount) | ||
| } | ||
| } |
There was a problem hiding this comment.
[nitpick] Fetching up to 1000 full MR records to sum user_notes_count is expensive (large JSON payloads). Consider an early exit when totalNotes surpasses a configured threshold, making this metric optional, or parallelizing requests; alternatively expose a flag to skip comment aggregation to reduce API load.
| } | ||
|
|
||
| // getIssueCommentCount gets the total count of comments on issues | ||
| func (c *RestClient) getIssueCommentCount(ctx context.Context, projectID interface{}) (int, error) { |
There was a problem hiding this comment.
[nitpick] Enumerating up to 1000 issues (10 pages * 100) to accumulate user_notes_count can significantly increase scan time for large projects. Provide a configurable limit, batching strategy, or a flag to skip/comment metrics to mitigate latency and API quota consumption.
internal/api/rest_client.go
Outdated
| func (c *RestClient) getIssueCommentCount(ctx context.Context, projectID interface{}) (int, error) { | ||
| // Similar to MR comments, we need to fetch issues and sum up their notes | ||
| totalNotes := 0 | ||
| issueParams := url.Values{} | ||
| issueParams.Set("scope", "all") | ||
| issueParams.Set("per_page", "100") | ||
|
|
||
| for page := 1; page <= 10; page++ { // Limit to first 1000 issues |
There was a problem hiding this comment.
[nitpick] Enumerating up to 1000 issues (10 pages * 100) to accumulate user_notes_count can significantly increase scan time for large projects. Provide a configurable limit, batching strategy, or a flag to skip/comment metrics to mitigate latency and API quota consumption.
| func (c *RestClient) getIssueCommentCount(ctx context.Context, projectID interface{}) (int, error) { | |
| // Similar to MR comments, we need to fetch issues and sum up their notes | |
| totalNotes := 0 | |
| issueParams := url.Values{} | |
| issueParams.Set("scope", "all") | |
| issueParams.Set("per_page", "100") | |
| for page := 1; page <= 10; page++ { // Limit to first 1000 issues | |
| // getIssueCommentCount gets the total count of comments on issues. | |
| // maxPages limits the number of pages to scan (each page contains up to 100 issues). | |
| // If skipCommentMetrics is true, the function returns 0 without scanning. | |
| func (c *RestClient) getIssueCommentCount(ctx context.Context, projectID interface{}, maxPages int, skipCommentMetrics bool) (int, error) { | |
| if skipCommentMetrics { | |
| return 0, nil | |
| } | |
| totalNotes := 0 | |
| issueParams := url.Values{} | |
| issueParams.Set("scope", "all") | |
| issueParams.Set("per_page", "100") | |
| for page := 1; page <= maxPages; page++ { // Limit to maxPages * 100 issues |
| for _, issue := range pageIssues { | ||
| if userNotesCount, ok := issue["user_notes_count"].(float64); ok { | ||
| totalNotes += int(userNotesCount) | ||
| } |
There was a problem hiding this comment.
[nitpick] Enumerating up to 1000 issues (10 pages * 100) to accumulate user_notes_count can significantly increase scan time for large projects. Provide a configurable limit, batching strategy, or a flag to skip/comment metrics to mitigate latency and API quota consumption.
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 28 out of 30 changed files in this pull request and generated 3 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
internal/api/rest_client.go
Outdated
| path := fmt.Sprintf("/projects/%v/merge_requests", projectID) | ||
| body, _, err := c.doRequest(ctx, "GET", path, params) | ||
| if err != nil { | ||
| return 0, err | ||
| } |
There was a problem hiding this comment.
Project identifier is interpolated without URL-encoding; if a string path (namespace/project) is ever supplied, this endpoint will break. Use the already implemented c.encodeProjectID(projectID) as elsewhere: encodedProjectID := c.encodeProjectID(projectID); path := fmt.Sprintf("/projects/%s/merge_requests", encodedProjectID).
| func (c *RestClient) GetProjectStatistics(ctx context.Context, projectID interface{}) (*ProjectStatistics, error) { | ||
| // In GitLab API, statistics are included when you get a project with statistics=true | ||
| // So we'll fetch the project and return its statistics | ||
| project, err := c.GetProject(ctx, projectID) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| if project.Statistics == nil { | ||
| // Return empty statistics if not available | ||
| project.Statistics = &ProjectStatistics{} | ||
| } | ||
|
|
||
| // Get additional statistics that aren't included in the basic project response | ||
| // These require separate API calls | ||
|
|
||
| // Get merge request count | ||
| mrCount, err := c.getMergeRequestCount(ctx, projectID) | ||
| if err == nil { | ||
| project.Statistics.MergeRequestCount = mrCount | ||
| } | ||
|
|
||
| // Get branch count | ||
| branchCount, err := c.getBranchCount(ctx, projectID) | ||
| if err == nil { | ||
| project.Statistics.BranchCount = branchCount | ||
| } | ||
|
|
||
| // Get tag count | ||
| tagCount, err := c.getTagCount(ctx, projectID) | ||
| if err == nil { | ||
| project.Statistics.TagCount = tagCount | ||
| } | ||
|
|
||
| // Get member count | ||
| memberCount, err := c.getMemberCount(ctx, projectID) | ||
| if err == nil { | ||
| project.Statistics.MemberCount = memberCount | ||
| } | ||
|
|
||
| // Get milestone count | ||
| milestoneCount, err := c.getMilestoneCount(ctx, projectID) | ||
| if err == nil { | ||
| project.Statistics.MilestoneCount = milestoneCount | ||
| } | ||
|
|
||
| // Get release count | ||
| releaseCount, err := c.getReleaseCount(ctx, projectID) | ||
| if err == nil { | ||
| project.Statistics.ReleaseCount = releaseCount | ||
| } | ||
|
|
||
| // Check if wiki actually has pages (only if wiki is enabled in settings) | ||
| if project.WikiEnabled { | ||
| project.Statistics.HasWikiPages = c.hasWikiPages(ctx, projectID) | ||
| } else { | ||
| project.Statistics.HasWikiPages = false | ||
| } | ||
|
|
||
| // Get comment counts and review counts (these are more expensive operations) | ||
| // Get merge request review count | ||
| mrReviewCount, err := c.getMergeRequestReviewCount(ctx, projectID) | ||
| if err == nil { | ||
| project.Statistics.MergeRequestReviewCount = mrReviewCount | ||
| } | ||
|
|
||
| // Get merge request comment count | ||
| mrCommentCount, err := c.getMergeRequestCommentCount(ctx, projectID) | ||
| if err == nil { | ||
| project.Statistics.MergeRequestCommentCount = mrCommentCount | ||
| } | ||
|
|
||
| // Get issue comment count | ||
| issueCommentCount, err := c.getIssueCommentCount(ctx, projectID) | ||
| if err == nil { | ||
| project.Statistics.IssueCommentCount = issueCommentCount | ||
| } | ||
|
|
||
| return project.Statistics, nil |
There was a problem hiding this comment.
This method can trigger >30 HTTP requests per project (each count + up to 10 pages for reviews, MR comments, and issue comments), which will not scale and risks rate limiting. Consider batching (e.g., reuse MR/issue listings to derive multiple counts, allow a 'depth' or 'fast' mode, and short-circuit optional expensive metrics behind a flag).
cmd/root.go
Outdated
| // truncate truncates a string to a maximum length | ||
| func truncate(s string, maxLen int) string { | ||
| if len(s) <= maxLen { | ||
| return s | ||
| } | ||
| if maxLen <= 3 { | ||
| return s[:maxLen] | ||
| } | ||
| return gitlabProjects | ||
| return s[:maxLen-3] + "..." |
There was a problem hiding this comment.
Duplicate implementation of truncate (also defined in services/scanner.go lines 322-330); extract into a shared utility to avoid divergence and reduce duplication.
|
Facelift and rewrite to be more in line with the output of gh-repo-stats. |
|
@antgrutta thanks for this work, this is a huge PR so hoping everything works as expected, we should definitely add some tests though to make sure the changes introduced don't break our cli. 😅 |
|
@amenocal, thanks, yes agreed, unit tests should be next on the task list. I did testing locally, the tool is going to start to be used by the MSFT Factory team and having a consistent experience with the other repo-stats tools is key as the ongoing support for this tool is going to ramp up quite soon. More to come... |
This pull request introduces several improvements to the project's GitHub workflow automation, issue tracking, documentation, and code ownership management. The changes help standardize development practices, automate builds and releases, and provide clear templates for issue reporting and feature requests.
Workflow and Automation Improvements
.github/workflows/build.ymlto automate build steps for pushes and pull requests tomain, ensuring consistent CI for Go projects..github/workflows/release-drafter.ymlto automatically update release drafts on pushes tomain, streamlining release management..github/workflows/release.ymlto use newer, pinned versions ofactions/checkoutandgh-extension-precompile, and switched to specifying Go version viago.modfor improved reproducibility..github/workflows/auto-tag-and-release.yml, consolidating release automation and reducing redundancy.Documentation and Best Practices
.github/copilot-instructions.mdwith comprehensive guidelines and best practices for developing GitHub CLI extensions in Go, covering architecture, error handling, testing, performance, security, and documentation standards.Issue and Project Management
.github/ISSUE_TEMPLATE/bug_report.mdand.github/ISSUE_TEMPLATE/feature_request.mdto standardize bug and feature request submissions, improving triage and communication. [1] [2].github/ISSUE_TEMPLATE/config.ymlto disable blank issues, ensuring contributors use structured templates..github/CODEOWNERSto set default ownership for all files to@mona-actions/team-es, clarifying responsibility.Legal and Licensing
LICENSEto reflect copyright year as 2025.