oss-signal 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial CLI with Markdown and JSON output.
6
+ - Added maintainer-readiness checks for community files, automation, and package hygiene.
7
+ - Added tests and CI workflow.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 oss-signal contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # oss-signal
2
+
3
+ [![CI](https://github.com/SalmonPlays/oss-signal/actions/workflows/ci.yml/badge.svg)](https://github.com/SalmonPlays/oss-signal/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/oss-signal.svg)](https://www.npmjs.com/package/oss-signal)
5
+ [![npm downloads](https://img.shields.io/npm/dm/oss-signal.svg)](https://www.npmjs.com/package/oss-signal)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
+
8
+ `oss-signal` is a dependency-light CLI for auditing open-source repository maintenance readiness.
9
+
10
+ It checks the files and automation that reduce maintainer load: README, license, contributing guide, security policy, CI, tests, issue templates, pull request templates, Dependabot, and release notes. The output is a score plus concrete next steps in Markdown or JSON.
11
+
12
+ ![oss-signal example output](docs/assets/terminal-report.svg)
13
+
14
+ ## Why
15
+
16
+ Open-source projects often fail quietly because the maintainer workflow is undocumented. `oss-signal` gives maintainers a repeatable checklist they can run locally, in CI, or before asking contributors to help.
17
+
18
+ ## Use Cases
19
+
20
+ - Maintainers can run it before publishing a new project.
21
+ - Contributors can attach a report to a cleanup issue or pull request.
22
+ - Teams can gate release readiness with `--fail-under`.
23
+ - Foundations and working groups can compare repository hygiene across many projects.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install --global oss-signal
29
+ ```
30
+
31
+ For local development:
32
+
33
+ ```bash
34
+ git clone https://github.com/SalmonPlays/oss-signal.git
35
+ cd oss-signal
36
+ npm install
37
+ npm test
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ Audit the current directory:
43
+
44
+ ```bash
45
+ oss-signal
46
+ ```
47
+
48
+ Write a Markdown report:
49
+
50
+ ```bash
51
+ oss-signal /path/to/repo --format markdown --output oss-signal-report.md
52
+ ```
53
+
54
+ Use JSON in automation:
55
+
56
+ ```bash
57
+ oss-signal . --format json --fail-under 80
58
+ ```
59
+
60
+ Generate a report that can be attached to an issue:
61
+
62
+ ```bash
63
+ oss-signal . --format markdown --output docs/maintainer-readiness.md
64
+ ```
65
+
66
+ ## Checks
67
+
68
+ `oss-signal` currently checks:
69
+
70
+ - Community files: README, license, contributing guide, security policy, code of conduct, changelog, support policy
71
+ - Automation: CI workflows, tests, issue templates, pull request template, Dependabot, CodeQL or similar security workflow
72
+ - Package hygiene: package metadata and lockfile presence
73
+
74
+ See [docs/rules.md](docs/rules.md) for rule details and scoring weights.
75
+
76
+ ## Real Output
77
+
78
+ This repository audits itself at **100/100 (A)**:
79
+
80
+ ```text
81
+ Score: 100/100 (A)
82
+
83
+ Summary:
84
+ - Passed: 15
85
+ - Failed: 0
86
+ - Total checks: 15
87
+ ```
88
+
89
+ See [docs/self-audit.md](docs/self-audit.md) for the full self-audit report.
90
+
91
+ ## Field Audits
92
+
93
+ `oss-signal` has been run against public repositories to produce maintainer-readiness reports and respectful issue drafts:
94
+
95
+ - [platformatic/massimo report](docs/outreach/platformatic-massimo-report.md) and [issue #159](https://github.com/platformatic/massimo/issues/159)
96
+ - [supermarkt/checkjebon report](docs/outreach/supermarkt-checkjebon-report.md) and [issue #22](https://github.com/supermarkt/checkjebon/issues/22)
97
+ - [sammorrisdesign/interactive-feed report](docs/outreach/sammorrisdesign-interactive-feed-report.md) and [issue #14](https://github.com/sammorrisdesign/interactive-feed/issues/14)
98
+
99
+ See [docs/outreach](docs/outreach) for the reports and draft issue text. Drafts are not posted automatically; maintainers should only receive specific, useful, and respectful suggestions.
100
+
101
+ ## Example Recommendation Output
102
+
103
+ ```text
104
+ Score: 86/100 (B)
105
+
106
+ Recommended next steps:
107
+ - Static security analysis: Add a CodeQL or equivalent security scanning workflow.
108
+ - Support policy: Add SUPPORT.md describing where to ask questions.
109
+ ```
110
+
111
+ See [docs/examples/minimal-repo-report.md](docs/examples/minimal-repo-report.md) for a small repository example with missing maintainer files.
112
+
113
+ ## Exit Codes
114
+
115
+ By default, `oss-signal` exits with `0` after writing a report.
116
+
117
+ When `--fail-under <score>` is provided, it exits with `1` if the score is below the threshold:
118
+
119
+ ```bash
120
+ oss-signal . --fail-under 80
121
+ ```
122
+
123
+ ## CI
124
+
125
+ Add this to a GitHub Actions workflow:
126
+
127
+ ```yaml
128
+ - run: npx oss-signal . --fail-under 80
129
+ ```
130
+
131
+ Full workflow example:
132
+
133
+ ```yaml
134
+ name: Repository health
135
+
136
+ on:
137
+ pull_request:
138
+ push:
139
+ branches: [main]
140
+
141
+ jobs:
142
+ oss-signal:
143
+ runs-on: ubuntu-latest
144
+ steps:
145
+ - uses: actions/checkout@v4
146
+ - uses: actions/setup-node@v4
147
+ with:
148
+ node-version: 22
149
+ - run: npx oss-signal . --format markdown --output oss-signal-report.md --fail-under 80
150
+ - uses: actions/upload-artifact@v4
151
+ with:
152
+ name: oss-signal-report
153
+ path: oss-signal-report.md
154
+ ```
155
+
156
+ ## Current Limitations
157
+
158
+ - It inspects local files only; GitHub URL mode is on the roadmap.
159
+ - It checks deterministic maintenance signals, not code quality.
160
+ - A high score does not prove a project is important. It proves the maintainer workflow is documented and automatable.
161
+
162
+ ## Roadmap
163
+
164
+ - GitHub API mode for public repository URLs
165
+ - Ecosystem-specific profiles for Python, Rust, Go, and JavaScript packages
166
+ - SARIF output for code scanning dashboards
167
+ - Rules for release automation and provenance metadata
168
+
169
+ ## Contributing
170
+
171
+ Contributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request.
172
+
173
+ ## Security
174
+
175
+ Please report security issues privately. See [SECURITY.md](SECURITY.md).
176
+
177
+ ## License
178
+
179
+ MIT
@@ -0,0 +1,22 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="920" height="520" viewBox="0 0 920 520" role="img" aria-labelledby="title desc">
2
+ <title id="title">oss-signal terminal output</title>
3
+ <desc id="desc">Example terminal output showing an OSS Signal repository health score of 100 out of 100.</desc>
4
+ <rect width="920" height="520" rx="18" fill="#0d1117"/>
5
+ <rect x="0" y="0" width="920" height="48" rx="18" fill="#161b22"/>
6
+ <circle cx="30" cy="24" r="7" fill="#ff5f56"/>
7
+ <circle cx="54" cy="24" r="7" fill="#ffbd2e"/>
8
+ <circle cx="78" cy="24" r="7" fill="#27c93f"/>
9
+ <text x="110" y="31" fill="#8b949e" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="14">oss-signal</text>
10
+ <text x="34" y="86" fill="#7ee787" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="16">$ oss-signal . --format markdown</text>
11
+ <text x="34" y="130" fill="#f0f6fc" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="24" font-weight="700">OSS Signal Report</text>
12
+ <text x="34" y="174" fill="#f0f6fc" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18">Score: </text>
13
+ <text x="108" y="174" fill="#7ee787" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18" font-weight="700">100/100 (A)</text>
14
+ <text x="34" y="222" fill="#8b949e" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="15">Summary</text>
15
+ <text x="34" y="252" fill="#f0f6fc" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="15">PASS README</text>
16
+ <text x="34" y="280" fill="#f0f6fc" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="15">PASS License</text>
17
+ <text x="34" y="308" fill="#f0f6fc" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="15">PASS Contributing guide</text>
18
+ <text x="34" y="336" fill="#f0f6fc" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="15">PASS Security policy</text>
19
+ <text x="34" y="364" fill="#f0f6fc" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="15">PASS Continuous integration</text>
20
+ <text x="34" y="392" fill="#f0f6fc" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="15">PASS Tests</text>
21
+ <text x="34" y="442" fill="#7ee787" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="16">No missing checks. Keep the report current as the repository evolves.</text>
22
+ </svg>
@@ -0,0 +1,21 @@
1
+ # Example Report: Minimal Repository
2
+
3
+ This is representative output for a tiny repository that only has a README.
4
+
5
+ ```text
6
+ Score: 12/100 (F)
7
+
8
+ Summary:
9
+ - Passed: 1
10
+ - Failed: 14
11
+ - Total checks: 15
12
+
13
+ Recommended next steps:
14
+ - Continuous integration (12 pts): Add a GitHub Actions workflow that runs linting and tests on pushes and pull requests.
15
+ - License (10 pts): Add an OSI-approved license file such as MIT, Apache-2.0, BSD-3-Clause, or MPL-2.0.
16
+ - Tests (10 pts): Add focused tests for public behavior and document the test command.
17
+ - Contributing guide (9 pts): Add CONTRIBUTING.md with local setup, test commands, review expectations, and release notes guidance.
18
+ - Security policy (9 pts): Add SECURITY.md with supported versions, reporting instructions, and response expectations.
19
+ ```
20
+
21
+ The goal is not to shame small projects. The goal is to turn implicit maintainer expectations into a short, reviewable checklist.
@@ -0,0 +1,20 @@
1
+ # Field Audits
2
+
3
+ This directory contains public-repository audit reports generated with `oss-signal`.
4
+
5
+ These reports are examples of how maintainers or contributors can use the tool before opening a cleanup issue or pull request. They are based on local repository contents plus a manual check of GitHub community profile signals where needed.
6
+
7
+ Important notes:
8
+
9
+ - These projects are not affiliated with `oss-signal`.
10
+ - A low score does not mean the project is low quality.
11
+ - Missing files may be intentional or handled outside the repository.
12
+ - Any external issue or pull request should be respectful, specific, and easy for the maintainer to close.
13
+
14
+ ## Audited Repositories
15
+
16
+ | Repository | Local score | Draft | Posted issue |
17
+ | --- | ---: | --- | --- |
18
+ | `platformatic/massimo` | 58/100 | [issue draft](platformatic-massimo-issue-draft.md) | [#159](https://github.com/platformatic/massimo/issues/159) |
19
+ | `supermarkt/checkjebon` | 21/100 | [issue draft](supermarkt-checkjebon-issue-draft.md) | [#22](https://github.com/supermarkt/checkjebon/issues/22) |
20
+ | `sammorrisdesign/interactive-feed` | 31/100 | [issue draft](sammorrisdesign-interactive-feed-issue-draft.md) | [#14](https://github.com/sammorrisdesign/interactive-feed/issues/14) |
@@ -0,0 +1,24 @@
1
+ # Issue Draft: platformatic/massimo
2
+
3
+ Posted as [platformatic/massimo#159](https://github.com/platformatic/massimo/issues/159).
4
+
5
+ Title:
6
+
7
+ ```text
8
+ Add issue and pull request templates for contributor triage
9
+ ```
10
+
11
+ Body:
12
+
13
+ ```markdown
14
+ Hi maintainers. I ran a local maintainer-readiness audit with `oss-signal` against this repository and noticed two small GitHub workflow files that may help contributor triage:
15
+
16
+ - `.github/ISSUE_TEMPLATE/bug_report.md`
17
+ - `.github/PULL_REQUEST_TEMPLATE.md`
18
+
19
+ GitHub's community profile endpoint shows this repository already has a README, license, contributing guide, and organization-level code of conduct, so this is not a broad cleanup request. The highest-signal next step looks like adding templates that ask for reproduction steps, expected behavior, test coverage, and release-note impact.
20
+
21
+ I am happy to open a small PR with template files if that would be useful. If this is intentionally handled elsewhere, feel free to close.
22
+ ```
23
+
24
+ Local report: [platformatic-massimo-report.md](platformatic-massimo-report.md)
@@ -0,0 +1,43 @@
1
+ # OSS Signal Report
2
+
3
+ Repository: `https://github.com/platformatic/massimo`
4
+ Generated: 2026-06-01T05:32:40.366Z
5
+
6
+ Score: **58/100** (F)
7
+
8
+ ## Summary
9
+
10
+ - Passed: 7
11
+ - Failed: 8
12
+ - Total checks: 15
13
+
14
+ ## Checks
15
+
16
+ | Status | Check | Why it matters |
17
+ | --- | --- | --- |
18
+ | PASS | README | A clear README is the front door for users and contributors. |
19
+ | PASS | License | A license tells downstream users what they may legally do with the code. |
20
+ | PASS | Contributing guide | Maintainers get better issues and pull requests when expectations are documented. |
21
+ | FAIL | Security policy | Responsible disclosure needs a private, documented path. |
22
+ | FAIL | Code of conduct | Community norms reduce ambiguity during difficult interactions. |
23
+ | FAIL | Changelog | Users need a durable place to understand release impact. |
24
+ | FAIL | Support policy | Support boundaries help maintainers avoid turning every request into unpaid consulting. |
25
+ | PASS | Continuous integration | CI catches regressions before maintainers merge changes. |
26
+ | PASS | Tests | Tests make review safer and lower the cost of outside contributions. |
27
+ | FAIL | Issue templates | Issue templates collect the facts maintainers need to reproduce and triage. |
28
+ | FAIL | Pull request template | PR templates nudge contributors to include tests, docs, and review context. |
29
+ | FAIL | Dependency update automation | Automated dependency updates reduce security and compatibility drift. |
30
+ | FAIL | Static security analysis | Static analysis finds common vulnerability patterns before releases. |
31
+ | PASS | Node package metadata | Package metadata makes installation, testing, and release automation discoverable. |
32
+ | PASS | Dependency lockfile | Lockfiles make CI and contributor setup reproducible. |
33
+
34
+ ## Recommended Next Steps
35
+
36
+ - **Security policy** (9 pts): Add SECURITY.md with supported versions, reporting instructions, and response expectations.
37
+ - **Code of conduct** (6 pts): Add CODE_OF_CONDUCT.md, for example the Contributor Covenant.
38
+ - **Changelog** (6 pts): Keep CHANGELOG.md with dated release entries and migration notes.
39
+ - **Issue templates** (5 pts): Add bug report and feature request templates under .github/ISSUE_TEMPLATE/.
40
+ - **Pull request template** (5 pts): Add .github/PULL_REQUEST_TEMPLATE.md with a short checklist.
41
+ - **Dependency update automation** (5 pts): Add .github/dependabot.yml for the package ecosystems used in the repository.
42
+ - **Support policy** (4 pts): Add SUPPORT.md describing where to ask questions, what is in scope, and expected response times.
43
+ - **Static security analysis** (4 pts): Add a CodeQL or equivalent security scanning workflow.
@@ -0,0 +1,28 @@
1
+ # Issue Draft: sammorrisdesign/interactive-feed
2
+
3
+ Posted as [sammorrisdesign/interactive-feed#14](https://github.com/sammorrisdesign/interactive-feed/issues/14).
4
+
5
+ Title:
6
+
7
+ ```text
8
+ Clarify license and contributor workflow
9
+ ```
10
+
11
+ Body:
12
+
13
+ ```markdown
14
+ Hi maintainers. I ran a local maintainer-readiness audit with `oss-signal` and noticed that GitHub's community profile does not currently detect a license file for this repository.
15
+
16
+ Because license choice has to come from the maintainer, I do not want to guess one in a PR. It may be worth adding an explicit `LICENSE` file if you intend others to reuse, fork, or contribute to the project.
17
+
18
+ The same audit also flagged a few optional maintainer-workflow files that could help if you want outside contributions:
19
+
20
+ - `CONTRIBUTING.md`
21
+ - `SECURITY.md`
22
+ - `.github/ISSUE_TEMPLATE/bug_report.md`
23
+ - `.github/PULL_REQUEST_TEMPLATE.md`
24
+
25
+ I can open a small PR for the non-license templates if that would be useful. If this project is not intended for outside contribution, feel free to close.
26
+ ```
27
+
28
+ Local report: [sammorrisdesign-interactive-feed-report.md](sammorrisdesign-interactive-feed-report.md)
@@ -0,0 +1,46 @@
1
+ # OSS Signal Report
2
+
3
+ Repository: `https://github.com/sammorrisdesign/interactive-feed`
4
+ Generated: 2026-06-01T05:32:40.592Z
5
+
6
+ Score: **31/100** (F)
7
+
8
+ ## Summary
9
+
10
+ - Passed: 4
11
+ - Failed: 11
12
+ - Total checks: 15
13
+
14
+ ## Checks
15
+
16
+ | Status | Check | Why it matters |
17
+ | --- | --- | --- |
18
+ | PASS | README | A clear README is the front door for users and contributors. |
19
+ | FAIL | License | A license tells downstream users what they may legally do with the code. |
20
+ | FAIL | Contributing guide | Maintainers get better issues and pull requests when expectations are documented. |
21
+ | FAIL | Security policy | Responsible disclosure needs a private, documented path. |
22
+ | FAIL | Code of conduct | Community norms reduce ambiguity during difficult interactions. |
23
+ | FAIL | Changelog | Users need a durable place to understand release impact. |
24
+ | FAIL | Support policy | Support boundaries help maintainers avoid turning every request into unpaid consulting. |
25
+ | PASS | Continuous integration | CI catches regressions before maintainers merge changes. |
26
+ | FAIL | Tests | Tests make review safer and lower the cost of outside contributions. |
27
+ | FAIL | Issue templates | Issue templates collect the facts maintainers need to reproduce and triage. |
28
+ | FAIL | Pull request template | PR templates nudge contributors to include tests, docs, and review context. |
29
+ | FAIL | Dependency update automation | Automated dependency updates reduce security and compatibility drift. |
30
+ | FAIL | Static security analysis | Static analysis finds common vulnerability patterns before releases. |
31
+ | PASS | Node package metadata | Package metadata makes installation, testing, and release automation discoverable. |
32
+ | PASS | Dependency lockfile | Lockfiles make CI and contributor setup reproducible. |
33
+
34
+ ## Recommended Next Steps
35
+
36
+ - **License** (10 pts): Add an OSI-approved license file such as MIT, Apache-2.0, BSD-3-Clause, or MPL-2.0.
37
+ - **Tests** (10 pts): Add focused tests for public behavior and document the test command.
38
+ - **Contributing guide** (9 pts): Add CONTRIBUTING.md with local setup, test commands, review expectations, and release notes guidance.
39
+ - **Security policy** (9 pts): Add SECURITY.md with supported versions, reporting instructions, and response expectations.
40
+ - **Code of conduct** (6 pts): Add CODE_OF_CONDUCT.md, for example the Contributor Covenant.
41
+ - **Changelog** (6 pts): Keep CHANGELOG.md with dated release entries and migration notes.
42
+ - **Issue templates** (5 pts): Add bug report and feature request templates under .github/ISSUE_TEMPLATE/.
43
+ - **Pull request template** (5 pts): Add .github/PULL_REQUEST_TEMPLATE.md with a short checklist.
44
+ - **Dependency update automation** (5 pts): Add .github/dependabot.yml for the package ecosystems used in the repository.
45
+ - **Support policy** (4 pts): Add SUPPORT.md describing where to ask questions, what is in scope, and expected response times.
46
+ - **Static security analysis** (4 pts): Add a CodeQL or equivalent security scanning workflow.
@@ -0,0 +1,26 @@
1
+ # Issue Draft: supermarkt/checkjebon
2
+
3
+ Posted as [supermarkt/checkjebon#22](https://github.com/supermarkt/checkjebon/issues/22).
4
+
5
+ Title:
6
+
7
+ ```text
8
+ Document contributor and security reporting workflow
9
+ ```
10
+
11
+ Body:
12
+
13
+ ```markdown
14
+ Hi maintainers. I ran a local maintainer-readiness audit with `oss-signal` and noticed a few lightweight repository-health files that could make future contributions easier to triage:
15
+
16
+ - `CONTRIBUTING.md` for local setup, tests, and review expectations
17
+ - `SECURITY.md` for private vulnerability reporting
18
+ - `.github/ISSUE_TEMPLATE/bug_report.md` for reproducible bug reports
19
+ - `.github/PULL_REQUEST_TEMPLATE.md` for test and docs checklists
20
+
21
+ GitHub's community profile already detects the README and license, so this would mainly document maintainer workflow rather than change product code.
22
+
23
+ I can open a focused PR with starter templates if that would be welcome. If these files are intentionally omitted, no problem; feel free to close.
24
+ ```
25
+
26
+ Local report: [supermarkt-checkjebon-report.md](supermarkt-checkjebon-report.md)
@@ -0,0 +1,48 @@
1
+ # OSS Signal Report
2
+
3
+ Repository: `https://github.com/supermarkt/checkjebon`
4
+ Generated: 2026-06-01T05:32:40.484Z
5
+
6
+ Score: **21/100** (F)
7
+
8
+ ## Summary
9
+
10
+ - Passed: 2
11
+ - Failed: 13
12
+ - Total checks: 15
13
+
14
+ ## Checks
15
+
16
+ | Status | Check | Why it matters |
17
+ | --- | --- | --- |
18
+ | PASS | README | A clear README is the front door for users and contributors. |
19
+ | PASS | License | A license tells downstream users what they may legally do with the code. |
20
+ | FAIL | Contributing guide | Maintainers get better issues and pull requests when expectations are documented. |
21
+ | FAIL | Security policy | Responsible disclosure needs a private, documented path. |
22
+ | FAIL | Code of conduct | Community norms reduce ambiguity during difficult interactions. |
23
+ | FAIL | Changelog | Users need a durable place to understand release impact. |
24
+ | FAIL | Support policy | Support boundaries help maintainers avoid turning every request into unpaid consulting. |
25
+ | FAIL | Continuous integration | CI catches regressions before maintainers merge changes. |
26
+ | FAIL | Tests | Tests make review safer and lower the cost of outside contributions. |
27
+ | FAIL | Issue templates | Issue templates collect the facts maintainers need to reproduce and triage. |
28
+ | FAIL | Pull request template | PR templates nudge contributors to include tests, docs, and review context. |
29
+ | FAIL | Dependency update automation | Automated dependency updates reduce security and compatibility drift. |
30
+ | FAIL | Static security analysis | Static analysis finds common vulnerability patterns before releases. |
31
+ | FAIL | Node package metadata | Package metadata makes installation, testing, and release automation discoverable. |
32
+ | FAIL | Dependency lockfile | Lockfiles make CI and contributor setup reproducible. |
33
+
34
+ ## Recommended Next Steps
35
+
36
+ - **Continuous integration** (12 pts): Add a GitHub Actions workflow that runs linting and tests on pushes and pull requests.
37
+ - **Tests** (10 pts): Add focused tests for public behavior and document the test command.
38
+ - **Contributing guide** (9 pts): Add CONTRIBUTING.md with local setup, test commands, review expectations, and release notes guidance.
39
+ - **Security policy** (9 pts): Add SECURITY.md with supported versions, reporting instructions, and response expectations.
40
+ - **Code of conduct** (6 pts): Add CODE_OF_CONDUCT.md, for example the Contributor Covenant.
41
+ - **Changelog** (6 pts): Keep CHANGELOG.md with dated release entries and migration notes.
42
+ - **Issue templates** (5 pts): Add bug report and feature request templates under .github/ISSUE_TEMPLATE/.
43
+ - **Pull request template** (5 pts): Add .github/PULL_REQUEST_TEMPLATE.md with a short checklist.
44
+ - **Dependency update automation** (5 pts): Add .github/dependabot.yml for the package ecosystems used in the repository.
45
+ - **Node package metadata** (5 pts): Add package.json with name, description, license, scripts, repository, and engines fields.
46
+ - **Support policy** (4 pts): Add SUPPORT.md describing where to ask questions, what is in scope, and expected response times.
47
+ - **Static security analysis** (4 pts): Add a CodeQL or equivalent security scanning workflow.
48
+ - **Dependency lockfile** (4 pts): Commit the lockfile for application-style projects, or document why this library intentionally omits one.
package/docs/rules.md ADDED
@@ -0,0 +1,43 @@
1
+ # Rules
2
+
3
+ `oss-signal` uses weighted checks. Missing high-weight items are the most likely to increase maintainer load or create downstream risk.
4
+
5
+ ## Community Files
6
+
7
+ | Rule | Weight | Signal |
8
+ | --- | ---: | --- |
9
+ | README | 12 | `README.md` or `README` |
10
+ | License | 10 | `LICENSE`, `LICENSE.md`, or `COPYING` |
11
+ | Contributing guide | 9 | `CONTRIBUTING.md`, `.github/CONTRIBUTING.md`, or `docs/CONTRIBUTING.md` |
12
+ | Security policy | 9 | `SECURITY.md` or `.github/SECURITY.md` |
13
+ | Code of conduct | 6 | `CODE_OF_CONDUCT.md` or `.github/CODE_OF_CONDUCT.md` |
14
+ | Changelog | 6 | `CHANGELOG.md`, `HISTORY.md`, or `RELEASES.md` |
15
+ | Support policy | 4 | `SUPPORT.md` or `.github/SUPPORT.md` |
16
+
17
+ ## Automation
18
+
19
+ | Rule | Weight | Signal |
20
+ | --- | ---: | --- |
21
+ | Continuous integration | 12 | Any YAML workflow under `.github/workflows/` |
22
+ | Tests | 10 | Common test directories or `*.test.*` / `*.spec.*` files |
23
+ | Issue templates | 5 | `.github/ISSUE_TEMPLATE/` or `.github/ISSUE_TEMPLATE.md` |
24
+ | Pull request template | 5 | `.github/PULL_REQUEST_TEMPLATE.md` or `PULL_REQUEST_TEMPLATE.md` |
25
+ | Dependency update automation | 5 | `.github/dependabot.yml` or `.github/dependabot.yaml` |
26
+ | Static security analysis | 4 | Workflow filename containing `codeql` or `security` |
27
+
28
+ ## Package Hygiene
29
+
30
+ | Rule | Weight | Signal |
31
+ | --- | ---: | --- |
32
+ | Node package metadata | 5 | `package.json` |
33
+ | Dependency lockfile | 4 | Common lockfiles such as `package-lock.json`, `pnpm-lock.yaml`, `poetry.lock`, `Cargo.lock`, or `go.sum` |
34
+
35
+ ## Scoring
36
+
37
+ The score is the percentage of available weighted points that pass. Grades are:
38
+
39
+ - A: 90-100
40
+ - B: 80-89
41
+ - C: 70-79
42
+ - D: 60-69
43
+ - F: below 60
@@ -0,0 +1,37 @@
1
+ # OSS Signal Report
2
+
3
+ Repository: `/Users/amon/Documents/Codex/2026-06-01/openai-s/outputs/oss-signal`
4
+ Generated: 2026-06-02T01:40:56.294Z
5
+
6
+ Score: **100/100** (A)
7
+
8
+ ## Summary
9
+
10
+ - Passed: 15
11
+ - Failed: 0
12
+ - Total checks: 15
13
+
14
+ ## Checks
15
+
16
+ | Status | Check | Why it matters |
17
+ | --- | --- | --- |
18
+ | PASS | README | A clear README is the front door for users and contributors. |
19
+ | PASS | License | A license tells downstream users what they may legally do with the code. |
20
+ | PASS | Contributing guide | Maintainers get better issues and pull requests when expectations are documented. |
21
+ | PASS | Security policy | Responsible disclosure needs a private, documented path. |
22
+ | PASS | Code of conduct | Community norms reduce ambiguity during difficult interactions. |
23
+ | PASS | Changelog | Users need a durable place to understand release impact. |
24
+ | PASS | Support policy | Support boundaries help maintainers avoid turning every request into unpaid consulting. |
25
+ | PASS | Continuous integration | CI catches regressions before maintainers merge changes. |
26
+ | PASS | Tests | Tests make review safer and lower the cost of outside contributions. |
27
+ | PASS | Issue templates | Issue templates collect the facts maintainers need to reproduce and triage. |
28
+ | PASS | Pull request template | PR templates nudge contributors to include tests, docs, and review context. |
29
+ | PASS | Dependency update automation | Automated dependency updates reduce security and compatibility drift. |
30
+ | PASS | Static security analysis | Static analysis finds common vulnerability patterns before releases. |
31
+ | PASS | Node package metadata | Package metadata makes installation, testing, and release automation discoverable. |
32
+ | PASS | Dependency lockfile | Lockfiles make CI and contributor setup reproducible. |
33
+
34
+ ## Recommended Next Steps
35
+
36
+ No missing checks. Keep the report current as the repository evolves.
37
+
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "oss-signal",
3
+ "version": "0.1.0",
4
+ "description": "A dependency-light CLI that audits open-source repository maintenance readiness.",
5
+ "type": "module",
6
+ "bin": {
7
+ "oss-signal": "./src/cli.js"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/SalmonPlays/oss-signal.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/SalmonPlays/oss-signal/issues"
15
+ },
16
+ "homepage": "https://github.com/SalmonPlays/oss-signal#readme",
17
+ "files": [
18
+ "src",
19
+ "docs",
20
+ "README.md",
21
+ "LICENSE",
22
+ "CHANGELOG.md"
23
+ ],
24
+ "scripts": {
25
+ "test": "node --test",
26
+ "lint": "node --check src/*.js test/*.test.js",
27
+ "audit:self": "node src/cli.js . --format markdown --output docs/self-audit.md",
28
+ "audit:check": "node src/cli.js . --format json --fail-under 100 > /dev/null",
29
+ "check": "npm run lint && npm test && npm run audit:check",
30
+ "prepublishOnly": "npm run check"
31
+ },
32
+ "keywords": [
33
+ "open-source",
34
+ "maintainer",
35
+ "audit",
36
+ "repository",
37
+ "cli"
38
+ ],
39
+ "author": "SalmonPlays",
40
+ "license": "MIT",
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "engines": {
45
+ "node": ">=18"
46
+ }
47
+ }
package/src/cli.js ADDED
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ import { promises as fs } from "node:fs";
3
+ import { auditRepository, renderMarkdown } from "./index.js";
4
+
5
+ const VERSION = "0.1.0";
6
+
7
+ async function main(argv) {
8
+ const options = parseArgs(argv);
9
+
10
+ if (options.help) {
11
+ process.stdout.write(helpText());
12
+ return;
13
+ }
14
+ if (options.version) {
15
+ process.stdout.write(`${VERSION}\n`);
16
+ return;
17
+ }
18
+
19
+ const report = await auditRepository(options.path, { maxFiles: options.maxFiles });
20
+ const body = options.format === "json" ? `${JSON.stringify(report, null, 2)}\n` : renderMarkdown(report);
21
+
22
+ if (options.output) {
23
+ await fs.writeFile(options.output, body, "utf8");
24
+ } else {
25
+ process.stdout.write(body);
26
+ }
27
+
28
+ if (typeof options.failUnder === "number" && report.score < options.failUnder) {
29
+ process.stderr.write(`oss-signal: score ${report.score} is below --fail-under ${options.failUnder}\n`);
30
+ process.exitCode = 1;
31
+ }
32
+ }
33
+
34
+ function parseArgs(argv) {
35
+ const options = {
36
+ path: ".",
37
+ format: "markdown",
38
+ output: undefined,
39
+ failUnder: undefined,
40
+ maxFiles: 20000,
41
+ help: false,
42
+ version: false
43
+ };
44
+
45
+ const positionals = [];
46
+
47
+ for (let index = 0; index < argv.length; index += 1) {
48
+ const arg = argv[index];
49
+ if (arg === "--help" || arg === "-h") {
50
+ options.help = true;
51
+ } else if (arg === "--version" || arg === "-v") {
52
+ options.version = true;
53
+ } else if (arg === "--format") {
54
+ options.format = requireValue(argv, ++index, "--format");
55
+ } else if (arg.startsWith("--format=")) {
56
+ options.format = arg.slice("--format=".length);
57
+ } else if (arg === "--output" || arg === "-o") {
58
+ options.output = requireValue(argv, ++index, arg);
59
+ } else if (arg.startsWith("--output=")) {
60
+ options.output = arg.slice("--output=".length);
61
+ } else if (arg === "--fail-under") {
62
+ options.failUnder = parseNumber(requireValue(argv, ++index, "--fail-under"), "--fail-under");
63
+ } else if (arg.startsWith("--fail-under=")) {
64
+ options.failUnder = parseNumber(arg.slice("--fail-under=".length), "--fail-under");
65
+ } else if (arg === "--max-files") {
66
+ options.maxFiles = parseNumber(requireValue(argv, ++index, "--max-files"), "--max-files");
67
+ } else if (arg.startsWith("--max-files=")) {
68
+ options.maxFiles = parseNumber(arg.slice("--max-files=".length), "--max-files");
69
+ } else if (arg.startsWith("-")) {
70
+ throw new Error(`Unknown option: ${arg}`);
71
+ } else {
72
+ positionals.push(arg);
73
+ }
74
+ }
75
+
76
+ if (positionals.length > 1) {
77
+ throw new Error(`Expected at most one repository path, got ${positionals.length}`);
78
+ }
79
+ if (positionals.length === 1) {
80
+ options.path = positionals[0];
81
+ }
82
+ if (!["markdown", "json"].includes(options.format)) {
83
+ throw new Error("--format must be either markdown or json");
84
+ }
85
+ return options;
86
+ }
87
+
88
+ function requireValue(argv, index, optionName) {
89
+ const value = argv[index];
90
+ if (!value || value.startsWith("-")) {
91
+ throw new Error(`${optionName} requires a value`);
92
+ }
93
+ return value;
94
+ }
95
+
96
+ function parseNumber(value, optionName) {
97
+ const parsed = Number(value);
98
+ if (!Number.isFinite(parsed)) {
99
+ throw new Error(`${optionName} must be a number`);
100
+ }
101
+ return parsed;
102
+ }
103
+
104
+ function helpText() {
105
+ return `oss-signal audits open-source repository maintenance readiness.
106
+
107
+ Usage:
108
+ oss-signal [path] [--format markdown|json] [--output file] [--fail-under score]
109
+
110
+ Options:
111
+ --format Output format. Defaults to markdown.
112
+ --output, -o Write the report to a file instead of stdout.
113
+ --fail-under Exit with code 1 when the score is below this value.
114
+ --max-files Maximum files to inspect. Defaults to 20000.
115
+ --version, -v Show the CLI version.
116
+ --help, -h Show this help message.
117
+ `;
118
+ }
119
+
120
+ main(process.argv.slice(2)).catch((error) => {
121
+ process.stderr.write(`oss-signal: ${error.message}\n`);
122
+ process.exitCode = 1;
123
+ });
package/src/index.js ADDED
@@ -0,0 +1,326 @@
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ const COMMUNITY_FILES = [
5
+ {
6
+ id: "readme",
7
+ label: "README",
8
+ weight: 12,
9
+ paths: ["README.md", "README"],
10
+ why: "A clear README is the front door for users and contributors.",
11
+ fix: "Add setup, usage, contribution, support, and project status sections to README.md."
12
+ },
13
+ {
14
+ id: "license",
15
+ label: "License",
16
+ weight: 10,
17
+ paths: ["LICENSE", "LICENSE.md", "COPYING"],
18
+ why: "A license tells downstream users what they may legally do with the code.",
19
+ fix: "Add an OSI-approved license file such as MIT, Apache-2.0, BSD-3-Clause, or MPL-2.0."
20
+ },
21
+ {
22
+ id: "contributing",
23
+ label: "Contributing guide",
24
+ weight: 9,
25
+ paths: ["CONTRIBUTING.md", ".github/CONTRIBUTING.md", "docs/CONTRIBUTING.md"],
26
+ why: "Maintainers get better issues and pull requests when expectations are documented.",
27
+ fix: "Add CONTRIBUTING.md with local setup, test commands, review expectations, and release notes guidance."
28
+ },
29
+ {
30
+ id: "security",
31
+ label: "Security policy",
32
+ weight: 9,
33
+ paths: ["SECURITY.md", ".github/SECURITY.md"],
34
+ why: "Responsible disclosure needs a private, documented path.",
35
+ fix: "Add SECURITY.md with supported versions, reporting instructions, and response expectations."
36
+ },
37
+ {
38
+ id: "code-of-conduct",
39
+ label: "Code of conduct",
40
+ weight: 6,
41
+ paths: ["CODE_OF_CONDUCT.md", ".github/CODE_OF_CONDUCT.md"],
42
+ why: "Community norms reduce ambiguity during difficult interactions.",
43
+ fix: "Add CODE_OF_CONDUCT.md, for example the Contributor Covenant."
44
+ },
45
+ {
46
+ id: "changelog",
47
+ label: "Changelog",
48
+ weight: 6,
49
+ paths: ["CHANGELOG.md", "HISTORY.md", "RELEASES.md"],
50
+ why: "Users need a durable place to understand release impact.",
51
+ fix: "Keep CHANGELOG.md with dated release entries and migration notes."
52
+ },
53
+ {
54
+ id: "support",
55
+ label: "Support policy",
56
+ weight: 4,
57
+ paths: ["SUPPORT.md", ".github/SUPPORT.md"],
58
+ why: "Support boundaries help maintainers avoid turning every request into unpaid consulting.",
59
+ fix: "Add SUPPORT.md describing where to ask questions, what is in scope, and expected response times."
60
+ }
61
+ ];
62
+
63
+ const AUTOMATION_FILES = [
64
+ {
65
+ id: "ci",
66
+ label: "Continuous integration",
67
+ weight: 12,
68
+ matcher: (tree) => tree.some((file) => file.startsWith(".github/workflows/") && /\.ya?ml$/i.test(file)),
69
+ why: "CI catches regressions before maintainers merge changes.",
70
+ fix: "Add a GitHub Actions workflow that runs linting and tests on pushes and pull requests."
71
+ },
72
+ {
73
+ id: "tests",
74
+ label: "Tests",
75
+ weight: 10,
76
+ matcher: (tree) => tree.some((file) => /(^|\/)(test|tests|spec|__tests__)\//i.test(file) || /\.(test|spec)\.[cm]?[jt]sx?$/i.test(file)),
77
+ why: "Tests make review safer and lower the cost of outside contributions.",
78
+ fix: "Add focused tests for public behavior and document the test command."
79
+ },
80
+ {
81
+ id: "issue-templates",
82
+ label: "Issue templates",
83
+ weight: 5,
84
+ matcher: (tree) => tree.some((file) => file.startsWith(".github/ISSUE_TEMPLATE/") || file === ".github/ISSUE_TEMPLATE.md"),
85
+ why: "Issue templates collect the facts maintainers need to reproduce and triage.",
86
+ fix: "Add bug report and feature request templates under .github/ISSUE_TEMPLATE/."
87
+ },
88
+ {
89
+ id: "pull-request-template",
90
+ label: "Pull request template",
91
+ weight: 5,
92
+ matcher: (tree) => tree.includes(".github/PULL_REQUEST_TEMPLATE.md") || tree.includes("PULL_REQUEST_TEMPLATE.md"),
93
+ why: "PR templates nudge contributors to include tests, docs, and review context.",
94
+ fix: "Add .github/PULL_REQUEST_TEMPLATE.md with a short checklist."
95
+ },
96
+ {
97
+ id: "dependabot",
98
+ label: "Dependency update automation",
99
+ weight: 5,
100
+ matcher: (tree) => tree.includes(".github/dependabot.yml") || tree.includes(".github/dependabot.yaml"),
101
+ why: "Automated dependency updates reduce security and compatibility drift.",
102
+ fix: "Add .github/dependabot.yml for the package ecosystems used in the repository."
103
+ },
104
+ {
105
+ id: "codeql",
106
+ label: "Static security analysis",
107
+ weight: 4,
108
+ matcher: (tree) => tree.some((file) => file.startsWith(".github/workflows/") && /codeql|security/i.test(file)),
109
+ why: "Static analysis finds common vulnerability patterns before releases.",
110
+ fix: "Add a CodeQL or equivalent security scanning workflow."
111
+ }
112
+ ];
113
+
114
+ const PACKAGE_FILES = [
115
+ {
116
+ id: "package-json",
117
+ label: "Node package metadata",
118
+ weight: 5,
119
+ matcher: (tree) => tree.includes("package.json"),
120
+ why: "Package metadata makes installation, testing, and release automation discoverable.",
121
+ fix: "Add package.json with name, description, license, scripts, repository, and engines fields."
122
+ },
123
+ {
124
+ id: "lockfile",
125
+ label: "Dependency lockfile",
126
+ weight: 4,
127
+ matcher: (tree) => tree.some((file) => ["package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock", "uv.lock", "poetry.lock", "Pipfile.lock", "Cargo.lock", "go.sum"].includes(file)),
128
+ why: "Lockfiles make CI and contributor setup reproducible.",
129
+ fix: "Commit the lockfile for application-style projects, or document why this library intentionally omits one."
130
+ }
131
+ ];
132
+
133
+ const DEFAULT_IGNORE_DIRS = new Set([
134
+ ".git",
135
+ "node_modules",
136
+ "vendor",
137
+ "dist",
138
+ "build",
139
+ "coverage",
140
+ ".next",
141
+ ".turbo",
142
+ ".venv",
143
+ "__pycache__"
144
+ ]);
145
+
146
+ export async function auditRepository(root, options = {}) {
147
+ const absoluteRoot = path.resolve(root ?? ".");
148
+ const tree = await listRepositoryFiles(absoluteRoot, options);
149
+ const fileSet = new Set(tree);
150
+ const checks = [
151
+ ...COMMUNITY_FILES.map((rule) => checkPathRule(rule, fileSet)),
152
+ ...AUTOMATION_FILES.map((rule) => checkMatcherRule(rule, tree)),
153
+ ...PACKAGE_FILES.map((rule) => checkMatcherRule(rule, tree))
154
+ ];
155
+
156
+ const totalWeight = checks.reduce((sum, check) => sum + check.weight, 0);
157
+ const earnedWeight = checks.filter((check) => check.passed).reduce((sum, check) => sum + check.weight, 0);
158
+ const score = totalWeight === 0 ? 0 : Math.round((earnedWeight / totalWeight) * 100);
159
+
160
+ return {
161
+ tool: "oss-signal",
162
+ version: "0.1.0",
163
+ root: absoluteRoot,
164
+ generatedAt: new Date().toISOString(),
165
+ score,
166
+ grade: gradeForScore(score),
167
+ summary: summarizeChecks(checks),
168
+ checks,
169
+ recommendations: checks
170
+ .filter((check) => !check.passed)
171
+ .sort((a, b) => b.weight - a.weight)
172
+ .map(({ id, label, weight, why, fix }) => ({ id, label, weight, why, fix }))
173
+ };
174
+ }
175
+
176
+ export function renderMarkdown(report) {
177
+ const lines = [
178
+ "# OSS Signal Report",
179
+ "",
180
+ `Repository: \`${report.root}\``,
181
+ `Generated: ${report.generatedAt}`,
182
+ "",
183
+ `Score: **${report.score}/100** (${report.grade})`,
184
+ "",
185
+ "## Summary",
186
+ "",
187
+ `- Passed: ${report.summary.passed}`,
188
+ `- Failed: ${report.summary.failed}`,
189
+ `- Total checks: ${report.summary.total}`,
190
+ "",
191
+ "## Checks",
192
+ "",
193
+ "| Status | Check | Why it matters |",
194
+ "| --- | --- | --- |"
195
+ ];
196
+
197
+ for (const check of report.checks) {
198
+ lines.push(`| ${check.passed ? "PASS" : "FAIL"} | ${escapeTable(check.label)} | ${escapeTable(check.why)} |`);
199
+ }
200
+
201
+ lines.push("", "## Recommended Next Steps", "");
202
+ if (report.recommendations.length === 0) {
203
+ lines.push("No missing checks. Keep the report current as the repository evolves.");
204
+ } else {
205
+ for (const recommendation of report.recommendations) {
206
+ lines.push(`- **${recommendation.label}** (${recommendation.weight} pts): ${recommendation.fix}`);
207
+ }
208
+ }
209
+
210
+ lines.push("");
211
+ return `${lines.join("\n")}\n`;
212
+ }
213
+
214
+ export async function listRepositoryFiles(root, options = {}) {
215
+ const maxFiles = options.maxFiles ?? 20000;
216
+ const files = [];
217
+
218
+ async function walk(currentDir, relativeDir = "") {
219
+ if (files.length >= maxFiles) {
220
+ return;
221
+ }
222
+
223
+ let entries;
224
+ try {
225
+ entries = await fs.readdir(currentDir, { withFileTypes: true });
226
+ } catch (error) {
227
+ if (error.code === "EACCES" || error.code === "ENOENT") {
228
+ return;
229
+ }
230
+ throw error;
231
+ }
232
+
233
+ entries.sort((a, b) => a.name.localeCompare(b.name));
234
+
235
+ for (const entry of entries) {
236
+ if (files.length >= maxFiles) {
237
+ return;
238
+ }
239
+ const relativePath = path.posix.join(relativeDir, entry.name);
240
+ const fullPath = path.join(currentDir, entry.name);
241
+
242
+ if (entry.isDirectory()) {
243
+ if (!DEFAULT_IGNORE_DIRS.has(entry.name)) {
244
+ await walk(fullPath, relativePath);
245
+ }
246
+ } else if (entry.isFile()) {
247
+ files.push(relativePath);
248
+ }
249
+ }
250
+ }
251
+
252
+ await walk(root);
253
+ return files;
254
+ }
255
+
256
+ function checkPathRule(rule, fileSet) {
257
+ const matchedPath = rule.paths.find((candidate) => fileSet.has(candidate));
258
+ return {
259
+ id: rule.id,
260
+ label: rule.label,
261
+ weight: rule.weight,
262
+ passed: Boolean(matchedPath),
263
+ evidence: matchedPath ? [matchedPath] : [],
264
+ why: rule.why,
265
+ fix: rule.fix
266
+ };
267
+ }
268
+
269
+ function checkMatcherRule(rule, tree) {
270
+ const passed = rule.matcher(tree);
271
+ return {
272
+ id: rule.id,
273
+ label: rule.label,
274
+ weight: rule.weight,
275
+ passed,
276
+ evidence: passed ? findEvidence(rule.id, tree) : [],
277
+ why: rule.why,
278
+ fix: rule.fix
279
+ };
280
+ }
281
+
282
+ function findEvidence(id, tree) {
283
+ if (id === "ci" || id === "codeql") {
284
+ return tree.filter((file) => file.startsWith(".github/workflows/")).slice(0, 5);
285
+ }
286
+ if (id === "tests") {
287
+ return tree.filter((file) => /(^|\/)(test|tests|spec|__tests__)\//i.test(file) || /\.(test|spec)\.[cm]?[jt]sx?$/i.test(file)).slice(0, 5);
288
+ }
289
+ if (id === "issue-templates") {
290
+ return tree.filter((file) => file.startsWith(".github/ISSUE_TEMPLATE/") || file === ".github/ISSUE_TEMPLATE.md").slice(0, 5);
291
+ }
292
+ if (id === "pull-request-template") {
293
+ return tree.filter((file) => file === ".github/PULL_REQUEST_TEMPLATE.md" || file === "PULL_REQUEST_TEMPLATE.md").slice(0, 5);
294
+ }
295
+ if (id === "dependabot") {
296
+ return tree.filter((file) => file === ".github/dependabot.yml" || file === ".github/dependabot.yaml").slice(0, 5);
297
+ }
298
+ if (id === "package-json") {
299
+ return tree.includes("package.json") ? ["package.json"] : [];
300
+ }
301
+ if (id === "lockfile") {
302
+ return tree.filter((file) => ["package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock", "uv.lock", "poetry.lock", "Pipfile.lock", "Cargo.lock", "go.sum"].includes(file)).slice(0, 5);
303
+ }
304
+ return [];
305
+ }
306
+
307
+ function summarizeChecks(checks) {
308
+ const passed = checks.filter((check) => check.passed).length;
309
+ return {
310
+ total: checks.length,
311
+ passed,
312
+ failed: checks.length - passed
313
+ };
314
+ }
315
+
316
+ function gradeForScore(score) {
317
+ if (score >= 90) return "A";
318
+ if (score >= 80) return "B";
319
+ if (score >= 70) return "C";
320
+ if (score >= 60) return "D";
321
+ return "F";
322
+ }
323
+
324
+ function escapeTable(value) {
325
+ return String(value).replaceAll("|", "\\|");
326
+ }