diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..c6f651c --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @PatrickSys diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..3fb23b1 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,10 @@ +## What / Why + +Explain the change briefly. + +## Release notes + +This repo uses an automated release flow. + +- Please use a Conventional-Commits style PR title (we squash-merge): `feat: ...`, `fix: ...`, `docs: ...`, `chore: ...`, `refactor: ...` +- If this should not appear in release notes, say so here diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..20202b3 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,49 @@ +name: Publish + +on: + push: + tags: + - 'v*' + +permissions: + contents: read + +jobs: + npm: + name: Publish to npm + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - uses: pnpm/action-setup@v2 + with: + version: 10 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + cache: 'pnpm' + + - name: Install + run: pnpm install --frozen-lockfile + + - name: Verify tag matches package.json version + run: node scripts/ci/ensure-tag-matches-package.mjs + env: + GITHUB_REF_NAME: ${{ github.ref_name }} + + - name: Quality gates + run: | + pnpm lint + pnpm format:check + pnpm type-check + pnpm test + pnpm build + + - name: Publish + run: pnpm publish --access public --no-git-checks + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..b8ca6b0 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,19 @@ +name: Release Please + +on: + push: + branches: [master] + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + name: Create/Update Release PR + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + with: + release-type: node + package-name: codebase-context diff --git a/.prettierrc b/.prettierrc index 8867f14..3aaae9e 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,5 +3,6 @@ "trailingComma": "none", "singleQuote": true, "printWidth": 100, - "tabWidth": 2 + "tabWidth": 2, + "endOfLine": "auto" } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a434521..5cfdc64 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,6 @@ src/ **Tests** - Run `pnpm test`. We use Vitest for unit and smoke testing. - ## Adding a Framework Analyzer 1. Create `src/analyzers/react/index.ts` @@ -77,3 +76,23 @@ The server logs to stderr, so you can see what it's doing. - Open PR with what you changed and why No strict commit format, just be clear about what you're doing. + +## Release Notes (PR titles) + +This repo publishes to npm via an automated Release PR flow. + +To keep releases predictable and human-readable, please use a Conventional-Commits style **PR title** (we usually squash-merge, and the PR title becomes the commit message): + +- `feat: ...` (new feature) +- `fix: ...` (bug fix) +- `docs: ...` (docs-only) +- `chore: ...` (maintenance) +- `refactor: ...` (refactor) + +Examples: + +- `feat: add memory store for team conventions` +- `fix: avoid creating directories on invalid ROOT_PATH` +- `docs: clarify MCP client config and npx --yes` + +Maintainers: release steps are documented in `RELEASING.md`. diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..89bcb4d --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,42 @@ +# Releasing + +This repo publishes an npm package: `codebase-context`. + +We use a clean OSS-style flow: + +- PRs merge into `master` (nothing publishes on merge) +- A release is created by a dedicated **Release PR** opened/updated automatically +- When the Release PR is merged, CI creates a git tag like `v1.2.3` +- Tag pushes trigger CI to publish to npm + +## One-time setup (maintainers) + +1. Add a repository secret: `NPM_TOKEN` + - Create an npm access token with publish rights for `codebase-context` + - Add it in GitHub: Settings > Secrets and variables > Actions > New repository secret + - If your npm tokens expire (for example after 90 days), rotate the token and update this secret before it expires + +2. (Recommended) Protect `master` + - Require PRs (no direct pushes) + - Require the `Tests` workflow to pass + +This repo also uses `CODEOWNERS` so PRs from non-owners require an approval from `@PatrickSys`. + +## Normal release flow + +1. Merge changes into `master` via PRs. + - Recommended: use **Squash and merge** so the PR title becomes the commit message. + - Release automation relies on Conventional-Commits style messages like `feat: ...` / `fix: ...`. + +2. Wait for the bot PR named like `release-please--branches--master`. + - It bumps `package.json` and updates `CHANGELOG.md` + - If it already exists, it gets updated automatically as new PRs merge + +3. When you're ready to ship, merge the Release PR. + - This creates a git tag `vX.Y.Z` and a GitHub Release + - The `Publish` workflow runs on the tag and publishes to npm + +## Notes + +- Publishing is triggered only by `v*` tags. +- The publish workflow verifies `tag == v${package.json.version}` and fails fast if they don't match. diff --git a/scripts/ci/ensure-tag-matches-package.mjs b/scripts/ci/ensure-tag-matches-package.mjs new file mode 100644 index 0000000..8fd2a5f --- /dev/null +++ b/scripts/ci/ensure-tag-matches-package.mjs @@ -0,0 +1,29 @@ +import fs from 'node:fs'; + +const tag = process.env.GITHUB_REF_NAME; +if (!tag) { + console.error('Missing GITHUB_REF_NAME'); + process.exit(1); +} + +if (!tag.startsWith('v')) { + console.error(`Expected tag like v1.2.3, got: ${tag}`); + process.exit(1); +} + +const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); +const version = pkg?.version; +if (!version || typeof version !== 'string') { + console.error('Missing package.json version'); + process.exit(1); +} + +const expectedTag = `v${version}`; +if (tag !== expectedTag) { + console.error( + `Tag/package.json version mismatch. Tag is ${tag}, but package.json is ${version} (expected ${expectedTag}).` + ); + process.exit(1); +} + +console.log(`OK: ${tag} matches package.json version ${version}`);