Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/resource.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ body:
- type: markdown
attributes:
value: |
Submit one resource per issue. Eligible resources are free and open, work with Codex CLI, and have either
100+ GitHub stars or a first commit more than 14 days old. See contributing.md for details.
Submit one resource per issue. Eligible resources are free and open, work with Codex CLI, and have 100+ GitHub stars.
Resources below that are reviewed on distinctiveness and quality. See contributing.md for details.
The automated check only confirms the form is complete; maintainers make the inclusion decision.
- type: input
id: project
Expand Down
28 changes: 19 additions & 9 deletions contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ External contributions are welcome through **issues only**. Pull request creatio
3. For new resources, provide a project link, suggested section, factual description, Codex integration evidence, and verifiable external usage evidence.
4. Maintainers review the issue and update the list if accepted. Submission does not guarantee inclusion.

Resource issues receive automated field, evidence-link, and duplicate-URL checks. The bot reports GitHub stars, age of the first commit (when stars are under 100), and archive status in a summary table, and updates its result when you edit the issue. A project under 100 stars and under 14 days old gets `manual-review`; the bot never closes an issue. `validation-passed` means basic checks passed, not approval or verification of the evidence. Missing information gets `needs-evidence` or `needs-changes`; archived repositories and failed metadata queries get `manual-review`. The bot does not decide inclusion; maintainers apply the inclusion criteria below.
Resource issues receive automated field, evidence-link, and duplicate-URL checks. The bot reports GitHub stars and archive status in a summary table, and updates its result when you edit the issue. A project under 100 stars gets `manual-review`; the bot never closes an issue. `validation-passed` means basic checks passed, not approval or verification of the evidence. Missing information gets `needs-evidence` or `needs-changes`; archived repositories and failed metadata queries get `manual-review`. The bot does not decide inclusion; maintainers apply the inclusion criteria below.

## Contribute Codex Commands, Workflows, and Templates

Expand All @@ -26,15 +26,25 @@ Built an MCP Server, Agent Skill, or Micro-SaaS for Codex CLI?

## Inclusion Criteria

A resource must meet **all** of the following.
Every resource must pass the **requirements**. Then a maintainer judges it on the **quality** points. Stars are one signal, not the whole test.

1. **Adoption or maturity.** Either:
- it has at least 100 GitHub stars; or
- the oldest commit on its default branch is more than 14 days old, and it has kept receiving commits since.
2. **Codex support.** It works with Codex CLI, shown by code or documentation, not only a line in its README.
3. **Open access.** Closed-source projects, and projects that need a sign-up or payment, are judged only on signals from other users (stars, downloads, third-party listings), not on the author's claims. A paid tier does not rule out a free, public core that works without an account.
4. **Scope.** The resource must be usable from Codex CLI. An MCP server qualifies when its documentation gives a working Codex setup, such as a `codex mcp add` command or a `config.toml` block. Servers that work with any MCP client qualify under this rule, provided they document that setup.
5. **Entry style.** One resource per issue, with one entry, one section, and one factual line on what it does. Keep the tone neutral. Issues that bundle several resources must be split.
### Requirements

1. **Free and open.** Closed-source projects, and projects that need a sign-up or payment, count only with signals from other users (stars, downloads, third-party listings), not the author's claims. A paid tier does not rule out a free, public core that works without an account.
2. **Works with Codex CLI.** Shown by code or documentation. An MCP server qualifies when its documentation gives a working Codex setup, such as a `codex mcp add` command or a `config.toml` block. Servers that work with any MCP client qualify under this rule, provided they document that setup.
3. **Maintained.** Not archived, and still receiving changes.
4. **Entry style.** One resource per issue, with one entry, one section, and one factual line on what it does. Keep the tone neutral. Issues that bundle several resources must be split.

### Quality

A maintainer checks these points:

- **Distinct.** It adds something the same section does not already cover, or has a clear difference for Codex CLI users.
- **Real use.** It has 100 or more GitHub stars, or outside evidence such as third-party listings, issues or pull requests from people outside the project, or other projects that depend on it (for example npm or PyPI dependents). Download counts and release counts alone do not count, since automated installs and frequent releases are easy to produce.
- **Active.** Recent commits, and issues get answers.
- **Accurate.** The one-line description matches what the project does.

A resource with fewer than 100 stars is listed only if it passes the requirements and clearly meets at least two quality points. The submission must give the reason in the issue.

Submissions that miss these criteria are not merged. Being submitted does not guarantee inclusion, and a maintainer may decline one without a full review.

Expand Down
32 changes: 6 additions & 26 deletions scripts/validate-submission.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ const LABELS = {
const STATUS = ['validation-pending', 'validation-passed', 'needs-evidence', 'needs-changes', 'manual-review'];
const MARKER = '<!-- submission-validation -->';
const MIN_STARS = 100;
const MIN_AGE_DAYS = 14;
const REQUIRED = ['Project name and URL', 'Suggested README section', 'Factual description',
'Codex integration evidence', 'External usage evidence', 'Your relationship to the project'];

Expand Down Expand Up @@ -62,24 +61,10 @@ function inspect(body, readme) {
return { evidence, changes, repository };
}

// Thresholds match contributing.md. A submission below them is labelled for manual review, never closed by the bot.
function criteria({ stars, ageDays }) {
// Threshold matches contributing.md. Below it the submission is labelled for manual review, never closed by the bot.
function criteria({ stars }) {
if (stars >= MIN_STARS) return { met: true, reason: `${stars} stars >= ${MIN_STARS}` };
if (ageDays !== null && ageDays >= MIN_AGE_DAYS) return { met: true, reason: `${ageDays.toFixed(1)} days >= ${MIN_AGE_DAYS}` };
const age = ageDays === null ? 'age unknown' : `${ageDays.toFixed(1)} days`;
return { met: false, reason: `${stars} stars < ${MIN_STARS} and ${age} < ${MIN_AGE_DAYS} days` };
}

// Age is measured from the oldest commit: a repository's creation date can be later than its history.
async function firstCommitAgeDays(github, { owner, repo }, branch) {
const first = await github.rest.repos.listCommits({ owner, repo, sha: branch, per_page: 1 });
if (!first.data.length) return null;
const last = (first.headers.link || '').match(/[?&]page=(\d+)>;\s*rel="last"/);
const oldest = last
? (await github.rest.repos.listCommits({ owner, repo, sha: branch, per_page: 1, page: Number(last[1]) })).data[0]
: first.data[0];
const ageDays = (Date.now() - new Date(oldest.commit.committer.date).getTime()) / 86400000;
return Number.isFinite(ageDays) ? ageDays : null;
return { met: false, reason: `${stars} stars < ${MIN_STARS}` };
}

async function run({ github, context }) {
Expand Down Expand Up @@ -107,18 +92,14 @@ async function run({ github, context }) {
const result = inspect(issue.body || '', fs.readFileSync('README.md', 'utf8'));
const notes = [];
let manual = false;
let stars = null, archived = null, ageDays = null, meets = null;
let stars = null, archived = null, meets = null;
if (result.repository) {
try {
const { data: target } = await github.rest.repos.get(result.repository);
stars = target.stargazers_count;
archived = target.archived;
if (target.archived) { manual = true; notes.push('Archived project: maintainer review required.'); }
if (stars < MIN_STARS) {
try { ageDays = await firstCommitAgeDays(github, result.repository, target.default_branch); }
catch { notes.push('Commit history could not be read; age not checked.'); }
}
const verdict = criteria({ stars, ageDays });
const verdict = criteria({ stars });
meets = verdict.met;
if (!verdict.met) { manual = true; notes.push(`Below inclusion threshold: ${verdict.reason}. Maintainer decides.`); }
} catch {
Expand All @@ -139,12 +120,11 @@ async function run({ github, context }) {
await github.rest.issues.addLabels({ ...repo, issue_number, labels });
const problems = [...result.evidence, ...result.changes, ...notes];
const statusLabels = labels.filter(name => name !== 'resource-submission');
const summary = { status: statusLabels, stars, archived, ageDays: ageDays === null ? null : Math.round(ageDays), meetsCriteria: meets, problems };
const summary = { status: statusLabels, stars, archived, meetsCriteria: meets, problems };
const body = `${MARKER}\n## Submission checks\n\n` +
`**Status:** ${statusLabels.map(name => `\`${name}\``).join(' ')}\n\n` +
'| Item | Value |\n|---|---|\n' +
`| Stars | ${stars ?? 'unknown'} |\n` +
`| Age | ${ageDays === null ? (stars >= MIN_STARS ? 'not needed' : 'unknown') : `${Math.round(ageDays)} days`} |\n` +
`| Archived | ${archived === null ? 'unknown' : archived ? 'yes' : 'no'} |\n` +
`| Meets criteria | ${meets === null ? 'unknown' : meets ? 'yes' : 'no'} |\n` +
`| Problems | ${problems.length} |\n\n` +
Expand Down
11 changes: 5 additions & 6 deletions tests/test-submission.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,10 @@ test('requires evidence links, missing fields and checked checklist', () => {
test('does not treat low stars or age as a local rejection rule', () => {
assert.deepEqual(inspect(body, '').changes, []);
});
test('meets criteria at 100 stars or 14 days old, otherwise reports both numbers', () => {
assert.equal(criteria({ stars: 100, ageDays: null }).met, true);
assert.equal(criteria({ stars: 52, ageDays: 14 }).met, true);
assert.equal(criteria({ stars: 52, ageDays: 13.9 }).met, false);
const below = criteria({ stars: 3, ageDays: null });
test('meets criteria at 100 stars; age alone does not qualify', () => {
assert.equal(criteria({ stars: 100 }).met, true);
assert.equal(criteria({ stars: 99 }).met, false);
const below = criteria({ stars: 3 });
assert.equal(below.met, false);
assert.match(below.reason, /3 stars < 100 and age unknown < 14 days/);
assert.match(below.reason, /3 stars < 100/);
});
Loading