Skip to content
Open
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
46 changes: 31 additions & 15 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,50 @@
<!-- All Jam&Sw pull requests follow the OpenSpec format. Every section below is required.
<!-- Jam&Sw pull requests.
Feature PRs (title `feat:` or branch `feat/`) must also fill the
"Feature PRs only" block at the bottom. For docs, chore, fix, ci, test,
refactor, style, build, and perf PRs, delete that block and keep just
Summary, Changes, and Verification.
See https://github.com/Jam-Sw/.github/blob/main/CONTRIBUTING.md -->

## Why
## Summary

<!-- Concise statement of the problem this PR solves and its impact. -->
<!-- One or two sentences on what ships and why. -->

## What Changes
## Changes

<!-- Bullet summary of the changes in this PR. -->
<!-- Bullet summary of the changes, noun-first and concrete. -->

-

## Verification

<!-- Checklist a reviewer can follow: a command or a user-visible behavior per line.
Feature PRs use hierarchical numbering (1.1, 1.2, ...) mapping onto the scenario. -->

- [ ] 1.1
- [ ] 1.2

<!-- ───────── Feature PRs only: delete this entire block for non-feature PRs ─────────

## Why

Concise statement of the problem this PR solves and its impact.

## What Changes

- Each bullet is one observable difference in behavior or structure.

## MODIFIED Requirements

<!-- State the new/changed behavior in RFC 2119 language (SHALL, MUST, SHOULD, MAY).
Include "(Previously: ...)" notes for changed behavior. -->
State the new or changed behavior in RFC 2119 language (SHALL, MUST, SHOULD, MAY).
Include "(Previously: ...)" notes for changed behavior. Use ADDED Requirements for
new behavior and REMOVED Requirements, with rationale, for removed behavior.

### Requirement:

## Scenario

<!-- Concrete, observable test case a reviewer can follow. -->

GIVEN
WHEN
THEN

## Verification

<!-- Numbered checklist (1.1, 1.2, ...) mapping onto the scenario. Check items off as you verify. -->

- [ ] 1.1
- [ ] 1.2
──────────────────────────────────────────────────────────────────────────── -->
44 changes: 34 additions & 10 deletions .github/workflows/validate-openspec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,37 @@ jobs:
return;
}
const body = target.body || '';
const title = (target.title || '').trim();
const branch = isPR ? ((context.payload.pull_request.head || {}).ref || '') : '';

const issue_number = target.number;
const { owner, repo } = context.repo;
const labels = (target.labels || []).map(l => typeof l === 'string' ? l : l.name);

async function clearNeedsInfo() {
if (labels.includes(label)) {
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: label })
.catch(e => core.warning(`Could not remove label: ${e.message}`));
}
}

// The OpenSpec proposal format only applies to changes that add or
// alter requirements, i.e. features. Docs, chore, fix, ci, test,
// refactor, style, build, and perf changes carry no requirement
// delta and are exempt, as are bug reports and other non-feature
// issues. A target is treated as a feature when its title uses the
// conventional `feat:` prefix or its branch starts with `feat/`.
const isFeature =
/^feat(\([^)]*\))?!?:/i.test(title) ||
/^feat\//i.test(branch) ||
labels.includes('feature') ||
labels.includes('enhancement');

if (!isFeature) {
await clearNeedsInfo();
core.info('Not a feature change; OpenSpec proposal format not required.');
return;
}

const required = [
{ name: 'Why', pattern: /^#{1,3}\s*Why\b|^\*{2}Why\*{2}/im },
Expand All @@ -41,28 +72,21 @@ jobs:
];

const missing = required.filter(r => !r.pattern.test(body)).map(r => r.name);
const issue_number = target.number;
const { owner, repo } = context.repo;

const labels = (target.labels || []).map(l => typeof l === 'string' ? l : l.name);

if (missing.length === 0) {
if (labels.includes(label)) {
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: label })
.catch(e => core.warning(`Could not remove label: ${e.message}`));
}
await clearNeedsInfo();
core.info('All OpenSpec sections present.');
return;
}

const comment = [
`### OpenSpec format check failed`,
``,
`This ${isPR ? 'pull request' : 'issue'} body is missing required OpenSpec sections:`,
`This feature ${isPR ? 'pull request' : 'issue'} is missing required OpenSpec sections:`,
``,
...missing.map(m => `- [ ] **${m}**`),
``,
`Please edit the body to include them. See the [contributing guide](https://github.com/Jam-Sw/.github/blob/main/CONTRIBUTING.md) for the format.`,
`Feature changes must propose the new behavior formally. Non-feature changes (docs, chore, fix, ci, test, refactor, style, build, perf) are exempt and need no spec sections. See the [contributing guide](https://github.com/Jam-Sw/.github/blob/main/CONTRIBUTING.md) for the format.`,
].join('\n');

// Forked-PR tokens are read-only; fall back to failing the check.
Expand Down
38 changes: 27 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
# Contributing to Jam&Sw

Every issue and pull request in the Jam&Sw organization follows the
**OpenSpec format**. It makes changes reviewable, testable, and honest: a
reader should be able to understand why a change exists, what the correct
behavior is, and exactly how to verify it: without reading the diff first.
Feature work in the Jam&Sw organization follows the **OpenSpec format**. It
makes changes reviewable, testable, and honest: a reader should be able to
understand why a change exists, what the correct behavior is, and exactly how
to verify it, without reading the diff first.

## When the OpenSpec format is required

The format applies to **feature changes**: anything that adds or alters a
requirement or user-visible behavior. A change is treated as a feature when
its title uses the conventional `feat:` prefix or its branch starts with
`feat/`.

Everything else is exempt and needs no spec sections: `docs:`, `chore:`,
`fix:`, `ci:`, `test:`, `refactor:`, `style:`, `build:`, and `perf:` changes,
along with bug reports and other non-feature issues. Write a plain Summary /
Changes / Verification body for those. Forcing requirement language and
GIVEN/WHEN/THEN scenarios onto a docs typo or a dependency bump adds noise,
not clarity.

## The OpenSpec format

Every issue and PR body contains these sections, in order:
Every **feature** issue and PR body contains these sections, in order:

### 1. Why

Expand Down Expand Up @@ -70,14 +84,16 @@ SHALL…", never "It should…").

## Enforcement

- Issue forms in this repository require every OpenSpec section, and blank
issues are disabled organization-wide.
- The reusable workflow
[`validate-openspec.yml`](.github/workflows/validate-openspec.yml) checks
issue and PR bodies for the required sections. Repositories opt in with the
two-line caller in [`examples/validate.yml`](examples/validate.yml).
- Bodies missing sections get a `needs-info` label and a comment listing
what's missing; the label is removed automatically once the body is fixed.
feature issue and PR bodies for the required sections. Repositories opt in
with the two-line caller in [`examples/validate.yml`](examples/validate.yml).
- The workflow only runs its checks on feature changes (title `feat:` or
branch `feat/`, or an issue labeled `feature`/`enhancement`). Non-feature
changes pass automatically.
- Feature bodies missing sections get a `needs-info` label and a comment
listing what's missing; the label is removed automatically once the body is
fixed, or once the change is reclassified as non-feature.

## Repository-local overrides

Expand Down