diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 43f51c1..91a9b2f 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -11,7 +11,7 @@ "source": "./plugins/probitas", "description": "Probitas scenario testing framework integration", "category": "testing", - "homepage": "https://probitas-test.github.io/documents/" + "homepage": "https://probitas-test.github.io/" } ] } diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md deleted file mode 100644 index 9e9af3f..0000000 --- a/.claude/CLAUDE.md +++ /dev/null @@ -1,36 +0,0 @@ -# Probitas Claude Plugins Marketplace - -Claude Code plugin marketplace for Probitas ecosystem. - -## Structure - -``` -claude-plugins/ -├── .claude-plugin/ -│ └── marketplace.json # Marketplace definition -├── plugins/ -│ └── probitas/ # Probitas plugin -│ ├── .claude-plugin/ -│ ├── rules/ -│ ├── skills/ -│ ├── commands/ -│ └── agents/ -└── .claude/ # This repo's Claude config -``` - -## Commands - -```bash -# No build step - this is a pure Markdown/JSON repository -``` - -## Documentation Source - -Plugin content is based on Probitas documentation: - -- Site: https://probitas-test.github.io/documents/ -- LLM sitemap: https://probitas-test.github.io/documents/llms.txt - -## Updating Plugin Content - -Use `/doc:update` to sync plugin content with latest Probitas documentation. diff --git a/.claude/commands/doc/update.md b/.claude/commands/doc/update.md index f3cb08f..a0e7eb8 100644 --- a/.claude/commands/doc/update.md +++ b/.claude/commands/doc/update.md @@ -8,18 +8,17 @@ description: Sync plugin content with latest Probitas documentation Use WebFetch to get the latest docs: - - https://probitas-test.github.io/documents/llms.txt - - https://probitas-test.github.io/documents/docs/scenario/index.md - - https://probitas-test.github.io/documents/docs/client/index.md - - https://probitas-test.github.io/documents/docs/expect/index.md - - https://probitas-test.github.io/documents/docs/configuration/index.md + - https://probitas-test.github.io/llms.txt + - https://probitas-test.github.io/docs/scenario/index.md + - https://probitas-test.github.io/docs/client/index.md + - https://probitas-test.github.io/docs/expect/index.md + - https://probitas-test.github.io/docs/configuration/index.md 2. **Review and update plugin files** Compare fetched docs with current plugin content: - - `plugins/probitas/rules/*.md` - Conventions and guidelines - - `plugins/probitas/skills/probitas/SKILL.md` - Patterns and quick reference + - `plugins/probitas/skills/*/SKILL.md` - Patterns and quick reference - `plugins/probitas/commands/*.md` - Slash command definitions - `plugins/probitas/agents/*.md` - Agent definitions diff --git a/.claude/rules/adr.md b/.claude/rules/adr.md new file mode 100644 index 0000000..e4a69b4 --- /dev/null +++ b/.claude/rules/adr.md @@ -0,0 +1,19 @@ +--- +paths: "docs/adr/**" +--- + +# Writing an ADR + +Before writing, fixing or reviewing a record in `docs/adr/`, read +`docs/adr/README.md` and follow "How to write one" step by step. It is the +whole rule; this file only makes sure it is read at the moment of writing. + +The three steps people skip: + +- Write the title first, as one sentence. It is the decision; everything else + restates it or follows from it. +- Ask of every line: would changing this take a new record that supersedes + this one? If an ordinary change would do, the line is specification — leave + it out. +- When fixing a record, rewrite it from the title. Do not edit the previous + draft. diff --git a/.claude/rules/formatting.md b/.claude/rules/formatting.md index 5304d33..5df7083 100644 --- a/.claude/rules/formatting.md +++ b/.claude/rules/formatting.md @@ -4,9 +4,10 @@ paths: "**/*.md, **/*.json" # Formatting Rules -After modifying `*.md` or `*.json` files, run `deno fmt` to ensure consistent -formatting. +After modifying `*.md` or `*.json` files, run `just fmt` (which runs +`deno fmt`) to ensure consistent formatting. `just ci` fails on unformatted +files. ```bash -deno fmt +just fmt ``` diff --git a/.claude/rules/probitas-research.md b/.claude/rules/probitas-research.md index dfca3ae..8a49901 100644 --- a/.claude/rules/probitas-research.md +++ b/.claude/rules/probitas-research.md @@ -20,7 +20,7 @@ Check `plugins/probitas/` for existing documentation: If plugin resources don't cover the question: -- **LLM Sitemap**: https://probitas-test.github.io/documents/llms.txt +- **LLM Sitemap**: https://probitas-test.github.io/llms.txt - Comprehensive documentation optimized for AI consumption - Use WebFetch to retrieve specific sections diff --git a/.claude/rules/spec.md b/.claude/rules/spec.md new file mode 100644 index 0000000..c8c6fc1 --- /dev/null +++ b/.claude/rules/spec.md @@ -0,0 +1,21 @@ +--- +paths: "docs/spec/**" +--- + +# Writing a spec + +Before writing, fixing or reviewing a file in `docs/spec/`, read +`docs/spec/README.md` and follow "How to write one". It is the whole rule; +this file only makes sure it is read at the moment of writing. + +The checks people skip: + +- Ask of every line: can a test pin this down? Then it belongs in a test. + Would a user call a change to it a change in behaviour? If not, it is not + spec. +- Numbered steps, mapping tables, other programs' file locations, exact + strings and ordering rules are candidates to take out — unless they + describe a format this project defines for others to write. +- A review fix that does not change what a user sees does not touch the spec. +- Do not copy the level of detail of neighbouring specs; they may be too + detailed themselves. diff --git a/.envrc.sample b/.envrc.sample index 3550a30..11af9c3 100644 --- a/.envrc.sample +++ b/.envrc.sample @@ -1 +1,2 @@ +# Copy to .envrc and run `direnv allow` to enter the Nix dev shell automatically. use flake diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ca5e98b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# Check text out with LF on every OS, so a Windows checkout builds and tests +# the same bytes as the others. +* text=auto eol=lf diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 0418d03..0579432 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -6,16 +6,37 @@ on: pull_request: branches: [main] +# A pull request only needs its latest commit checked. A run on main waits +# for the one before it instead of cancelling it, so each one finishes and +# saves the caches that pull requests start from. +concurrency: + group: check-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read + jobs: - fmt: - name: Format + ci: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - - uses: denoland/setup-deno@v2 + - uses: actions/checkout@v7 with: - deno-version: v2.x - - - name: Check formatting - run: deno fmt --check + persist-credentials: false + - uses: cachix/install-nix-action@v31 + with: + extra_nix_config: | + experimental-features = nix-command flakes + # The store the development shell comes from. Only an exact match is + # restored, and only main saves, so pull requests cannot evict the + # caches each other start from. + - uses: nix-community/cache-nix-action@v7 + with: + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('flake.nix', 'flake.lock') }} + save: ${{ github.ref == 'refs/heads/main' }} + # Load the development shell (flake.nix) into the job. + - uses: nicknovitski/nix-develop@v1 + # The same gate as local development (justfile). + - run: just ci + env: + CI: "1" diff --git a/.gitignore b/.gitignore index e07aa5b..01bdfb6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ .DS_Store .coverage +/.envrc +.direnv/ +result +/.claude/worktrees/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..fc46fdb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,74 @@ +# probitas-test Claude Plugins + +Official Claude Code plugin marketplace for +[Probitas](https://probitas-test.github.io/) ecosystem. + +There is no build step: the plugins are Markdown and JSON. + +## How it is built + +- **Each layer holds one thing.** Put a fact in the one layer that owns it, + and nowhere else. + + | Layer | Holds | + | -------------- | ---------------------------------------------------- | + | `docs/adr/` | one decision and why it was made | + | `docs/spec/` | the behaviour users and other components can rely on | + | Plugin content | the skills, commands and agents the plugins ship | + | Commits | why a change was made | + + Before writing, fixing or reviewing a record, read `docs/adr/README.md`; + before writing, fixing or reviewing a spec, read `docs/spec/README.md`. + Follow their writing order. +- **Dependencies are current.** Before adding or bumping one, look up its + latest release in the registry. Do not copy a version from memory or from + another repository. + +When editing anything under `plugins/`, follow +[`.claude/rules/plugin-maintenance.md`](.claude/rules/plugin-maintenance.md). +For questions about Probitas itself, follow +[`.claude/rules/probitas-research.md`](.claude/rules/probitas-research.md). + +## Quality gate + +Run `just ci` before reporting a change as done. CI runs the same recipe. It +checks the formatting of Markdown, JSON and Nix files and that every plugin +manifest parses; `just fmt` fixes the formatting. + +## Layout + +| Path | What lives there | +| --------------------------------- | ------------------------------------------------------------------------- | +| `.claude-plugin/marketplace.json` | the marketplace definition | +| `plugins/probitas/` | the Probitas plugin: `.claude-plugin/`, `skills/`, `commands/`, `agents/` | +| `docs/` | ADRs and specs | +| `.claude/` | this repository's own rules and commands | +| `.github/` | CI | + +## Documentation source + +Plugin content is based on the Probitas documentation: + +- Site: https://probitas-test.github.io/ +- LLM sitemap: https://probitas-test.github.io/llms.txt + +Use `/doc:update` to sync the plugin content with the latest documentation. + +## Language + +- README, docs and commit messages: English +- Plugin content and code comments: English + +## Commits + +[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), as +described in [`.gitmessage`](.gitmessage) (install it with +`git config commit.template .gitmessage`): + +``` +[optional scope]: +``` + +- Types: `feat`, `fix`, `deps`, `refactor`, `test`, `docs`, `style`, `chore`. +- Scopes name the area touched, such as `probitas` (the plugin) or + `marketplace`. diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..36c1019 --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,80 @@ +# Architecture Decision Records + +The significant decisions behind claude-plugins, one decision per file. + +## Conventions + +- Files are named `YYYYMMDD-.md` after the day the decision was made, so + listing the directory in name order lists the decisions in time order. Numbers + and an index are avoided because parallel changes would collide on them. +- The slug is the title in kebab case, so a file name and its title always lead + to each other. +- Each record has a status and the sections Context, Decision, Alternatives + considered and Consequences. +- An accepted record is not rewritten. When the decision changes, a new record + supersedes it, and both say so in their status. +- Records refer to each other by file name. A record does not link to the + specification, which keeps changing; the decision is complete within the + record. + +## What belongs in a record + +A record holds one decision, not everything decided while doing the work. + +For every line, ask: **if this changed, would it take a new record that +supersedes this one?** + +- Yes — it is part of the decision. Keep it. +- No, an ordinary change to the code or the specification would do — it is + specification or implementation. Leave it out. + +Formats, file locations, encodings, UI details and the order of steps almost +always answer no. + +## How to write one + +Write in this order, and do not judge details line by line along the way: + +1. **Title** — the decision as one sentence. This sentence is the decision. +2. **Decision** — restate the title, with only what the replacement test above + keeps. +3. **Alternatives considered** — only options that would have replaced the + title's decision, each with why it was not taken. +4. **Consequences** — only what follows directly from the decision. +5. **Context** — only what a reader needs to see why the title was the answer. + +When a record under review needs fixing, rewrite it from the title without +looking at the previous draft. Editing the draft keeps the details the new +decision no longer needs. + +Before committing, apply the replacement test to every line once more. + +## Example + +This is the size to aim for. + +```markdown +# Keep the user's words in a dictionary of their own + +Status: Accepted + +## Context + +Words the user registers were appended to the bundled dictionary. Every update +replaces the bundled dictionary, so the user's words were lost. + +## Decision + +The user's words live in a separate dictionary that updates never touch. + +## Alternatives considered + +- Merge the user's words back into the bundled dictionary after each update. + A failed merge still loses them, and the two sources become impossible to + tell apart. + +## Consequences + +- Updates can replace the bundled dictionary wholesale. +- Lookups consult two dictionaries. +``` diff --git a/docs/spec/README.md b/docs/spec/README.md new file mode 100644 index 0000000..63ea3e9 --- /dev/null +++ b/docs/spec/README.md @@ -0,0 +1,60 @@ +# Specification + +What claude-plugins promises to whoever uses it, one topic per file. + +## Who reads it + +Users of claude-plugins, and authors of other programs that work with it. A spec +holds what they can rely on, not everything that is true of the code. + +## What belongs in a spec + +Each layer holds one thing: + +| Layer | Holds | +| ----- | ------------------------------------------------------------------ | +| ADR | one decision and why it was made | +| Spec | the behaviour users and other components can rely on | +| Tests | the details that pin the behaviour down: rules, boundaries, tables | +| Code | how it is done | + +Tests hold the what in detail, so a spec is narrower than the tests. What +stays in a spec is what tests express poorly: the flow a user goes through, a +promise such as "existing data is never rewritten", the scope of what is +handled. + +For every line, ask: + +1. **Can a test pin this down (and should one)?** Then it goes in a test, not + here. +2. **Would a change to this be called a change in behaviour by a user?** If + not, it is not spec. + +These usually fail the questions; when one appears, take it out: + +- numbered steps (an algorithm) +- mapping tables +- where another program keeps its files +- exact strings: headers, file names +- ordering rules, screen layout + +### The exception: formats claude-plugins defines + +A format that claude-plugins defines and that others write — its own data files, +its configuration — is read by outside authors, so the spec describes it in +full. A format someone else defines (another program's files) is not +claude-plugins's to promise; reading it is left to the code and the tests. + +## How to write one + +1. Write first, briefly, what claude-plugins promises. Leave the details out. +2. Write the details as test names. Tests named as sentences make the list of + tests a readable specification. +3. When review leads to a fix, do not touch the spec unless the behaviour a + user sees changes. + +Before committing, ask both questions of every line once more. + +A spec is rewritten in place, in the same change as the behaviour it +describes. It never carries "to be updated" notes, and it does not say why — +the reasons live in `docs/adr/`. diff --git a/flake.lock b/flake.lock index b2ee991..b308072 100644 --- a/flake.lock +++ b/flake.lock @@ -36,9 +36,7 @@ }, "probitas-flake": { "inputs": { - "flake-utils": [ - "flake-utils" - ], + "flake-utils": "flake-utils", "nixpkgs": [ "nixpkgs" ] @@ -59,7 +57,6 @@ }, "root": { "inputs": { - "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", "probitas-flake": "probitas-flake" } diff --git a/flake.nix b/flake.nix index 7950d9e..1fd3b3b 100644 --- a/flake.nix +++ b/flake.nix @@ -3,32 +3,57 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - flake-utils.url = "github:numtide/flake-utils"; probitas-flake.url = "github:probitas-test/probitas"; probitas-flake.inputs.nixpkgs.follows = "nixpkgs"; - probitas-flake.inputs.flake-utils.follows = "flake-utils"; }; - outputs = { self, nixpkgs, flake-utils, probitas-flake }: - flake-utils.lib.eachDefaultSystem (system: - let - pkgs = import nixpkgs { + outputs = + { + self, + nixpkgs, + probitas-flake, + }: + let + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); + pkgsFor = + system: + import nixpkgs { inherit system; overlays = [ probitas-flake.overlays.default ]; }; - in { - packages.default = pkgs.probitas; + in + { + devShells = forAllSystems ( + system: + let + pkgs = pkgsFor system; + in + { + default = pkgs.mkShell { + packages = [ + # Formats the Markdown and JSON of the plugins. + pkgs.deno + # Lets the plugin's commands be tried against real scenarios. + pkgs.probitas + # Checks that the plugin manifests parse. + pkgs.jq + pkgs.just + pkgs.nixfmt + ]; - devShells.default = pkgs.mkShell { - packages = with pkgs; [ - deno - pagefind - probitas - ]; + shellHook = '' + export DENO_NO_UPDATE_CHECK=1 + ''; + }; + } + ); - shellHook = '' - export DENO_NO_UPDATE_CHECK=1 - ''; - }; - }); + formatter = forAllSystems (system: nixpkgs.legacyPackages.${system}.nixfmt); + }; } diff --git a/justfile b/justfile new file mode 100644 index 0000000..a27de12 --- /dev/null +++ b/justfile @@ -0,0 +1,24 @@ +# Run `nix develop` (or use direnv) first so every tool is on PATH. + +set shell := ["bash", "-euo", "pipefail", "-c"] + +# List the recipes. +default: + @just --list + +# Format the Markdown, JSON and Nix files. +fmt: + deno fmt + git ls-files -z '*.nix' | xargs -0 nixfmt + +# Check formatting without changing files. +fmt-check: + deno fmt --check + git ls-files -z '*.nix' | xargs -0 nixfmt --check + +# Check that the marketplace and every plugin manifest parse as JSON. +check-manifests: + jq empty .claude-plugin/marketplace.json plugins/*/.claude-plugin/plugin.json + +# Everything CI runs. +ci: fmt-check check-manifests diff --git a/plugins/probitas/.claude-plugin/plugin.json b/plugins/probitas/.claude-plugin/plugin.json index f19463d..a45c057 100644 --- a/plugins/probitas/.claude-plugin/plugin.json +++ b/plugins/probitas/.claude-plugin/plugin.json @@ -5,5 +5,8 @@ "author": { "name": "probitas-test", "email": "lambdalisue+probitas@gmail.com" - } + }, + "homepage": "https://github.com/probitas-test/claude-plugins", + "repository": "https://github.com/probitas-test/claude-plugins", + "license": "MIT" }