CLAUDE.md · git:20260908.d27f257 · 2026-09-08 · sha256 9121dd00fabb1dd4

CLAUDE.md git:20260908.d27f257A

Immutable. This exact content is served forever at /api/v1/blob/9121dd00fabb1dd4.

# Working in this repository

`AGENTS.md` is a symlink to this file, so Claude Code and Codex read the same instructions.

## Finish every PR with a real test

`pnpm check` passing is not evidence the feature works. Fixtures carry the shapes their author
thought of; real repositories carry the ones nobody did. Before marking a PR ready:

1. `pnpm build` — test the bundle at `packages/cli/bin/refs.mjs`, not the TypeScript source.
2. Clone a real upstream repository from GitHub into a scratch directory.
3. Drive the feature end to end against it with a scratch `REFS_HOME`, using the same `--json`
   path an agent uses.
4. Compare what refs reports against ground truth computed from git itself — e.g. package names
   from `git ls-tree` at both revisions, diffed. Report the comparison, not just the output.
5. Run any command refs printed **verbatim** — the string it printed, not an argv you rebuilt from
   the same parts. A suggested command that does not execute is a bug, and rebuilding the argv
   hides exactly the bugs that matter: a `<ref>` placeholder passes straight through a hand-built
   array while a shell reads it as an input redirection.
6. Exercise the lifecycle: `remove`, then confirm nothing is left (config, state, checkout,
   pruned parent directories, no orphans in `doctor`, `resolve` answers `unmatched_query`), then
   `add` again and confirm behaviour is unchanged.

To put a ref at a chosen point in history without fabricating anything, clone the real repository,
`git checkout -B main <old-sha>` in it, `refs add` from that `file://` path with
`REFS_ALLOW_FILE_URLS=1` so refs builds the config from real state, then advance the clone and
`refs sync`. The range is then real upstream history.

Why this is a rule: a feature once shipped with 988 green tests, including end-to-end ones against
`file://` fixtures, and reported nothing at all on TanStack Query — two negation patterns in its
`pnpm-workspace.yaml` silenced every finding. No fixture used a negation pattern.

## Tests

- A test that pins a fix must FAIL when the fix is reverted. Check it: revert, run, restore.
  A test that passes either way pins nothing.
- That check proves the TESTS do not distinguish a change — never that nothing does. It is evidence
  for writing a test, not for deleting code. Logic removed on that basis here turned out to be
  load-bearing on a shape no test covered.
- A test that runs the built bundle must REBUILD it, unconditionally. `pnpm check` does not build,
  so reusing whatever is on disk tests whatever was last built — and the revert check above then
  passes against code that no longer exists.
- Assert the outcome, not the absence of a complaint. "The next run is quiet" holds for a command
  that failed outright.
- Test through the real boundary where one exists — real git over a fake runner, the CLI over
  internal calls — when the thing under test is git's or the CLI's behaviour.
- `pnpm check` before pushing; `pnpm test:coverage` at the end. Coverage ratchet: 96/90/98/96.

## Review findings

Verify every finding against the code before acting on it, and reproduce it before fixing it.
Findings have been wrong, and one proposed fix would have introduced a regression. Say so plainly
when a finding does not hold; do not implement it to be agreeable.

## Workspace pattern fidelity

`packages/core/src/workspaces.ts` emulates part of npm's and pnpm's workspace resolution, and that
surface has no natural end. The stopping rule is the DIRECTION a divergence errs in:

- **Missing a declared package is a defect.** `refs resolve` then answers `not_found` for source
  that is present.
- **Including an excluded one is tolerated.** The entry points at real code in a repository the
  user asked to track; the cost is an extra routing target.

Known divergences of the tolerated kind are listed in that file's header. A review finding in that
direction belongs on the list, not in a fix.

## Design constraints

- **Never add a verb.** A benchmark measured 0/324 adoption of new refs commands by agents.
  Extend a command already on the agent's path instead.
- **Everything inside a checkout is untrusted third-party content** (`skills/refs/SKILL.md` §4).
  Never copy manifest text into `config.toml`: that moves untrusted content into a file refs
  later reads as its own configuration. Names and paths are structurally verifiable and may be
  used; descriptions are written from source evidence, never copied.
- **Verified is not shell-safe.** Anything interpolated into a command refs prints goes through
  `shellQuote` (`packages/cli/src/shell-quote.ts`). `zPackagePath` permits `$()`, backticks and
  spaces.
- A failure to look is never evidence. An unreadable manifest means `unknown`/`unverifiable`,
  never a claim about a package.

## Lint limits that shape files

oxlint enforces these, and they are cheaper to design for than to hit:

- 300 lines per file, 50 lines per function, 10 statements per function, 3 parameters.
- Imports sort by the first member name, multi-specifier before single-specifier.
- `node/no-sync` — sync fs calls in tests need `// eslint-disable-next-line node/no-sync`.

Run `pnpm fmt` before `pnpm check`. The root `CHANGELOG.md` is outside the formatter's scope:
copy it to `packages/cli/`, run `pnpm fmt`, copy it back.

## Commits, PRs, issues

- One PR per topic. The commit body explains **why**, not what.
- No AI-attribution trailers.
- The repository is public. Issues, PRs, commits and the changelog carry no paths under `~`, no
  local config details, and no references to `_local/*` (gitignored — issues must stand alone).