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
9 changes: 5 additions & 4 deletions .agent/skills/sync-oss-spec/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ This skill is complementary to `update-spec`. `update-spec` propagates a spec ed
BASELINE=$(cat .agent/skills/sync-oss-spec/.last-updated)
```

2. Check whether `OSS_SPEC.md` or `src/validate.rs` changed since the baseline — those are the two inputs that can invalidate previously-passing conformance:
2. Check whether `OSS_SPEC.md` or `src/validate/` changed since the baseline — those are the two inputs that can invalidate previously-passing conformance:

```sh
git log --oneline "$BASELINE"..HEAD -- OSS_SPEC.md src/validate.rs
git log --oneline "$BASELINE"..HEAD -- OSS_SPEC.md src/validate/
git diff --name-only "$BASELINE"..HEAD
```

Expand All @@ -50,14 +50,15 @@ This skill is complementary to `update-spec`. `update-spec` propagates a spec ed
| §8.4 missing `CHANGELOG.md` | Create an empty Keep-a-Changelog-formatted file; do **not** hand-author entries |
| §9 Makefile target missing | Add the missing target to `Makefile` and verify it runs end-to-end |
| §10.1/§10.3/§10.4 missing workflow | Create `.github/workflows/<file>.yml`; cross-reference `templates/_common/.github/workflows/` for the canonical template |
| §10.3 floating or under-pinned toolchain | Edit the workflow to pin at or above the spec minimums in `MIN_TOOLCHAIN_VERSIONS` (`validate.rs`) |
| §10.3 floating or under-pinned toolchain | Edit the workflow to pin at or above the spec minimums in `MIN_TOOLCHAIN_VERSIONS` (`src/validate/toolchain.rs`) |
| §11.1 missing `docs/` content | Create the topic file, then run `update-docs` |
| §11.2 website drift | Run `make website` and inspect `website/src/generated/`; follow up with `update-website` |
| §13.5 `prompts/<name>/` has no versioned file | Add `prompts/<name>/1_0_0.md` with the required YAML front matter (`name`, `description`, `version: 1.0.0`) and `## System` / `## User` sections |
| §15 missing issue / PR templates | Create the templates under `.github/ISSUE_TEMPLATE/` or `.github/PULL_REQUEST_TEMPLATE.md` |
| §19 raw print statement outside `src/output.rs` | Route the call through `output::status` / `output::info` / `output::warn` / `output::error` |
| §20 inline `#[cfg(test)] mod { … }` block in `src/` | Move the tests to `tests/<module>_test.rs` and replace with `#[cfg(test)] mod <name>_test;` or delete the gate |
| §20.2 test file stem does not end with `_test(s)` / `Test(s)` | Rename the file so the stem matches the regex `_?[Tt]ests?$` |
| §20.5 source file exceeds 1000 lines | **Preferred:** split the file by concern into sibling modules / helpers (see `src/validate/` for the canonical example — one submodule per coherent group of checks). **Common easy case:** if the file also has a §20 inline-test violation, extracting the test block to `tests/<stem>_test.<ext>` usually resolves both at once. **Escape hatch:** if the size is genuinely justified (generated code, cohesive state machine, third-party snapshot), add `oss-spec:allow-large-file: <reason>` in any comment within the file's first 20 lines — the reason must be non-empty. |
| §21.2 `.claude/skills` is not a symlink | Replace it with `ln -s ../.agent/skills .claude/skills` |
| §21.3 SKILL.md missing front matter fields | Add `name:` / `description:` to the front matter |
| §21.4 missing `.last-updated` | Touch the file and record the current `HEAD`: `git rev-parse HEAD > .agent/skills/<skill>/.last-updated` |
Expand All @@ -66,7 +67,7 @@ This skill is complementary to `update-spec`. `update-spec` propagates a spec ed

## Update checklist

- [ ] Read the baseline from `.last-updated` and diff `OSS_SPEC.md` / `src/validate.rs`
- [ ] Read the baseline from `.last-updated` and diff `OSS_SPEC.md` / `src/validate/`
- [ ] Run `cargo run -q -- validate --no-ai` and record every structural violation
- [ ] Run `cargo run -q -- validate .` and record every AI finding worth acting on
- [ ] Walk the mapping table and fix each violation at its source
Expand Down
7 changes: 4 additions & 3 deletions .agent/skills/update-spec/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ description: "Use when OSS_SPEC.md has been edited. Bumps the spec version field
| New required root file | `src/validate.rs` (`required_files`), `templates/_common/<file>`, `README.md`, tests |
| New required directory | `src/validate.rs` (`required_dirs`), `templates/_common/<dir>/`, tests |
| New required symlink | `src/validate.rs` (`symlinks`), `src/bootstrap.rs::create_agents_symlinks` (if rooted at AGENTS.md), tests |
| New §19 content rule | `src/validate.rs` (new validator fn), `tests/validate_test.rs`, `src/fix.rs` if auto-fixable |
| New required workflow | `src/validate.rs` (`required_workflows`), `templates/_common/.github/workflows/<file>.tmpl`, tests |
| New required agent skill | `src/validate.rs::check_agent_skills`, `templates/_common/.agent/skills/<name>/`, tests |
| New §19 content rule | `src/validate/content.rs` (or appropriate submodule), `tests/validate_test.rs`, `src/fix.rs` if auto-fixable |
| New §20.5-style size/shape rule with opt-in exception marker | `src/validate/content.rs` (walker + marker regex), `tests/validate_test.rs`, `prompts/fix-conformance/<next>.md` (scope of auto-fix), `templates/_common/AGENTS.md.tmpl` (contributor guidance) |
| New required workflow | `src/validate/structural.rs` (`required_workflows`), `templates/_common/.github/workflows/<file>.tmpl`, tests |
| New required agent skill | `src/validate/agent_skills.rs::check`, `templates/_common/.agent/skills/<name>/`, tests |
| Spec-wide guidance touching AGENTS.md | `templates/_common/AGENTS.md.tmpl`, this repo's `AGENTS.md` |
| README-visible change | `README.md` (run `update-readme` afterwards) |
| Docs-visible change | `docs/` (run `update-docs` afterwards) |
Expand Down
16 changes: 11 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ src/
├── embedded.rs # include_dir!("templates")
├── bootstrap.rs # walks embedded tree → writes target dir
├── git.rs # git init / gh repo create wrappers
├── validate.rs # §19 conformance validator
├── validate/ # §19 conformance validator (structural, content, toolchain, agent_skills)
├── fix.rs # zag-driven auto-fix agent
├── agent_help.rs # §12 CLI discoverability contract
└── output.rs # central logging + styled output (§19 logging)
Expand All @@ -61,7 +61,7 @@ Dependency direction is top-down: `main` → `lib` → `cli` → (`interview`, `
|---|---|
| New CLI flag / subcommand | `src/cli.rs` (clap) + `src/agent_help.rs` (commands table, COMMAND_SPECS, EXAMPLES) + `man/oss-spec.md` |
| New template file | `templates/_common/`, `templates/<lang>/`, or `templates/cli/` |
| New §19 conformance rule | `src/validate.rs` |
| New §19 conformance rule | `src/validate/` (structural checks in `structural.rs`, content checks in `content.rs`, toolchain in `toolchain.rs`, agent skills in `agent_skills.rs`) |
| New auto-fix behavior | `src/fix.rs` (zag agent orchestration) |
| New AI-driven step | `src/ai.rs` (thin wrapper) + caller in `interview.rs` |
| New language overlay | `templates/<lang>/`, plus `Language` enum variant in `manifest.rs` |
Expand All @@ -76,15 +76,21 @@ Dependency direction is top-down: `main` → `lib` → `cli` → (`interview`, `
- Use `tempfile::tempdir()` for any test that writes files.
- Snapshot tests use `insta`. The self-conformance test runs `validate::run(".")` against this repo and must always pass.

## Source file size (§20.5)

- Non-test source files must stay under **1000 physical lines**. When a file crosses the limit, the fix is almost always to split by concern — see `src/validate/` for the canonical example (the validator was split into `mod`, `structural`, `content`, `agent_skills`, and `toolchain` for exactly this reason).
- A file may opt out with an `oss-spec:allow-large-file: <reason>` marker in any comment within its first 20 lines. The reason must be non-empty and genuinely justify the size — reviewers will push back on markers that just paper over a skippable refactor. Valid motivations: generated code, cohesive state machines, third-party snapshots, inherent rule-catalogue density.
- When `oss-spec fix` runs into a §20.5 violation, it only attempts the easy refactor (extracting an inline `#[cfg(test)]` block, which often resolves both §20 and §20.5 at once). Genuinely large source files are left for a human to split or annotate manually.

## Documentation sync points

When you change… | Update…
--- | ---
A CLI flag or subcommand | `man/oss-spec.md`, `docs/agent/help-agent.txt`, `agent_help::COMMANDS_TABLE`, `agent_help::COMMAND_SPECS`, `README.md` Usage table
A template file | `templates/_common/` (or overlay) — and re-run `oss-spec validate` against a generated demo
A §19 rule | `src/validate.rs`, `OSS_SPEC.md`, this `## Documentation sync points` table
A toolchain version bump (Rust / Python / Node / Go) | the repo-root pin file (`rust-toolchain.toml`, `.python-version`, `.nvmrc`, or `go.mod`'s `toolchain` directive), its `templates/<lang>/` counterpart, `templates/_common/.github/workflows/ci.yml.tmpl`, and `MIN_TOOLCHAIN_VERSIONS` in `src/validate.rs` (§10.5 local/CI parity, §10.3 minimums)
An LLM prompt's source of truth (spec text, validator rule, manifest enum, rendering-context key) | A new file under `prompts/<name>/<major>_<minor>_<patch>.md` (never edit an existing versioned file — bump semver and create a new one per §13.5). Run the `update-prompts` skill or let the `maintenance` sweep pick it up.
A §19 rule | `src/validate/` (appropriate submodule), `OSS_SPEC.md`, this `## Documentation sync points` table
A toolchain version bump (Rust / Python / Node / Go) | the repo-root pin file (`rust-toolchain.toml`, `.python-version`, `.nvmrc`, or `go.mod`'s `toolchain` directive), its `templates/<lang>/` counterpart, `templates/_common/.github/workflows/ci.yml.tmpl`, and `MIN_TOOLCHAIN_VERSIONS` in `src/validate/toolchain.rs` (§10.5 local/CI parity, §10.3 minimums)
An LLM prompt's source of truth (spec text, validator rule, manifest enum, rendering-context key) | A new file under `prompts/<name>/<major>_<minor>_<patch>.md` (never edit an existing versioned file — bump semver and create a new one per §13.5). Touch `src/prompts.rs` afterwards (e.g. `touch src/prompts.rs`) so the `include_dir!` proc-macro picks up the new embedded file on the next build. Run the `update-prompts` skill or let the `maintenance` sweep pick it up.
The list of supported languages | `manifest::Language`, `templates/<lang>/`, `Makefile.tmpl`, `ci.yml.tmpl`, `dependabot.yml.tmpl`
`OSS_SPEC.md` | Bump the `version` field in its YAML front matter (semver — `feat!`/breaking bumps major, `feat` or new mandate bumps minor, pure clarifications bump patch). Also update `README.md`, `docs/`, `templates/_common/AGENTS.md.tmpl`, and this file as needed. The spec is mirrored into generated projects via the symlink `templates/_common/OSS_SPEC.md -> ../../OSS_SPEC.md`, so there is only one source of truth.

Expand Down
89 changes: 87 additions & 2 deletions OSS_SPEC.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Open Source Project Bootstrap Specification
description: A prescriptive, language-agnostic specification for bootstrapping a new open source project with the licensing, documentation, automation, governance, and release plumbing that users and contributors expect from a well-run OSS codebase.
version: 2.2.0
version: 2.3.0
---

# Open Source Project Bootstrap Specification
Expand Down Expand Up @@ -1296,7 +1296,14 @@ outside the output module except for machine-readable output required by
a contract (e.g. §12 agent discoverability surfaces, which require plain
text on stdout with no ANSI escapes).

## 20. Test organization
## 20. Source and test organization

This section covers how source code is organized — both the separation
of tests from production source and the size of source files
themselves. The two rules reinforce each other: keeping tests in
dedicated files makes it easier to keep source files small, and the
size cap in §20.5 makes it harder for inline tests to accumulate
unnoticed.

Tests must live in **dedicated test files**, separate from the source
files they exercise. Inline test blocks embedded in production source
Expand Down Expand Up @@ -1373,6 +1380,84 @@ that tells agents and contributors:
- Any test-specific dependencies or setup (e.g. `tempfile` crate,
Docker containers, fixture files).

### 20.5 Source file size limits

No non-test source file may exceed **1000 physical lines** (raw
newline-delimited lines, as reported by `wc -l`). Test files — those
whose stem matches the §20.2 regex `_?[Tt]ests?$` — are exempt; their
size is governed by whatever the test subject requires.

The limit is a **size smell**, not a precise complexity metric.
Physical lines are deliberately chosen over SLOC or cyclomatic
complexity so the rule is trivial to measure, predictable for
contributors, and immune to language-specific comment conventions. A
file over 1000 lines is almost always doing too much: aggregating
unrelated responsibilities, hiding inline tests, or waiting to be
split by concern.

**Why this rule.** Three motivations converge here:

1. **Readability.** Files that fit in a single screenful of a human
reviewer's attention — or a single AI agent's working context —
get reviewed carefully. Files that exceed it get skimmed.
2. **Decomposition pressure.** A hard line cap pushes authors to
extract submodules, helpers, and sibling files before a large
concern calcifies into an unsplittable monolith.
3. **Teeth for §20.** The easiest way to blow the 1000-line limit is
to keep tests inline. §20.5 and §20 reinforce each other:
extracting inline test blocks to their own file is usually
sufficient to bring a large source file back under the cap.

#### 20.5.1 Exception mechanism

A file may declare itself exempt by carrying an **allow-large-file
marker** in any comment within its **first 20 lines**:

```
oss-spec:allow-large-file: <reason>
```

The marker's comment syntax follows the host language (`//` for
C-family, `#` for Python/Ruby/shell, `--` for SQL/Haskell, etc.) —
only the literal `oss-spec:allow-large-file:` token and the reason
are checked. The reason **must be non-empty**: a marker with no
motivation does not exempt the file. Validators must reject
`oss-spec:allow-large-file:` followed only by whitespace.

Exceptions are expected to be **rare and per-file**, not a project-
wide dial. Legitimate reasons include:

- **Generated code** — a file produced by a build step (protobuf,
OpenAPI bindings, parser tables) that is not meant to be edited by
hand.
- **Cohesive state machines** — a single enum or match tree whose
arms cannot be meaningfully split without obscuring the design.
- **Third-party snapshots** — vendored code checked in verbatim.
- **Inherent density** — a configuration schema, rule catalogue, or
lookup table that only grows linearly with real-world coverage.

Reviewers should treat an added or edited marker the same as any
other code change: ask whether the reason is honest, whether the
file has since become splittable, and whether the alternative (a
mechanical split) is genuinely worse than leaving the file oversized.

#### 20.5.2 Auto-fix scope

When `oss-spec fix` (or an equivalent automated refactor) encounters
a §20.5 violation, it must only attempt an **easy** refactor:
extracting inline test blocks (a §20 violation that commonly
co-occurs with §20.5) into a separate file under `tests/`. In
practice, doing so resolves both findings at once on files whose
bulk came from tests.

Automated refactors of **genuinely large source files** — splitting
modules, extracting helpers, decomposing responsibilities — are out
of scope. They require design judgment the tooling cannot
responsibly make. When the auto-fixer sees a §20.5 violation on a
file without a companion §20 violation, it must leave the file
alone and surface the finding for a human to either split manually
or annotate with an `oss-spec:allow-large-file:` marker.

## 21. Agent skills — maintenance playbooks for drift-prone artifacts

### 21.1 Motivation
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![release](https://github.com/niclaslindstedt/oss-spec/actions/workflows/release.yml/badge.svg)](https://github.com/niclaslindstedt/oss-spec/actions/workflows/release.yml)
[![pages](https://github.com/niclaslindstedt/oss-spec/actions/workflows/pages.yml/badge.svg)](https://github.com/niclaslindstedt/oss-spec/actions/workflows/pages.yml)
[![crates](https://img.shields.io/crates/v/oss-spec.svg)](https://crates.io/crates/oss-spec)
[![spec](https://img.shields.io/badge/OSS__SPEC-v2.2.0-blueviolet)](OSS_SPEC.md)
[![spec](https://img.shields.io/badge/OSS__SPEC-v2.3.0-blueviolet)](OSS_SPEC.md)
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

## Why?
Expand Down
7 changes: 6 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ src/
├── embedded.rs # include_dir!("templates")
├── bootstrap.rs # walks embedded tree → writes target dir
├── git.rs # git init / gh repo create wrappers
├── validate.rs # §19 conformance validator
├── validate/ # §19 conformance validator
│ ├── mod.rs # Report/Violation types and orchestrator
│ ├── structural.rs # required files/dirs/symlinks/workflows
│ ├── content.rs # §19.4 output module, §20 inline tests, §20.5 file size
│ ├── agent_skills.rs# §21 .agent/skills/ tree and per-skill checks
│ └── toolchain.rs # §10.3/§10.5 pin-file and CI parity
├── fix.rs # zag-driven auto-fix agent
├── agent_help.rs # §12 CLI discoverability contract
└── output.rs # central logging + styled output (§19 logging)
Expand Down
Loading