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
2 changes: 1 addition & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
]
],
"linked": [],
"access": "restricted",
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
Expand Down
78 changes: 46 additions & 32 deletions .claude/skills/pair-capability-design-manual-tests/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Each test case follows the [manual-test-case-template](../../../.pair/knowledge/
| Argument | Required | Description |
| --- | --- | --- |
| `$output` | No | Directory where suite files are written. Default: `qa/release-validation/` at project root. |
| `$scope` | No | Artifact categories to cover: `website`, `cli`, `dataset`, `registry`, `all` (default: `all`). Comma-separated for multiple. |
| `$scope` | No | Artifact categories to cover. Auto-discovered from the project (e.g., `website`, `api`, `cli`, `dataset`, `mobile`). Use `all` (default) or comma-separated category names. |

## Algorithm

Expand All @@ -37,29 +37,34 @@ Execute in sequence. For every step, follow the **check → skip → act → ver
Analyze the project to build an inventory of testable surfaces. For each category, read the relevant sources:

1. **Check**: Which artifact categories are in `$scope`?
2. **Act**: For each category in scope, discover:
2. **Act**: Discover categories dynamically from the project. Scan for:
- **Deployable artifacts**: websites, APIs, CLI tools, mobile apps, desktop apps, libraries
- **Data/content artifacts**: datasets, configuration bundles, documentation packages
- **Distribution channels**: package registries, app stores, CDNs, container registries

| Category | Discovery Sources | What to Extract |
| --- | --- | --- |
| **Website** | Deployment config, `package.json` scripts, adoption files, sitemap, route files | Base URL, page routes, interactive features (search, forms), responsive breakpoints, meta tags, accessibility targets |
| **CLI** | `package.json` `bin` field, Commander command definitions, `--help` output | Command names, flags, positional args, exit code expectations, output formats |
| **Dataset** | KB config files, registry definitions, adoption files | Registries, install strategies (mirror/add), validation commands, expected directory structure |
| **Registry** | `package.json` `publishConfig`, workflow files, adoption files | Package scope, registry URL, publish mechanism, expected metadata |
Common discovery sources:

| Signal | Where to Look | What It Reveals |
| --- | --- | --- |
| Web framework | `package.json` dependencies, framework config files | Website/API category |
| `bin` field | `package.json` | CLI category |
| `publishConfig` | `package.json`, workflow files | Registry/distribution category |
| Release artifacts | CI/CD workflows, release scripts | Artifact categories (ZIP, TGZ, Docker, etc.) |
| Deployment config | Vercel, Docker, Kubernetes, serverless configs | Deployment targets |

1. **Act**: For each category, also read:
- **PRD** (`.pair/product/adopted/PRD.md`) — for user-facing requirements and acceptance criteria
- **Architecture** (`.pair/adoption/tech/architecture.md`) — for deployment topology
- **Way of working** (`.pair/adoption/tech/way-of-working.md`) — for release process, quality gates
- **Tech stack** (`.pair/adoption/tech/tech-stack.md`) — for framework specifics (e.g., Next.js → check SSR, static pages)
3. **Act**: For each discovered category, also read adoption files if available:
- **PRD** — for user-facing requirements and acceptance criteria
- **Architecture** — for deployment topology
- **Way of working** — for release process, quality gates
- **Tech stack** — for framework specifics

2. **Verify**: Surface inventory built. Present to user:
4. **Verify**: Surface inventory built. Present to user:

```text
DISCOVERED SURFACES:
├── Website: [N pages, N interactive features, N responsive breakpoints]
├── CLI: [N commands, N flags]
├── Dataset: [N registries, N validation rules]
└── Registry: [N packages, N distribution channels]
├── [Category 1]: [N artifacts, N features]
├── [Category 2]: [N artifacts, N features]
└── [Category N]: [N artifacts, N features]
```

Ask: _"Proceed with these surfaces? Add or remove anything?"_
Expand All @@ -68,21 +73,30 @@ Ask: _"Proceed with these surfaces? Add or remove anything?"_

For each category, design critical paths ordered by release risk.

1. **Act**: Apply the following heuristic to group tests:
1. **Act**: Group tests into critical paths (CPs) based on discovered categories. Apply these heuristics:

**Grouping rules:**
- One CP per major artifact category (e.g., website, CLI, API, dataset)
- Split large categories into sub-CPs by concern (e.g., artifact integrity vs functional tests)
- Merge small categories (< 3 tests) into the nearest related CP

**Priority assignment:**
- **P0**: Release blockers — artifacts exist, checksum valid, core functionality works
- **P1**: Important — secondary features, documentation completeness, edge cases
- **P2**: Nice-to-have — cosmetic, search, non-critical integrations

**Naming convention:** `CP{N}-{category-slug}.md` (e.g., `CP1-website-critical-path.md`, `CP2-api-endpoints.md`)

**Example** (a project with website + CLI + registry):

| CP Pattern | Category | Priority | Covers |
| --- | --- | --- | --- |
| CP1 | Website Critical Path | P0 | Landing loads, core navigation, responsive, meta tags, favicon, key CTAs |
| CP2 | CLI Artifact Critical Path | P0 | Checksum verification, extraction, binary execution, version output |
| CP3 | CLI Functional Path | P0-P1 | Install, update, key commands, error handling, idempotency |
| CP4 | Dataset Validation | P1 | KB structure, validation commands, content integrity |
| CP5 | Website Docs Completeness | P1 | All doc pages return 200, sidebar matches routes |
| CP6 | Website Search & Navigation | P1-P2 | Search functionality, responsive navigation, 404 handling |
| CP7 | Registry Publish | P2 | Package visibility, install from registry, functional after install |
| CP | Category | Priority | Covers |
| --- | --- | --- | --- |
| CP1 | Website Critical Path | P0 | Landing loads, navigation, responsive, meta |
| CP2 | CLI Artifact Integrity | P0 | Download, checksum, extraction, version output |
| CP3 | CLI Functional | P0-P1 | Core commands, error handling, idempotency |
| CP4 | Registry Publish | P1 | Package visibility, install from registry |

- **Skip** CPs for categories not in `$scope`.
- **Merge** if a category has very few tests (< 3) — combine into the nearest related CP.
- **Split** if a category has many tests (> 20) — break into sub-CPs (e.g., CP3a, CP3b).
Adapt the number, naming, and content of CPs to match the actual project — do not use this example as a fixed template.

1. **Verify**: CP plan built. Present CP outline with estimated test counts before generating files.

Expand All @@ -94,7 +108,7 @@ For each CP, generate individual test cases.
- Assign ID: `MT-CP{N}{NN}` (e.g., `MT-CP101`, `MT-CP201`)
- Set priority: P0 (blocks release) / P1 (important) / P2 (nice-to-have)
- Define preconditions (reference earlier test IDs where needed)
- Write concrete, observable steps using variables (`$VERSION`, `$BASE_URL`, `$WORKDIR`, `$RELEASE_URL`, `$REGISTRY`)
- Write concrete, observable steps using variables (e.g., `$VERSION`, `$BASE_URL`, `$WORKDIR`, `$RELEASE_URL` — define project-specific variables in the suite README)
- Write objective expected results (HTTP status, exit code, file existence, string match — no subjective criteria)
- Add notes for edge cases, platform differences, related tests

Expand Down
16 changes: 6 additions & 10 deletions .claude/skills/pair-capability-execute-manual-tests/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,21 @@ Execute in sequence. For every step, follow the **check → skip → act → ver

### Step 2: Resolve Variables

1. **Check**: Are all required variables resolvable (`$VERSION`, `$BASE_URL`, `$WORKDIR`, `$RELEASE_URL`, `$REGISTRY`)?
1. **Check**: Are all variables declared in the suite `README.md` Variables table resolvable?
2. **Skip**: Variables already provided via arguments.
3. **Act**: For each unresolved variable:
3. **Act**: For each unresolved variable, follow the "How to resolve" column in the suite README. Common patterns:
- `$VERSION`: extract from artifact (`--version` flag) or release tag.
- `$BASE_URL`: read from deployment config, adoption files, or ask the user.
- `$WORKDIR`: create isolated temp directory: `mktemp -d /tmp/manual-test.XXXXX`.
- `$RELEASE_URL`: derive from `$VERSION` and repo URL.
- `$REGISTRY`: read from adoption files or default.
- **Additional variables**: resolve any extra variables declared in the suite `README.md` Variables table (e.g. auth tokens, config files). Follow the "How to resolve" column for each.
- **Project-specific variables**: resolve per the suite README instructions (e.g., auth tokens, registry URLs, API keys).
4. **Verify**: All variables resolved. Present to user for confirmation:

```text
VARIABLES RESOLVED:
├── VERSION: [value]
├── BASE_URL: [value]
├── WORKDIR: [value]
├── RELEASE_URL: [value]
├── REGISTRY: [value]
└── [additional]: [per suite README]
├── [var1]: [value]
├── [var2]: [value]
└── [varN]: [value]
```

Ask: _"Proceed with these values?"_
Expand Down
116 changes: 8 additions & 108 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
required: true
type: string
publish:
description: 'Set to "true" to run the publish-gh-packages job (gated)'
description: 'Set to "true" to publish package to npmjs.org'
required: false
type: boolean
release:
Expand Down Expand Up @@ -193,44 +193,19 @@ jobs:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

publish-gh-packages:
publish-npm:
runs-on: ubuntu-latest
needs: release
permissions:
contents: read
packages: write
env:
# Use the workflow-provided token if available; consumers can also set a repository secret
# Publishing requires write:packages scope. GITHUB_TOKEN is sufficient for public repos.
GITHUB_TOKEN: ${{ github.token }}
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Run this job for manual dispatch with publish=true OR when a tag push starting with v* occurs
if: >
(github.event_name == 'workflow_dispatch' && github.event.inputs.publish == 'true')
|| (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v'))
steps:
- name: Publish gate check
# Decide at runtime if we should actually publish. If this run was a manual workflow_dispatch
# require inputs.publish == 'true'; for push/tag events skip the gate and proceed.
run: |
set -euo pipefail
INP_EVENT='${{ github.event_name }}'
if [ "$INP_EVENT" = "workflow_dispatch" ]; then
INP_PUBLISH='${{ github.event.inputs.publish }}'
if [ "$INP_PUBLISH" != "true" ]; then
echo "Publish not requested (inputs.publish != true). Exiting job without publishing."
exit 0
fi
fi
echo "Publish requested or triggered by tag push; continuing with publish job."
- name: Checkout code
uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: '10.15.0'

- name: Setup Node.js
uses: actions/setup-node@v4
with:
Expand All @@ -239,89 +214,14 @@ jobs:
- name: Download TGZ artifact
uses: actions/download-artifact@v4
with:
# Prefer normalized version (no leading v) exposed by the release job
name: pair-cli-tgz-${{ needs.release.outputs.version_no_v || needs.release.outputs.version || steps.version.outputs.version }}
name: pair-cli-tgz-${{ needs.release.outputs.version_no_v || needs.release.outputs.version }}

- name: Validate package metadata and token
run: |
set -euo pipefail
V=${{ needs.release.outputs.version_no_v || needs.release.outputs.version || steps.version.outputs.version || github.event.inputs.version }}
TGZ=pair-cli-${V}.tgz
echo "Looking for expected files: $TGZ and pair-cli-<version>.meta.json in workspace:" && ls -la || true
if [ ! -f "$TGZ" ]; then
echo "Warning: expected tarball $TGZ not found. Listing available .tgz files:" && ls -la *.tgz 2>/dev/null || true
# Continue: attempt to locate a .tgz produced by the artifact bundle
fi
# Prefer reading package metadata produced at pack time (meta.json) inside the artifact
META_FILE=pair-cli-${V}.meta.json
if [ ! -f "$META_FILE" ]; then
ALT_META=$(ls pair-cli-*.meta.json 2>/dev/null | head -n1 || true)
if [ -n "$ALT_META" ]; then
echo "Info: exact META_FILE $META_FILE not found; using discovered meta file: $ALT_META"
META_FILE="$ALT_META"
else
echo "Info: no pair-cli-*.meta.json found in workspace; will fall back to workspace package.json (diagnostics below)"
fi
fi
if [ -n "${META_FILE:-}" ] && [ -f "$META_FILE" ]; then
PKG_NAME=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$META_FILE','utf8')).name)")
PKG_PRIVATE=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$META_FILE','utf8')).private)")
echo "Found package metadata file: $META_FILE"
else
# Fallback to workspace package.json for validation
echo "Falling back to workspace package.json for validation"
PKG_NAME=$(node -e "console.log(require('./apps/pair-cli/package.json').name)")
PKG_PRIVATE=$(node -e "console.log(require('./apps/pair-cli/package.json').private)")
fi
echo "Found package: $PKG_NAME (private=$PKG_PRIVATE)"
if [ "$PKG_PRIVATE" = "true" ] || [ "$PKG_PRIVATE" = "1" ]; then
echo "Aborting publish: package.private is true"
exit 1
fi
if [[ "$PKG_NAME" != @foomakers/* ]]; then
echo "Aborting publish: package.name must be scoped to @foomakers for GitHub Packages publishing. Found: $PKG_NAME"
exit 1
fi
# Prefer job-provided GITHUB_TOKEN; if not present, check repository secrets.GITHUB_TOKEN
# For public repos, GITHUB_TOKEN has sufficient permissions for publishing.
if [ -z "${GITHUB_TOKEN:-}" ] && [ -z "${NODE_AUTH_TOKEN:-}" ]; then
echo "Warning: No GITHUB_TOKEN/NODE_AUTH_TOKEN available in this run."
echo "If you intended to publish, re-run with the 'publish' input (workflow_dispatch) and ensure the GITHUB_TOKEN secret is configured for the repository or use a personal token in secrets.GITHUB_TOKEN."
echo "Aborting publish to avoid accidental public package publication."
exit 1
fi
echo "Token availability: GITHUB_TOKEN=${GITHUB_TOKEN:+present} NODE_AUTH_TOKEN=${NODE_AUTH_TOKEN:+present}"

- name: Publish to GitHub Packages
- name: Publish to npmjs.org
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
V=${{ needs.release.outputs.version_no_v || needs.release.outputs.version || steps.version.outputs.version || github.event.inputs.version }}
V=${{ needs.release.outputs.version_no_v || needs.release.outputs.version }}
TGZ=pair-cli-${V}.tgz
echo "Publishing $TGZ to GitHub Packages"
# If NODE_AUTH_TOKEN isn't set explicitly, fall back to job-provided GITHUB_TOKEN
if [ -z "${NODE_AUTH_TOKEN:-}" ] && [ -n "${GITHUB_TOKEN:-}" ]; then
NODE_AUTH_TOKEN="$GITHUB_TOKEN"
fi

# Configure npm auth for GitHub Packages; write to $HOME/.npmrc so npm always finds it
if [ -n "${NODE_AUTH_TOKEN:-}" ]; then
printf "@foomakers:registry=https://npm.pkg.github.com/\n//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}\nalways-auth=true\n" > "$HOME/.npmrc"
chmod 600 "$HOME/.npmrc"
echo "Wrote npm auth to $HOME/.npmrc"
else
echo "No NODE_AUTH_TOKEN available; npm publish may fail due to missing credentials"
fi

npm publish "$TGZ" --registry https://npm.pkg.github.com/
echo "Published. Verifying via npm view..."
sleep 3
# Determine package name from meta file if present (artifact produced it at pack time)
if [ -f "pair-cli-${V}.meta.json" ]; then
PKG_PUB_NAME=$(node -e "console.log(JSON.parse(require('fs').readFileSync('pair-cli-${V}.meta.json','utf8')).name)")
else
PKG_PUB_NAME=$(node -e "console.log(require('./apps/pair-cli/package.json').name)")
fi
echo "Verifying published package: $PKG_PUB_NAME"
npm view "$PKG_PUB_NAME" version --registry https://npm.pkg.github.com/ || true
chmod +x scripts/publish-npm.sh
./scripts/publish-npm.sh "$TGZ"
12 changes: 5 additions & 7 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,19 +189,17 @@ pnpm exec changeset version # Generate version bumps + changelogs
- Cache stored in `node_modules/.cache/turbo`
- `turbo clean` to clear cache if needed

## GitHub Packages
## npmjs.org

`@foomakers/pair-cli` is published on GitHub Packages. The repository is **public**, so no authentication is required to install.
`@foomakers/pair-cli` is published on npmjs.org as a public package. No `.npmrc` or token needed to install:

**`.npmrc` (user-level or project-level):**

```ini
@foomakers:registry=https://npm.pkg.github.com/
```bash
npx @foomakers/pair-cli install
```

**CI (GitHub Actions) — publishing only:**

Publishing still requires authentication with `write:packages` scope. The workflow uses `GITHUB_TOKEN` automatically.
Publishing requires `NPM_TOKEN` secret (granular access token from npmjs.org scoped to `@foomakers`).

## Environment Variables

Expand Down
24 changes: 12 additions & 12 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,36 +114,36 @@ scripts/package-manual.sh <version>

Requires: `ncc` (`@vercel/ncc`), `dts-bundle-generator`, `zip`, `sha256sum`/`shasum`.

## GitHub Packages
## npmjs.org

The release produces a `.tgz` for publishing to GitHub Packages.
The release produces a `.tgz` for publishing to npmjs.org. The package is public — no authentication required for consumers.

### CI authentication (publishing)

Publishing requires `write:packages` scope. The workflow uses `GITHUB_TOKEN` automatically:
Publishing uses an `NPM_TOKEN` secret (granular access token from npmjs.org):

```yaml
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "@foomakers:registry=https://npm.pkg.github.com/" > ~/.npmrc
echo "//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: ./scripts/publish-npm.sh "$TGZ"
```

### Consumer installation

The repository is **public** — no authentication token is required to install packages. Consumers only need registry configuration:
No `.npmrc` or token needed — the package is public on the default npm registry:

```ini
# ~/.npmrc
@foomakers:registry=https://npm.pkg.github.com/
```bash
npx @foomakers/pair-cli install
# or
npm install -g @foomakers/pair-cli
```

## Repository Configuration

### Token and permissions

- **`GITHUB_TOKEN`** (default): sufficient for creating tags, releases, and publishing to GitHub Packages.
- **`GITHUB_TOKEN`** (default): sufficient for creating tags, releases, and uploading release assets.
- **`NPM_TOKEN`**: required for publishing to npmjs.org. Granular access token scoped to `@foomakers`.
- **`GH_RELEASE_TOKEN`** (optional PAT): only needed if org policy prevents `GITHUB_TOKEN` from creating tags/releases. Add as repository secret with `repo` scope.
- Workflow permissions: "Read and write permissions" required.

Expand Down
Loading
Loading