From a5242ad29bbc2a3f26e0965515f3541bbe6ca7f4 Mon Sep 17 00:00:00 2001 From: Dan Sabin Date: Sat, 17 Oct 2026 18:32:42 -0500 Subject: [PATCH] Move the plugin into plugins/freethinking and prepare the directory listing The plugin's manifest, skills, agents, rules, hooks and README move under plugins/freethinking/, with the marketplace file left at the root pointing at that folder, so that Anthropic's plugin directory scans only the plugin and not the eval suite or the spec records. The manifest gains the listing fields and the icon; the hook scripts take the shape the directory's validator follows; the Makefile builds a temporary run tree for every eval target because the runner reads cases only from inside the plugin root; the checks are re-rooted and a listing check is added; the README is split into the plugin's and the repository's. Spec: .claude/specs/directory-listing/. Co-Authored-By: Claude Fable 5.1 --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 9 - .claude/specs/directory-listing/design.md | 218 +++++++++++++++++ .../specs/directory-listing/requirements.md | 122 ++++++++++ .claude/specs/directory-listing/tasks.md | 74 ++++++ .github/CONTRIBUTING.md | 18 +- Makefile | 55 +++-- README.md | 220 +++++------------- checks/test_agents.py | 5 +- checks/test_descriptions.py | 5 +- checks/test_hook_encoder.py | 122 ++++++++-- checks/test_issue_forms.py | 9 +- checks/test_listing.py | 116 +++++++++ checks/test_report_skill.py | 3 +- checks/test_rules_size.py | 7 +- evals/README.md | 53 +++-- hooks/load-rules-subagent.sh | 52 ----- hooks/load-rules.sh | 19 -- plugins/freethinking/.claude-plugin/icon.png | Bin 0 -> 122489 bytes .../freethinking/.claude-plugin/plugin.json | 25 ++ plugins/freethinking/README.md | 152 ++++++++++++ .../freethinking/agents}/task-executor.md | 0 .../freethinking/agents}/wave-reviewer.md | 0 .../freethinking/hooks}/hooks.json | 0 .../freethinking/hooks/load-rules-subagent.sh | 53 +++++ plugins/freethinking/hooks/load-rules.sh | 28 +++ .../freethinking/rules}/code-changes.md | 0 .../freethinking/rules}/collaboration.md | 0 .../freethinking/rules}/comments.md | 0 .../freethinking/rules}/communication.md | 0 .../freethinking/rules}/workflow.md | 0 .../freethinking/skills}/bugfix/SKILL.md | 0 .../skills}/implement-spec/SKILL.md | 0 .../freethinking/skills}/pr/SKILL.md | 0 .../skills}/pr/pull_request_template.md | 0 .../freethinking/skills}/report/SKILL.md | 0 .../skills}/report/templates/answer.md | 0 .../skills}/report/templates/decision.md | 0 .../skills}/report/templates/handback.md | 0 .../skills}/report/templates/status.md | 0 .../freethinking/skills}/start-task/SKILL.md | 0 .../start-task/spec-template/design.md | 0 .../start-task/spec-template/requirements.md | 0 .../skills}/start-task/spec-template/tasks.md | 0 44 files changed, 1043 insertions(+), 324 deletions(-) delete mode 100644 .claude-plugin/plugin.json create mode 100644 .claude/specs/directory-listing/design.md create mode 100644 .claude/specs/directory-listing/requirements.md create mode 100644 .claude/specs/directory-listing/tasks.md create mode 100644 checks/test_listing.py delete mode 100755 hooks/load-rules-subagent.sh delete mode 100755 hooks/load-rules.sh create mode 100644 plugins/freethinking/.claude-plugin/icon.png create mode 100644 plugins/freethinking/.claude-plugin/plugin.json create mode 100644 plugins/freethinking/README.md rename {agents => plugins/freethinking/agents}/task-executor.md (100%) rename {agents => plugins/freethinking/agents}/wave-reviewer.md (100%) rename {hooks => plugins/freethinking/hooks}/hooks.json (100%) create mode 100755 plugins/freethinking/hooks/load-rules-subagent.sh create mode 100755 plugins/freethinking/hooks/load-rules.sh rename {rules => plugins/freethinking/rules}/code-changes.md (100%) rename {rules => plugins/freethinking/rules}/collaboration.md (100%) rename {rules => plugins/freethinking/rules}/comments.md (100%) rename {rules => plugins/freethinking/rules}/communication.md (100%) rename {rules => plugins/freethinking/rules}/workflow.md (100%) rename {skills => plugins/freethinking/skills}/bugfix/SKILL.md (100%) rename {skills => plugins/freethinking/skills}/implement-spec/SKILL.md (100%) rename {skills => plugins/freethinking/skills}/pr/SKILL.md (100%) rename {skills => plugins/freethinking/skills}/pr/pull_request_template.md (100%) rename {skills => plugins/freethinking/skills}/report/SKILL.md (100%) rename {skills => plugins/freethinking/skills}/report/templates/answer.md (100%) rename {skills => plugins/freethinking/skills}/report/templates/decision.md (100%) rename {skills => plugins/freethinking/skills}/report/templates/handback.md (100%) rename {skills => plugins/freethinking/skills}/report/templates/status.md (100%) rename {skills => plugins/freethinking/skills}/start-task/SKILL.md (100%) rename {skills => plugins/freethinking/skills}/start-task/spec-template/design.md (100%) rename {skills => plugins/freethinking/skills}/start-task/spec-template/requirements.md (100%) rename {skills => plugins/freethinking/skills}/start-task/spec-template/tasks.md (100%) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index f49fcbd..53e018a 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -7,7 +7,7 @@ "plugins": [ { "name": "freethinking", - "source": "./", + "source": "./plugins/freethinking", "description": "Shared engineering skills: standard workflows and prompts for day-to-day development." } ] diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json deleted file mode 100644 index af8f18d..0000000 --- a/.claude-plugin/plugin.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "freethinking", - "version": "0.11.0", - "description": "Shared engineering skills: standard workflows and prompts for day-to-day development.", - "author": { - "name": "Developer Handbook contributors" - }, - "license": "MIT" -} diff --git a/.claude/specs/directory-listing/design.md b/.claude/specs/directory-listing/design.md new file mode 100644 index 0000000..5917de6 --- /dev/null +++ b/.claude/specs/directory-listing/design.md @@ -0,0 +1,218 @@ +# Design Document + +## Overview + +The plugin's files move into `plugins/freethinking/` and everything else stays at the repository root, so the folder the Directory scans holds only what an installed plugin loads. Three things follow from that move. The eval runner only reads cases from inside the plugin root, so the Makefile builds a throwaway Run_Tree per invocation, a copy of `plugins/freethinking/` with a copy of `evals/` inside it, and writes results back to the root. The Directory's validator follows a subfolder plugin's hook scripts only when they use literal paths under `${CLAUDE_PLUGIN_ROOT}` and nothing else, so both scripts are rewritten to that shape, each reading `rules/*.md` straight into one `awk` program. The free checks, the Makefile's validate target and the four guides are re-pointed at the new paths, and the manifest gains the listing fields with the README gaining the Disclosure_Section. + +## Architecture + +```mermaid +flowchart LR + subgraph root["Repository root"] + MP[".claude-plugin/marketplace.json
source: ./plugins/freethinking"] + subgraph plugin["plugins/freethinking/ (scanned by the Directory, loaded by Claude Code)"] + MAN[".claude-plugin/plugin.json + icon.png"] + SK["skills/ agents/ rules/ hooks/"] + PR["README.md + Disclosure_Section"] + end + EV["evals/"] + CK["checks/"] + MK["Makefile"] + SP[".claude/specs/"] + end + MP --> plugin + MK -->|"validate"| CK + MK -->|"validate"| MAN + CK -->|"read"| SK + MK -->|"eval-*"| RT + subgraph RT["Run_Tree (temp, deleted after the run)"] + RP["copy of plugins/freethinking/"] + RE["copy of evals/ (no results/)"] + RG[".git (git init)"] + end + RT -->|"--output-dir"| RES["evals/results/<timestamp>/"] +``` + +```mermaid +sequenceDiagram + participant C as Contributor + participant M as make eval-skill-pr + participant T as Run_Tree + participant H as claude plugin eval + participant S as scaffold.sh (copy) + C->>M: make eval-skill-pr EVAL_ARGS="--runs 1" + M->>T: mktemp -d; copy plugins/freethinking/. and evals/ (minus results/); git init + M->>H: claude plugin eval --tag pr --output-dir /evals/results/ ... + H->>S: run scaffold in the case sandbox + S->>T: git rev-parse --show-toplevel = Run_Tree; bash $ROOT/evals/fixtures/setup.sh + H-->>M: table, exit status + M->>T: rm -rf (trap on EXIT, pass or fail) + M-->>C: exit status of claude plugin eval +``` + +Key decision, the plugin in `plugins/freethinking/` with the marketplace file left at the root. The Directory scans the folder holding the manifest and nothing above it, and a marketplace entry may point at any folder in the repository, so this is the one layout that hides `evals/`, `checks/` and `.claude/specs/` from the scan while keeping them in the repository beside the code they test and record (Requirements 1.1 to 1.4). The install id does not change, because neither name changes, so no `renames` entry is needed (Requirement 1.6). Rejected alternative, a second repository for the evals: the runner would still need the cases inside a plugin folder, so the copy would still be needed, and a case would no longer sit beside the skill it grades. Rejected alternative, `export-ignore` in `.gitattributes`: the Directory stops validation on it. Rejected alternative, a gitignored copy of `evals/` inside `plugins/freethinking/`: it leaves a transient tree in the source folder that `claude plugin validate` and `--plugin-dir` would see, and a stale copy would grade stale cases. + +Key decision, the Run_Tree is a plain copy with `git init`, plus the one repository file a scaffold reads, `.github/pull_request_template.md`. Probed on 2026-10-16: `--eval-dir ../evals` is refused ("must stay inside the plugin root"), an absolute path is refused, and a symbolic link to `evals/` outside the plugin is refused by name at run time, while a copy with a git repository initialised at its top ran a scaffolded case to a score of 1.00 with results written to the real tree through `--output-dir`. The `git init` is what lets the 23 scaffolds that locate `evals/fixtures/setup.sh` through `git rev-parse --show-toplevel` keep working unchanged (Requirement 5.3). The copy is about 350 small files; `evals/results/` is excluded so old results are never copied. Rejected alternative, scaffolds that locate the fixtures by a relative path from `$0`: 23 edits for no gain once the copy has a git top. Rejected alternative, `--output-dir` left at the default: results would land inside the Run_Tree and be deleted with it. + +Key decision, both hook scripts become one `awk` program each over `"${CLAUDE_PLUGIN_ROOT}"/rules/*.md`. The Validator_Shape forbids any other variable, any command substitution and any call to another file, so the session script's `$(dirname "$0")` fallback and loop variable go, and the subagent script stops running the session script and capturing its output (Requirement 4.1). `awk` reading the files directly replaces the sentinel trick, because the files are the input rather than a captured string: each record is emitted with `\n`, which reproduces every file that ends in a newline byte for byte and adds one to a file that does not, in both scripts alike, so the two outputs stay equal (Requirement 4.3). The two-line "Handbook rules" header is now written in both scripts, with a comment in each naming the other, which is the cost of not calling across files. The `awk` program itself is inline in the script, as it is today; the portal flagged only the script-runs-script step on the current layout, and Requirement 7 is where a hold on an inline `awk` would show. Rejected alternative, leaving the scripts as they are: one hold, "Scripts the validator couldn't follow", on every version. + +Key decision, `plugins//` rather than a single `plugin/` folder. The docs' marketplace layout keeps every plugin under `plugins/`, so a second plugin is a sibling folder and one more entry in the marketplace file, and nothing moves twice (Requirement 6.5). The Makefile and each check name the folder once, in `PLUGIN` and a top-of-file constant. What this design does not do is split the eval suite: the runner ties a suite to one plugin's eval directory, so a second plugin would need `evals//` and the Makefile copying the matching one; that is one edit when the day comes and nothing now. Rejected alternative, splitting `evals/` today: churn in 274 files and every eval target's enumeration for a plugin that does not exist. + +Key decision, two READMEs. The Directory shows the plugin's README as the listing's description, so `plugins/freethinking/README.md` keeps the user-facing sections and ends with the Disclosure_Section, while a short root README orients a contributor and takes the Testing and "Add or change a skill or rule" sections, which describe the repository's checks and evals rather than the installed plugin (Requirements 3.1 to 3.5). Rejected alternative, one README at the root with a link from `plugins/freethinking/`: the Directory requires a README of at least 40 words inside the plugin folder. + +## Components and Interfaces + +```mermaid +classDiagram + class Makefile { + +validate() + +check() + +eval-targets() + -run_tree(flags) + } + class load_rules_sh { + +run(CLAUDE_PLUGIN_ROOT) text + } + class load_rules_subagent_sh { + +run(CLAUDE_PLUGIN_ROOT) json + } + class Checks { + +test_hook_encoder + +test_rules_size + +test_agents / test_descriptions / test_report_skill / test_issue_forms + +test_eval_cases + } + Makefile --> Checks : make check + Checks --> load_rules_sh : runs + Checks --> load_rules_subagent_sh : runs +``` + +### Repository layout + +Moves files; no logic. + +| Operation | Input | Output | Requirements | +|-----------|-------|--------|--------------| +| `git mv` of `.claude-plugin/plugin.json`, `.claude-plugin/icon.png`, `skills/`, `agents/`, `rules/`, `hooks/`, `README.md` into `plugins/freethinking/` | the root tree | the Plugin_Folder, with `.claude-plugin/marketplace.json` left at the root | 1.1, 1.2, 1.3 | +| `marketplace.json` entry `source` | `"./"` | `"./plugins/freethinking"` | 1.4 | +| new root `README.md` | the moved README's Testing and "Add or change" sections | a root README that names the layout and links to `plugins/freethinking/README.md` and the contributing guide | 3.4 | + +### Manifest + +`plugins/freethinking/.claude-plugin/plugin.json`, fields in this order: `name`, `displayName`, `version`, `description`, `author`, `homepage`, `repository`, `license`, `icon`, `keywords`. Values as Requirement 2 gives them. `claude plugin validate ./plugins/freethinking --strict` is the check (Requirements 2.1 to 2.6). + +### Hook scripts + +| Script | Behaviour | Requirements | +|--------|-----------|--------------| +| `plugins/freethinking/hooks/load-rules.sh` | `[ -d "${CLAUDE_PLUGIN_ROOT:-}/rules" ] \|\| exit 0`; then one `awk` over `"${CLAUDE_PLUGIN_ROOT}"/rules/*.md` that prints the header, then every record with `\n`, a blank line before each file after the first and one after the last, so the text equals today's `cat` loop output | 4.1, 4.2, 4.4 | +| `plugins/freethinking/hooks/load-rules-subagent.sh` | `[ -d "${CLAUDE_PLUGIN_ROOT:-}/rules" ] \|\| { echo "load-rules-subagent.sh: no rules under CLAUDE_PLUGIN_ROOT, so this sub-agent gets no rules" >&2; exit 1; }`; then one `awk` over the same files that prints the JSON prefix, the header and every record JSON-escaped with `\\n`, the same blank-line rule, and the JSON suffix | 4.1, 4.3, 4.4 | + +Both scripts keep `set -eu` and `#!/bin/sh`. Neither names `$0`, `$1`, `$f`, `$dir`, `$hooks` or `$text`. The `awk` escape table is the one in today's subagent script. + +### Makefile + +| Variable or target | Definition | Requirements | +|--------------------|------------|--------------| +| `PLUGIN := plugins/freethinking` | the Plugin_Folder | 5.1, 6.2 | +| the results folder, set inside each recipe as `results=$$(pwd)/evals/results/$$(date +%Y-%m-%dT%H-%M-%S)` | one Results_Folder per eval recipe, made with `mkdir -p` before the run; computed in the recipe rather than in a make variable so two targets in one invocation get two folders and `make help` runs no `date` | 5.2 | +| `validate: check` | `claude plugin validate . --strict`, `claude plugin validate ./$(PLUGIN) --strict`, `claude plugin validate ./$(PLUGIN)/skills --strict` | 6.1, 6.2 | +| `RUN_EVAL` (a `define` the eval recipes expand) | `set -eu; results=$$(pwd)/evals/results/$$(date +%Y-%m-%dT%H-%M-%S); run=$$(mktemp -d "$${TMPDIR:-/tmp}/freethinking-eval.XXXXXX"); trap 'rm -rf "$$run"' EXIT; trap 'exit 130' HUP INT TERM; cp -R $(PLUGIN)/. "$$run"/; tar -C . --exclude=evals/results -cf "$$run/evals.tar" evals; tar -C "$$run" -xf "$$run/evals.tar"; rm "$$run/evals.tar"; mkdir -p "$$run/.github"; cp .github/pull_request_template.md "$$run/.github/"; git -C "$$run" init -q; mkdir -p "$$results"; claude plugin eval "$$run" --trust-plugin --ablation none --no-publish --max-cost-usd $(EVAL_MAX_COST_USD) --output-dir "$$results" $(EVAL_ARGS) $(CASE_ARGS) $(1)`. The copy of `evals/` is three commands rather than a pipeline, because under `/bin/sh` a pipeline hides the first `tar`'s failure from `set -e`. The pull request template is copied in because the `opens-pr-with-ticket` scaffold reads it from the repository top, the one file outside `evals/` any scaffold reads. The second `trap` turns a signal into an exit so the EXIT trap runs under `dash` too, where a signal alone would skip it | 5.1, 5.2, 5.3, 5.5 | +| `eval`, `eval-hook`, `eval-skill-%`, `eval-rule-%`, `eval-agent-%` | each recipe is `$(call RUN_EVAL,--tag $*)` or the `eval` form with no tag; names, enumeration, per-target flags and `help` unchanged | 5.4 | + +The recipe is one shell line, so `set -e` and the `trap` hold for the whole run and the exit status of `claude plugin eval` is the recipe's status. The `PATH := bin:$(PATH)` exports stay as they are: `bin` is relative to the case sandbox the harness creates, not to the repository. + +### Checks + +| File | Change | Requirements | +|------|--------|--------------| +| `test_agents.py`, `test_descriptions.py`, `test_report_skill.py`, `test_rules_size.py` | `PLUGIN = ROOT / "plugins" / "freethinking"` (or the `os.path.join` form), one constant at the top of each file, and every `skills`, `agents`, `rules`, `hooks` path under it; `test_rules_size.py` sets `CLAUDE_PLUGIN_ROOT` to the Plugin_Folder | 4.6, 6.1 | +| `test_issue_forms.py` | the skill, agent, rule and hook enumeration reads under `plugins/freethinking/`; `FORMS_DIR` stays at the root | 6.1 | +| `test_listing.py` (new) | reads the Manifest; asserts `README.md` in the Plugin_Folder with at least 40 words outside fenced blocks; asserts `LICENSE` in the Plugin_Folder or a `license` field; opens the file the `icon` field names, checks the 8-byte PNG signature and reads width and height from the IHDR chunk with `struct`, and asserts square and 512 to 2048 | 8.1, 8.2, 8.3, 8.4 | +| `test_eval_cases.py` | unchanged; `evals/` is still at the root | 5.6 | +| `test_hook_encoder.py` | `roundtrip(part, text)` writes a temp folder with `rules/a.md` holding `text`, runs the subagent script with `CLAUDE_PLUGIN_ROOT` set to it, and compares the decoded `additionalContext` with the session script's output on the same folder, byte for byte; `test_fixed_table` and `test_seeded_fuzz` keep their inputs, with a file that does not end in a newline as one more fixed case; `test_real_run` runs both scripts on the Plugin_Folder and compares; a fourth test proves the no-rules branch: an empty temp folder gives the session script exit 0 with no output and the subagent script a non-zero exit with one stderr line | 4.4, 4.5 | + +The fixed table writes one file per case; the 500 fuzz strings are written twenty to a folder, as `rules/f00.md` to `rules/f19.md`, so the fuzz test makes 25 folders and 50 subprocesses and the whole check runs in about three seconds, and the multi-file blank-line separator is exercised on fuzz data as well as on the real rules. + +### Guides + +| File | Change | Requirements | +|------|--------|--------------| +| `plugins/freethinking/README.md` | the moved README minus Testing and "Add or change"; Install section without the read-access and `gh auth` paragraphs; the Disclosure_Section last, with the sentences Requirement 3.2 lists | 3.1, 3.2, 3.3, 3.5 | +| `README.md` (root) | title, one paragraph on what the repository is, a short layout list (`plugins/freethinking/`, `evals/`, `checks/`, `.claude/specs/`), links to `plugins/freethinking/README.md` and `.github/CONTRIBUTING.md`, then "Add or change a skill or rule" and Testing, the Validator subsection naming the three validate commands and the Evals subsection saying an Eval_Target copies the plugin and `evals/` into a temporary tree and writes results to `evals/results/` | 3.4, 6.4 | +| `.github/CONTRIBUTING.md` | `claude --plugin-dir ./plugins/freethinking`; the Test section's link to the root README's Testing section stays valid; one sentence that `evals/` sits outside the plugin and the Makefile copies it in for a run | 6.4 | +| `evals/README.md` | "the Makefile runs `claude plugin eval` against a temporary copy of `plugins/freethinking/` with `evals/` inside it" in place of `claude plugin eval . --tag `; results path unchanged; the scaffold note that `git rev-parse --show-toplevel` resolves to the copy | 6.4 | + +## Data Models + +Manifest, the fields the Directory reads: + +| Field | Type | Value | Requirement | +|-------|------|-------|-------------| +| `name` | string | `freethinking`, unchanged | 1.4 | +| `displayName` | string | `Freethinking` | 2.1 | +| `version` | string | `0.12.0` | 2.4 | +| `author` | object | `name`, `email`, `url` as Requirement 2.2 | 2.2 | +| `homepage`, `repository` | string (URL) | the GitHub repository | 2.1 | +| `license` | string | `MIT` | 2.1 | +| `icon` | path | `./.claude-plugin/icon.png`, a 1024 by 1024 PNG | 2.1, 2.5 | +| `keywords` | array of strings | the eight of Requirement 2.3 | 2.3 | + +Run_Tree, as the Makefile builds it: + +| Entry | Source | Notes | +|-------|--------|-------| +| `/` | `mktemp -d` under `$TMPDIR` or `/tmp` | deleted by the `trap` on exit | +| `/.claude-plugin/`, `skills/`, … | `cp -R plugins/freethinking/.` | the plugin as installed | +| `/evals/` | `tar` of `evals/` excluding `evals/results` | cases, fixtures, graders | +| `/.github/pull_request_template.md` | `cp` from the repository root | read by the `opens-pr-with-ticket` scaffold | +| `/.git` | `git init -q` | gives the scaffolds their top level | + +## Correctness Properties + +### Property 1: The plugin folder is the plugin + +*For any* commit after the move, the set of skill, agent, rule and hook names found under `plugins/freethinking/` equals the set found at the root before it, and no path under `plugins/freethinking/` starts with `evals/`, `checks/` or `.claude/specs/`. + +**Validates: Requirements 1.1, 1.3** + +### Property 2: Both hooks say the same rules + +*For any* folder with a `rules/` subfolder of Markdown files, the `additionalContext` the subagent script prints, JSON-decoded, equals the bytes the session script prints for the same folder. + +**Validates: Requirements 4.2, 4.3** + +### Property 3: An eval run leaves only results behind + +*For any* Eval_Target invocation, pass or fail, the repository differs afterwards only under `evals/results/`, and no Run_Tree remains under `$TMPDIR`. + +**Validates: Requirements 5.1, 5.2** + +### Property 4: Every named path exists + +*For any* backticked repository path in the root README, `plugins/freethinking/README.md`, the contributing guide and `evals/README.md`, a file or folder exists at that path after the move. + +**Validates: Requirement 6.6** + +## Error Handling + +| Condition | Category | Behavior | Requirements | +|-----------|----------|----------|--------------| +| `CLAUDE_PLUGIN_ROOT` unset or without `rules/` | MISSING_INPUT | session script exits 0 with no output; subagent script exits 1 with one stderr line | 4.4 | +| A rule file without a trailing newline | EDGE | both scripts print it with a trailing newline, so their outputs stay equal | 4.3 | +| `mktemp`, `cp`, `tar` or `git init` fails | BUILD_FAILURE | `set -e` stops the recipe with that command's error; the `trap` removes whatever was made; no eval runs | 5.5 | +| `claude plugin eval` exits non-zero | EVAL_FAILURE | the `trap` removes the Run_Tree; the recipe exits with the same status; results already written stay | 5.1 | +| A listing field missing or mistyped | VALIDATION | `claude plugin validate ./plugins/freethinking --strict` fails and so does `make validate` and CI | 2.6, 6.1 | +| README under 40 words, no license, or an icon that is missing, not PNG, not square or outside 512 to 2048 px | VALIDATION | `test_listing.py` fails and so does `make validate` and CI | 8.1, 8.2, 8.3 | +| A guide names a path that no longer exists | REVIEW | the wave reviewer reports it as a finding, fixed in the same wave | 6.6 | +| The portal still holds a version | MANUAL | the author records the finding on issue #43 | 7.1, 7.2 | + +## Testing Strategy + +- Property 2 is proved by `checks/test_hook_encoder.py`: the fixed table, the 500 seeded fuzz strings and the real plugin, each written as a rules file and compared across the two scripts. Property 1 is an ephemeral check in the move's task: list the component names before and after and diff them. +- Example tests cover each Error Handling row the checks can reach: the no-rules branch of both scripts in the encoder check, and the size limits in `test_rules_size.py` against `plugins/freethinking/`. +- `test_listing.py` is proved on the real Plugin_Folder and by an ephemeral negative check in its task: a temp folder with a 300 px icon and a 10-word README fails each assertion. +- `make validate` is the integration check for the layout, the checks and the manifest, and CI runs it on the pull request. +- One eval invocation with a scaffolded case, `make eval-skill-pr EVAL_ARGS="--runs 1 --case opens-pr-with-stub-gh"`, proves Property 3 and Requirement 5.3 on the real suite: the results folder appears at the root, the scaffold ran from the copy, and nothing is left in `$TMPDIR`. One `make eval-hook EVAL_ARGS="--runs 1"` proves the rewritten hooks load the rules in a session and a sub-agent. +- A local install proves Requirement 1.6: `claude plugin marketplace add ` from another folder, `claude plugin install freethinking@freethinkingit`, and `claude plugin details freethinking` listing five skills, two agents and two hooks. +- Requirement 7 is the author's manual check in the developer portal after the merge, recorded on issue #43. diff --git a/.claude/specs/directory-listing/requirements.md b/.claude/specs/directory-listing/requirements.md new file mode 100644 index 0000000..fa42db6 --- /dev/null +++ b/.claude/specs/directory-listing/requirements.md @@ -0,0 +1,122 @@ +# Requirements Document + +## Introduction + +The plugin is submitted to Anthropic's plugin directory, which scans every file in the folder that holds the manifest. Today that folder is the repository root, so the scan reads the eval suite and the spec records as if an installed plugin ran them, and holds the submission for a reviewer on each version. This task moves the plugin into a folder of its own, leaves the test and design material outside it, gives the manifest the fields the listing shows, writes down what the plugin runs and sends, and keeps every check and eval target working from the new layout. Contributors keep the same commands; users keep the same install id; the directory's reviewer sees only the plugin. + +## Glossary + +- **Plugin_Folder**: The folder `plugins/freethinking/` at the repository root that holds the Manifest and every file an installed plugin loads: `skills/`, `agents/`, `rules/`, `hooks/`, the plugin README and the Icon. +- **Repository_Root**: The top folder of the repository, holding the Marketplace_File, `evals/`, `checks/`, `.claude/specs/`, the Makefile, the CI workflow, the contributing guide, the LICENSE and the root README. +- **Manifest**: The file `plugins/freethinking/.claude-plugin/plugin.json`. +- **Marketplace_File**: The file `.claude-plugin/marketplace.json` at the Repository_Root, whose one entry names the plugin and points at the Plugin_Folder. +- **Listing_Fields**: The Manifest fields the Directory shows: `displayName`, `homepage`, `repository`, `icon`, `keywords`, `author` and `version`. +- **Icon**: The square PNG at `plugins/freethinking/.claude-plugin/icon.png`, 1024 pixels on each side. +- **Directory**: Anthropic's plugin directory, submitted to from the developer portal at claude.ai/directory/manage. +- **Validator_Shape**: The form of a hook script that the Directory's validator follows when the Plugin_Folder is a subfolder: every path written in full from `${CLAUDE_PLUGIN_ROOT}`, no other shell variable, no command substitution, no package launcher, and no call to another file in the plugin. +- **Hook_Script**: One of `hooks/load-rules.sh` and `hooks/load-rules-subagent.sh` in the Plugin_Folder. +- **Run_Tree**: A temporary folder, built by the Makefile for one eval invocation, holding a copy of the Plugin_Folder's contents with a copy of `evals/` inside it and a git repository initialised at its top. +- **Results_Folder**: The folder `evals/results//` at the Repository_Root where one eval invocation writes `aggregate-result.json` and `report.html`. +- **Free_Check**: A `unittest` file under `checks/`, run by `make check` and `make validate` with no model call. +- **Disclosure_Section**: The last section of the plugin README, titled "What the plugin runs and sends", written for the Directory's reviewer. +- **Listing_Check**: The Free_Check `checks/test_listing.py` that proves the directory's mechanical listing rules on the Plugin_Folder: the README length, the license and the Icon. +- **Eval_Target**: Any of `make eval`, `make eval-hook`, `make eval-skill-`, `make eval-rule-` and `make eval-agent-`. + +## Requirements + +### Requirement 1: The plugin lives in its own folder + +**User Story:** As the plugin's maintainer, I want every file an installed plugin loads in one folder and nothing else in it, so that the Directory scans the plugin and not the repository. + +#### Acceptance Criteria + +1. THE Plugin_Folder SHALL hold the Manifest, the Icon, `skills/`, `agents/`, `rules/`, `hooks/` and a `README.md`, each moved from the Repository_Root with its content otherwise unchanged except where a later requirement says. +2. THE Repository_Root SHALL keep the Marketplace_File, `evals/`, `checks/`, `.claude/specs/`, `Makefile`, `.github/`, `LICENSE` and `.gitignore` where they are. +3. THE Plugin_Folder SHALL contain no file under `evals/`, `checks/` or `.claude/specs/`, and no symbolic link. +4. THE Marketplace_File SHALL keep its `name` `freethinkingit` and its entry's `name` `freethinking`, and SHALL point the entry's `source` at `./plugins/freethinking`. +5. WHEN `claude plugin validate . --strict` runs at the Repository_Root and `claude plugin validate ./plugins/freethinking --strict` runs, THE System SHALL print `Validation passed` for each with no warning. +6. WHEN a machine with the plugin installed from this marketplace runs `claude plugin update freethinking@freethinkingit`, THE System SHALL install the new version under the same install id, with every skill, agent, rule and hook loaded. + +### Requirement 2: The manifest carries the listing fields + +**User Story:** As a developer browsing the Directory, I want the listing to show the plugin's name, publisher, icon, links and tags, so that I can judge it before installing. + +#### Acceptance Criteria + +1. THE Manifest SHALL set `displayName` to `Freethinking`, `homepage` and `repository` to `https://github.com/freethinkingit/dev_tools`, `icon` to `./.claude-plugin/icon.png`, and `license` to `MIT`. +2. THE Manifest SHALL set `author` to name `FreeThinking IT`, email `hello@freethinking.it` and url `https://freethinking.it`. +3. THE Manifest SHALL set `keywords` to `developer-workflow`, `feature-development`, `bug-fixing`, `pull-requests`, `code-review`, `spec-driven`, `engineering-standards` and `accountability`. +4. THE Manifest SHALL set `version` to `0.12.0`. +5. THE Icon SHALL be a PNG of 1024 by 1024 pixels at the path the Manifest names. +6. IF a Listing_Field is absent, misspelt or not of the type the manifest reference gives, THEN `claude plugin validate ./plugins/freethinking --strict` SHALL fail, and THE System SHALL not be submitted. + +### Requirement 3: The plugin README discloses what runs and sends + +**User Story:** As the Directory's reviewer, I want the README to say what the plugin runs, reads and sends, so that the security scan's holds can be confirmed against a stated behaviour. + +#### Acceptance Criteria + +1. THE plugin README SHALL end with the Disclosure_Section. +2. THE Disclosure_Section SHALL state that the `pr` and `bugfix` skills read issues and open pull requests through the `gh` command already signed in on the user's machine and run `git` on the current repository; that the `SessionStart` and `SubagentStart` hooks read only the files under the plugin's `rules/`; that no skill, agent or hook reads a credential, runs a package launcher or sends anything anywhere else; and that the repository's `evals/` and `.claude/specs/` folders are the test suite and the design records, outside the plugin and never run by an installed plugin. +3. THE plugin README's Install section SHALL describe a public repository: no mention of read access, `gh auth login` or `gh auth setup-git`. +4. THE root README SHALL say what the repository holds, link to the plugin README and the contributing guide, and carry the Testing section that the plugin README holds today, since the checks and evals belong to the repository and not to the installed plugin. +5. THE plugin README SHALL keep the Install, Stay up to date, Skills, Rules, Agents and "Tune the plugin for a repository" sections as they are today. + +### Requirement 4: The hook scripts take the validator's shape + +**User Story:** As the plugin's maintainer, I want the two hook scripts to be ones the Directory's validator follows, so that a submission from a subfolder carries no hold for an unfollowed script. + +#### Acceptance Criteria + +1. THE Hook_Scripts SHALL each be in Validator_Shape. +2. WHEN `hooks/load-rules.sh` runs with `CLAUDE_PLUGIN_ROOT` set to the Plugin_Folder, THE System SHALL print the "Handbook rules" heading and its introductory sentence followed by the content of every `rules/*.md` file, in name order, each followed by a newline, exactly as the script prints today. +3. WHEN `hooks/load-rules-subagent.sh` runs with `CLAUDE_PLUGIN_ROOT` set to the Plugin_Folder, THE System SHALL print one JSON object whose `hookSpecificOutput.additionalContext` is the exact text of criterion 2, with every byte of it reproduced after JSON decoding, trailing newlines and non-ASCII text included. +4. IF `CLAUDE_PLUGIN_ROOT` is unset or names a folder with no `rules/`, THEN `hooks/load-rules.sh` SHALL exit 0 and print nothing, and `hooks/load-rules-subagent.sh` SHALL exit non-zero with one line on stderr saying the sub-agent gets no rules. +5. THE Free_Check for the JSON encoder SHALL prove criterion 3 by running the subagent Hook_Script against a fixture rules folder it writes itself, covering quotes, backslashes, control characters, non-ASCII text and trailing newlines, in place of feeding the script on stdin. +6. THE Free_Check for the rules size SHALL keep its limit on the session Hook_Script's output and on each rule file, run against the Plugin_Folder. + +### Requirement 5: The eval targets run from a copy + +**User Story:** As a contributor, I want `make eval--` to keep working with the evals outside the plugin, so that moving them costs no change to a case. + +#### Acceptance Criteria + +1. WHEN an Eval_Target runs, THE Makefile SHALL build a Run_Tree, run `claude plugin eval` against it with the same flags, tags and `EVAL_ARGS` as today, and delete the Run_Tree afterwards, whether the eval passed or failed. +2. WHEN an Eval_Target runs, THE System SHALL write the run's `aggregate-result.json` and `report.html` to a Results_Folder at the Repository_Root, and nothing to `evals/` inside the Run_Tree that survives it. +3. WHEN a case's scaffold runs in the Run_Tree, THE System SHALL resolve `git rev-parse --show-toplevel` to the Run_Tree, so that `$ROOT/evals/fixtures/setup.sh` is the copy inside it and no scaffold or fixture file changes. +4. THE Eval_Targets SHALL keep their names, the enumeration from the `evals/` folders, the per-target flags, `EVAL_MAX_COST_USD`, `EVAL_ARGS` and `make help`'s output. +5. IF the Run_Tree cannot be built, THEN THE Makefile SHALL stop with the failing command's error and run no eval. +6. THE Free_Check for eval cases SHALL keep reading the cases under `evals/` at the Repository_Root. + +### Requirement 6: The free checks and the guides follow the layout + +**User Story:** As a contributor, I want `make validate` and the guides to describe the repository as it is, so that a change to one part still reaches every part it must. + +#### Acceptance Criteria + +1. THE Free_Checks that read `skills/`, `agents/`, `rules/`, `hooks/` or the Manifest SHALL read them under the Plugin_Folder, and `make validate` SHALL pass. +2. THE `validate` target SHALL run `make check`, then `claude plugin validate . --strict` for the Marketplace_File and `claude plugin validate ./plugins/freethinking --strict` for the plugin. +3. THE CI workflow SHALL keep running `make validate` unchanged. +4. THE contributing guide, the root README, the plugin README and `evals/README.md` SHALL name paths as they are after the move, including that the plugin loads for one session with `claude --plugin-dir ./plugins/freethinking`, and SHALL say that `evals/` sits outside the plugin and how an Eval_Target runs it. +5. THE Makefile SHALL name the Plugin_Folder in one `PLUGIN` variable, and each Free_Check that reads the Plugin_Folder SHALL name it in one constant at its top, so that adding a second plugin under `plugins/` is one edit per file. +6. IF a backticked repository path in the contributing guide, the root README, the plugin README or `evals/README.md` names a file or folder that does not exist after the move, THEN the wave review SHALL report it as a finding to fix in this task. + +### Requirement 7: The portal confirms the result + +**User Story:** As the plugin's maintainer, I want the developer portal's validation of the new layout to carry no hold, so that each version goes live without a reviewer wait. + +#### Acceptance Criteria + +1. WHEN the author validates the repository in the Directory's developer portal with the plugin path `plugins/freethinking` after the change merges, THE report SHALL show no finding with the result "Policy hold" and no finding with the result "Blocks". +2. WHILE a warning remains in that report, THE System SHALL record the warning's title and cause as a comment on issue #43, so the next change knows what is left. + +### Requirement 8: The listing rules are checked before the portal + +**User Story:** As the plugin's maintainer, I want the directory's mechanical listing rules checked by `make validate`, so that a wrong icon, which the portal takes only once, never reaches a submission. + +#### Acceptance Criteria + +1. THE Listing_Check SHALL fail when the Plugin_Folder has no `README.md`, or when that README holds fewer than 40 words outside fenced code blocks. +2. THE Listing_Check SHALL fail when the Plugin_Folder has no `LICENSE` file and the Manifest has no `license` field. +3. THE Listing_Check SHALL fail when the Manifest's `icon` names no file, or names a file that is not a PNG, not square, or not between 512 and 2048 pixels on each side, read from the PNG header with the standard library only. +4. THE Listing_Check SHALL pass on the Plugin_Folder as this task leaves it, and `make validate` SHALL run it with the other Free_Checks. diff --git a/.claude/specs/directory-listing/tasks.md b/.claude/specs/directory-listing/tasks.md new file mode 100644 index 0000000..3b0d746 --- /dev/null +++ b/.claude/specs/directory-listing/tasks.md @@ -0,0 +1,74 @@ +# Implementation Plan + +## Overview + +The move comes first, because every other task writes paths under `plugins/freethinking/`. Then, in one wave, the manifest, the hook scripts, the re-rooted checks, the Makefile and the four guides, which touch disjoint files. The two checks that depend on new content, the encoder check on the rewritten hooks and the listing check on the new manifest, follow. The last wave is the author's: the eval runs that cost tokens and the local install. Every task is proved by `make validate` plus the ephemeral check its text names; the eval runs are the integration proof. Requirement 7, the portal's validation, happens after the merge and is recorded on issue #43, so it is not a task here. + +## Tasks + +- [x] 1. Move the plugin into `plugins/freethinking/` + - `git mv` `.claude-plugin/plugin.json`, `skills/`, `agents/`, `rules/`, `hooks/` and `README.md` under `plugins/freethinking/`, and move the untracked `.claude-plugin/icon.png` to `plugins/freethinking/.claude-plugin/icon.png`; leave `.claude-plugin/marketplace.json` at the root and set its entry's `source` to `./plugins/freethinking`. Ephemeral check for Property 1: list the skill, agent, rule and hook names at the old and new paths and diff them; `claude plugin validate . --strict` passes + - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5_ + +- [x] 2. Fill the manifest's listing fields + - In `plugins/freethinking/.claude-plugin/plugin.json`, set `displayName`, `version` 0.12.0, `author`, `homepage`, `repository`, `license`, `icon` and `keywords` to the values of Requirement 2, in the field order the design gives; `claude plugin validate ./plugins/freethinking --strict` passes with no warning + - _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6_ + +- [x] 3. Rewrite the hook scripts to the validator's shape + - Rewrite `plugins/freethinking/hooks/load-rules.sh` and `load-rules-subagent.sh` as the design's table says: one `awk` program each over `"${CLAUDE_PLUGIN_ROOT}"/rules/*.md`, no other variable, no command substitution, no call between them, the header written in both with a comment naming the other, and the no-rules branches of Requirement 4.4. Ephemeral check: run the old scripts from `git show HEAD:` and the new ones on the real rules folder and diff the session output and the decoded subagent context, byte for byte + - _Requirements: 4.1, 4.2, 4.3, 4.4_ + +- [x] 4. Re-point the free checks + - [x] 4.1 Re-root the existing checks on the plugin folder + - In `test_agents.py`, `test_descriptions.py`, `test_report_skill.py`, `test_rules_size.py` and `test_issue_forms.py`, add one `PLUGIN` constant at the top and read `skills/`, `agents/`, `rules/`, `hooks/` under it; `test_rules_size.py` sets `CLAUDE_PLUGIN_ROOT` to it; `test_eval_cases.py` is untouched; `make check` passes + - _Requirements: 4.6, 6.1, 6.5_ + - [x] 4.2 Rewrite the encoder check around a fixture rules folder + - In `test_hook_encoder.py`, make `roundtrip` write a temp folder with `rules/a.md`, run both scripts with `CLAUDE_PLUGIN_ROOT` set to it and compare byte for byte; keep the fixed table and the seeded fuzz, add a file with no trailing newline to the fixed table, make `test_real_run` compare both scripts on the plugin folder, and add the no-rules test of Requirement 4.4; `make check` passes + - _Requirements: 4.4, 4.5_ + - [x] 4.3 Add the listing check + - Write `checks/test_listing.py` as the design's Checks row says: README of at least 40 words outside fenced blocks, `LICENSE` or a `license` field, and the icon a square PNG of 512 to 2048 px read from the IHDR chunk with `struct`. Ephemeral negative check: a temp folder with a 300 px PNG and a 10-word README fails each assertion; `make check` passes on the real folder + - _Requirements: 8.1, 8.2, 8.3, 8.4_ + +- [x] 5. Teach the Makefile the layout and the Run_Tree + - Add `PLUGIN := plugins/freethinking`; make `validate` run `check`, then the three strict validate commands; add the `RUN_EVAL` define of the design, with a per-recipe results folder, `mktemp`, the traps, `cp -R` of the plugin, `tar` of `evals/` without `results/` and no pipeline, the pull request template, `git init`, `mkdir -p` of the results folder and `claude plugin eval` with `--output-dir`; expand it in every eval recipe with the same tags; keep the per-target flags, `help` and the `PATH` exports. Ephemeral check: `make -n eval-skill-pr` shows the recipe, and `make validate` passes + - _Requirements: 5.1, 5.2, 5.4, 5.5, 6.2, 6.3, 6.5_ + +- [x] 6. Update the guides + - [x] 6.1 The plugin README + - In `plugins/freethinking/README.md`, drop the read-access sentence and the `gh auth` paragraph from Install, cut the "Add or change a skill or rule" and Testing sections, and add the Disclosure_Section last with the sentences of Requirement 3.2; every backticked path in the file exists + - _Requirements: 3.1, 3.2, 3.3, 3.5_ + - [x] 6.2 The root README + - Write a new `README.md` at the root: the title, what the repository is, the layout list, links to `plugins/freethinking/README.md` and `.github/CONTRIBUTING.md`, then the "Add or change a skill or rule" and Testing sections moved from the plugin README, with the Validator subsection naming the three validate commands and the Evals subsection saying how an Eval_Target copies the plugin and `evals/` into a temporary tree; every backticked path in the file exists + - _Requirements: 3.4, 6.4_ + - [x] 6.3 The contributing guide and the evals README + - In `.github/CONTRIBUTING.md`, `claude --plugin-dir ./plugins/freethinking` and one sentence on `evals/` sitting outside the plugin; in `evals/README.md`, the Makefile's copy in place of `claude plugin eval . --tag ` and the note that `git rev-parse --show-toplevel` resolves to the copy; every backticked path in both files exists + - _Requirements: 6.4_ + +- [x] 7. Prove the run tree and the install by hand + - [x] 7.1 Run two eval targets and keep the tables + - The author runs `make eval-skill-pr EVAL_ARGS="--runs 1 --case opens-pr-with-stub-gh"` and `make eval-hook EVAL_ARGS="--runs 1"`, confirms the results folder appeared under `evals/results/`, that nothing was left under `$TMPDIR`, and pastes both tables into the PR's Testing section + - **Property 3: An eval run leaves only results behind** + - **Validates: Requirements 5.1, 5.2, 5.3** + - [x] 7.2 Install from the checkout + - The author registers the checkout as a local marketplace from another folder, installs `freethinking@freethinkingit`, runs `claude plugin details freethinking` and records that it lists five skills, two agents and two hooks, then removes the probe marketplace + - _Requirements: 1.6_ + +## Notes + +- Every task runs `make validate` before it is done; a task's ephemeral check is created in a scratch location and deleted afterwards. +- Task 7's two leaves are actions for the author, since the eval runs cost tokens and the install touches the author's own Claude settings; they get no sub-agent. +- The spec records (`.claude/specs/`) are not edited to match the move; they stay as written. +- Requirement 7, the portal's validation after merge, is recorded on issue #43 and is not a task. + +## Task Dependency Graph + +```json +{ + "waves": [ + { "id": 0, "tasks": ["1"] }, + { "id": 1, "tasks": ["2", "3", "4.1", "5", "6.1", "6.2", "6.3"] }, + { "id": 2, "tasks": ["4.2", "4.3"] }, + { "id": 3, "tasks": ["7.1", "7.2"] } + ] +} +``` diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 768399f..e068ade 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,7 +1,8 @@ # Contributing -This repository is a Claude Code plugin: shared skills, agents and rules for a -software team; its hooks put the rules in every session's context. This guide +This repository holds a Claude Code plugin, in `plugins/freethinking/`: shared +skills, agents and rules for a software team, whose hooks put the rules in every +session's context. This guide says how to set up, test and submit a change, and how the repository uses issues, labels, milestones and discussions. @@ -14,19 +15,24 @@ the [Code of Conduct](CODE_OF_CONDUCT.md). Report a vulnerability as the Clone the repository and work from its root: ```bash -claude --plugin-dir . # load your working copy for one session -make validate # the unit tests and the validators; CI runs this on every PR +claude --plugin-dir ./plugins/freethinking # load your working copy for one session +make validate # the unit tests and the validators; CI runs this on every PR ``` ## Test -The free checks are `make validate`: the unit tests under `checks/`, then the -two validators. They make no model call. +The free checks are `make validate`: the unit tests under `checks/`, then +three `claude plugin validate` runs, on `.`, `./plugins/freethinking` and +`./plugins/freethinking/skills`. They make no model call. Evals run by component with `make eval--`, such as `make eval-skill-pr`, and `make eval-hook` for the hooks. Every case is a model call on your own Claude seat, so evals run by hand, never in CI. Paste the table the command prints into the Testing section of your pull request. +`evals/` sits outside the plugin folder, so Anthropic's plugin directory, which +scans every file in the plugin folder when the plugin is submitted, sees only +the plugin, and each `make eval-*` target copies the plugin and `evals/` into a +temporary tree to run them. A change to a skill, agent, rule or hook comes with an eval case or a change to one. A new skill comes with at least one case under `evals/skills//`. A diff --git a/Makefile b/Makefile index c43b3b1..379d009 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Checks for the handbook plugin. # -# make validate free: check, manifests and skill frontmatter (CI runs this) +# make validate free: check, marketplace, plugin manifest and skill frontmatter (CI runs this) # make check free: the Python checks under checks/, such as the hook's JSON encoder # make help list the eval targets that exist # make eval every eval case @@ -19,18 +19,42 @@ # The eval-skill-*, eval-rule-* and eval-agent-* targets are enumerated from # the evals/ directories and select their cases with --tag, so every case # carries its component's name as a tag and a new case needs no Makefile -# change. A case that needs its own flags gets one line in the "Per-target -# flags" block below; those flags are kept in CASE_ARGS so EVAL_ARGS on the -# command line still adds to them. The cost ceiling is per invocation, so raise -# it for the whole suite: make eval EVAL_MAX_COST_USD=5. Results go to -# evals/results// (gitignored). Paste the table the command prints -# into the PR's Testing section. +# change. The runner reads cases only from inside the plugin root, and the +# plugin lives in $(PLUGIN) with evals/ beside it at the repository root, so +# each eval target runs claude plugin eval against a temporary copy of the +# plugin with evals/ copied inside it and a git repository initialised at its +# top, for the scaffolds that find evals/fixtures/ through git rev-parse. The +# pr scaffold also reads .github/pull_request_template.md from that top, so +# that one file is copied in too. The copy is deleted when the run ends, pass or fail. A case that needs its own +# flags gets one line in the "Per-target flags" block below; those flags are +# kept in CASE_ARGS so EVAL_ARGS on the command line still adds to them. The +# cost ceiling is per invocation, so raise it for the whole suite: make eval +# EVAL_MAX_COST_USD=5. Results go to evals/results// at the +# repository root (gitignored). Paste the table the command prints into the +# PR's Testing section. +PLUGIN := plugins/freethinking EVAL_MAX_COST_USD ?= 2 EVAL_ARGS ?= CASE_ARGS := -EVAL = claude plugin eval . --trust-plugin --ablation none --no-publish \ - --max-cost-usd $(EVAL_MAX_COST_USD) $(EVAL_ARGS) $(CASE_ARGS) + +define RUN_EVAL +set -eu; \ +results=$$(pwd)/evals/results/$$(date +%Y-%m-%dT%H-%M-%S); \ +run=$$(mktemp -d "$${TMPDIR:-/tmp}/freethinking-eval.XXXXXX"); \ +trap 'rm -rf "$$run"' EXIT; trap 'exit 130' HUP INT TERM; \ +cp -R $(PLUGIN)/. "$$run"/; \ +tar -C . --exclude=evals/results -cf "$$run/evals.tar" evals; \ +tar -C "$$run" -xf "$$run/evals.tar"; \ +rm "$$run/evals.tar"; \ +mkdir -p "$$run/.github"; \ +cp .github/pull_request_template.md "$$run/.github/"; \ +git -C "$$run" init -q; \ +mkdir -p "$$results"; \ +claude plugin eval "$$run" --trust-plugin --ablation none --no-publish \ + --max-cost-usd $(EVAL_MAX_COST_USD) --output-dir "$$results" \ + $(EVAL_ARGS) $(CASE_ARGS) $(1) +endef SKILLS := $(notdir $(patsubst %/,%,$(wildcard evals/skills/*/))) RULES := $(notdir $(patsubst %/,%,$(wildcard evals/rules/*/))) @@ -43,7 +67,8 @@ AGENT_TARGETS := $(addprefix eval-agent-,$(AGENTS)) validate: check claude plugin validate . --strict - claude plugin validate ./skills --strict + claude plugin validate ./$(PLUGIN) --strict + claude plugin validate ./$(PLUGIN)/skills --strict check: python3 -m unittest discover -s checks @@ -77,16 +102,16 @@ eval: CASE_ARGS += --allow-tools Bash Write Edit eval: export PATH := bin:$(PATH) eval: - $(EVAL) + $(call RUN_EVAL) eval-hook: - $(EVAL) --tag hook + $(call RUN_EVAL,--tag hook) $(SKILL_TARGETS): eval-skill-%: - $(EVAL) --tag $* + $(call RUN_EVAL,--tag $*) $(RULE_TARGETS): eval-rule-%: - $(EVAL) --tag $* + $(call RUN_EVAL,--tag $*) $(AGENT_TARGETS): eval-agent-%: - $(EVAL) --tag $* + $(call RUN_EVAL,--tag $*) diff --git a/README.md b/README.md index e60bfef..18dbb3b 100644 --- a/README.md +++ b/README.md @@ -1,161 +1,48 @@ # Developer Handbook -Shared Claude Code skills and rules for a software team, packaged as a plugin. -Install it once and the team's standard workflows and development rules are -available in every repo you work in. - -## Install - -You need [Claude Code](https://claude.com/claude-code) and read access to this -repo. From any terminal: - -```bash -claude plugin marketplace add freethinkingit/dev_tools -claude plugin install freethinking@freethinkingit -``` - -The plugin loads the next time you start Claude Code. In a session that is -already open, run `/reload-plugins`. - -The clone uses the git credentials already on your machine and never prompts. -If the first command fails on access, sign in with `gh auth login`, run -`gh auth setup-git`, and try again. - -## Stay up to date - -Auto-update is off by default for team marketplaces. Turn it on once: - -1. Run `/plugin` in a Claude Code session. -2. Open the **Marketplaces** tab and select `freethinkingit`. -3. Select **Enable auto-update**. - -To update by hand: - -```bash -claude plugin update freethinking@freethinkingit -``` - -## Skills - -| Skill | Run it with | What it does | -| ----- | ----------- | ------------ | -| `start-task` | `/freethinking:start-task `, or ask Claude to change something, or to start or spec a task | The front door for any change: reads the code the request touches, then sends one contained change to the `task-executor` agent, a defect to `bugfix` and everything else to a spec, asking you before taking either of the first two. On the spec route, writes `requirements.md`, `design.md` and `tasks.md` in `.claude/specs//` before any code, following the team template, with you approving each document before the next. | -| `implement-spec` | `/freethinking:implement-spec [task] [task\|wave\|spec]`, or ask Claude to implement, build or continue a spec | Builds a task from its approved spec, wave by wave from `tasks.md`, each leaf task in its own sub-agent, ticking a task only once its tests pass, running the full suite at the end of every wave, and handing back to you after each task, after each wave, or only once every wave is done (with a short report after each wave and a final review of the whole run), whichever you chose. Pauses with a proposal when the design cannot be followed, and hands off to `pr` when the spec is done. | -| `bugfix` | `/freethinking:bugfix [what you already know]`, or ask Claude to fix a bug | Reads the ticket, and anything you already know that you put after it, reproduces the bug before touching code, confirms the root cause with you, fixes the cause with a regression test, verifies the fix, then hands off to `pr` or leaves its findings on the ticket. | -| `pr` | `/freethinking:pr [ticket] [base-branch]`, or ask Claude to open a PR | Takes the ticket as its first argument and reads it, offers to create the branch and commit if needed, walks the branch's changes with you one at a time with a short set of review prompts, drafts the title and description from your answers, and opens the PR with `gh` once you approve. Needs the `gh` CLI signed in. | -| `report` | `/freethinking:report [mode]`, or the session invokes it when it hands back work, asks you for a decision, reports status or answers a question | Writes that message for you in one of four shapes, handback, decision, status or answer, each from a template; the handback and status templates put problems first, and every template names things by what they are. A repository can replace a template under `.freethinking/report/`. The skill edits nothing. | - -## Rules - -The plugin also loads a short set of team rules into every Claude Code session, -in every repository, so Claude follows them without being asked. They live in -`rules/`: - -| File | Covers | -| ---- | ------ | -| `rules/workflow.md` | Which skill to use when, routing on what you said or a file on disk and taking the first rule that matches: `bugfix` when you call it a bug, `implement-spec` when a spec exists, `pr` for pull requests, no skill for a typo-sized change, and `start-task` for any other change, which reads the code and picks between the `task-executor` agent for one contained change, `bugfix` for a defect and a spec for the rest, asking before the first two. Also: never work on the default branch, and say so when the approach changes. | -| `rules/code-changes.md` | How to change code: read the surrounding code first, match the project's style and libraries, make the smallest change that does the job, add a test with every behaviour change, run what you touched, and leave no debug residue behind. | -| `rules/comments.md` | How to comment: docstrings on public contracts only, saying intent, parameters, returns and exceptions; inline comments only for non-obvious details such as workarounds and edge cases, never for what a line does; no divider or closing-brace comments; no commented-out dead code. | -| `rules/collaboration.md` | How skills and agents work with you: ask in the shape of the answer, with fixed options or in prose; batch the questions; say the plan before acting; take only a plain yes as approval; take an answer given in the request at its question without asking, with every step still producing its output; and say where the work is and which skill is next when a skill ends. | -| `rules/communication.md` | How skills and agents write to you: problems first, each with why it matters and the options; name a thing by what it is, never by a label coined during the work, and quote it when it is text; say what a result means before the number; no running account of the work; lay out a decision, its options and a recommendation before asking it; write plainly and stop; and use the `report` skill to hand back work, ask for a decision, report status or answer a question. | - -Claude Code does not load a `CLAUDE.md` or a `rules/` folder from a plugin by -itself, so `hooks/hooks.json` registers two hooks. The `SessionStart` hook runs -`hooks/load-rules.sh`, which prints every `.md` file under `rules/` into the -session context on every session start, whatever started it (a new session, -a resume, a clear, a compact or a fork). The `SubagentStart` hook runs -`hooks/load-rules-subagent.sh`, which puts the same rules into every sub-agent, -which is how both agents below get them. Keep the -combined rules under 10,000 characters; Claude Code truncates hook output past -that, and `make validate` fails when they go over, or when `collaboration` or -`communication` goes over its own limit of 1,600 characters. A rule a repository needs -to override belongs in that repository's own `CLAUDE.md`, which Claude reads -alongside these. - -## Agents - -The plugin ships two agents under `agents/`. `implement-spec` launches both, -`start-task` launches the executor for one contained change, and you can -launch either directly: mention it in a session as -`@agent-freethinking:task-executor` or `@agent-freethinking:wave-reviewer`, or have a -skill or session launch it with the Agent tool as -`subagent_type: freethinking:task-executor` or `freethinking:wave-reviewer`. - -What the prompt carries: - -- **`task-executor`**: the change, in your own words. When the task comes from - a spec, say so in the prompt; that sentence switches it to spec mode, where - it writes no test unless the task asks for one. -- **`wave-reviewer`**: the files that changed, or a git range; the requirement - or design text they implement, if there is any; the files that cite them; - and, when they come from a spec, the sentence "These changes come from a - spec wave", which is the only thing that waives a missing test. - -| Agent | What it does | What it returns | -| ----- | ------------ | --------------- | -| `task-executor` | Builds one well-specified change as the smallest change in the project's style, proves it, and deletes its scratch work. Stops and asks instead of guessing when the task is unclear, the design does not fit the code, or the task would need a new dependency, framework or tool. `start-task` launches it for one contained change, and `implement-spec` for every leaf task. | A Summary: `status` (`done` or `stopped`), `files changed`, `checks run` with their results, the ephemeral check it ran, or why none applied, what it is `unsure` of, and, when stopped, the question or evidence under `stopped because`. | -| `wave-reviewer` | Reviews one set of changes against the requirements and design they implement and the files that cite them, gathering the diff itself and changing nothing. `implement-spec` launches it at the end of every wave, and once more over the whole run when you chose to hand back only at the end. | Findings: a `verdict` (`holds` or `findings`), the `base` commit the diffs were measured against, then each problem with its `file`, an exact `quote`, the `problem` and a `severity` (`blocker`, `should-fix` or `nit`), and what it is `unsure` of. | - -Both agents follow one protocol for their reports, and a new agent must too: -its report opens with its status on the first line, `status: done` or `stopped` -for the executor and `verdict: holds` or `findings` for the reviewer, has one -field for each thing the launcher must act on, always has an `unsure` field, -quotes exactly whatever it quotes, and has nothing after the shape. - -The executor works in one of two modes, set by the launch prompt alone: - -- **Spec mode**, when the prompt says the task comes from a spec, in any words; - `implement-spec` says "This task is a leaf task from a spec; tests are tasks - of their own." Tests are tasks of their own there, so it writes a test only - when the task asks for one, and proves a code task with an ephemeral check - that it deletes and reports in its Summary. -- **Standalone mode**, otherwise. The change comes with its test, written the - way the project writes tests. - -The reviewer treats a missing test as a finding unless the launch says the -changes come from a spec wave. +This repository holds the `freethinking` Claude Code plugin, its eval suite, +its free checks and its spec records. To install and use the plugin, read +[the plugin README](plugins/freethinking/README.md). To change it, read +[the contributing guide](.github/CONTRIBUTING.md). + +- `plugins/freethinking/`: the plugin itself, with its manifest, skills, + agents, rules, hooks and its README. +- `evals/`: the eval cases, kept outside the plugin folder because Anthropic's + plugin directory scans every file in that folder when the plugin is + submitted. +- `checks/`: the free unit tests. +- `.claude/specs/`: the design records of past tasks. +- `.claude-plugin/marketplace.json`: the marketplace entry that points at the + plugin folder. ## Add or change a skill or rule -Each skill is a folder under `skills/` with a `SKILL.md`; each rule is a Markdown -file under `rules/`. The [contributing guide](.github/CONTRIBUTING.md) says how -to set up, test and submit a change. - -## Tune the plugin for a repository - -A repository can keep its own versions of the plugin's template files in a -`.freethinking/` folder at its root, laid out as `/.md`, such as -`.freethinking/report/handback.md`. A file there replaces the plugin's file of -the same name whole; nothing is merged. Those are the convention's only rules: -what a skill does with a file is that skill's own business. This release -applies the convention to the four templates of the `report` skill: -`handback.md`, `decision.md`, `status.md` and `answer.md`. The `report` skill -reads them as shapes to write in and ignores any action a template asks for, -as its own text says. A repository's `handback.md` may use its own sections: -`implement-spec` reads a wave report by what it holds, problems, what was done, -checks, files and the next step, and the plugin's own evals grade only the -plugin's template, so renaming sections costs nothing but the two verdict lines -being found by content rather than by name. The spec templates of `start-task` and the default -pull request template of `pr` are candidates to move there later. +Each skill is a folder under `plugins/freethinking/skills/` with a `SKILL.md`; +each rule is a Markdown file under `plugins/freethinking/rules/`. The +[contributing guide](.github/CONTRIBUTING.md) says how to set up, test and +submit a change. To try a change in one session, load the plugin from your +clone with `claude --plugin-dir ./plugins/freethinking`. ## Testing -Using the plugin needs only Claude Code, except the `pr` skill, which needs -the `gh` CLI signed in. Developing it needs make, python3 and Claude Code, and -CI adds Node only to install Claude Code. +Using the plugin needs only Claude Code, except the `pr` and `bugfix` skills, +which need the `gh` CLI signed in. Developing it needs make, python3 and Claude +Code, and CI adds Node only to install Claude Code. -`make validate` runs the unit tests and then the validator, with no model call, -and CI runs `make validate` on every PR. Run it after every change to a skill, -rule, agent, the hook, the manifest or `checks/`. It needs make, python3 and -Claude Code; make and python3 ship with git in the macOS Command Line Tools, -and there is no venv, pip or requirements file. +`make validate` runs the unit tests and then the validators, with no model +call, and CI runs `make validate` on every PR. Run it after every change to a +skill, rule, agent, the hooks, the manifest or `checks/`. It needs make, +python3 and Claude Code; make and python3 ship with git in the macOS Command +Line Tools, and there is no venv, pip or requirements file. ### Validator -`claude plugin validate . --strict` and `claude plugin validate ./skills --strict` -are built into Claude Code and make no model call. They check the manifests and -skill frontmatter; for an agent file they check only that a frontmatter block -with a description exists. +`claude plugin validate . --strict` checks the marketplace at the repository +root, `claude plugin validate ./plugins/freethinking --strict` checks the +plugin, and `claude plugin validate ./plugins/freethinking/skills --strict` +checks the skills. They are built into Claude Code and make no model call. They +check the manifests and skill frontmatter; for an agent file they check only +that a frontmatter block with a description exists. ### Unit tests @@ -184,11 +71,18 @@ test, so you can run only the ones for what you changed: ```text evals/ hook// # the SessionStart and SubagentStart hooks that load the rules - rules/// # one folder per rules/.md - skills/// # one folder per skills// - agents/// # one folder per agents/.md + rules/// # one folder per plugins/freethinking/rules/.md + skills/// # one folder per plugins/freethinking/skills// + agents/// # one folder per plugins/freethinking/agents/.md ``` +`evals/` sits outside the plugin, but `claude plugin eval` reads cases only +from inside the plugin root. So every eval target builds a temporary tree: a +copy of `plugins/freethinking/` with a copy of `evals/`, minus its results, +inside it, and `git init` at its top. It runs `claude plugin eval` against that +tree, writes the results to `evals/results//` at the repository +root, and deletes the tree afterwards whether the run passed or failed. + Each case runs Claude in a sandbox with only this plugin loaded. Every case is a real model call on your own Claude seat, so evals run by hand, not in CI. The dollar figures printed are list-price estimates, not charges. @@ -208,21 +102,21 @@ make eval EVAL_MAX_COST_USD=5 # raise the pe The `eval-skill-`, `eval-rule-` and `eval-agent-` targets are enumerated from the folders under `evals/` and select cases by the tag that names the component, so a new case folder gets its target with no Makefile -change; `make help` shows them. A case that needs its own flags gets one line -in the Makefile's "Per-target flags" block: the implement-spec, pr, -code-changes, task-executor and wave-reviewer targets run their cases' scaffold -scripts with `--scaffold`, and the project trees those scaffolds copy live -under `evals/fixtures/`. implement-spec and task-executor grant Bash, Write and -Edit, pr and wave-reviewer grant Bash, and code-changes grants Write and Edit; -the four that grant Bash put `bin` first on the PATH for the wrappers the +change; `make help` shows them. A target whose cases need flags gets one line +in the Makefile's "Per-target flags" block, which is the list of the targets +that run their cases' scaffold scripts with `--scaffold` and the tools each +grants; the project trees those scaffolds copy live under `evals/fixtures/`. +The targets that grant Bash put `bin` first on the PATH for the wrappers the scaffolds write. `eval` carries the union so the whole suite runs. Each invocation runs under a list-price ceiling, `EVAL_MAX_COST_USD`, default $2. Each case runs three times by default; iterate with `EVAL_ARGS="--runs 1"`, -confirm as `evals/README.md` says (2 runs for deterministic graders, 3 when any -is llm), and run the table you paste for a rule or skill-description change with -`EVAL_ARGS="--ablation with-without"` so the Δ column shows what the plugin -added. Results land in `evals/results//` (gitignored). - -`evals/README.md` covers writing a case, reading results, keeping a run's -transcript to debug a failure, and what the eval sandbox allows. +confirm as [`evals/README.md`](evals/README.md) says (2 runs for deterministic +graders, 3 when any is llm), and run the table you paste for a rule or +skill-description change with `EVAL_ARGS="--ablation with-without"` so the Δ +column shows what the plugin added. Results land in +`evals/results//` (gitignored). + +[`evals/README.md`](evals/README.md) covers writing a case, reading results, +keeping a run's transcript to debug a failure, and what the eval sandbox +allows. diff --git a/checks/test_agents.py b/checks/test_agents.py index 7540774..5380373 100644 --- a/checks/test_agents.py +++ b/checks/test_agents.py @@ -5,8 +5,9 @@ from pathlib import Path ROOT = Path(__file__).resolve().parent.parent -AGENTS_DIR = ROOT / "agents" -SKILLS_DIR = ROOT / "skills" +PLUGIN = ROOT / "plugins" / "freethinking" +AGENTS_DIR = PLUGIN / "agents" +SKILLS_DIR = PLUGIN / "skills" # The allowlist of tool names the handbook's agents may declare; extend it here when an agent needs another. KNOWN_TOOLS = { diff --git a/checks/test_descriptions.py b/checks/test_descriptions.py index b6e0ab1..06f1ec1 100644 --- a/checks/test_descriptions.py +++ b/checks/test_descriptions.py @@ -9,8 +9,9 @@ from pathlib import Path ROOT = Path(__file__).resolve().parent.parent -AGENTS_DIR = ROOT / "agents" -SKILLS_DIR = ROOT / "skills" +PLUGIN = ROOT / "plugins" / "freethinking" +AGENTS_DIR = PLUGIN / "agents" +SKILLS_DIR = PLUGIN / "skills" DESCRIPTION_LIMIT = 1024 diff --git a/checks/test_hook_encoder.py b/checks/test_hook_encoder.py index b7eabf6..fa5ad3d 100644 --- a/checks/test_hook_encoder.py +++ b/checks/test_hook_encoder.py @@ -1,71 +1,143 @@ """Proves the hand-written awk JSON encoder in hooks/load-rules-subagent.sh. -The encoder must round-trip any rule text byte for byte, since a mis-escaped -rule would reach a sub-agent garbled or not at all. +For any rules folder holding at least one .md file, the subagent hook's +additionalContext, JSON-decoded, must be the exact bytes load-rules.sh prints +for the same folder, since a mis-escaped rule would reach a sub-agent garbled +or not at all. With no rules folder the session hook prints nothing, and with +an empty one only the header; in both cases the subagent hook fails loudly. """ import json import os import random import subprocess +import tempfile import unittest ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -HOOK = os.path.join(ROOT, "hooks", "load-rules-subagent.sh") -LOAD_RULES = os.path.join(ROOT, "hooks", "load-rules.sh") +PLUGIN = os.path.join(ROOT, "plugins", "freethinking") +HOOK = os.path.join(PLUGIN, "hooks", "load-rules-subagent.sh") +LOAD_RULES = os.path.join(PLUGIN, "hooks", "load-rules.sh") -# NUL is left out: it cannot cross a shell pipeline as text and never appears in a rule. +HEADER = ( + b"# Handbook rules\n\n" + b"Team rules that apply in every repository. They are shipped by the handbook plugin;" + b" to change one, open a PR against the rules/ folder of the handbook repository.\n\n" +) + +# NUL is left out: awk cannot carry it as text and it never appears in a rule. CONTROLS = [chr(c) for c in range(0x01, 0x20)] +def run_hook(script, folder): + env = dict(os.environ) + if folder is None: + env.pop("CLAUDE_PLUGIN_ROOT", None) + else: + env["CLAUDE_PLUGIN_ROOT"] = folder + return subprocess.run([script], capture_output=True, env=env) + + class HookEncoderTest(unittest.TestCase): - """The encoder round-trips hard inputs and wraps the real rules for SubagentStart.""" + """Both hooks agree on hard inputs and on the real rules, and handle a missing rules folder.""" def fail_check(self, part, why, text=None): where = "" if text is None else "\n input: %r" % text self.fail("hook encoder: %s failed: %s%s" % (part, why, where)) - def hook_json(self, part, args, stdin=None, text=None): - run = subprocess.run(args, input=stdin, capture_output=True) + def session_output(self, part, folder, text=None): + run = run_hook(LOAD_RULES, folder) + if run.returncode != 0: + why = run.stderr.decode("utf-8", "replace").strip() + self.fail_check(part, "load-rules.sh exited %d: %s" % (run.returncode, why), text) + return run.stdout + + def hook_json(self, part, folder, text=None): + run = run_hook(HOOK, folder) if run.returncode != 0: why = run.stderr.decode("utf-8", "replace").strip() self.fail_check(part, "hook exited %d: %s" % (run.returncode, why), text) try: - return json.loads(run.stdout.decode("utf-8"))["hookSpecificOutput"] + out = json.loads(run.stdout.decode("utf-8"))["hookSpecificOutput"] except (ValueError, KeyError, TypeError) as err: self.fail_check(part, "output is not the hook JSON (%s): %r" % (err, run.stdout), text) + if out.get("hookEventName") != "SubagentStart": + self.fail_check(part, "hookEventName is %r, not SubagentStart" % out.get("hookEventName"), text) + return out - def roundtrip(self, part, text): - want = text.encode("utf-8") - got = self.hook_json(part, [HOOK, "-"], want, text).get("additionalContext") + def compare(self, part, folder, text=None): + """Fails unless the decoded additionalContext is the session output byte for byte; returns both.""" + want = self.session_output(part, folder, text) + got = self.hook_json(part, folder, text).get("additionalContext") if not isinstance(got, str) or got.encode("utf-8") != want: - self.fail_check(part, "decoded text differs: %r" % got, text) + self.fail_check(part, "decoded additionalContext %r is not the load-rules.sh output %r" % (got, want), text) + return want, got + + def roundtrip(self, part, text): + with tempfile.TemporaryDirectory() as folder: + os.makedirs(os.path.join(folder, "rules")) + with open(os.path.join(folder, "rules", "a.md"), "wb") as f: + f.write(text.encode("utf-8")) + want, got = self.compare(part, folder, text) + if text not in got: + self.fail_check(part, "decoded additionalContext does not contain the input", text) + return want + + def roundtrip_batch(self, part, texts): + """Writes each text as its own rule file, in name order, and checks the folder as one run.""" + with tempfile.TemporaryDirectory() as folder: + os.makedirs(os.path.join(folder, "rules")) + for i, text in enumerate(texts): + with open(os.path.join(folder, "rules", "f%02d.md" % i), "wb") as f: + f.write(text.encode("utf-8")) + _, got = self.compare(part, folder, texts) + for text in texts: + if text.endswith("\n") and text not in got: + self.fail_check(part, "decoded additionalContext does not contain one of the inputs", text) def test_fixed_table(self): - """The known-hard inputs round-trip.""" + """The known-hard inputs give the same text from both hooks.""" fixed = ["", "\"", "\\", "\\\"", "é", "no trailing newline", "one\r\ntwo\r\n"] + fixed += ["first line\nlast line without a newline"] fixed += CONTROLS + ["line " + c + " end\n" for c in CONTROLS] for text in fixed: self.roundtrip("fixed table", text) + framed = self.roundtrip("fixed table", "rule text\nno newline at end") + if framed != HEADER + b"rule text\nno newline at end\n\n": + self.fail_check("fixed table", "load-rules.sh output is not the header, the file, then a blank line: %r" % framed) def test_seeded_fuzz(self): - """500 strings of 0 to 2,000 characters, weighted to the hard characters, round-trip.""" + """500 strings of 0 to 2,000 characters, weighted to the hard characters, give the same text from both hooks. + + The strings are written 20 to a rules folder, so the run stays short and + the blank line between files is checked on fuzz data too. + """ pool = list("The quick brown fox 0123456789 .,;:!?-_/()[]{}") pool += list("\"\\\n\r") * 8 + CONTROLS * 2 + [chr(0x7f)] pool += list("éßñ日本語€🙂") * 2 rng = random.Random(1) - for _ in range(500): - self.roundtrip("fuzz", "".join(rng.choices(pool, k=rng.randint(0, 2000)))) + texts = ["".join(rng.choices(pool, k=rng.randint(0, 2000))) for _ in range(500)] + for start in range(0, len(texts), 20): + self.roundtrip_batch("fuzz", texts[start:start + 20]) def test_real_run(self): - """With no argument the hook wraps the load-rules.sh output for SubagentStart.""" - rules = subprocess.run([LOAD_RULES], capture_output=True, check=True).stdout - real = self.hook_json("real run", [HOOK]) - if real.get("hookEventName") != "SubagentStart": - self.fail_check("real run", "hookEventName is %r, not SubagentStart" % real.get("hookEventName")) - got = real.get("additionalContext") - if not isinstance(got, str) or got.encode("utf-8") != rules: - self.fail_check("real run", "additionalContext is not the load-rules.sh output") + """On the plugin folder both hooks give the same rules text.""" + want, _ = self.compare("real run", PLUGIN) + if not want.startswith(HEADER) or len(want) <= len(HEADER) + 1: + self.fail_check("real run", "load-rules.sh printed no rules after the header") + + def test_no_rules(self): + """With CLAUDE_PLUGIN_ROOT unset or without rules/, the session hook prints nothing and the subagent hook fails with one line.""" + with tempfile.TemporaryDirectory() as empty: + for case, folder in (("unset", None), ("no rules/", empty)): + part = "no rules (%s)" % case + session = run_hook(LOAD_RULES, folder) + if session.returncode != 0 or session.stdout: + self.fail_check(part, "load-rules.sh exited %d with output %r" % (session.returncode, session.stdout)) + sub = run_hook(HOOK, folder) + lines = sub.stderr.decode("utf-8", "replace").splitlines() + if sub.returncode == 0 or sub.stdout or len(lines) != 1: + self.fail_check(part, "hook exited %d, stdout %r, stderr %r" % (sub.returncode, sub.stdout, sub.stderr)) if __name__ == "__main__": diff --git a/checks/test_issue_forms.py b/checks/test_issue_forms.py index 1a9f466..f666d9a 100644 --- a/checks/test_issue_forms.py +++ b/checks/test_issue_forms.py @@ -6,6 +6,7 @@ from pathlib import Path ROOT = Path(__file__).resolve().parent.parent +PLUGIN = ROOT / "plugins" / "freethinking" FORMS_DIR = ROOT / ".github" / "ISSUE_TEMPLATE" CATCH_ALL = "Something else" @@ -23,10 +24,10 @@ def component_options(path): def components_on_disk(): """Return the dropdown entries the repository's components call for, catch-all excluded.""" - skills = ["{} (skill)".format(p.parent.name) for p in sorted((ROOT / "skills").glob("*/SKILL.md"))] - agents = ["{} (agent)".format(p.stem) for p in sorted((ROOT / "agents").glob("*.md"))] - rules = ["{} (rule)".format(p.stem) for p in sorted((ROOT / "rules").glob("*.md"))] - events = json.loads((ROOT / "hooks" / "hooks.json").read_text(encoding="utf-8"))["hooks"] + skills = ["{} (skill)".format(p.parent.name) for p in sorted((PLUGIN / "skills").glob("*/SKILL.md"))] + agents = ["{} (agent)".format(p.stem) for p in sorted((PLUGIN / "agents").glob("*.md"))] + rules = ["{} (rule)".format(p.stem) for p in sorted((PLUGIN / "rules").glob("*.md"))] + events = json.loads((PLUGIN / "hooks" / "hooks.json").read_text(encoding="utf-8"))["hooks"] hooks = ["{} hook".format(event) for event in sorted(events)] return set(skills + agents + rules + hooks) diff --git a/checks/test_listing.py b/checks/test_listing.py new file mode 100644 index 0000000..f81ce8f --- /dev/null +++ b/checks/test_listing.py @@ -0,0 +1,116 @@ +"""The plugin folder meets the plugin directory's mechanical listing rules. + +The directory portal rejects a plugin whose README holds fewer than 40 words +outside code blocks, that has no license, or whose icon is not a square PNG of +512 to 2048 pixels a side. These checks catch each before a submission does. +""" + +import json +import re +import struct +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +PLUGIN = ROOT / "plugins" / "freethinking" + +README_MIN_WORDS = 40 +ICON_MIN_PX = 512 +ICON_MAX_PX = 2048 +PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" +FENCE_OPENER = re.compile(r"`{3,}|~{3,}") + + +def read_manifest(folder): + """Return the parsed ``.claude-plugin/plugin.json`` of a plugin folder.""" + return json.loads((folder / ".claude-plugin" / "plugin.json").read_text(encoding="utf-8")) + + +def readme_word_count(folder): + """Return the number of words in the folder's ``README.md`` outside fenced code blocks, or None if there is no README.""" + readme = folder / "README.md" + if not readme.is_file(): + return None + return sum(len(line.split()) for line in prose_lines(readme.read_text(encoding="utf-8"))) + + +def prose_lines(text): + """Yield the lines of Markdown text that lie outside fenced code blocks; an unclosed fence runs to the end.""" + fence = None + for line in text.splitlines(): + stripped = line.strip() + if fence is None: + opener = FENCE_OPENER.match(stripped) + if opener: + fence = opener.group() + else: + yield line + elif stripped.startswith(fence) and stripped == fence[0] * len(stripped): + fence = None + + +def has_license(folder, manifest): + """Return True when the folder has a ``LICENSE`` file or the manifest has a ``license`` field.""" + return (folder / "LICENSE").is_file() or bool(manifest.get("license")) + + +def icon_size(folder, manifest): + """Return the ``(width, height)`` of the PNG the manifest's ``icon`` field names. + + Raises ValueError naming the problem when the field is missing or not a + string, the path is absolute or leaves the plugin folder, the file is + absent, or the file is not a PNG. + """ + icon = manifest.get("icon") + if not icon: + raise ValueError("the manifest has no icon field") + if not isinstance(icon, str): + raise ValueError("icon {!r} is not a string".format(icon)) + if Path(icon).is_absolute(): + raise ValueError("icon {} is an absolute path; it must be relative to the plugin folder".format(icon)) + path = folder / icon + if not path.resolve().is_relative_to(folder.resolve()): + raise ValueError("icon {} resolves outside the plugin folder".format(icon)) + if not path.is_file(): + raise ValueError("icon {} names no file".format(icon)) + header = path.read_bytes()[:24] + if len(header) < 24 or header[:8] != PNG_SIGNATURE or header[12:16] != b"IHDR": + raise ValueError("icon {} is not a PNG".format(icon)) + return struct.unpack(">II", header[16:24]) + + +class ListingTest(unittest.TestCase): + def setUp(self): + self.manifest = read_manifest(PLUGIN) + + def test_readme_has_enough_words(self): + words = readme_word_count(PLUGIN) + self.assertIsNotNone(words, "{} has no README.md".format(PLUGIN.relative_to(ROOT))) + self.assertGreaterEqual( + words, + README_MIN_WORDS, + "README.md has {} words outside fenced blocks; the minimum is {}".format(words, README_MIN_WORDS), + ) + + def test_license_is_declared(self): + self.assertTrue( + has_license(PLUGIN, self.manifest), + "{} has no LICENSE file and plugin.json has no license field".format(PLUGIN.relative_to(ROOT)), + ) + + def test_icon_is_a_square_png_of_the_right_size(self): + try: + width, height = icon_size(PLUGIN, self.manifest) + except ValueError as error: + self.fail(str(error)) + self.assertEqual(width, height, "icon is {} by {} px; it must be square".format(width, height)) + for side in (width, height): + with self.subTest(side=side): + self.assertTrue( + ICON_MIN_PX <= side <= ICON_MAX_PX, + "icon side is {} px; it must be {} to {} px".format(side, ICON_MIN_PX, ICON_MAX_PX), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/checks/test_report_skill.py b/checks/test_report_skill.py index 41821da..863a881 100644 --- a/checks/test_report_skill.py +++ b/checks/test_report_skill.py @@ -9,7 +9,8 @@ from pathlib import Path ROOT = Path(__file__).resolve().parent.parent -REPORT_DIR = ROOT / "skills" / "report" +PLUGIN = ROOT / "plugins" / "freethinking" +REPORT_DIR = PLUGIN / "skills" / "report" SKILL = REPORT_DIR / "SKILL.md" TEMPLATES_DIR = REPORT_DIR / "templates" diff --git a/checks/test_rules_size.py b/checks/test_rules_size.py index 9b8f816..7149a3d 100644 --- a/checks/test_rules_size.py +++ b/checks/test_rules_size.py @@ -10,8 +10,9 @@ import unittest ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -LOAD_RULES = os.path.join(ROOT, "hooks", "load-rules.sh") -RULES = os.path.join(ROOT, "rules") +PLUGIN = os.path.join(ROOT, "plugins", "freethinking") +LOAD_RULES = os.path.join(PLUGIN, "hooks", "load-rules.sh") +RULES = os.path.join(PLUGIN, "rules") HOOK_LIMIT = 10000 RULE_LIMITS = { @@ -24,7 +25,7 @@ class RulesSizeTest(unittest.TestCase): """The hook output and each limited rule stay within their character limits.""" def test_hook_output_is_under_the_limit(self): - env = dict(os.environ, CLAUDE_PLUGIN_ROOT=ROOT) + env = dict(os.environ, CLAUDE_PLUGIN_ROOT=PLUGIN) run = subprocess.run([LOAD_RULES], env=env, capture_output=True, check=True) count = len(run.stdout.decode("utf-8")) self.assertLess( diff --git a/evals/README.md b/evals/README.md index 3e01d29..9fc4141 100644 --- a/evals/README.md +++ b/evals/README.md @@ -8,9 +8,9 @@ list-price estimates, usually ten to thirty cents a case. ```text evals/ hook// # the SessionStart and SubagentStart hooks that load the rules - rules/// # one folder per rules/.md - skills/// # one folder per skills// - agents/// # one folder per agents/.md + rules/// # one folder per plugins/freethinking/rules/.md + skills/// # one folder per plugins/freethinking/skills// + agents/// # one folder per plugins/freethinking/agents/.md fixtures// # a project the scaffolds build, copied in by fixtures/setup.sh results/ # one folder per run, gitignored ``` @@ -25,10 +25,14 @@ make eval-hook make eval # the whole suite ``` -Every case carries its component's name as a tag, and each target is -`claude plugin eval . --tag ` plus the flags its cases need, which live -in the Makefile's "Per-target flags" block. Anything extra goes through -`EVAL_ARGS`, and the per-invocation cost ceiling through `EVAL_MAX_COST_USD` +Every case carries its component's name as a tag. `evals/` sits outside the +plugin folder (`plugins/freethinking/`); because `claude plugin eval` reads +cases only from inside the plugin, each target runs it against a temporary copy +of `plugins/freethinking/` with `evals/` inside, with `--tag ` plus the +flags its cases need, which live in the Makefile's "Per-target flags" block. +Results come back to `evals/results/`, and the copy is deleted after the run, +pass or fail. Anything extra goes through `EVAL_ARGS`, and the +per-invocation cost ceiling through `EVAL_MAX_COST_USD` (default $2, which the full suite and `make eval-skill-implement-spec` need raised): ```bash @@ -38,13 +42,13 @@ make eval EVAL_MAX_COST_USD=5 ``` Run only the target of the component your change touched, never the whole -suite, unless the change is to `hooks/` or the Makefile. A single run is noisy, -so iterate with `EVAL_ARGS="--runs 1"`, then confirm before trusting a change: -pass `--runs 2` when every grader of the case is `regex`, `tool_used` or -`file_exists`, and `--runs 3` when any grader is `llm`. A target whose cases -mix the two confirms at 3, or run it at 2 and then each `llm` case at 3 with -one `--case` each. Without `--runs`, a plain `make eval-…` still runs each case -three times and the table shows the mean. +suite, unless the change is to `plugins/freethinking/hooks/` or the Makefile. +A single run is noisy, so iterate with `EVAL_ARGS="--runs 1"`, then confirm +before trusting a change: pass `--runs 2` when every grader of the case is +`regex`, `tool_used` or `file_exists`, and `--runs 3` when any grader is `llm`. +A target whose cases mix the two confirms at 3, or run it at 2 and then each +`llm` case at 3 with one `--case` each. Without `--runs`, a plain +`make eval-…` still runs each case three times and the table shows the mean. `--case` takes one glob, and when you pass it more than once the last flag wins, so run several named cases with one invocation each. @@ -170,14 +174,19 @@ workspace, commits it and checks out the branch; `wrappers` adds the `bin/` wrappers for cases that grant Bash and put `bin` on the PATH. Each case's `scaffold.sh` is its own regular file, never a symlink: it finds the setup script from the repository root, `git -C "$(dirname "$0")" rev-parse ---show-toplevel`, calls it and adds only what the case needs. The pr cases -are the exception to the fixture rule: each carries a full copy of the fake -`gh` and the git wrapper in its own scaffold, because the fake must be -reachable through a relative `bin` entry built in the workspace, so a change -to the fake is made in every pr scaffold. Extra files a case needs live in its own `files/` folder; the harness copies nothing from -it, the case's scaffold does. Copied before `setup.sh` runs, they land in its -one commit, on main and on the branch, as works-one-wave does; copied after, -they are left uncommitted. Give a case one grader on the result and one on the +--show-toplevel`, calls it and adds only what the case needs. Inside a run the +scaffold runs from the temporary copy, where that command resolves to the +copy's top. That top holds `evals/fixtures/` and +`.github/pull_request_template.md`, the two things scaffolds read from the +repository, so nothing in a scaffold changes. The pr cases are the exception +to the fixture rule: each carries a full copy of the fake `gh` and the git +wrapper in its own scaffold, because the fake must be reachable through a +relative `bin` entry built in the workspace, so a change to the fake is made in +every pr scaffold. Extra files a case needs +live in its own `files/` folder; the harness copies nothing from it, the case's +scaffold does. Copied before `setup.sh` runs, they land in its one commit, on +main and on the branch, as works-one-wave does; copied after, they are left +uncommitted. Give a case one grader on the result and one on the steps where both make sense. Prefer graders that read the transcript or a file (`regex`, `tool_used`, `file_exists`, `target: { source: file }`); an `llm` grader adds three judge calls per run. `file_exists` counts only files created diff --git a/hooks/load-rules-subagent.sh b/hooks/load-rules-subagent.sh deleted file mode 100755 index 0c968c9..0000000 --- a/hooks/load-rules-subagent.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/sh -# SubagentStart hook: prints the handbook rules so Claude Code adds them to a -# sub-agent's context. For that event plain stdout is not added to the context; -# only the additionalContext field of a JSON object on stdout is. So this script -# runs load-rules.sh, the SessionStart hook, and wraps its output in that JSON, -# which keeps rules/ the one source for both. The JSON string encoding is POSIX -# awk, so the plugin needs no python3, node or jq. Given `-` as its only -# argument it encodes stdin instead of the rules, which is what the hook check -# feeds it. Exits non-zero on any failure. -set -eu - -hooks="$(dirname "$0")" - -# Read the whole text with a sentinel after it, so that trailing newlines, -# which command substitution strips, reach the encoder and come back out. -if [ $# -eq 0 ]; then - text=$("$hooks/load-rules.sh" && printf X) || { - echo "${0##*/}: load-rules.sh failed, so this sub-agent gets no rules" >&2 - exit 1 - } -elif [ $# -eq 1 ] && [ "$1" = - ]; then - text=$(cat && printf X) -else - echo "usage: ${0##*/} [-]" >&2 - exit 1 -fi - -# Escape backslash, double quote and every control character (0x01-0x1f) and -# pass every other byte through, so non-ASCII text stays as its UTF-8 bytes. -# awk reads a line at a time: each line is printed one behind, with \n between -# lines, and the sentinel ends the last one, so the text is reproduced exactly. -printf '%s' "$text" | awk ' -function emit(s, i, n, c) { - n = length(s) - for (i = 1; i <= n; i++) { - c = substr(s, i, 1) - if (c in esc) c = esc[c] - printf "%s", c - } -} -BEGIN { - for (code = 1; code < 32; code++) esc[sprintf("%c", code)] = sprintf("\\u%04x", code) - esc["\b"] = "\\b"; esc["\t"] = "\\t"; esc["\n"] = "\\n"; esc["\f"] = "\\f"; esc["\r"] = "\\r" - esc["\""] = "\\\""; esc["\\"] = "\\\\" - printf "{\"hookSpecificOutput\": {\"hookEventName\": \"SubagentStart\", \"additionalContext\": \"" -} -NR > 1 { emit(prev); printf "\\n" } -{ prev = $0 } -END { - emit(substr(prev, 1, length(prev) - 1)) - printf "\"}}\n" -}' diff --git a/hooks/load-rules.sh b/hooks/load-rules.sh deleted file mode 100755 index 2e2cae1..0000000 --- a/hooks/load-rules.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -# SessionStart hook: prints the handbook rules so Claude Code adds them to the -# session context. Claude Code does not load CLAUDE.md or rules/ from a plugin -# on its own, so this script concatenates every file under rules/ instead. -# Keep the total under 10,000 characters; Claude Code truncates hook output past that. -set -eu - -root="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}" -dir="$root/rules" -[ -d "$dir" ] || exit 0 - -printf '# Handbook rules\n\n' -printf 'Team rules that apply in every repository. They are shipped by the handbook plugin; to change one, open a PR against the rules/ folder of the handbook repository.\n\n' - -for f in "$dir"/*.md; do - [ -f "$f" ] || continue - cat "$f" - printf '\n' -done diff --git a/plugins/freethinking/.claude-plugin/icon.png b/plugins/freethinking/.claude-plugin/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ea1b5b3e212103511ad97181556570bd81ac70f8 GIT binary patch literal 122489 zcmeEug%F+Rrp}zR&)#d@>t6R-dm_|S)X+0xUMnI~=m@$Kd$It-B-w)zVST_FH|HT+G$CZGm(u?W3= zFNr`92S#F~1)?i}5C}e_1bRPv|Avj}pV$BSD3CT6!C2(MQE{M9w9f#|&y`QU{`n^S zV@-f9;{Q1r9*`J}@#?#5^4|-@fib{#@C*Gt8!>$@f?D3d_sLDE}Sz{W$(N zS^r&)|4r6^%=v$o^&jU5{GS{C2Rr|VS^t6E{{j4eW7Pkm;eUC^|ERW zZNy*=ngAzX5(sQFq{-;pPrJWf5y#;{u$7eES`1Uk7E5Y6PGfVD#aVR_#uJiAw71af#(iQI+%TzvL5~= zhBlLW`?@9L*5=XynZS`xn^sme+JnI zA7ts0f&1~aO^s##%t5tJvtcu}4YM+H;>7W`qaar6(i*vFv?ISNlV+%#nbx;;WD=<} zReq@*fQuCLL5?uT22WJlS_PwV8+9sKe%SI0@bkAocLpUO+p7ES84ReXsH54fSGS9> z7-nvY=z3>Cs;o?^O+7V&8;F17yj@MWDLbD?Z?I>3Xc`gLpQHuu!FAH`G-B{b<1&Ev zPSr|ye`@FV4XKgu_`dr%5ykM7HQ(D>y}pZn4m6FN)BZIMHo(&Ys8@kprewSmjyn`+iL?z`wE8AnI#2ut}g#KnX zfsIVL2uIAYt+G+Si}ApK7}tPoEJ%^-j@i=Up__+?hpH;3D%i3rum<-LEMyF?p;?O` z@OT65`5XOWhMv_U7DM097@hRks}IJKlX`D`zJmvMr>ATI7E9``0IDDl_wVUw)}qud zUjy%q$~J9Iko$|4Oi6OL&rCni@*(Mm)Gxg+@>cjiR-6M**|Aiw zRgEZUY;5fDO?SJ8pC4pn}>^#qcpNVY=`31{G*T(|Vc1_GL?z(xn zdwhsS@lcT(QYC=Skh=uaZ_zw@l#PYjwY|5AU>m?qZ<+@{_!02q8PRcSdL!50l+e-U z-UdO@TSh2Imt%g{7kvXTK!>o80q_?2wPwge2ei+GkCj)wz#XCaJbY*9JorpS0M_pqE{Q8IGS8_L zxZd5oql~ZVQ2o2azHHX^8UA*JgPmR03uX=)cVYCB+@EiAG952j*#M&KMBu6_MgPaR z9VN7Ok+vK?6LiGEK~WM9mQp|pguH|irE?>%DZe5Qz`cbo<0;YX1@u7thi*psB>Qd^ zUmCOKPH(GZOvshQbhXyh_k)2q01qBoFe#7$v3Y?tmae=z3#_q)s5n(tF{MpG9_|D- z=d0$EA0wVQjeDpOh<||J8Ivp8I$QP0OTNH~iT;HJ>L3w9aA`>ijtWYaijOd4!glKI zvsq=}%~}6J&xY`Cgm+LZ(!1@^0KrE&yl7zDc1OOO{prOEqeB{|JQ#N<3ROTr-=M8Jro&N4H%_)xhv~)o24%>ELLH+`Lvo*?X!6cyu9n zP9P7GceFyvEnkgYZyQ6@&20OQY6Xm`)!W~`CZ7naB(!fYD$-9vMN+_%4Pxg3nmU4! zk*Bje3B`3QOMi zKvyaGbwtWNd_@@=vF-n%j*w*@MN6z#J~$X6&3v7iZPKh^WMpI`ptszX(F3g9i9>QR zKliSk?fe+_L>iS9(G?0CY`xv&$`)@&1v4nzb&jXm#Z>JaEiAD6Vttz6nAyx+6THS+ zhE*b{v7$Sqv@<+#3WB0a#DHWXVMtA6n@*c%;;qWQv^6qz&3r2@w4#(P)c5 zal9yNDo@q>iAKOs)O&L}H6cKS5HU|!18G?HBJG66CXc02bxO-j2T%+~(Nip7J`&LRCq;PQ4l?g}%SR>k=%*%#y9zf6mfHt-Ve8%Orf6^;s*u`9)= z?sS0GwK{ml-aQS*Y~_weq9q)uqn8`4j8c6EtyK9U5}HBoBGp*-rDj}R+ETGyzt)3@ zA_`9J8$?_a3etuM&tE?@CYpP&$*RWqlC>MYj0FacMv`*j uY*a}PukRgG5xw>3S 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ʝ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

E+(og(u3Viq?JMyF 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%0Asp@``YW*F!^9Uqfb7a2S`s1-HkNZwdX2iI9 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, or ask Claude to change something, or to start or spec a task | The front door for any change: reads the code the request touches, then sends one contained change to the `task-executor` agent, a defect to `bugfix` and everything else to a spec, asking you before taking either of the first two. On the spec route, writes `requirements.md`, `design.md` and `tasks.md` in `.claude/specs//` before any code, following the team template, with you approving each document before the next. | +| `implement-spec` | `/freethinking:implement-spec [task] [task\|wave\|spec]`, or ask Claude to implement, build or continue a spec | Builds a task from its approved spec, wave by wave from `tasks.md`, each leaf task in its own sub-agent, ticking a task only once its tests pass, running the full suite at the end of every wave, and handing back to you after each task, after each wave, or only once every wave is done (with a short report after each wave and a final review of the whole run), whichever you chose. Pauses with a proposal when the design cannot be followed, and hands off to `pr` when the spec is done. | +| `bugfix` | `/freethinking:bugfix [what you already know]`, or ask Claude to fix a bug | Reads the ticket, and anything you already know that you put after it, reproduces the bug before touching code, confirms the root cause with you, fixes the cause with a regression test, verifies the fix, then hands off to `pr` or leaves its findings on the ticket. | +| `pr` | `/freethinking:pr [ticket] [base-branch]`, or ask Claude to open a PR | Takes the ticket as its first argument and reads it, offers to create the branch and commit if needed, walks the branch's changes with you one at a time with a short set of review prompts, drafts the title and description from your answers, and opens the PR with `gh` once you approve. Needs the `gh` CLI signed in. | +| `report` | `/freethinking:report [mode]`, or the session invokes it when it hands back work, asks you for a decision, reports status or answers a question | Writes that message for you in one of four shapes, handback, decision, status or answer, each from a template; the handback and status templates put problems first, and every template names things by what they are. A repository can replace a template under `.freethinking/report/`. The skill edits nothing. | + +## Rules + +The plugin also loads a short set of team rules into every Claude Code session, +in every repository, so Claude follows them without being asked. They live in +`rules/`: + +| File | Covers | +| ---- | ------ | +| `rules/workflow.md` | Which skill to use when, routing on what you said or a file on disk and taking the first rule that matches: `bugfix` when you call it a bug, `implement-spec` when a spec exists, `pr` for pull requests, no skill for a typo-sized change, and `start-task` for any other change, which reads the code and picks between the `task-executor` agent for one contained change, `bugfix` for a defect and a spec for the rest, asking before the first two. Also: never work on the default branch, and say so when the approach changes. | +| `rules/code-changes.md` | How to change code: read the surrounding code first, match the project's style and libraries, make the smallest change that does the job, add a test with every behaviour change, run what you touched, and leave no debug residue behind. | +| `rules/comments.md` | How to comment: docstrings on public contracts only, saying intent, parameters, returns and exceptions; inline comments only for non-obvious details such as workarounds and edge cases, never for what a line does; no divider or closing-brace comments; no commented-out dead code. | +| `rules/collaboration.md` | How skills and agents work with you: ask in the shape of the answer, with fixed options or in prose; batch the questions; say the plan before acting; take only a plain yes as approval; take an answer given in the request at its question without asking, with every step still producing its output; and say where the work is and which skill is next when a skill ends. | +| `rules/communication.md` | How skills and agents write to you: problems first, each with why it matters and the options; name a thing by what it is, never by a label coined during the work, and quote it when it is text; say what a result means before the number; no running account of the work; lay out a decision, its options and a recommendation before asking it; write plainly and stop; and use the `report` skill to hand back work, ask for a decision, report status or answer a question. | + +Claude Code does not load a `CLAUDE.md` or a `rules/` folder from a plugin by +itself, so `hooks/hooks.json` registers two hooks. The `SessionStart` hook runs +`hooks/load-rules.sh`, which prints every `.md` file under `rules/` into the +session context on every session start, whatever started it (a new session, +a resume, a clear, a compact or a fork). The `SubagentStart` hook runs +`hooks/load-rules-subagent.sh`, which puts the same rules into every sub-agent, +which is how both agents below get them. Keep the +combined rules under 10,000 characters; Claude Code truncates hook output past +that, and `make validate` fails when they go over, or when `collaboration` or +`communication` goes over its own limit of 1,600 characters. A rule a repository needs +to override belongs in that repository's own `CLAUDE.md`, which Claude reads +alongside these. + +## Agents + +The plugin ships two agents under `agents/`. `implement-spec` launches both, +`start-task` launches the executor for one contained change, and you can +launch either directly: mention it in a session as +`@agent-freethinking:task-executor` or `@agent-freethinking:wave-reviewer`, or have a +skill or session launch it with the Agent tool as +`subagent_type: freethinking:task-executor` or `freethinking:wave-reviewer`. + +What the prompt carries: + +- **`task-executor`**: the change, in your own words. When the task comes from + a spec, say so in the prompt; that sentence switches it to spec mode, where + it writes no test unless the task asks for one. +- **`wave-reviewer`**: the files that changed, or a git range; the requirement + or design text they implement, if there is any; the files that cite them; + and, when they come from a spec, the sentence "These changes come from a + spec wave", which is the only thing that waives a missing test. + +| Agent | What it does | What it returns | +| ----- | ------------ | --------------- | +| `task-executor` | Builds one well-specified change as the smallest change in the project's style, proves it, and deletes its scratch work. Stops and asks instead of guessing when the task is unclear, the design does not fit the code, or the task would need a new dependency, framework or tool. `start-task` launches it for one contained change, and `implement-spec` for every leaf task. | A Summary: `status` (`done` or `stopped`), `files changed`, `checks run` with their results, the ephemeral check it ran, or why none applied, what it is `unsure` of, and, when stopped, the question or evidence under `stopped because`. | +| `wave-reviewer` | Reviews one set of changes against the requirements and design they implement and the files that cite them, gathering the diff itself and changing nothing. `implement-spec` launches it at the end of every wave, and once more over the whole run when you chose to hand back only at the end. | Findings: a `verdict` (`holds` or `findings`), the `base` commit the diffs were measured against, then each problem with its `file`, an exact `quote`, the `problem` and a `severity` (`blocker`, `should-fix` or `nit`), and what it is `unsure` of. | + +Both agents follow one protocol for their reports, and a new agent must too: +its report opens with its status on the first line, `status: done` or `stopped` +for the executor and `verdict: holds` or `findings` for the reviewer, has one +field for each thing the launcher must act on, always has an `unsure` field, +quotes exactly whatever it quotes, and has nothing after the shape. + +The executor works in one of two modes, set by the launch prompt alone: + +- **Spec mode**, when the prompt says the task comes from a spec, in any words; + `implement-spec` says "This task is a leaf task from a spec; tests are tasks + of their own." Tests are tasks of their own there, so it writes a test only + when the task asks for one, and proves a code task with an ephemeral check + that it deletes and reports in its Summary. +- **Standalone mode**, otherwise. The change comes with its test, written the + way the project writes tests. + +The reviewer treats a missing test as a finding unless the launch says the +changes come from a spec wave. + +## Tune the plugin for a repository + +A repository can keep its own versions of the plugin's template files in a +`.freethinking/` folder at its root, laid out as `/.md`, such as +`.freethinking/report/handback.md`. A file there replaces the plugin's file of +the same name whole; nothing is merged. Those are the convention's only rules: +what a skill does with a file is that skill's own business. This release +applies the convention to the four templates of the `report` skill: +`handback.md`, `decision.md`, `status.md` and `answer.md`. The `report` skill +reads them as shapes to write in and ignores any action a template asks for, +as its own text says. A repository's `handback.md` may use its own sections: +`implement-spec` reads a wave report by what it holds, problems, what was done, +checks, files and the next step, and the plugin's own evals grade only the +plugin's template, so renaming sections costs nothing but the two verdict lines +being found by content rather than by name. The spec templates of `start-task` and the default +pull request template of `pr` are candidates to move there later. + +## What the plugin runs and sends + +The `pr` and `bugfix` skills read issues through the `gh` command already +signed in on your machine; `bugfix` also comments on issues and hands off to +`pr`, which opens the pull request. Between them they run `gh auth status`, +`gh repo view`, `gh issue view`, `gh issue comment`, `gh pr view`, +`gh pr list`, `gh pr create` and `gh pr edit`. + +The `start-task`, `implement-spec`, `bugfix` and `pr` skills and the +`wave-reviewer` agent run `git` on the current repository (status, diff, log, +branch, rev-parse, merge-base, `checkout -b`, and in `pr` add and commit); +only `pr` reaches the remote, with `git fetch` and `git push` to the +repository's own `origin` and the `gh` commands above. + +The `SessionStart` and `SubagentStart` hooks read only the files under the +plugin's `rules/` folder. + +No skill, agent or hook reads a credential, runs a package launcher or sends +anything anywhere else. + +The source repository's `evals/` and `.claude/specs/` folders are its test +suite and its design records. They sit outside the plugin, and an installed +plugin never runs them. diff --git a/agents/task-executor.md b/plugins/freethinking/agents/task-executor.md similarity index 100% rename from agents/task-executor.md rename to plugins/freethinking/agents/task-executor.md diff --git a/agents/wave-reviewer.md b/plugins/freethinking/agents/wave-reviewer.md similarity index 100% rename from agents/wave-reviewer.md rename to plugins/freethinking/agents/wave-reviewer.md diff --git a/hooks/hooks.json b/plugins/freethinking/hooks/hooks.json similarity index 100% rename from hooks/hooks.json rename to plugins/freethinking/hooks/hooks.json diff --git a/plugins/freethinking/hooks/load-rules-subagent.sh b/plugins/freethinking/hooks/load-rules-subagent.sh new file mode 100755 index 0000000..183fb12 --- /dev/null +++ b/plugins/freethinking/hooks/load-rules-subagent.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# SubagentStart hook: prints the handbook rules so Claude Code adds them to a +# sub-agent's context. For that event plain stdout is not added to the context; +# only the additionalContext field of a JSON object on stdout is. So this script +# prints the same text as load-rules.sh, the SessionStart hook, wrapped in that +# JSON. The JSON string encoding is POSIX awk, so the plugin needs no python3, +# node or jq. Exits non-zero on any failure. +# +# The plugin directory's validator only accepts a hook script that writes every +# path in full from CLAUDE_PLUGIN_ROOT, uses no other variable, no command +# substitution and calls no other file in the plugin. So this script cannot run +# load-rules.sh: it writes the same header and reads rules/ itself. +# checks/test_hook_encoder.py pins the same header in its HEADER constant, so a +# header change touches the three together. +set -eu + +[ -d "${CLAUDE_PLUGIN_ROOT:-}/rules" ] || { + echo "load-rules-subagent.sh: no rules under CLAUDE_PLUGIN_ROOT, so this sub-agent gets no rules" >&2 + exit 1 +} + +# With no .md file under rules/ the glob stays unexpanded and ls fails; an empty +# rules/ gives the sub-agent no rules, so it fails the same way as its own +# no-rules/ check above. load-rules.sh, by contrast, prints the header and exits 0. +ls "${CLAUDE_PLUGIN_ROOT}"/rules/*.md >/dev/null 2>&1 || { + echo "load-rules-subagent.sh: no rule files under CLAUDE_PLUGIN_ROOT/rules, so this sub-agent gets no rules" >&2 + exit 1 +} + +# Escape backslash, double quote and every control character (0x01-0x1f) and +# pass every other byte through, so non-ASCII text stays as its UTF-8 bytes. +# Each record ends with an escaped newline, as load-rules.sh prints it with one. +awk ' +function emit(s, i, n, c) { + n = length(s) + for (i = 1; i <= n; i++) { + c = substr(s, i, 1) + if (c in esc) c = esc[c] + printf "%s", c + } +} +BEGIN { + for (code = 1; code < 32; code++) esc[sprintf("%c", code)] = sprintf("\\u%04x", code) + esc["\b"] = "\\b"; esc["\t"] = "\\t"; esc["\n"] = "\\n"; esc["\f"] = "\\f"; esc["\r"] = "\\r" + esc["\""] = "\\\""; esc["\\"] = "\\\\" + printf "{\"hookSpecificOutput\": {\"hookEventName\": \"SubagentStart\", \"additionalContext\": \"" + printf "# Handbook rules\\n\\n" + printf "Team rules that apply in every repository. They are shipped by the handbook plugin; to change one, open a PR against the rules/ folder of the handbook repository.\\n\\n" +} +FNR == 1 && NR > 1 { printf "\\n" } +{ emit($0); printf "\\n" } +END { printf "\\n\"}}\n" } +' "${CLAUDE_PLUGIN_ROOT}"/rules/*.md diff --git a/plugins/freethinking/hooks/load-rules.sh b/plugins/freethinking/hooks/load-rules.sh new file mode 100755 index 0000000..4f7d228 --- /dev/null +++ b/plugins/freethinking/hooks/load-rules.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# SessionStart hook: prints the handbook rules so Claude Code adds them to the +# session context. Claude Code does not load CLAUDE.md or rules/ from a plugin +# on its own, so this script prints every file under rules/ instead. +# Keep the total under 10,000 characters; Claude Code truncates hook output past that. +# +# The plugin directory's validator only accepts a hook script that writes every +# path in full from CLAUDE_PLUGIN_ROOT, uses no other variable, no command +# substitution and calls no other file in the plugin. So the rule files are +# printed by one awk program, and load-rules-subagent.sh, the SubagentStart +# hook, writes the same header and text itself. checks/test_hook_encoder.py +# pins the same header in its HEADER constant, so a header change touches the +# three together. With no .md file under rules/ the glob stays unexpanded, so +# ls fails and only the header is printed. +set -eu + +[ -d "${CLAUDE_PLUGIN_ROOT:-}/rules" ] || exit 0 + +printf '# Handbook rules\n\n' +printf 'Team rules that apply in every repository. They are shipped by the handbook plugin; to change one, open a PR against the rules/ folder of the handbook repository.\n\n' + +ls "${CLAUDE_PLUGIN_ROOT}"/rules/*.md >/dev/null 2>&1 || exit 0 + +awk ' +FNR == 1 && NR > 1 { print "" } +{ print } +END { print "" } +' "${CLAUDE_PLUGIN_ROOT}"/rules/*.md diff --git a/rules/code-changes.md b/plugins/freethinking/rules/code-changes.md similarity index 100% rename from rules/code-changes.md rename to plugins/freethinking/rules/code-changes.md diff --git a/rules/collaboration.md b/plugins/freethinking/rules/collaboration.md similarity index 100% rename from rules/collaboration.md rename to plugins/freethinking/rules/collaboration.md diff --git a/rules/comments.md b/plugins/freethinking/rules/comments.md similarity index 100% rename from rules/comments.md rename to plugins/freethinking/rules/comments.md diff --git a/rules/communication.md b/plugins/freethinking/rules/communication.md similarity index 100% rename from rules/communication.md rename to plugins/freethinking/rules/communication.md diff --git a/rules/workflow.md b/plugins/freethinking/rules/workflow.md similarity index 100% rename from rules/workflow.md rename to plugins/freethinking/rules/workflow.md diff --git a/skills/bugfix/SKILL.md b/plugins/freethinking/skills/bugfix/SKILL.md similarity index 100% rename from skills/bugfix/SKILL.md rename to plugins/freethinking/skills/bugfix/SKILL.md diff --git a/skills/implement-spec/SKILL.md b/plugins/freethinking/skills/implement-spec/SKILL.md similarity index 100% rename from skills/implement-spec/SKILL.md rename to plugins/freethinking/skills/implement-spec/SKILL.md diff --git a/skills/pr/SKILL.md b/plugins/freethinking/skills/pr/SKILL.md similarity index 100% rename from skills/pr/SKILL.md rename to plugins/freethinking/skills/pr/SKILL.md diff --git a/skills/pr/pull_request_template.md b/plugins/freethinking/skills/pr/pull_request_template.md similarity index 100% rename from skills/pr/pull_request_template.md rename to plugins/freethinking/skills/pr/pull_request_template.md diff --git a/skills/report/SKILL.md b/plugins/freethinking/skills/report/SKILL.md similarity index 100% rename from skills/report/SKILL.md rename to plugins/freethinking/skills/report/SKILL.md diff --git a/skills/report/templates/answer.md b/plugins/freethinking/skills/report/templates/answer.md similarity index 100% rename from skills/report/templates/answer.md rename to plugins/freethinking/skills/report/templates/answer.md diff --git a/skills/report/templates/decision.md b/plugins/freethinking/skills/report/templates/decision.md similarity index 100% rename from skills/report/templates/decision.md rename to plugins/freethinking/skills/report/templates/decision.md diff --git a/skills/report/templates/handback.md b/plugins/freethinking/skills/report/templates/handback.md similarity index 100% rename from skills/report/templates/handback.md rename to plugins/freethinking/skills/report/templates/handback.md diff --git a/skills/report/templates/status.md b/plugins/freethinking/skills/report/templates/status.md similarity index 100% rename from skills/report/templates/status.md rename to plugins/freethinking/skills/report/templates/status.md diff --git a/skills/start-task/SKILL.md b/plugins/freethinking/skills/start-task/SKILL.md similarity index 100% rename from skills/start-task/SKILL.md rename to plugins/freethinking/skills/start-task/SKILL.md diff --git a/skills/start-task/spec-template/design.md b/plugins/freethinking/skills/start-task/spec-template/design.md similarity index 100% rename from skills/start-task/spec-template/design.md rename to plugins/freethinking/skills/start-task/spec-template/design.md diff --git a/skills/start-task/spec-template/requirements.md b/plugins/freethinking/skills/start-task/spec-template/requirements.md similarity index 100% rename from skills/start-task/spec-template/requirements.md rename to plugins/freethinking/skills/start-task/spec-template/requirements.md diff --git a/skills/start-task/spec-template/tasks.md b/plugins/freethinking/skills/start-task/spec-template/tasks.md similarity index 100% rename from skills/start-task/spec-template/tasks.md rename to plugins/freethinking/skills/start-task/spec-template/tasks.md