Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/stale-texts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@rrlab/cli": patch
"@rrlab/astro-plugin": patch
"@rrlab/biome-plugin": patch
"@rrlab/oxc-plugin": patch
"@rrlab/ts-plugin": patch
"@rrlab/tsdown-plugin": patch
"@rrlab/vitest-plugin": patch
"@variablelab/vland": patch
"@variablelab/clibuddy": patch
"@variablelab/loggy": patch
---

The npm description, and the README where it changed, say what the code does.
36 changes: 9 additions & 27 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,21 @@ You are an agent maintaining this monorepo. The microkernel refactor that produc

## Layout

```
dx/
├── shared/ # @variablelab/* — shared utilities (clibuddy, loggy)
├── run-run/ # @rrlab/* — the rrlab product
│ ├── cli/ # @rrlab/cli — the kernel
│ ├── *-plugin/ # @rrlab/<tool>-plugin — the official plugins, one per wrapped tool
│ └── *-config/ # @rrlab/<tool>-config — shared presets
└── vland/ # @variablelab/vland CLI + the scaffolds it emits
├── cli/ # @variablelab/vland — the `vland init` CLI
└── templates/ # scaffolds emitted by `vland init`
```
The folders and what each holds: [docs/development.md](docs/development.md#layout).

This repo hosts **two npm orgs** intentionally: `@variablelab/*` (Variableland's utilities + the `vland` CLI) and `@rrlab/*` (the `rr` CLI product). Cross-pollination is fine — clibuddy and loggy are shared utilities both stacks depend on.

Stack: pnpm workspaces, Turborepo, Changesets, `tsdown` for builds, vitest for tests, Biome for lint/format, TypeScript strict, Node 24 for development (pinned in `mise.toml`). Published packages declare `engines.node` as `>=22.18.0`, the first Node 22 that strips types by default, which `rr` needs to load `run-run.config.{ts,mts}`. CI runs the full check on both that floor (`mise.floor.toml`; run it locally with `MISE_ENV=floor`) and the `mise.toml` version. Bump the floor in `mise.floor.toml` and every `engines.node` together.
Stack: pnpm workspaces, Turborepo, Changesets, `tsdown` for builds, vitest for tests, Biome for lint/format, TypeScript strict.

## Build & verify

```bash
pnpm --filter @rrlab/cli build # build a single package
pnpm --filter @rrlab/cli test # test a single package
pnpm test # all (turbo handles affected detection)
pnpm rr check # dogfooded lint + format + tsc
pnpm build && pnpm test && pnpm rr check # full check
```
Setup, the Node floor, the everyday commands and CI are in [docs/development.md](docs/development.md).

`pnpm rr check` must be green before any commit. If `pnpm rr lint --fix` or `pnpm rr format --fix` applies safe fixes, run them and re-verify.

## Working principles (non-negotiable)

1. **Strict test discipline.** Every feature + bug fix lands with tests:
- Features: a happy-path test and at least one failure case (in `test/integration/` or `src/**/__tests__/`).
- Fixes: a regression test that would have caught the bug.
1. **Strict test discipline.** Every feature and fix lands with the tests [docs/contributing.md](docs/contributing.md#pull-requests) asks for, in the folders [docs/development.md](docs/development.md#tasks) names.
- Refactors with no behaviour change: existing tests must still pass; remove tests only when they were asserting implementation details that no longer exist.
- If you can't write a meaningful test, the design is probably wrong — stop and report.

Expand All @@ -46,21 +28,20 @@ pnpm build && pnpm test && pnpm rr check # full check
- Pattern drift (e.g., one file uses `for...of`, the neighbour uses `.forEach()`) → align with the closer neighbour's pattern.
- Don't go on cleanup spelunking expeditions for files unrelated to your task. Stay in the blast radius of what you're already touching.

3. **Atomic commits**, imperative present-tense English: `add PluginRegistry`, `fix tsc cwd handling on monorepos`, `refactor edit-json idempotency`. Use Changesets (`pnpm changeset`) for any change to a publishable package.
3. **Atomic commits** as [Conventional Commits](docs/contributing.md#pull-requests), such as `feat(cli): add PluginRegistry` or `fix(ts-plugin): handle tsc cwd on monorepos`. A change to what a package ships to npm needs a changeset (`pnpm changeset`). See [docs/contributing.md](docs/contributing.md#changesets) for the bump.

4. **Never publish to npm.** No `pnpm publish`, no `changeset publish`, no `npm publish`. The human handles publication.

5. **Don't invent design.** If you face a real architectural ambiguity, invoke the `arch-critic` subagent and record the outcome in `decisions/NNN-<slug>.md` (format documented in `decisions/README.md`). Bug fixes, mechanical changes, and decisions already in `decisions/` don't need arch-critic.

## Coding conventions

- **Imports**: `#src/...` aliases inside a package; `@rrlab/...` / `@variablelab/...` across packages.
- **Imports**: `#src/...` inside `@rrlab/cli` and `@variablelab/vland`, which map it in `imports`. Relative paths in the other packages. Package names (`@rrlab/...`, `@variablelab/...`) across packages.
- **Exports**: named exports preferred; default exports only when the consumer needs them (e.g. plugin factories).
- **Services**: classes ending in `Service`, dependencies via constructor. Only `logger` is a singleton.
- **Commands**: factory functions named `create<Name>Command(ctx)` returning a configured commander `Command`. A command is a thin wrapper: it resolves its provider (throwing `MissingPluginError` with its `providers` list when absent) and delegates to exactly one action. See `decisions/015-command-action-service-layering.md`.
- **Actions**: free functions in `src/actions/` taking a **single options object** named `<Name>ActionConfig` — never positional params. Dependencies (`ctx`, the resolved provider) sit at the top level; parsed flags/positional args nest under `options` / `args` (reuse the verb-option types from `src/types/tool.ts`). Actions import only `services`/`render`/`lib` — never the CLI framework.
- **Commands and actions** (in `@rrlab/cli`): see "Commands are thin wrappers over actions" in [run-run/cli/CLAUDE.md](run-run/cli/CLAUDE.md#commands-are-thin-wrappers-over-actions) and [decisions/015](decisions/015-command-action-service-layering.md).
- **Errors**: domain errors are classes — kernel-internal ones in `src/errors/` (e.g. `MissingPluginError`), SDK ones in `src/lib/plugin/errors.ts` (`MultipleProvidersError`). Each builds its user-facing message from structured constructor args. Do NOT set `this.name` — keep the plain `Error:` prefix, so output stays clean.
- **Tests**: vitest. Unit tests in `src/**/__tests__/*.test.ts`. Integration tests in `test/integration/*.test.ts`. Inline snapshots for CLI output assertions.
- **Tests**: vitest, in the folders [docs/development.md](docs/development.md#tasks) names. Inline snapshots for CLI output assertions.
- **No `any`.** If TypeScript pushes you there, stop and report — it usually means a design problem.
- **Match neighbours.** Before introducing a new pattern, scan 2-3 nearby files. If they use a different style, follow theirs.

Expand All @@ -72,6 +53,7 @@ pnpm build && pnpm test && pnpm rr check # full check

## Going deeper

- **[`docs/contributing.md`](docs/contributing.md)** — branches, commits, tests, changesets and releases.
- **`run-run/CLAUDE.md`** — orientation for working on the `@rrlab/*` ecosystem (plugin contract, conventions for adding/modifying plugins).
- **`run-run/cli/CLAUDE.md`** — kernel internals (commander patterns, context lifecycle, edit-json DSL, KDL spec emission).
- **`decisions/`** — one file per architectural decision. Read before designing anything that touches plugin lifecycle, dependency model, or templating. Format / template in `decisions/README.md`.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The monorepo of run-run and vland.

- [`run-run/`](run-run): run-run. The CLI is in `cli/`, a plugin per tool in `*-plugin/` and the presets in `*-config/`. The [run-run README](run-run/cli/README.md#plugins) lists the plugins.
- [`vland/`](vland): vland. The CLI is in `cli/`, and the templates it starts a project from are in `templates/`.
- [`shared/`](shared): `@variablelab/clibuddy` and `@variablelab/loggy`, internal to the two CLIs.
- [`shared/`](shared): the internal packages the CLIs depend on.

## Contributing

Expand Down
4 changes: 2 additions & 2 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ dx takes bug reports, fixes and small features for run-run, vland and their pack

## Pull requests

1. Branch from `main` with a prefix: `feat/`, `fix/`, `docs/`, `chore/` or `test/`.
1. Branch from `main` with a prefix: `feat/`, `fix/`, `docs/`, `ci/`, `chore/` or `test/`.
2. Write commits as [Conventional Commits](https://www.conventionalcommits.org), such as `fix(oxc-plugin): stop installing a flaky oxlint-tsgolint`. The pull request title takes the same form, because it becomes the squash commit.
3. A feature lands with a test of the happy path and at least one failure case, and a fix with a regression test.
4. Run `pnpm rr check` and `pnpm test` before you push.
Expand All @@ -29,4 +29,4 @@ Nobody edits versions or `CHANGELOG.md` by hand, and nobody publishes from a clo

When changesets land on `main`, the Release job opens or updates a "chore: update versions" pull request as `vland-bot`. A maintainer merges it, and the next run on `main` publishes each new version to npm, with npm trusted publishing, and tags it.

A new tag of `@rrlab/cli` or `@variablelab/vland` regenerates the `CLI.md` of both CLIs in a "chore: regenerate CLI docs" pull request, which a maintainer merges too.
A new tag of a CLI regenerates the `CLI.md` of every CLI in the cli docs workflow, in a "chore: regenerate CLI docs" pull request, which a maintainer merges too.
2 changes: 1 addition & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ How to work on dx in a clone. The [contributing guide](contributing.md) covers h

- [`run-run/`](../run-run): run-run. `cli/` is `@rrlab/cli`, the kernel and the `rr` command. Each `*-plugin/` wraps one tool, and each `*-config/` is a preset that a plugin installs. [`run-run/CLAUDE.md`](../run-run/CLAUDE.md) covers the architecture: the plugin contract and how to add or change a plugin.
- [`vland/`](../vland): vland. `cli/` is `@variablelab/vland`, and `templates/` holds the projects `vland init` starts from.
- [`shared/`](../shared): `@variablelab/clibuddy` and `@variablelab/loggy`, the internal packages both CLIs depend on.
- [`shared/`](../shared): `@variablelab/clibuddy` and `@variablelab/loggy`, the internal packages the CLIs depend on.
- [`decisions/`](../decisions): the architecture decisions, one file each.
- `docs/`: this guide and the [contributing guide](contributing.md).

Expand Down
19 changes: 7 additions & 12 deletions run-run/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CLAUDE.md — `run-run/` (the `@rrlab/*` ecosystem)

Orientation for working on the `@rrlab/*` packages. Prerequisite reading: the repo-root `CLAUDE.md` (working principles, build commands, coding conventions).
Orientation for working on the `@rrlab/*` packages. Prerequisite reading: the repo-root `CLAUDE.md` (working principles, coding conventions).

## Why `rr` exists — and how it's meant to be invoked

Expand All @@ -10,7 +10,7 @@ This means:

- **Do not wrap `rr` in `package.json` scripts.** Aliases like `"lint": "rr lint"`, `"format": "rr format --fix"`, `"tscheck": "rr tsc"`, `"check": "rr check"` re-introduce the very indirection rr was created to remove (`pnpm lint` → `rr lint` → `biome check` is three layers for what should be one). The user types `rr lint`, not `pnpm lint`.
- **The expected setup**: `node_modules/.bin` on `PATH` — typically via mise (`[env] _.path = ["{{config_root}}/node_modules/.bin"]`) — so `rr` is directly invokable from the project root without a package-manager hop. Templates ship a `mise.toml` that wires this for new projects.
- **`package.json scripts` may still exist** — but only for things `rr` does NOT cover (`vitest run`, `tsx watch`, `node dist/...`, `vite dev`, `turbo run dev`, `changeset publish`). These are not single-command aliases; they're either different tools or multi-step compositions.
- **`package.json scripts` may still exist** — but only for things `rr` does NOT cover (`tsx watch`, `node dist/...`, `vite dev`, `turbo run dev`, `changeset publish`). These are not single-command aliases; they're either different tools or multi-step compositions.
- **Lifecycle / orchestration hooks MAY call `rr` directly.** lefthook git hooks, `prepublishOnly`, GitHub Actions workflows — those are triggered by other systems, not typed by the developer, so they don't fight the "one entry point" goal. A `pre-commit: rr jsc --fix-staged` lefthook job is exactly right.
- **READMEs teach `rr <cmd>`, not `pnpm <cmd>`.** Mention the mise PATH note once; from then on every developer-facing instruction is `rr <cmd>`.

Expand All @@ -27,7 +27,7 @@ If you find yourself tempted to add a `pnpm <script>` that exists only to call `
| `ts-plugin/` | `@rrlab/ts-plugin` | `tsc` (TypeScript). Has `install()` that scaffolds `tsconfig.json` from a preset. |
| `tsdown-plugin/` | `@rrlab/tsdown-plugin` | `pack` (tsdown). |
| `vitest-plugin/` | `@rrlab/vitest-plugin` | `test` (vitest), a streaming passthrough — see `decisions/016-vitest-passthrough-plugin.md`. |
| `ts-config/` | `@rrlab/ts-config` | Shared tsconfig presets: `react`, `dom/app`, `dom/lib`, `no-dom/app`, `no-dom/lib`. |
| `ts-config/` | `@rrlab/ts-config` | Shared tsconfig presets, listed in [its README](ts-config/README.md#presets). |
| `biome-config/` | `@rrlab/biome-config` | Shared biome preset. |
| `tsdown-config/` | `@rrlab/tsdown-config` | Shared tsdown configuration helpers. |

Expand Down Expand Up @@ -78,12 +78,7 @@ Every plugin's host-side tool (`@biomejs/biome`, `typescript`, etc.) is a `peerD

## Required reading order before changing anything in here

1. The `decisions/` folder at the repo root. Five load-bearing decisions today:
- **001** all-peer dependencies.
- **002** no `bin` shims.
- **003** wire plugins via the registry (`ctx.plugins`) + drop of `future.oxc`.
- **004** templating strategy + edit-json DSL.
- **005** `@rrlab/tsdown-plugin` scaffolding + `@rrlab/tsdown-config` shape.
1. The [`decisions/`](../decisions) folder at the repo root, one file per decision. Read the ones on the area you're changing.
2. `run-run/cli/CLAUDE.md` if you're going to touch the kernel.
3. The `*.test.ts` files of the area you're modifying. Tests are the executable spec for the existing behaviour.

Expand All @@ -108,7 +103,7 @@ Every official plugin exports `default = definePlugin({...})` — a plain `Plugi

### Templating: code-as-template + edit-json DSL

The local config files plugins scaffold are **thin wrappers** pointing at the shared `@rrlab/*-config` package via `extends`. Two flavours of `FileOp`:
The local config files plugins scaffold are **thin wrappers** pointing at the shared `@rrlab/*-config` package via `extends`. The `FileOp` kinds are the union in `run-run/cli/src/lib/plugin/types.ts`. Config scaffolding uses these:

```ts
// new file: full content as a string (constructed in code, no template engine)
Expand All @@ -133,7 +128,7 @@ The local config files plugins scaffold are **thin wrappers** pointing at the sh

Paths are JSON Pointer (RFC 6901). Ops are NOT strict RFC 6902 — see `decisions/004-templating-and-edit-json-dsl.md` for why.

For TS modules (`eslint.config.ts` etc. if we ever add them), the escape hatch is `{ kind: "edit-text", path, edit: (src) => string }` — the plugin does AST surgery in-process via `magicast` (already a kernel dep) and returns the new text.
For TS modules, such as the `tsdown.config.ts` that tsdown-plugin patches, the escape hatch is `{ kind: "edit-text", path, edit: (src) => string }`: the plugin parses the module with `magicast`, from its own `dependencies`, and returns the new text.

### Adding a new plugin (checklist)

Expand All @@ -145,7 +140,7 @@ For TS modules (`eslint.config.ts` etc. if we ever add them), the escape hatch i
6. `src/__tests__/tool-versions.test.ts`: read this plugin's own `package.json` and assert that every `TOOL_VERSIONS[<name>].install` range fits within `peerDependencies[<name>]` (`semver.subset`). Copy the shape from any sibling plugin's test verbatim — duplication across plugins is cheaper than a kernel-level helper.
7. `src/__tests__/install.test.ts`: cover install (default preset, declined, per-preset deps), uninstall (delete-vs-edit decision). Use `fs.mkdtemp` + a real tmp dir, not mocks.
8. Add the alias to `run-run/cli/src/lib/plugin/directory.ts` `PLUGINS_DIRECTORY`, listing every capability it provides in that entry's `capabilities` array (so the `MissingPluginError` hint — derived via `providersOf(capability)` — suggests it).
9. Add an integration test in `run-run/cli/test/integration/<capability>.test.ts` if the plugin provides a capability that doesn't yet have e2e coverage.
9. Add an integration test in `run-run/cli/test/integration/<command>.test.ts` (such as `tsc.test.ts` for `typecheck`) if the plugin provides a capability that doesn't have one yet.
10. If the plugin needs a shared config, create `run-run/<tool>-config/` alongside (mirror `ts-config` / `biome-config`).
11. Update the dogfooded `run-run.config.mts` at the repo root if it makes sense (its `plugins` array is what we use today).
12. Add the plugin's row to the "What's here" table above and to the "Plugins" table in `run-run/cli/README.md`. That README ships to npm, so the change needs a `@rrlab/cli` changeset. `plugin-docs.test.ts` fails until both rows exist.
Expand Down
2 changes: 1 addition & 1 deletion run-run/astro-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@rrlab/astro-plugin",
"version": "0.1.1",
"description": "Astro plugin for @rrlab/cli — provides the typecheck capability via astro check.",
"description": "run-run plugin for Astro: the typecheck capability, through astro check.",
"homepage": "https://github.com/withvariablelab/dx/tree/main/run-run/astro-plugin#readme",
"bugs": {
"url": "https://github.com/withvariablelab/dx/issues"
Expand Down
2 changes: 1 addition & 1 deletion run-run/biome-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@rrlab/biome-plugin",
"version": "1.2.1",
"description": "Biome plugin for @rrlab/cli — provides lint, format, and jsc capabilities.",
"description": "run-run plugin for Biome: the lint, format and jscheck capabilities.",
"homepage": "https://github.com/withvariablelab/dx/tree/main/run-run/biome-plugin#readme",
"bugs": {
"url": "https://github.com/withvariablelab/dx/issues"
Expand Down
Loading
Loading