| Surface | Language |
|---|---|
| Branch names, commit messages, PR titles/bodies | English |
| Source code and code comments | English |
| CLI output, usage docs, generated context packs | i18n (currently ja-JP, en-US) |
Internal planning notes that are not meant for public consumption belong in .local/ (gitignored).
Conventional Commits. Examples:
feat(init): create .code-pact and design layouts
fix(verify): treat missing progress event as VERIFICATION_FAILED
chore(deps): pin node engines to >=24
docs(readme): clarify json response shape
test(progress): cover expanded_work for project-b fixture
- One CLI command (or one cohesive concern) per branch:
feat/<command>orchore/<scope>. - Open a PR even for solo work; do not push directly to
main. - Inside a PR, prefer many small commits. Squash-merge at PR merge time is acceptable.
- CI must be green before merge:
pnpm typecheck && pnpm test && pnpm build.
MVP runtime dependencies are limited to yaml and zod. Do not add CLI frameworks (commander), color libraries (picocolors), front-matter parsers (gray-matter), glob libraries (globby), or process libraries (execa) without an explicit RFC in .local/decisions/.
Use built-ins instead:
- CLI parsing:
node:utilparseArgs - Subprocess:
node:child_processspawn - Front-matter: small in-repo parser + the existing
yamlpackage - File listing:
node:fs/promisesreaddir
vitest, snapshot tests for command output where useful.- Fixtures live under
tests/fixtures/. Snapshot any change deliberately.
When --json is set, stdout must be JSON only. All human-readable logs, warnings, and progress lines must go to stderr. Use the shared response shape:
{ "ok": true, "data": { } }{ "ok": false, "error": { "code": "PHASE_NOT_FOUND", "message": "..." }, "data": { } }Stable error code strings are the public contract; do not rename them lightly.
From v0.2.0-alpha.0 onward, release tags are signed with SSH so the GitHub tag page shows a "Verified" badge and downstream consumers can audit the chain locally. This section is for the maintainer cutting a release; it is not required for ordinary contributors who only open PRs.
# 1. Tell git to sign tags with SSH (not GPG/OpenPGP).
git config --local gpg.format ssh
# 2. Point at the SSH key you want to use as your signing key.
# The key must be an existing public key file; ~/.ssh/id_ed25519.pub
# is the common choice. Do NOT use a separate, unlisted key.
git config --local user.signingkey ~/.ssh/id_ed25519.pub
# 3. Sign every annotated tag created in this repo by default.
git config --local tag.gpgSign trueIn https://github.com/settings/keys, add the same SSH public key as a Signing Key (it is fine to also have it registered as an Authentication Key — they are separate registrations even for the same key). Only keys registered as Signing Keys produce the "Verified" badge on tag and commit pages.
git verify-tag requires an allowedSignersFile that maps SSH public keys to identities. Without it, the command returns signature trust unknown even for a tag your key actually signed.
Per-repo setup:
git config --local gpg.ssh.allowedSignersFile .git_allowed_signers
# .git_allowed_signers contents (one line, your email + your public key):
# you@example.com ssh-ed25519 AAAA...your-public-key... commentThe .git_allowed_signers file is intentionally not committed in v0.2 because there is only one maintainer; if more maintainers are added, we will commit a curated file and pin the path globally. For now, each maintainer keeps their own local copy.
git tag -s vX.Y.Z -m "vX.Y.Z" # stable cut (v1.0+)
git tag -s vX.Y.Z-rc.N -m "vX.Y.Z-rc.N" # prerelease cut, if any
git verify-tag vX.Y.Z
# Expected: "Good \"git\" signature for <your-email>"After git push origin vX.Y.Z, the tag page on GitHub should show a green "Verified" badge. If it does not, double-check that the SSH public key is registered as a Signing Key (not just an Authentication Key) on your GitHub profile.
v0.1.0-alpha.0 is unsigned (predates this policy). It is intentionally not re-tagged; moving it would invalidate the npm publish that points at the original commit. The signed-release policy applies from v0.2.0-alpha.0 forward.
npm publishes the first version of a package with both the chosen tag (e.g. --tag alpha) and the implicit latest tag set. npm dist-tag rm code-pact latest is rejected with HTTP 400 by the registry — the latest tag is reserved and can only be moved, not deleted.
Starting with v1.0.0:
- Stable releases (
v1.x.0,v1.x.ypatches) publish tolatestby default. Plainnpm publishis enough —1.x.yis not a prerelease string, so npm puts it onlatestautomatically. - The historical
alphatag continues to point atv0.9.0-alpha.0sonpm install code-pact@alphakeeps working for users pinned to pre-v1.0 behaviour. Past alphas are not maintained but the tag is not deleted. - Future prerelease cuts (if any) should use
--tag rc/--tag betaand not auto-promote tolatestuntil the corresponding stable cut.
Pre-v1.0 history (kept for reference): during the alpha period, every release was published with --tag alpha AND the latest tag was manually moved (npm dist-tag add code-pact@<version> latest) so plain npm install code-pact returned the newest alpha rather than the oldest v0.1.0-alpha.0. The v1.0.0 publish replaces that workaround.