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
97 changes: 67 additions & 30 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,67 @@ for including the prompt or the key instructions you used.

**What we care about, regardless of who wrote it:**

- Tests pass
- Types are strict (no `any`, no `@ts-ignore` without a comment)
- Lint is green (`pnpm lint`)
- Coverage 80% (100% on critical modules)
- Commit messages follow Conventional Commits
- PR has a clear description — "what, why, how to verify"
- You understand the diff and can discuss design during review
- Tests pass
- Types are strict (no `any`, no `@ts-ignore` without a comment)
- Lint is green (`pnpm lint`)
- Coverage >= 80% (100% on critical modules)
- Commit messages follow Conventional Commits
- PR has a clear description — "what, why, how to verify"
- You understand the diff and can discuss design during review

**What gets you rejected:**

- Obviously untested AI slop (generated code that doesn't run)
- PRs with no description, just "here's some code"
- Hidden AI use that makes review confusing
- Obviously untested AI slop (generated code that doesn't run)
- PRs with no description, just "here's some code"
- Hidden AI use that makes review confusing

We don't care if you used AI, we care if the PR is good.

## Code of Conduct

All contributors agree to follow the [Code of Conduct](./CODE_OF_CONDUCT.md).

## Workflow
## Architecture overview

`@focus-mcp/cli` operates in two modes:

- **MCP server mode** (`focus start`) — exposes bricks over JSON-RPC stdio. The CLI wraps `@focus-mcp/core` and injects host adapters (npm, filesystem, http). All AI clients (Claude Code, Cursor, Codex…) connect in this mode.
- **CLI mode** — interactive commands (`focus add`, `focus list`, `focus search`, `focus catalog`, `focus browse`) and the `focus_self_update` mechanism for in-place updates.

The CLI is a thin UI layer. **Business logic belongs in `@focus-mcp/core`**, never in the CLI commands. Commands must stay testable: each `src/commands/<name>.ts` exports a pure function that takes structured input and returns a string.

## Git workflow

```
main ← stable releases only (never commit directly)
develop ← integration branch (persistent, never force-delete)
feat/* ← feature branches, branch FROM develop
fix/* ← bug fix branches, branch FROM develop
docs/* ← documentation branches
```

1. **Open an issue** using the "Bug report" or "Feature request" template (or discuss in an existing one).
2. **Branch from `develop`** (`develop` is the persistent working branch — never branch from `main`).
2. **Branch from `develop`** — never from `main`.
3. **Write the tests first.** We enforce strict TDD (Red → Green → Refactor). A PR without accompanying tests will be sent back.
4. **Open a PR** targeting `develop`. `main` is release-only.
5. The PR must pass **the whole CI**: lint, typecheck, tests (coverage ≥ 80 %), REUSE, gitleaks, build, commitlint.
4. **Open a PR targeting `develop`.** `main` is release-only.
5. **Auto-merge** is enabled: once CI passes and at least one review is approved, the PR merges automatically.
6. **Never force-push** to `develop` or `main`.

> `develop` is the persistent working branch. Never branch from `main` and never delete `develop`.

## Commit conventions

Enforced by commitlint (`config/commitlint.config.js`):

| Rule | Value |
|------|-------|
| Types allowed | `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`, `release` |
| Header max length | 100 characters |
| Body max line length | disabled |
| Footer max line length | disabled |
| Subject case | lowercase (not UPPER, not PascalCase, not Start Case) |

Scope is the command or subsystem: `feat(list): ...`, `fix(start): ...`, `docs(readme): ...`.

## Quality gates

Expand All @@ -58,32 +92,27 @@ pnpm build
pnpm reuse # REUSE compliance (SPDX headers)
```

Never use `--no-verify` to bypass these checks — CI enforces them regardless.

## Non-negotiable rules

1. **Strict TDD** — tests first. Coverage 80 % global (the `vitest` config enforces this).
1. **Strict TDD** — tests first. Coverage >= 80% global (the `vitest` config enforces this).
2. **No `any`**, no `!` non-null assertion, no untyped `catch`.
3. **No `console.*` outside `src/bin/` and `src/commands/`.** Use structured logging from `@focus-mcp/core` everywhere else.
4. **ESM only**, `node:` protocol for Node built-ins.
5. **SPDX headers** in every source file (`SPDX-FileCopyrightText: 2026 FocusMCP contributors` + `SPDX-License-Identifier: MIT`). For JSON files, add a sibling `.license` file (REUSE convention).
6. **Conventional Commits** — enforced by commitlint (`feat(list): ...`, `fix(info): ...`, `docs(readme): ...`). Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`.
6. **Conventional Commits** — enforced by commitlint (`feat(list): ...`, `fix(info): ...`). Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`.
7. **npm scope is `@focus-mcp`** (with hyphen). Never write `@focusmcp` in new code, docs, or commit messages.
8. **Pure command functions.** Every `src/commands/<name>.ts` exports a function that takes structured input and returns a string (or throws). Only `src/bin/focus.ts` is allowed to touch `process.*`, stdin/stdout, or the filesystem — this keeps the commands trivially testable.
9. **No scope creep.** Stick to the problem described in the linked issue.
10. **Logic in core, not in CLI.** If a feature requires non-trivial logic, it belongs in `@focus-mcp/core`. The CLI only wires adapters and formats output.

## Publishing (for maintainers)
## Common pitfalls

Releases are handled by two GitHub Actions workflows — no Changesets "Version Packages" PR:

| Workflow | Trigger | npm tag |
|---|---|---|
| `dev-publish.yml` | push to `develop` | `dev` |
| `stable-publish.yml` | push to `main` | `latest` |

To cut a release: bump the version in `package.json` on `develop`, merge to `main`.

## Commit sign-off / DCO

By contributing you certify the [Developer Certificate of Origin](https://developercertificate.org/). Use `git commit --signoff` (`-s`) to add a `Signed-off-by` trailer. Signed commits (GPG/SSH) are strongly recommended.
- **`develop` ↔ `main` divergence** — if CI reports that `develop` is behind `main`, wait for the maintainer to run the back-merge workflow. Do not manually merge `main` into your branch.
- **Snapshot drift in tests** — if you change output formatting, run `pnpm test --update-snapshots` and commit the updated snapshots alongside your change.
- **`focus_self_update` side effects** — the self-update command replaces the running binary. Tests for it must not exercise real npm installs; use the fixtures/mock adapters.
- **Namespace clash** — tool names are prefixed with the brick namespace (e.g. `bricks:`). If adding a new brick namespace, ensure it does not conflict with existing tool names.

## Review

Expand All @@ -92,9 +121,17 @@ Maintainers check:
- problem statement in the linked issue;
- tests match the feature scope;
- code style + typing (lint, typecheck);
- coverage stayed 80 % after the change;
- coverage stayed >= 80% after the change;
- docs updated if a user-facing command changed (including `focus browse` TUI if applicable).

## Commit sign-off / DCO

By contributing you certify the [Developer Certificate of Origin](https://developercertificate.org/). Use `git commit --signoff` (`-s`) to add a `Signed-off-by` trailer. Signed commits (GPG/SSH) are strongly recommended.

## Security

Vulnerabilities must be reported **privately** — see [SECURITY.md](./SECURITY.md).

## Release process

See [docs/RELEASE.md](./docs/RELEASE.md) for the full release guide (for maintainers).
126 changes: 126 additions & 0 deletions docs/RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<!--
SPDX-FileCopyrightText: 2026 FocusMCP contributors
SPDX-License-Identifier: MIT
-->

# Release guide — @focus-mcp/cli

This document is for **maintainers** only. External contributors do not need to follow this process.

## Overview

Two publish workflows exist:

| Workflow | Trigger | npm tag |
|----------|---------|---------|
| `dev-publish.yml` | push to `develop` | `dev` |
| `stable-publish.yml` | push to `main` | `latest` |

There is no Changesets "Version Packages" PR. Version bumps are made directly in `package.json` on `develop` before merging to `main`.

## Release order

The CLI depends on `@focus-mcp/core`. If the release includes a core update:

1. Release **core** first (see `focus-mcp/core` release guide).
2. Update the `@focus-mcp/core` version in `cli/package.json`.
3. Then release **cli**.

## Pre-conditions

Before cutting a stable release:

- `develop` and `main` are aligned (no divergence — run `/sync-status` to check).
- All open PRs blocking the milestone are merged to `develop`.
- CI is green on `develop` (lint, typecheck, tests, coverage, build, REUSE, gitleaks).
- `package.json` version on `develop` is already bumped to the target version.
- `CHANGELOG.md` (if maintained) reflects the new version.

## Using the `/release` skill

```
/release cli <bump>
```

Where `<bump>` is `patch`, `minor`, or `major`. The skill:

1. Verifies pre-conditions.
2. Bumps the version in `package.json` on `develop`.
3. Commits `chore: release vX.Y.Z`.
4. Opens a sync PR (`develop` → `main`).
5. CI on `main` runs `stable-publish.yml` → publishes to npm with the `latest` tag.
6. The back-merge workflow re-syncs `main` → `develop`.

## Manual fallback

If the `/release` skill is unavailable or fails:

```bash
# 1. Ensure you are on develop and up to date
git checkout develop
git fetch origin && git rebase origin/develop

# 2. Bump the version (edit package.json manually or use npm version)
npm version patch --no-git-tag-version # or minor / major

# 3. Verify build
pnpm build

# 4. Commit
git add package.json
git commit -m "chore: release vX.Y.Z"

# 5. Push develop — triggers dev-publish.yml (npm tag: dev)
git push origin develop

# 6. Open a PR: develop → main
gh pr create --title "chore: release vX.Y.Z" --base main --head develop \
--body "Stable release — merge to trigger stable-publish.yml"

# 7. Once merged, stable-publish.yml publishes to npm (tag: latest)
```

## Recovery back-merge

If the back-merge workflow fails after a release (i.e. `main` is ahead of `develop`):

```bash
/back-merge cli
```

Or manually:

```bash
git checkout -b chore/back-merge-main-$(date +%Y%m%d)
git fetch origin
git merge origin/main --no-ff -m "chore: back-merge main → develop"
git push origin HEAD
gh pr create --title "chore: back-merge main → develop" --base develop --head HEAD \
--body "Recovery back-merge after release."
```

## Verification post-release

After the stable workflow completes:

```bash
# Check the published version
npm view @focus-mcp/cli version
npm view @focus-mcp/cli dist-tags

# Verify git tag
git fetch --tags origin
git tag --sort=-version:refname | head -5

# Check GitHub Release was created
gh release list --repo focus-mcp/cli --limit 5

# Smoke-test the published package
npx @focus-mcp/cli --version
```

## npm OIDC Trusted Publishing

No `NPM_TOKEN` secret is used. Publishing relies on npm OIDC Trusted Publishing (configured since July 2025). The workflows require `id-token: write` permission and a registered Trusted Publisher on npmjs.com.

See [../RELEASE_OIDC_SETUP.md](../RELEASE_OIDC_SETUP.md) at the root of the `focusmcp` workspace for setup instructions (or the equivalent in the org-level docs).
Loading