From 52882f861614e2eb1f7a54a5fb1c0b08aeb2a4eb Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Fri, 28 Aug 2026 21:10:40 -0700 Subject: [PATCH] docs: add CONTRIBUTING.md covering AI policy and nightlies The repository had no contributor guide, and no written statement of what we expect from a contribution written with a coding agent. It also had no home for the local development workflow: `docs-npx-cli` is an error-level rule with a `raw` scope, so it reaches inside fenced code blocks and cannot be suppressed in-file, which makes it impossible for a README to name `pnpm cli` at all. That is correct for a README, read by people without this repository checked out, and it leaves nowhere to document the local scripts. CONTRIBUTING.md sits outside every Vale scope, so the rules stay strict everywhere they apply and the contributor guide can still name the commands a contributor actually runs. The header says so, to stop a later change from "fixing" it by adding a scope. The nightly section moves here from the README, which keeps a pointer. --- CONTRIBUTING.md | 145 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 23 +++----- 2 files changed, 153 insertions(+), 15 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..005b7b80 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,145 @@ +# Contributing + +Thanks for working on Taskless. + +This file sits outside every Vale scope in `.taskless/rules/vale/`, which is +deliberate. The `docs-npx-cli` rule rewrites `pnpm cli` and +`pnpm dlx @taskless/cli` to the published `npx @taskless/cli` form, at error +level, with a `raw` scope that reaches inside fenced code blocks and cannot be +suppressed with an in-file directive. That rule is right for a README, which is +read by people who do not have this repository checked out. It makes a +contributor guide impossible to write, because a contributor guide has to name +the local scripts. So this document is out of scope, and the rules stay strict +everywhere they apply. + +## Using AI to contribute + +We use coding agents here, and you are welcome to. Three things hold. + +**You own what your agent does.** A pull request from your account is your pull +request. "The agent wrote it" is not a defect report, an excuse, or a +mitigation. Review the diff before you open it, the same way you would review +your own work before asking someone else to read it. + +**You are expected to be able to explain what it does.** Not line by line, but +you should be able to say what the change does, why it is shaped that way, and +what you considered and rejected. If a reviewer asks why a function exists and +the honest answer is that the agent put it there, the change is not ready. This +is the practical test, and it is the one that matters: an agent will happily +produce a confident, plausible, wrong change, and the contributor is the only +person positioned to catch that before review. + +**We are not running an attestation system, and we might.** There is no +[vouch](https://github.com/vouch-dev/vouch)-style signing or provenance +requirement on contributions today, and no plan to add one on a schedule. If the +volume or the risk changes, we are open to exploring it. Raise an issue if you +think we have reached that point. + +Nothing here asks you to disclose that you used an agent. The standard is the +same whether you did or not. + +## Local development + +```bash +pnpm install +pnpm build # dist/ is a build artifact; nothing rebuilds it for you +pnpm typecheck +pnpm lint +pnpm test +``` + +Run the locally built CLI with `pnpm cli`, which executes +`./packages/cli/dist/index.js`. + +**`pnpm cli` runs the last build, not your working tree.** A stale `dist/` +serves stale behavior, including stale `agent ` recipes, which are +embedded into the bundle at build time rather than fetched at runtime. Run +`pnpm build` first whenever the answer depends on current source. This has +bitten us: an agent followed a recipe from a `dist/` built 26 commits earlier +and authored four rules against a documented contract that had changed since. + +OpenSpec runs through `pnpm openspec`. A bare `openspec` is not on `PATH` here. + +## Nightlies + +Work that has merged to `main` but is not yet released is published as +`@taskless/cli-nightly`, so unreleased behavior can be installed and exercised +without waiting for a release. Use one when you want to reproduce a report +against merged work, or check whether something is already fixed on `main` +without building the repository yourself. + +**A nightly is also how you use this CLI from another repository.** To exercise +unreleased behavior somewhere other than this checkout, merge it to `main` and +install the nightly that follows. + +```bash +npx @taskless/cli-nightly@latest --version +``` + +A few properties worth knowing before you rely on one: + +- **The executable is `taskless`, the same as the release.** Every documented + invocation, skill, and recipe works unchanged against a nightly. +- **Because the binary name is the same, a nightly and `@taskless/cli` collide + when both are installed globally.** That is not a supported configuration. A + nightly is a drop-in for the release it anticipates, not a companion to it. + Use one or the other globally, or install the nightly into a project. +- **Versions look like `0.11.0-20260818123456x05b3c88`**: the release the + nightly anticipates, the UTC build time, and the commit it was built from. + Every one is a prerelease and the newest always carries the `latest` tag, so + installing with no version gives you the most recent nightly. +- **A nightly is published on each push to `main` that has changesets pending**, + from a commit that passed validation, and is the same build as the release it + anticipates. When the Version Packages PR merges, the changesets are consumed + and the real `@taskless/cli` release publishes instead. + +### Testing against a nightly + +Install it into a scratch project rather than globally, so it cannot collide +with a release you have installed: + +```bash +mkdir /tmp/taskless-probe && cd /tmp/taskless-probe +npm init -y +npm install @taskless/cli-nightly@latest +npx taskless --version # confirm the version you meant to test +npx taskless init +``` + +Two things that will confuse you if you do not know them. + +**A nightly bakes its own invocation into everything it ships.** The build +rewrites `npx @taskless/cli` to `npx @taskless/cli-nightly@` in every +skill, command, and recipe, so an agent reading them calls the nightly rather +than the released package. That is intended. If you are comparing a nightly's +generated content against a release's, the invocation string differing is not +the bug you are looking for. + +**Agents working in this repository cannot run a nightly.** `.claude/settings.json` +denies `Bash(npx:*)` and `Bash(pnpm dlx @taskless/cli*)`. That is deliberate: an +agent reaching for a published package is usually about to test something other +than the tree it is working in. Build and use `pnpm cli` instead, which is the +only path that reflects uncommitted work. + +## Pull requests + +Read `CLAUDE.md` for the repository's working agreements. The parts that most +often trip people up: + +- **Commits are GPG-signed.** `git commit -S`. Note that `main` itself shows + unsigned commits, because GitHub re-signs nothing when it rebases. A commit + reading `N` before it reaches `main` is a real problem; one on `main` is not. +- **Rebase is the only merge method.** Squash and merge commits are disabled. +- **Ship a changeset** when the change has a release note, on the bottom branch + if you are stacking. The check warns and never fails, because whether a change + ships a release note is a judgement call. +- **Prefer stacking**, and aim to keep a hand-written diff under roughly 1200 + lines. A pull request far past that does not get reviewed, it gets approved. + +## Code style + +`.conventions/STYLEGUIDE-CODE.md` and `.conventions/STYLEGUIDE-UI.md` are +enforced, and `CLAUDE.md` points every agent at them. The house rules with the +sharpest edges are direct imports over barrel files, library types over +hand-written duplicates, and verifying build output in the build rather than by +parsing it afterwards. diff --git a/README.md b/README.md index a09782c1..e6dadd14 100644 --- a/README.md +++ b/README.md @@ -111,23 +111,16 @@ Work that has merged to `main` but is not yet released is published as exercised without waiting for a release: ```bash -npx @taskless/cli-nightly@latest --version # or: npm i -g @taskless/cli-nightly +npx @taskless/cli-nightly@latest --version ``` -- **The executable is `taskless`**, the same as the release. Every documented - invocation, skill, and recipe works unchanged against a nightly. -- **Because the binary name is the same, a nightly and `@taskless/cli` collide - when both are installed globally.** That is not a supported configuration: a - nightly is a drop-in for the release it anticipates, not a companion to it. - Use one or the other globally, or install the nightly into a project. -- Versions look like `0.11.0-20260818123456x05b3c88`. The release the nightly - anticipates, the UTC build time, and the commit it was built from. Every one - of them is a prerelease, and the newest always carries the `latest` tag, so - installing with no version gives you the most recent nightly. -- A nightly is published on each push to `main` that has changesets pending, and - is the same build as the release it anticipates, differing only in package - name and version. When the Version Packages PR merges, the changesets are - consumed and the real `@taskless/cli` release publishes instead. +The executable is `taskless`, the same as the release, so a nightly is a drop-in +for the release it anticipates rather than a companion to it. Installing both +globally collides on that binary and is not supported. + +See [CONTRIBUTING.md](./CONTRIBUTING.md) for how nightlies are versioned and +published, and for how to test against one without disturbing a release you +already have installed. ### Adding a new topic recipe