Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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"
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,4 @@ function isLocalPath(str: string): boolean {
- **Regression prevention**: Test stays in codebase to catch future breaks
- **Clarity**: Test documents expected behavior
- **Confidence**: Linting + tests pass before committing

3 changes: 2 additions & 1 deletion apps/pair-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"config.json"
],
"publishConfig": {
"registry": "https://npm.pkg.github.com/"
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"author": "Gianluca Carucci <gianluca@carucci.org>",
"private": false,
Expand Down
2 changes: 1 addition & 1 deletion apps/website/content/docs/contributing/release-process.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ The tag push triggers the **Release workflow** (`release.yml`) which:
|----------|-------------|
| `pair-cli-manual-{version}.zip` | Self-contained CLI bundle (ncc, no node_modules) |
| `pair-cli-manual-{version}.zip.sha256` | SHA256 checksum |
| `pair-cli-{version}.tgz` | npm package for GitHub Packages |
| `pair-cli-{version}.tgz` | npm package for npmjs.org |
| `pair-cli-{version}.tgz.sha256` | TGZ checksum |

### Knowledge Base
Expand Down
4 changes: 2 additions & 2 deletions apps/website/content/docs/getting-started/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ Get up and running with pair on macOS or Linux. By the end of this guide, your p
Install pair-cli globally:

```bash
npm install -g @pair/pair-cli
npm install -g @foomakers/pair-cli
```

Or with pnpm:

```bash
pnpm add -g @pair/pair-cli
pnpm add -g @foomakers/pair-cli
```

### Option 2: Manual Install (Offline)
Expand Down
4 changes: 2 additions & 2 deletions apps/website/content/docs/guides/adopter-checklist.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ Choose one installation method:
**npm Global Install:**

```bash
npm install -g @pair/pair-cli
npm install -g @foomakers/pair-cli
```

**pnpm Global Install:**

```bash
pnpm add -g @pair/pair-cli
pnpm add -g @foomakers/pair-cli
```

**Manual Install:**
Expand Down
2 changes: 1 addition & 1 deletion apps/website/content/docs/guides/cli-workflows.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ Add to your CI pipeline:
# In .github/workflows/ci.yml
- name: Install pair assets
run: |
npm install -g @pair/pair-cli
npm install -g @foomakers/pair-cli
pair-cli install
```

Expand Down
1 change: 1 addition & 0 deletions apps/website/content/docs/guides/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"install-from-url",
"customize-kb",
"adopter-checklist",
"packaging",
"troubleshooting",
"update-link"
]
Expand Down
Loading
Loading