diff --git a/.changeset/config.json b/.changeset/config.json index befcfccd..166aa0cd 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -15,7 +15,7 @@ ] ], "linked": [], - "access": "restricted", + "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", "ignore": [] diff --git a/.claude/skills/pair-capability-design-manual-tests/SKILL.md b/.claude/skills/pair-capability-design-manual-tests/SKILL.md index 0fb65fed..35f053ed 100644 --- a/.claude/skills/pair-capability-design-manual-tests/SKILL.md +++ b/.claude/skills/pair-capability-design-manual-tests/SKILL.md @@ -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 @@ -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?"_ @@ -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. @@ -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 diff --git a/.claude/skills/pair-capability-execute-manual-tests/SKILL.md b/.claude/skills/pair-capability-execute-manual-tests/SKILL.md index 3edefb1a..23d1d46c 100644 --- a/.claude/skills/pair-capability-execute-manual-tests/SKILL.md +++ b/.claude/skills/pair-capability-execute-manual-tests/SKILL.md @@ -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?"_ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 65948b44..8ebae2a9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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: @@ -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: @@ -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-.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" diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index ce416c57..fe48b13a 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -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 diff --git a/RELEASE.md b/RELEASE.md index bd2da9c2..5c3fbace 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -114,36 +114,36 @@ scripts/package-manual.sh 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. diff --git a/apps/pair-cli/package.json b/apps/pair-cli/package.json index 3df84a0b..5bdbaf51 100644 --- a/apps/pair-cli/package.json +++ b/apps/pair-cli/package.json @@ -14,7 +14,8 @@ "config.json" ], "publishConfig": { - "registry": "https://npm.pkg.github.com/" + "registry": "https://registry.npmjs.org/", + "access": "public" }, "author": "Gianluca Carucci ", "private": false, diff --git a/apps/website/content/docs/contributing/release-process.mdx b/apps/website/content/docs/contributing/release-process.mdx index 6ae7a7be..f5b230d4 100644 --- a/apps/website/content/docs/contributing/release-process.mdx +++ b/apps/website/content/docs/contributing/release-process.mdx @@ -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 diff --git a/apps/website/content/docs/getting-started/quickstart.mdx b/apps/website/content/docs/getting-started/quickstart.mdx index 0e73339e..f87d7f3e 100644 --- a/apps/website/content/docs/getting-started/quickstart.mdx +++ b/apps/website/content/docs/getting-started/quickstart.mdx @@ -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) diff --git a/apps/website/content/docs/guides/adopter-checklist.mdx b/apps/website/content/docs/guides/adopter-checklist.mdx index afb3d039..56be0ee7 100644 --- a/apps/website/content/docs/guides/adopter-checklist.mdx +++ b/apps/website/content/docs/guides/adopter-checklist.mdx @@ -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:** diff --git a/apps/website/content/docs/guides/cli-workflows.mdx b/apps/website/content/docs/guides/cli-workflows.mdx index 4cc7890c..ff67749e 100644 --- a/apps/website/content/docs/guides/cli-workflows.mdx +++ b/apps/website/content/docs/guides/cli-workflows.mdx @@ -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 ``` diff --git a/apps/website/content/docs/guides/meta.json b/apps/website/content/docs/guides/meta.json index b6c0392a..418d5ab7 100644 --- a/apps/website/content/docs/guides/meta.json +++ b/apps/website/content/docs/guides/meta.json @@ -5,6 +5,7 @@ "install-from-url", "customize-kb", "adopter-checklist", + "packaging", "troubleshooting", "update-link" ] diff --git a/apps/website/content/docs/guides/packaging.mdx b/apps/website/content/docs/guides/packaging.mdx new file mode 100644 index 00000000..942e143b --- /dev/null +++ b/apps/website/content/docs/guides/packaging.mdx @@ -0,0 +1,125 @@ +--- +title: Packaging a Knowledge Base +description: Package your KB for distribution using source or target layout modes. +--- + +import { Callout } from 'fumadocs-ui/components/callout'; + +## Overview + +The `pair package` command creates a distributable ZIP from your Knowledge Base. It supports two **layout modes** that determine which files are included: + +| Layout | Flag | What it packages | When to use | +|--------|------|-----------------|-------------| +| **Target** (default) | `--layout target` | Files from installed target directories (e.g., `.pair/knowledge/`, `.claude/skills/`) | Packaging an installed KB for redistribution | +| **Source** | `--layout source` | Files from the single source directory defined in `config.json` | Packaging the original KB dataset before installation | + +## Source vs Target Layout + +### Source Layout + +Packages from the **source directory** defined in each registry's `source` field in `config.json`. This is the canonical, pre-installation structure. + +```bash +pair package --layout source +``` + +Use source layout when: +- You maintain a KB dataset and want to distribute the **original files** +- You want a clean package without installation-time transformations (prefix, flatten) +- You're building a CI/CD pipeline that packages from the dataset directly + +**Example:** A KB with `config.json` registry `skills.source: ".skills"` packages from `.skills/category/name/SKILL.md`. + +### Target Layout + +Packages from **installed target directories** — the files as they appear after `pair install`. This is the default. + +```bash +pair package --layout target +``` + +Use target layout when: +- You want to package the KB **as users see it** after installation +- You need to include prefix/flatten transformations applied during install +- You're redistributing a customized KB from a project + +**Example:** The same skills registry with `flatten: true, prefix: "pair"` packages from `.claude/skills/pair-category-name/SKILL.md`. + + + Target layout excludes symlink targets automatically — only canonical (physical copy) targets are included. + + +## Packaging Walkthrough + +### 1. Validate before packaging + +```bash +# Validate with the same layout you'll package +pair kb validate --layout source +pair kb validate --layout target +``` + +### 2. Package + +```bash +# Source layout — original dataset +pair package --layout source -o dist/my-kb-source.zip + +# Target layout — installed structure (default) +pair package --layout target -o dist/my-kb-target.zip + +# With metadata +pair package --layout source \ + --name "My KB" \ + --version 1.0.0 \ + --description "Team knowledge base" +``` + +### 3. Verify the package + +```bash +# Inspect the ZIP contents +unzip -l dist/my-kb-source.zip + +# Validate the packaged KB +pair kb validate dist/my-kb-source.zip +``` + +## Organizational Packaging + +For enterprise distribution, add organizational metadata: + +```bash +pair package --layout source \ + --org \ + --org-name "Acme Corp" \ + --team "Platform" \ + --compliance "SOC2,ISO27001" \ + --distribution restricted +``` + +See [Organization Customization](/docs/customization/organization) for the full enterprise workflow. + +## Layout and config.json + +The layout mode maps to the `source` and `targets` fields in your `config.json` registries: + +```json +{ + "asset_registries": { + "skills": { + "source": ".skills", + "flatten": true, + "prefix": "pair", + "targets": [ + { "path": ".claude/skills", "mode": "canonical" }, + { "path": ".cursor/skills", "mode": "symlink" } + ] + } + } +} +``` + +- `--layout source` reads from `.skills/` +- `--layout target` reads from `.claude/skills/` (canonical target only — `.cursor/skills/` is excluded because it's a symlink) diff --git a/apps/website/content/docs/guides/troubleshooting.mdx b/apps/website/content/docs/guides/troubleshooting.mdx index 17b5c273..f34a8d29 100644 --- a/apps/website/content/docs/guides/troubleshooting.mdx +++ b/apps/website/content/docs/guides/troubleshooting.mdx @@ -9,7 +9,7 @@ Common issues and solutions for pair-cli installation and usage. ### Permission Errors -**Problem:** `npm install -g @pair/pair-cli` fails with permission errors. +**Problem:** `npm install -g @foomakers/pair-cli` fails with permission errors. **Solution:** Use a Node version manager or install locally. @@ -24,7 +24,7 @@ pnpm add -D @foomakers/pair-cli # Use with: pnpm dlx pair-cli # Option 3: Use sudo (not recommended) -sudo npm install -g @pair/pair-cli +sudo npm install -g @foomakers/pair-cli ``` ### Command Not Found @@ -209,7 +209,7 @@ pair-cli --version # Installation method which pair-cli -npm list -g @pair/pair-cli +npm list -g @foomakers/pair-cli ``` ### Support Channels @@ -233,4 +233,4 @@ npm list -g @pair/pair-cli - Always use Node 18+ with pnpm - Install globally with nvm, locally with pnpm dlx - Test in a clean directory first -- Keep pair-cli updated: `npm update -g @pair/pair-cli` +- Keep pair-cli updated: `npm update -g @foomakers/pair-cli` diff --git a/apps/website/content/docs/reference/cli/commands.mdx b/apps/website/content/docs/reference/cli/commands.mdx index fbf65d60..bcd15462 100644 --- a/apps/website/content/docs/reference/cli/commands.mdx +++ b/apps/website/content/docs/reference/cli/commands.mdx @@ -184,6 +184,7 @@ pair package [options] | Option | Short | Description | | ----------------------------- | ----- | ------------------------------------------------------------------- | +| `--layout ` | | Layout mode: `source` or `target` (default: `target`) | | `--output ` | `-o` | Output ZIP file path (default: `kb-package.zip`) | | `--source-dir ` | `-s` | Source directory to package (default: current directory) | | `--config ` | `-c` | Path to config.json file | @@ -208,9 +209,12 @@ pair package [options] ### Examples ```bash -# Package current directory +# Package current directory (target layout — default) pair package +# Package from source layout +pair package --layout source + # Package with custom output path pair package -o dist/kb-v1.0.0.zip diff --git a/apps/website/content/docs/support/faq.mdx b/apps/website/content/docs/support/faq.mdx index 9b8d1b74..d9c6c27d 100644 --- a/apps/website/content/docs/support/faq.mdx +++ b/apps/website/content/docs/support/faq.mdx @@ -17,7 +17,7 @@ pair-cli --version # Current pair-cli version (if installed) # Check installation location which pair-cli # Should show installation path -npm list -g @pair/pair-cli # Global npm installation check +npm list -g @foomakers/pair-cli # Global npm installation check ``` ## Permission Issues @@ -31,22 +31,22 @@ npm list -g @pair/pair-cli # Global npm installation check npm config set prefix ~/.npm-global echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.profile source ~/.profile -npm install -g @pair/pair-cli +npm install -g @foomakers/pair-cli # Option 2: Use pnpm (avoids permission issues) npm install -g pnpm -pnpm add -g @pair/pair-cli +pnpm add -g @foomakers/pair-cli # Option 3: Fix npm permissions (if you prefer npm) sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share} -npm install -g @pair/pair-cli +npm install -g @foomakers/pair-cli ``` **On Windows:** ```cmd # Run PowerShell as Administrator, then: -npm install -g @pair/pair-cli +npm install -g @foomakers/pair-cli ``` ### Problem: Can't write to `/usr/local/lib/node_modules` @@ -59,7 +59,7 @@ echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.profile source ~/.profile # Now install -npm install -g @pair/pair-cli +npm install -g @foomakers/pair-cli ``` ## Node Version Issues @@ -87,7 +87,7 @@ nvm alias default 18 # Verify node --version -npm install -g @pair/pair-cli +npm install -g @foomakers/pair-cli ``` **Fix with Volta (alternative):** @@ -103,17 +103,17 @@ volta install npm # Verify and install node --version -npm install -g @pair/pair-cli +npm install -g @foomakers/pair-cli ``` ### Problem: Multiple Node versions causing conflicts ```bash # Clean up and start fresh -npm uninstall -g @pair/pair-cli +npm uninstall -g @foomakers/pair-cli nvm use 18 # or your preferred version npm cache clean --force -npm install -g @pair/pair-cli +npm install -g @foomakers/pair-cli ``` ## pnpm Issues @@ -143,10 +143,10 @@ pnpm store prune pnpm cache clear # Remove existing installation -pnpm remove -g @pair/pair-cli +pnpm remove -g @foomakers/pair-cli # Reinstall fresh -pnpm add -g @pair/pair-cli +pnpm add -g @foomakers/pair-cli ``` ### Problem: `ERR_PNPM_STORE_BROKEN` or store corruption @@ -160,7 +160,7 @@ rm -rf ~/.pnpm-state # Reinstall pnpm and pair-cli npm install -g pnpm -pnpm add -g @pair/pair-cli +pnpm add -g @foomakers/pair-cli ``` ## Native Build Failures @@ -174,8 +174,8 @@ pnpm add -g @pair/pair-cli xcode-select --install # Clear and reinstall -npm uninstall -g @pair/pair-cli -npm install -g @pair/pair-cli +npm uninstall -g @foomakers/pair-cli +npm install -g @foomakers/pair-cli ``` **On Linux (Ubuntu/Debian):** @@ -186,8 +186,8 @@ sudo apt update sudo apt install build-essential python3-distutils # Clear and reinstall -npm uninstall -g @pair/pair-cli -npm install -g @pair/pair-cli +npm uninstall -g @foomakers/pair-cli +npm install -g @foomakers/pair-cli ``` **On Linux (CentOS/RHEL/Fedora):** @@ -199,8 +199,8 @@ sudo yum groupinstall "Development Tools" sudo dnf groupinstall "Development Tools" # Clear and reinstall -npm uninstall -g @pair/pair-cli -npm install -g @pair/pair-cli +npm uninstall -g @foomakers/pair-cli +npm install -g @foomakers/pair-cli ``` **On Windows:** @@ -248,7 +248,7 @@ npm config set registry https://registry.npmjs.org/ npm config set proxy http://username:password@proxy.company.com:8080 # Install through proxy -npm install -g @pair/pair-cli +npm install -g @foomakers/pair-cli ``` ## Corrupted Installs @@ -259,8 +259,8 @@ npm install -g @pair/pair-cli ```bash # Check if installed -npm list -g @pair/pair-cli -pnpm list -g @pair/pair-cli +npm list -g @foomakers/pair-cli +pnpm list -g @foomakers/pair-cli # Check PATH echo $PATH @@ -282,9 +282,9 @@ source ~/.profile ```bash # 1. Remove completely -npm uninstall -g @pair/pair-cli +npm uninstall -g @foomakers/pair-cli # or -pnpm remove -g @pair/pair-cli +pnpm remove -g @foomakers/pair-cli # 2. Clear caches npm cache clean --force @@ -296,7 +296,7 @@ rm -rf ~/.npm/_global/node_modules/@pair # or similar path for your system # 4. Reinstall fresh -npm install -g @pair/pair-cli +npm install -g @foomakers/pair-cli ``` ### Problem: Version conflicts or "command not found" after update @@ -313,7 +313,7 @@ find ~/.pnpm -name "*pair-cli*" 2>/dev/null # (Review the list first, then remove manually) # 3. Clean install -npm install -g @pair/pair-cli +npm install -g @foomakers/pair-cli # 4. Verify pair-cli --version @@ -334,7 +334,7 @@ pnpm --version # pnpm version (if installed) # pair-cli specific pair-cli --version # pair-cli version which pair-cli # Installation location -npm list -g @pair/pair-cli # Installation details +npm list -g @foomakers/pair-cli # Installation details # Environment echo $PATH # PATH variable diff --git a/apps/website/content/docs/tutorials/enterprise-adoption.mdx b/apps/website/content/docs/tutorials/enterprise-adoption.mdx index d38f03b2..9d1c2ab9 100644 --- a/apps/website/content/docs/tutorials/enterprise-adoption.mdx +++ b/apps/website/content/docs/tutorials/enterprise-adoption.mdx @@ -222,11 +222,7 @@ npm publish --registry https://npm.your-org.com ``` - If using GitHub Packages with a **public** repository, consumers only need registry configuration in `.npmrc`: - ```text - @your-org:registry=https://npm.pkg.github.com - ``` - No authentication token is required for installation. Publishing requires a token with `write:packages` scope. + If publishing to npmjs.org as a public scoped package, consumers need no extra configuration — `npm install @your-org/your-cli` works out of the box. Publishing requires an npm access token with write scope. ### 8. Onboard the first team diff --git a/apps/website/content/docs/tutorials/first-project.mdx b/apps/website/content/docs/tutorials/first-project.mdx index 2388e448..7d215c30 100644 --- a/apps/website/content/docs/tutorials/first-project.mdx +++ b/apps/website/content/docs/tutorials/first-project.mdx @@ -46,7 +46,7 @@ By the end of this tutorial you'll have: #### Option A: Global install (recommended) ```bash -pnpm add -g @pair/pair-cli +pnpm add -g @foomakers/pair-cli ``` Then run it anywhere: @@ -64,7 +64,7 @@ pair-cli --version Add pair-cli to your project so it's pinned to a specific version and available to every developer via `package.json`: ```bash -pnpm add -D @pair/pair-cli +pnpm add -D @foomakers/pair-cli ``` Then run it via `npx` or `pnpm`: diff --git a/apps/website/e2e/docs.e2e.test.ts b/apps/website/e2e/docs.e2e.test.ts index 458a5d6d..2e399555 100644 --- a/apps/website/e2e/docs.e2e.test.ts +++ b/apps/website/e2e/docs.e2e.test.ts @@ -29,8 +29,8 @@ test('quickstart journey: overview → quickstart with content verification', as const main = page.locator('main') await expect(main).toContainText('Prerequisites') await expect(main).toContainText('Node.js 18') - await expect(main).toContainText('npm install -g @pair/pair-cli') - await expect(main).toContainText('pnpm add -g @pair/pair-cli') + await expect(main).toContainText('npm install -g @foomakers/pair-cli') + await expect(main).toContainText('pnpm add -g @foomakers/pair-cli') await expect(main).toContainText('Verify Installation') await expect(main).toContainText('pair-cli --version') await expect(main).toContainText('pair-cli install') diff --git a/packages/knowledge-hub/dataset/.skills/capability/design-manual-tests/SKILL.md b/packages/knowledge-hub/dataset/.skills/capability/design-manual-tests/SKILL.md index 2ac6ea00..1500ba06 100644 --- a/packages/knowledge-hub/dataset/.skills/capability/design-manual-tests/SKILL.md +++ b/packages/knowledge-hub/dataset/.skills/capability/design-manual-tests/SKILL.md @@ -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 @@ -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?"_ @@ -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. @@ -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 diff --git a/packages/knowledge-hub/dataset/.skills/capability/execute-manual-tests/SKILL.md b/packages/knowledge-hub/dataset/.skills/capability/execute-manual-tests/SKILL.md index 879ce951..fee2a8e9 100644 --- a/packages/knowledge-hub/dataset/.skills/capability/execute-manual-tests/SKILL.md +++ b/packages/knowledge-hub/dataset/.skills/capability/execute-manual-tests/SKILL.md @@ -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?"_ diff --git a/qa/release-validation/CP2-cli-artifact-critical-path.md b/qa/release-validation/CP2-cli-artifact-critical-path.md index 1d0af1f5..32d914c4 100644 --- a/qa/release-validation/CP2-cli-artifact-critical-path.md +++ b/qa/release-validation/CP2-cli-artifact-critical-path.md @@ -204,7 +204,7 @@ - `name` == `@foomakers/pair-cli` - `version` == `$VERSION` - `private` == `false` -- `publishConfig.registry` == `https://npm.pkg.github.com/` +- `publishConfig.registry` == `https://registry.npmjs.org/` --- diff --git a/qa/release-validation/CP3-cli-install-update.md b/qa/release-validation/CP3-cli-install-update.md index ac7b7c30..47d1b8d4 100644 --- a/qa/release-validation/CP3-cli-install-update.md +++ b/qa/release-validation/CP3-cli-install-update.md @@ -453,3 +453,30 @@ - The default `config.json` has `skills.source: ".skills"` but the pair repo uses `.claude/skills/` — step 1 remaps this temporarily - Git cache key is a SHA-256 hash of the full URL (including `#ref`), independent of CLI version - Different URLs/refs produce different cache entries (no collisions) + +--- + +## MT-CP320: npx @foomakers/pair-cli install from npmjs.org (no prior config) + +**Priority**: P0 +**Preconditions**: Package published to npmjs.org, internet available, no `.npmrc` for `@foomakers` scope +**Category**: CLI Functional + +### Steps + +1. `mkdir -p $WORKDIR/project-npx && cd $WORKDIR/project-npx` +2. Verify no `.npmrc` exists in `$WORKDIR/project-npx/` or parent dirs that configures `@foomakers` scope +3. `npx @foomakers/pair-cli install` + +### Expected Result + +- Exit code 0 +- CLI downloaded from npmjs.org (no 404, no auth error) +- `.pair/knowledge/` directory created and non-empty +- `AGENTS.md` created at project root + +### Notes + +- This is the primary adoption path — validates that a user with zero configuration can install pair +- If 404: package not published to npmjs.org. If 401: registry still pointing to GitHub Packages +- Complements MT-CP301 (tests auto-download KB) by validating the npm registry path diff --git a/qa/release-validation/CP7-registry-publish.md b/qa/release-validation/CP7-registry-publish.md index 6bde8100..3bec33f8 100644 --- a/qa/release-validation/CP7-registry-publish.md +++ b/qa/release-validation/CP7-registry-publish.md @@ -1,23 +1,21 @@ -# CP7 — Registry Publish +# CP7 — Registry Publish (npmjs.org) -**Priority**: P2 -**Scope**: Package visibility on GitHub Packages, install from registry -**Preconditions**: `publish-gh-packages` job completed. `$WORKDIR` created outside the repo. `$WORKDIR/.npmrc` created by variable resolution step (uses `gh auth token` — no manual PAT needed). +**Priority**: P0 +**Scope**: Package visibility on npmjs.org, install from public registry +**Preconditions**: `publish-npm` job completed (or local `scripts/publish-npm.sh` run). `$WORKDIR` created outside the repo. --- -## MT-CP701: Package visible on GitHub +## MT-CP701: Package visible on npmjs.org -**Priority**: P2 +**Priority**: P0 **Preconditions**: Publish job ran **Category**: Registry ### Steps -1. Navigate to `https://github.com/foomakers/pair/pkgs/npm/pair-cli` - - Direct version link: `https://github.com/orgs/foomakers/packages/npm/pair-cli/$VERSION_ID` - - Release page with all artifacts: `https://github.com/foomakers/pair/releases/tag/v$VERSION` -2. Or run: `gh api /orgs/foomakers/packages?package_type=npm` (requires `read:packages` scope) +1. Open `https://www.npmjs.com/package/@foomakers/pair-cli/v/$VERSION` +2. Or run: `npm view @foomakers/pair-cli@$VERSION` ### Expected Result @@ -26,19 +24,19 @@ ### Notes -- The `gh api` alternative requires a token with `read:packages` scope. If unavailable, verify via browser or `curl` on the direct links above. +- No authentication required — package is public on npmjs.org. --- ## MT-CP702: npm view from registry -**Priority**: P2 -**Preconditions**: `$WORKDIR/.npmrc` created by variable resolution step +**Priority**: P0 +**Preconditions**: None (public package) **Category**: Registry ### Steps -1. `npm view @foomakers/pair-cli@$VERSION --registry=$REGISTRY --userconfig=$WORKDIR/.npmrc` +1. `npm view @foomakers/pair-cli@$VERSION` ### Expected Result @@ -47,14 +45,13 @@ ### Notes -- GitHub Packages npm **requires authentication** even for public repos. Auth is handled automatically via `$WORKDIR/.npmrc` (populated from `gh auth token` during variable resolution). -- If 401: run `gh auth refresh -h github.com -s read:packages` and re-resolve `$NPM_TOKEN`. +- No `.npmrc` or token needed — npmjs.org public packages are accessible without auth. --- ## MT-CP703: Install from registry into isolated project -**Priority**: P2 +**Priority**: P0 **Preconditions**: MT-CP702 passes **Category**: Registry @@ -62,19 +59,22 @@ 1. `mkdir -p $WORKDIR/registry-test && cd $WORKDIR/registry-test` 2. `npm init -y` -3. `cp $WORKDIR/.npmrc $WORKDIR/registry-test/.npmrc` -4. `npm install @foomakers/pair-cli@$VERSION` +3. `npm install @foomakers/pair-cli@$VERSION` ### Expected Result - npm install exits 0 - `node_modules/@foomakers/pair-cli/` exists +### Notes + +- No `.npmrc` needed — default npm registry is registry.npmjs.org. + --- ## MT-CP704: CLI functional after registry install -**Priority**: P2 +**Priority**: P0 **Preconditions**: MT-CP703 passes **Category**: Registry @@ -92,7 +92,7 @@ ## MT-CP705: Install + pair install from registry -**Priority**: P2 +**Priority**: P0 **Preconditions**: MT-CP703 passes **Category**: Registry diff --git a/qa/release-validation/CP8-packaging.md b/qa/release-validation/CP8-packaging.md new file mode 100644 index 00000000..1a43707b --- /dev/null +++ b/qa/release-validation/CP8-packaging.md @@ -0,0 +1,126 @@ +# CP8 — KB Packaging + +**Priority**: P1 +**Scope**: `pair package` with source and target layout modes, validation, metadata +**Preconditions**: Working CLI binary (from CP2). `$WORKDIR` created outside the repo. `$CLI` = path to working pair-cli binary. KB installed in `$WORKDIR/project-auto` (from MT-CP301). + +--- + +## MT-CP801: Package with target layout (default) + +**Priority**: P1 +**Preconditions**: MT-CP301 passes (KB installed in `$WORKDIR/project-auto`) +**Category**: Packaging + +### Steps + +1. `cd $WORKDIR/project-auto` +2. `$CLI package -o $WORKDIR/pkg-target.zip` + +### Expected Result + +- Exit code 0 +- `$WORKDIR/pkg-target.zip` exists and is non-empty +- ZIP contains files from installed target directories (e.g., `.pair/knowledge/`, `.claude/skills/`) + +--- + +## MT-CP802: Package with source layout + +**Priority**: P1 +**Preconditions**: KB source available at `$WORKDIR/kb-source` (from MT-CP303 setup) +**Category**: Packaging + +### Steps + +1. `cd $WORKDIR/kb-source` +2. `$CLI package --layout source -o $WORKDIR/pkg-source.zip` + +### Expected Result + +- Exit code 0 +- `$WORKDIR/pkg-source.zip` exists and is non-empty +- ZIP contains files from source directories (no prefix/flatten transforms applied) + +### Notes + +- Source layout reads from `config.json` `source` fields, not from installed targets + +--- + +## MT-CP803: Package with metadata flags + +**Priority**: P1 +**Preconditions**: MT-CP301 passes +**Category**: Packaging + +### Steps + +1. `cd $WORKDIR/project-auto` +2. `$CLI package --name "Test KB" --version $VERSION --author "Tester" -o $WORKDIR/pkg-meta.zip` + +### Expected Result + +- Exit code 0 +- `$WORKDIR/pkg-meta.zip` exists +- ZIP contains a manifest with name="Test KB", version=$VERSION, author="Tester" + +--- + +## MT-CP804: Validate target layout + +**Priority**: P1 +**Preconditions**: MT-CP301 passes +**Category**: Packaging + +### Steps + +1. `cd $WORKDIR/project-auto` +2. `$CLI kb-validate --layout target` + +### Expected Result + +- Exit code 0 +- Validation passes for target layout structure + +--- + +## MT-CP805: Validate source layout + +**Priority**: P1 +**Preconditions**: KB source available at `$WORKDIR/kb-source` +**Category**: Packaging + +### Steps + +1. `cd $WORKDIR/kb-source` +2. `$CLI kb-validate --layout source` + +### Expected Result + +- Exit code 0 +- Validation passes for source layout structure + +--- + +## MT-CP806: Source and target packages differ + +**Priority**: P2 +**Preconditions**: MT-CP801 and MT-CP802 pass +**Category**: Packaging + +### Steps + +1. `unzip -l $WORKDIR/pkg-target.zip | wc -l` +2. `unzip -l $WORKDIR/pkg-source.zip | wc -l` +3. Compare file paths in both ZIPs + +### Expected Result + +- Both ZIPs contain valid KB content +- File paths differ (target has prefix/flatten transforms, source has original structure) +- Both ZIPs can be installed via `$CLI install --source ` + +### Notes + +- This validates that `--layout` actually produces different output, not identical packages diff --git a/qa/release-validation/README.md b/qa/release-validation/README.md index 92a360c6..df1c71bf 100644 --- a/qa/release-validation/README.md +++ b/qa/release-validation/README.md @@ -4,15 +4,7 @@ Post-release manual tests that validate the pair website, CLI artifacts, and pub ## Prerequisites -The `gh` CLI token must include `read:packages` scope (required by CP7 — GitHub Packages registry tests). Verify and fix: - -```bash -# Check current scopes -gh auth status # look for 'read:packages' in Token scopes - -# Add if missing (one-time, persists across sessions) -gh auth refresh -h github.com -s read:packages -``` +No special auth scopes needed — the package is public on npmjs.org. ## Variables @@ -24,8 +16,6 @@ All test cases use these variables — resolve them before execution: | `$BASE_URL` | Production website URL (e.g. `https://pair.foomakers.com`) | | `$RELEASE_URL` | `https://github.com/foomakers/pair/releases/tag/v$VERSION` | | `$WORKDIR` | Temp directory **outside** the repo: `mktemp -d /tmp/pair-release-test.XXXXX` | -| `$REGISTRY` | `https://npm.pkg.github.com/` | -| `$NPM_TOKEN` | `gh auth token` (reuses authenticated `gh` CLI session; requires `read:packages` scope — see Prerequisites) | ## Critical Paths @@ -39,7 +29,8 @@ Execute in order. P0 blocks release sign-off. | CP4 | [CP4-kb-dataset.md](CP4-kb-dataset.md) | P1 | KB ZIP artifact, manifest, verify, info | | CP5 | [CP5-website-docs-completeness.md](CP5-website-docs-completeness.md) | P1 | All doc pages return 200, version consistency, internal links | | CP6 | [CP6-website-search-navigation.md](CP6-website-search-navigation.md) | P1 | Orama search, sidebar, prev/next, llms.txt, privacy | -| CP7 | [CP7-registry-publish.md](CP7-registry-publish.md) | P2 | GitHub Packages visibility, install from registry | +| CP7 | [CP7-registry-publish.md](CP7-registry-publish.md) | P0 | npmjs.org visibility, install from public registry | +| CP8 | [CP8-packaging.md](CP8-packaging.md) | P1 | `pair package` with source/target layouts, metadata, validation | ## Execution by AI Assistant @@ -64,7 +55,6 @@ This suite is designed to be executed by an AI coding assistant (Claude Code, Cu 1. **Resolve variables first**: before executing any test, resolve all `$VARIABLES` and state them explicitly 2. **Create $WORKDIR once**: `mktemp -d /tmp/pair-release-test.XXXXX` — reuse for all CP2/CP3/CP4 tests -3. **Create `$WORKDIR/.npmrc` once** for all CP7 tests: extract token via `gh auth token`, write scoped `.npmrc` with `@foomakers:registry` + auth. Reused by all registry tests. 3. **One CP at a time**: complete all tests in a CP before moving to the next; report partial results if context limit approaches 4. **Capture evidence inline**: for failures, capture command output or screenshot immediately — don't defer 5. **Final cleanup**: remove `$WORKDIR` only after report is generated @@ -81,8 +71,6 @@ This suite is designed to be executed by an AI coding assistant (Claude Code, Cu ### Workflow ```text -0. Verify gh auth scopes include read:packages (see Prerequisites) -0.5. Create $WORKDIR/.npmrc with scoped registry auth (uses $NPM_TOKEN from gh auth token) 1. Resolve $VERSION, $BASE_URL, $RELEASE_URL, create $WORKDIR 2. Execute CP1 → CP2 → CP3 → ... → CP7 3. For each test: record PASS/FAIL/SKIP with evidence @@ -104,7 +92,7 @@ Consult before each release. If any condition is true, update the corresponding - [ ] New integration page → update CP5 - [ ] Deploy target changed → update CP1 - [ ] New distribution channel (brew, npx global, etc.) → add to CP2 or new CP -- [ ] GitHub Packages config changed → update CP7 +- [ ] npmjs.org publish config changed → update CP7 - [ ] Landing page sections changed → update CP1 When updating, increment the test count in this README and add a changelog entry at the bottom of the affected CP file. diff --git a/scripts/diagnose-install.sh b/scripts/diagnose-install.sh index 882a8aa5..162abbbc 100755 --- a/scripts/diagnose-install.sh +++ b/scripts/diagnose-install.sh @@ -151,8 +151,8 @@ fi # Check npm global installation if command -v npm >/dev/null 2>&1; then - NPM_PAIR_INFO=$(npm list -g @pair/pair-cli 2>/dev/null || echo "not installed via npm") - if echo "$NPM_PAIR_INFO" | grep -q "@pair/pair-cli"; then + NPM_PAIR_INFO=$(npm list -g @foomakers/pair-cli 2>/dev/null || echo "not installed via npm") + if echo "$NPM_PAIR_INFO" | grep -q "@foomakers/pair-cli"; then print_status "ok" "npm global installation detected" print_status "info" "npm install info: $NPM_PAIR_INFO" else @@ -162,8 +162,8 @@ fi # Check pnpm global installation if command -v pnpm >/dev/null 2>&1; then - PNPM_PAIR_INFO=$(pnpm list -g @pair/pair-cli 2>/dev/null || echo "not installed via pnpm") - if echo "$PNPM_PAIR_INFO" | grep -q "@pair/pair-cli"; then + PNPM_PAIR_INFO=$(pnpm list -g @foomakers/pair-cli 2>/dev/null || echo "not installed via pnpm") + if echo "$PNPM_PAIR_INFO" | grep -q "@foomakers/pair-cli"; then print_status "ok" "pnpm global installation detected" else print_status "info" "not installed via pnpm globally" @@ -245,8 +245,8 @@ else echo "❌ pair-cli is not installed or not in PATH." echo "" echo "📖 Installation options:" - echo " 1. npm install -g @pair/pair-cli" - echo " 2. pnpm add -g @pair/pair-cli" + echo " 1. npm install -g @foomakers/pair-cli" + echo " 2. pnpm add -g @foomakers/pair-cli" echo " 3. Download manual install from GitHub releases" echo "" echo "🔧 If installation fails:" diff --git a/scripts/publish-npm.sh b/scripts/publish-npm.sh new file mode 100755 index 00000000..63d41980 --- /dev/null +++ b/scripts/publish-npm.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +set -euo pipefail + +# scripts/publish-npm.sh +# Publish a pair-cli TGZ to npmjs.org +# Usage: ./scripts/publish-npm.sh [--dry-run] +# +# Requires NPM_TOKEN env var (CI) or active npm login (local). + +REGISTRY="https://registry.npmjs.org/" +SCOPE="@foomakers" + +TGZ_PATH="" +DRY_RUN="" + +for arg in "$@"; do + case "$arg" in + --dry-run) DRY_RUN="--dry-run" ;; + *) TGZ_PATH="$arg" ;; + esac +done + +if [ -z "$TGZ_PATH" ]; then + echo "Usage: $0 [--dry-run]" + echo "Example: $0 release/pair-cli-0.4.2.tgz" + exit 1 +fi + +if [ ! -f "$TGZ_PATH" ]; then + echo "Error: TGZ file not found: $TGZ_PATH" + exit 1 +fi + +# Extract metadata from TGZ +TMPDIR=$(mktemp -d) +trap 'rm -rf "$TMPDIR"' EXIT + +tar xzf "$TGZ_PATH" -C "$TMPDIR" --include='*/package.json' 2>/dev/null || \ + tar xzf "$TGZ_PATH" -C "$TMPDIR" 'package/package.json' 2>/dev/null || true + +PKG_JSON=$(find "$TMPDIR" -name package.json -maxdepth 2 | head -n1) +if [ -z "$PKG_JSON" ]; then + echo "Error: no package.json found inside $TGZ_PATH" + exit 1 +fi + +PKG_NAME=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$PKG_JSON','utf8')).name)") +PKG_VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$PKG_JSON','utf8')).version)") +PKG_PRIVATE=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$PKG_JSON','utf8')).private || false)") + +echo "Package: $PKG_NAME@$PKG_VERSION" +echo "Registry: $REGISTRY" +echo "TGZ: $TGZ_PATH" +[ -n "$DRY_RUN" ] && echo "Mode: DRY RUN" + +# Validate metadata +if [ "$PKG_PRIVATE" = "true" ]; then + echo "Error: package.private is true — cannot publish" + exit 1 +fi + +if [[ "$PKG_NAME" != ${SCOPE}/* ]]; then + echo "Error: package name must be scoped to $SCOPE. Found: $PKG_NAME" + exit 1 +fi + +# Validate auth +if [ -n "${NPM_TOKEN:-}" ]; then + echo "Auth: NPM_TOKEN env var" + npm config set "${SCOPE}:registry" "$REGISTRY" + npm config set "//registry.npmjs.org/:_authToken" "$NPM_TOKEN" +elif npm whoami --registry "$REGISTRY" >/dev/null 2>&1; then + echo "Auth: npm login session" +else + echo "Error: No NPM_TOKEN env var and not logged in to npm." + echo " CI: set NPM_TOKEN secret" + echo " Local: run 'npm login --scope=$SCOPE'" + exit 1 +fi + +# Publish +echo "" +echo "Publishing $PKG_NAME@$PKG_VERSION to $REGISTRY..." +if npm publish "$TGZ_PATH" --access public --registry "$REGISTRY" ${DRY_RUN:+"$DRY_RUN"}; then + echo "Published successfully." +else + EXIT_CODE=$? + echo "Error: npm publish failed (exit $EXIT_CODE)" + echo " 403/409 = version already exists" + echo " 401 = token invalid or expired" + exit $EXIT_CODE +fi + +# Verify (skip for dry-run) +if [ -z "$DRY_RUN" ]; then + echo "" + echo "Verifying publication..." + sleep 3 + if npm view "$PKG_NAME@$PKG_VERSION" version --registry "$REGISTRY" >/dev/null 2>&1; then + PUBLISHED=$(npm view "$PKG_NAME@$PKG_VERSION" version --registry "$REGISTRY") + echo "Verified: $PKG_NAME@$PUBLISHED is live on $REGISTRY" + else + echo "Warning: npm view did not find $PKG_NAME@$PKG_VERSION — may need a few seconds to propagate" + fi +fi + +echo "" +echo "Done." diff --git a/scripts/workflows/release/create-registry-tgz.sh b/scripts/workflows/release/create-registry-tgz.sh index e957e265..32fc602a 100755 --- a/scripts/workflows/release/create-registry-tgz.sh +++ b/scripts/workflows/release/create-registry-tgz.sh @@ -93,8 +93,8 @@ fi # Change to artifact directory for npm operations cd "$ART_DIR" -# Ensure the package.json inside the extracted artifact is scoped for GitHub Packages -echo "Patching package.json for GitHub Packages registry and setting package name/version..." +# Ensure the package.json inside the extracted artifact is scoped for npmjs.org +echo "Patching package.json for npmjs.org registry and setting package name/version..." # Use a small Node script to robustly patch or create package.json; ensure name is @foomakers/pair-cli and version matches the normalized VERSION VERSION_ENV="$VERSION" VERSION="$VERSION" node <<'NODE' const fs = require('fs'); @@ -105,9 +105,10 @@ if (fs.existsSync(p)) { o.name = '@foomakers/pair-cli'; o.version = version; // ensure package is publishable - o.private = !!o.private ? o.private : false; + o.private = false; o.publishConfig = o.publishConfig || {}; - o.publishConfig.registry = 'https://npm.pkg.github.com/'; + o.publishConfig.registry = 'https://registry.npmjs.org/'; + o.publishConfig.access = 'public'; fs.writeFileSync(p, JSON.stringify(o, null, 2)); console.log('Patched package.json for registry publish:', o.name, o.version); } else { @@ -118,7 +119,7 @@ if (fs.existsSync(p)) { bin: { 'pair-cli': './bin/pair-cli' }, files: ['bundle-cli', 'bin', 'README.md', 'config.json', 'LICENSE', 'docs'], private: false, - publishConfig: { registry: 'https://npm.pkg.github.com/' } + publishConfig: { registry: 'https://registry.npmjs.org/', access: 'public' } }; fs.writeFileSync(p, JSON.stringify(o, null, 2)); console.log('Created package.json for registry publish:', o.name, o.version);