diff --git a/.pair/adoption/decision-log/2026-07-12-docs-website-ia-restructuring-assessment.md b/.pair/adoption/decision-log/2026-07-12-docs-website-ia-restructuring-assessment.md new file mode 100644 index 00000000..1f0c6031 --- /dev/null +++ b/.pair/adoption/decision-log/2026-07-12-docs-website-ia-restructuring-assessment.md @@ -0,0 +1,255 @@ +# Docs Website — Information Architecture Assessment + +**Scope:** `apps/website/content/docs/**` (69 `.mdx` pages, 12 sections + root), navigation (`meta.json` files), cross-links, entry points, and drift risk vs. the knowledge base. +**Framework:** Fumadocs (`fumadocs-core`/`fumadocs-mdx`/`fumadocs-ui`) on Next.js — nav is folder-based, ordered by per-folder `meta.json`; a folder without an `index.mdx` is sidebar-only (its direct URL 404s). +**Status:** Proposal only. Recommendation: **Option A** (targeted consolidation, see below). + +--- + +## 1. Current-state inventory + +```text +content/docs/ +├── index.mdx 1 page "Welcome" — thin (15 lines), no outbound links +├── getting-started/ 5 pages What is pair?, Quickstart, + solo/team/org variants +├── developer-journey/ 5 pages The 4-level process lifecycle (induction → execution) +├── concepts/ 7 pages SDLC, KB, skills, adoption files, canonical states, +│ agent integration, llms.txt — NO index.mdx +├── customization/ 5 pages Adopt → customize (team) → publish (org) → templates +├── integrations/ 6 pages One page per AI agent (Claude Code, Codex, Cursor, …) +├── pm-tools/ 4 pages One page per PM backend (filesystem, GH Projects, Linear) +├── guides/ 7 pages Grab-bag: cli-workflows, install-from-url, customize-kb, +│ adopter-checklist, packaging, troubleshooting, +│ update-link — NO index.mdx +├── migrations/ 2 pages Version-jump prompts (v0.4→v0.5) +├── tutorials/ 7 pages End-to-end walkthroughs (first-project, team-setup, …) +├── reference/ 10 pages cli/ (commands, examples), specs/ (2), catalogs (2), +│ kb-structure, configuration, quality-model, +│ skill-management — NO index.mdx (nor cli/, specs/) +├── support/ 3 pages index, general-faq ("FAQ"), faq ("Installation FAQ") +└── contributing/ 7 pages Dev setup, architecture, writing skills/guidelines/ + migration pages, release process +``` + +Total: **69 pages**. Root `meta.json` orders 13 sidebar entries in two groups (learn: getting-started → pm-tools; use/reference: guides → contributing) separated by `---`. + +Entry points: the marketing landing page links to `/docs` (root Welcome), `/docs/customization/team`, `/docs/customization/organization` (`app/(landing)/`). `llms.txt` / `llms-full.txt` are generated from the source loader (`lib/get-llm-text.ts`) so they follow the tree automatically. + +Staleness gate (`apps/website/lib/docs-staleness-check.ts`) covers exactly: skill count + skill rows in `reference/skills-catalog.mdx`, command anchors in `reference/cli/commands.mdx`, and `pair-cli ` references in `tutorials/*.mdx`. Nothing else. + +### Audience-journey read + +- **First-run user:** `getting-started/` is solid (overview + quickstart + 3 variants), but the actual `/docs` entry (Welcome) is a dead end — the good hub is one level down at `getting-started/index.mdx`. +- **Adopting team:** the journey is split across four sections with no principled boundary — `customization/` (adopt/team/org/templates), `guides/` (customize-kb, install-from-url, packaging, adopter-checklist), `pm-tools/`, `integrations/`. Half the KB-lifecycle tasks live in `customization/`, the other half in `guides/`, with pairwise duplication (see F4, F7, F8). +- **Contributor/maintainer:** `contributing/` + `reference/specs/` + `migrations/` serve this well; the weak spot is `reference/` mixing catalogs, config reference, and long-form explanation (`skill-management.mdx`). + +--- + +## 2. Findings + +| # | Severity | Lens | Finding | +|---|----------|------|---------| +| F1 | Major | Findability | **`/docs` root is a dead end.** `content/docs/index.mdx` is 15 lines with zero links; the landing page's docs CTA points here. Every journey starts with a sidebar hunt. The real overview already exists at `getting-started/index.mdx` ("What is pair?"). | +| F2 | Major | Findability | **8 broken internal links to index-less section URLs.** `concepts/`, `guides/`, `reference/` have no `index.mdx`, so their URLs 404 under Fumadocs, yet pages link to them: `/docs/concepts` ← `tutorials/index.mdx`, `tutorials/first-project.mdx`, `tutorials/existing-project.mdx`; `/docs/guides` ← `tutorials/index.mdx`, `tutorials/first-project.mdx`, `tutorials/team-setup.mdx`, `tutorials/enterprise-adoption.mdx`; `/docs/reference` ← `tutorials/index.mdx`. | +| F3 | Major | Consistency / Audience fit | **`guides/` is a grab-bag.** Seven unrelated pages spanning three audiences: adopter recipes (`customize-kb`, `adopter-checklist`), KB-publisher ops (`packaging`, `install-from-url`, `update-link`), CLI usage (`cli-workflows`), and support content (`troubleshooting`). No index page, no shared theme, no principled rule for what lands here vs. `customization/` or `reference/`. | +| F4 | Major | Redundancy | **Two overlapping troubleshooting/FAQ pages.** `support/faq.mdx` ("Installation FAQ", 361 lines) and `guides/troubleshooting.mdx` (236 lines) both cover permission errors, Node version issues, PATH/command-not-found — with different fixes recommended (e.g. npm-permissions vs. nvm). They cross-link each other. Guaranteed drift. | +| F5 | Major | Redundancy / Staleness | **Hardcoded skill counts have already drifted, and the gate doesn't see them.** Actual count: 35. Docs say **30** (`getting-started/index.mdx:18`), **32** (`developer-journey/index.mdx:43,120`, `developer-journey/execution.mdx:123`, `reference/kb-structure.mdx:124`), **34** (`reference/guidelines-catalog.mdx:160`). The gate then only checked `reference/skills-catalog.mdx`. | +| F6 | Major | Findability / Consistency | **Duplicate sidebar titles and undifferentiated parallel tracks.** "Team Setup" appears twice (`getting-started/quickstart-team.mdx` and `tutorials/team-setup.mdx`). The quickstart triad (solo/team/org) and the tutorial triad (first-project/team-setup/enterprise-adoption) are intentionally parallel (quick vs. deep) but their titles don't signal which is which. | +| F7 | Major | Redundancy | **KB packaging documented twice.** `guides/packaging.mdx` (125 lines) and the "Package for Distribution" / "Verify Your Package" sections of `customization/organization.mdx` (221 lines) describe the same `pair-cli package` procedure and layout modes. | +| F8 | Minor | Redundancy | **`guides/customize-kb.mdx`** (63-line recipe) restates the core of `customization/team.mdx` (identify → override → verify) and links to it. Fine as a recipe, wrong as a sibling section. | +| F9 | Minor | Consistency | **Inverted FAQ file naming.** `support/faq.mdx` is titled "Installation FAQ"; `support/general-faq.mdx` is titled "FAQ". Filename and content are swapped relative to reader expectation. | +| F10 | Minor | Consistency | **Section label vs. page title mismatch.** Sidebar says "Developer Journey"; its index page is titled "Process Lifecycle". Pick one. | +| F11 | Minor | Consistency | **Inconsistent section index coverage.** 8 sections have an index page; `concepts/`, `guides/`, `reference/` (and `reference/cli/`, `reference/specs/`) don't. This is the root cause of F2. | +| F12 | Minor | Findability | **`concepts/llms-txt.mdx` has zero inbound cross-links** — reachable only via sidebar. | +| F13 | Minor | Consistency / Growth | **CLI documentation is split across sections.** `guides/cli-workflows.mdx` (305 lines) and `guides/update-link.mdx` (338 lines, single-command deep dive) are CLI docs stranded outside `reference/cli/`; `reference/skill-management.mdx` (272 lines) is half explanation, half reference. | +| F14 | Minor | Growth | **New content has no principled home.** Integrations, PM tools, migrations scale cleanly (one page per item). But any new task-oriented page defaults to `guides/`, reinforcing the grab-bag. 13 top-level nav entries is already at the upper bound of scannable. | + +What is genuinely **fine** and should not be touched: `integrations/`, `pm-tools/`, `migrations/` (clean one-page-per-item patterns), `contributing/` (coherent, complete), `tutorials/` (well differentiated internally), `developer-journey/` content, and the two-group sidebar split. + +--- + +## 3. Proposed target structure (Option A — recommended) + +**Principle:** keep the 11 sections that work; dissolve `guides/` into the sections its pages actually belong to; make every section URL resolvable; fix the two duplication hotspots by merging. + +```text +content/docs/ +├── index.mdx REWRITE: real hub — cards to the 4 journeys + top tasks +├── getting-started/ +│ ├── index.mdx keep ("What is pair?") +│ ├── quickstart.mdx keep +│ ├── quickstart-solo.mdx keep — retitle "Quickstart: Solo" +│ ├── quickstart-team.mdx keep — retitle "Quickstart: Team" (fixes F6) +│ ├── quickstart-org.mdx keep — retitle "Quickstart: Organization" +│ └── checklist.mdx ← guides/adopter-checklist (post-setup verification) +├── tutorials/ unchanged (7 pages) +├── concepts/ +│ ├── index.mdx NEW: one-paragraph map of the 7 concepts (fixes F2/F11) +│ └── … 7 existing pages unchanged +├── developer-journey/ unchanged — align title: "Process Lifecycle" everywhere (F10) +├── customization/ becomes the single home of the KB lifecycle +│ ├── index.mdx keep (adopt → customize → publish narrative) +│ ├── adopt.mdx keep +│ ├── install-from-url.mdx ← guides/install-from-url (advanced install sources) +│ ├── team.mdx keep — absorbs guides/customize-kb as "Quick recipe" section +│ ├── organization.mdx keep — absorbs guides/packaging (dedupe, keep layout-modes +│ │ detail as a subsection) (fixes F7/F8) +│ └── templates.mdx keep +├── integrations/ unchanged (6 pages) +├── pm-tools/ unchanged (4 pages) +├── ─── (separator) +├── reference/ +│ ├── index.mdx NEW: what's in reference, links to catalogs/specs (F2/F11) +│ ├── cli/ +│ │ ├── commands.mdx keep (staleness-gate anchor — do not move) +│ │ ├── examples.mdx keep +│ │ ├── workflows.mdx ← guides/cli-workflows (fixes F13) +│ │ └── update-link.mdx ← guides/update-link +│ ├── specs/ unchanged (2 pages) +│ └── … 6 existing pages unchanged +├── migrations/ unchanged (2 pages) +├── support/ +│ ├── index.mdx keep (update quick links) +│ ├── general-faq.mdx keep URL — remains the general "FAQ" +│ └── troubleshooting.mdx NEW: merge of guides/troubleshooting + support/faq +│ (single install/setup problem-solving page) (fixes F4/F9) +└── contributing/ unchanged (7 pages) +``` + +`guides/` is **deleted** as a section. Top-level nav: 13 → 12 entries. Proposed root `meta.json` order: `index, getting-started, tutorials, concepts, developer-journey, customization, integrations, pm-tools, ---, reference, migrations, support, contributing` (tutorials promoted next to getting-started — they are the learn path, not an appendix). + +### Page-by-page mapping (all 69 pages) + +| Current path (`content/docs/…`) | Disposition | +|---|---| +| `index.mdx` | keep — **rewrite** as hub with links (F1) | +| `getting-started/index.mdx` | keep | +| `getting-started/quickstart.mdx` | keep | +| `getting-started/quickstart-solo.mdx` | keep — retitle "Quickstart: Solo" | +| `getting-started/quickstart-team.mdx` | keep — retitle "Quickstart: Team" | +| `getting-started/quickstart-org.mdx` | keep — retitle "Quickstart: Organization" | +| `developer-journey/index.mdx` | keep — unify title with sidebar label | +| `developer-journey/induction.mdx` | keep | +| `developer-journey/strategic-planning.mdx` | keep | +| `developer-journey/iteration.mdx` | keep | +| `developer-journey/execution.mdx` | keep — fix skill count | +| `concepts/ai-assisted-sdlc.mdx` | keep | +| `concepts/knowledge-base.mdx` | keep | +| `concepts/skills.mdx` | keep | +| `concepts/adoption-files.mdx` | keep | +| `concepts/canonical-states.mdx` | keep | +| `concepts/agent-integration.mdx` | keep | +| `concepts/llms-txt.mdx` | keep — add inbound links (from concepts index + kb-structure) | +| *(new)* `concepts/index.mdx` | **new page** | +| `customization/index.mdx` | keep | +| `customization/adopt.mdx` | keep | +| `customization/team.mdx` | keep — absorbs customize-kb recipe | +| `customization/organization.mdx` | keep — absorbs packaging content | +| `customization/templates.mdx` | keep | +| `guides/adopter-checklist.mdx` | **move** → `getting-started/checklist.mdx` | +| `guides/cli-workflows.mdx` | **move** → `reference/cli/workflows.mdx` | +| `guides/customize-kb.mdx` | **merge-into** `customization/team.mdx` | +| `guides/install-from-url.mdx` | **move** → `customization/install-from-url.mdx` | +| `guides/packaging.mdx` | **merge-into** `customization/organization.mdx` | +| `guides/troubleshooting.mdx` | **merge-into** `support/troubleshooting.mdx` (new) | +| `guides/update-link.mdx` | **move** → `reference/cli/update-link.mdx` | +| `integrations/index.mdx` | keep | +| `integrations/claude-code.mdx` | keep | +| `integrations/codex.mdx` | keep | +| `integrations/cursor.mdx` | keep | +| `integrations/github-copilot.mdx` | keep | +| `integrations/windsurf.mdx` | keep | +| `pm-tools/index.mdx` | keep | +| `pm-tools/filesystem.mdx` | keep | +| `pm-tools/github-projects.mdx` | keep | +| `pm-tools/linear.mdx` | keep | +| `migrations/index.mdx` | keep | +| `migrations/v0.4-to-v0.5.mdx` | keep | +| `tutorials/index.mdx` | keep — fix `/docs/guides`, `/docs/concepts`, `/docs/reference` links | +| `tutorials/first-project.mdx` | keep — fix section links | +| `tutorials/existing-project.mdx` | keep — fix section links | +| `tutorials/team-setup.mdx` | keep — fix `/docs/guides` link | +| `tutorials/enterprise-adoption.mdx` | keep — fix `/docs/guides` link | +| `tutorials/managing-ai-artifacts.mdx` | keep | +| `tutorials/release-testing.mdx` | keep | +| `reference/cli/commands.mdx` | keep (gate anchor — path frozen) | +| `reference/cli/examples.mdx` | keep | +| `reference/specs/cli-contracts.mdx` | keep | +| `reference/specs/kb-source-resolution.mdx` | keep | +| `reference/configuration.mdx` | keep | +| `reference/guidelines-catalog.mdx` | keep — fix skill count | +| `reference/kb-structure.mdx` | keep — fix skill count | +| `reference/quality-model.mdx` | keep | +| `reference/skill-management.mdx` | keep (splitting explanation vs. reference: deferred, low value) | +| `reference/skills-catalog.mdx` | keep (gate anchor — path frozen) | +| *(new)* `reference/index.mdx` | **new page** | +| `support/index.mdx` | keep — update quick links | +| `support/general-faq.mdx` | keep — remains "FAQ" (URL kept; rename to `faq` deferred, F9 accepted as cosmetic) | +| `support/faq.mdx` | **merge-into** `support/troubleshooting.mdx` (new) | +| `contributing/index.mdx` | keep | +| `contributing/development-setup.mdx` | keep | +| `contributing/architecture.mdx` | keep | +| `contributing/writing-skills.mdx` | keep | +| `contributing/writing-guidelines.mdx` | keep | +| `contributing/writing-migration-pages.mdx` | keep | +| `contributing/release-process.mdx` | keep | + +Nothing is deleted outright: 7 moves/merges, 3 new pages (root rewrite counts as rewrite), 60 keeps. Every current URL that changes gets a redirect (below). + +--- + +## 4. Alternatives considered + +### Option B — hygiene only (lighter touch) + +Fix F1, F2, F5, F6, F10, F11, F12 without moving any page: rewrite the root hub, add `concepts/index.mdx`, `guides/index.mdx`, `reference/index.mdx`, fix counts + extend the gate, retitle the quickstart variants. Merge only the FAQ/troubleshooting pair (F4) since that is active drift. + +- **Pro:** no redirects except one, ~1 day total, zero URL churn. +- **Con:** `guides/` grab-bag and the packaging/customize-kb duplication (F3, F7, F8, F13) remain; a `guides/index.mdx` would legitimize the grab-bag and new pages will keep accreting there. + +### Option C — full Diátaxis re-org (heavier) + +Top-level = Tutorials / How-to / Explanation / Reference; every section re-parented. + +- **Pro:** textbook IA. +- **Con:** every URL changes (69-entry redirect map), landing page + e2e tests + external links + `llms.txt` consumers all churn; the current sections *already* map ~1:1 onto Diátaxis (tutorials=tutorials, concepts=explanation, reference=reference) — the only real offender is `guides/`, which Option A fixes surgically. Cost far exceeds benefit. + +**Recommendation: Option A.** It removes every Major finding, changes only 7 URLs, and leaves the healthy 70% of the tree untouched. If you want to defer, Option B batch 1 below is a strict subset of Option A — no wasted work. + +--- + +## 5. Migration notes + +**Redirects** — add a `redirects()` block to `apps/website/next.config.mjs` (none exists today), permanent: + +| From | To | +|---|---| +| `/docs/guides/adopter-checklist` | `/docs/getting-started/checklist` | +| `/docs/guides/cli-workflows` | `/docs/reference/cli/workflows` | +| `/docs/guides/customize-kb` | `/docs/customization/team` | +| `/docs/guides/install-from-url` | `/docs/customization/install-from-url` | +| `/docs/guides/packaging` | `/docs/customization/organization` | +| `/docs/guides/troubleshooting` | `/docs/support/troubleshooting` | +| `/docs/guides/update-link` | `/docs/reference/cli/update-link` | +| `/docs/support/faq` | `/docs/support/troubleshooting` | +| `/docs/guides` | `/docs` (section removed) | + +**Cross-link updates** — inbound links into moved pages (from the link audit): `guides/install-from-url` ← 4 links (`tutorials/managing-ai-artifacts` ×2, `customization/adopt`, `customization/organization`); `guides/customize-kb` ← 5; `guides/troubleshooting` ← 4; `guides/cli-workflows` ← 3; `guides/update-link` ← 2; `guides/adopter-checklist` ← 1; `guides/packaging` ← 0; `support/faq` ← 5. Plus the 8 broken section links (F2). A repo-wide grep for `/docs/guides` and `/docs/support/faq` catches all of it. + +**Docs-staleness gate** (`apps/website/lib/docs-staleness-check.ts`): + +- Unaffected by Option A moves: its anchors (`reference/skills-catalog.mdx`, `reference/cli/commands.mdx`, `tutorials/`) keep their paths — this is why `commands.mdx` and `skills-catalog.mdx` are frozen in the mapping. +- **Extend it** (batch 1): run the `(\d+)\s+(?:pair\s+)?skills` count check across *all* of `content/docs/**/*.mdx`, not just the catalog — that turns F5 from recurring drift into a CI failure. Optionally add a dead-internal-link check (resolve `](/docs/...)` targets against the source loader), which would have caught F2. + +**Other touchpoints:** `llms.txt`/`llms-full.txt` regenerate from the source loader — no action. `e2e/docs.e2e.test.ts` hardcodes `getting-started/*` URLs (unchanged) but asserts sidebar titles "Solo Setup"/"Team Setup"/"Organization Setup" — update assertions when retitling (batch 1). Landing page links (`/docs`, `/docs/customization/team`, `/docs/customization/organization`) all survive unchanged. + +### Suggested batching (small PRs, each independently shippable) + +| Batch | Content | Effort | +|---|---|---| +| 1. Hygiene (no URL changes) | Rewrite root hub; add `concepts/index.mdx` + `reference/index.mdx`; fix 8 broken section links; fix 6 stale skill counts; extend staleness gate; retitle quickstart variants + align developer-journey title; update e2e title assertions; add llms-txt inbound links | ~0.5 day | +| 2. Support consolidation | Merge `support/faq.mdx` + `guides/troubleshooting.mdx` → `support/troubleshooting.mdx`; redirects; update 9 inbound links; update `support/index.mdx` | ~0.5 day | +| 3. Dissolve `guides/` | 4 moves + 2 content merges (`customize-kb`→team, `packaging`→organization); delete `guides/meta.json`; redirects; update ~15 inbound links; update root `meta.json` (drop guides, promote tutorials) | ~1 day | +| 4. (Optional, deferred) | Split `reference/skill-management.mdx` explanation/reference; rename `support/general-faq.mdx` → `faq.mdx` | ~0.5 day — recommend skipping unless it itches | + +Total for Option A: **~2 days** across 3 PRs. Batch 1 alone = Option B and is worth doing regardless of the go/no-go on batches 2–3. diff --git a/.pair/adoption/decision-log/2026-07-13-gate-tooling-code-in-tested-modules.md b/.pair/adoption/decision-log/2026-07-13-gate-tooling-code-in-tested-modules.md new file mode 100644 index 00000000..8a580081 --- /dev/null +++ b/.pair/adoption/decision-log/2026-07-13-gate-tooling-code-in-tested-modules.md @@ -0,0 +1,59 @@ +# Decision: Gate/tooling code lives in tested modules; scripts are thin entrypoints, never unit-tested + +## Date + +2026-07-13 + +## Status + +Active + +## Category + +Convention Adoption + +## Context + +Several repository quality gates started life as standalone scripts under `scripts/` with their logic inline, then grew a sibling vitest test that reached into the script to exercise that logic. Two anti-patterns emerged: + +- `scripts/docs-staleness-check.js` held all the docs-integrity logic (skill/guide count checks, catalog sync, dead-link scan) and its test (`apps/website/lib/docs-staleness-check.test.ts`) black-box **spawned the script** via `spawnSync` against fixture trees — testing an opaque process, not importable logic, and duplicating a real build artifact's behavior from outside. +- The same shape had already been corrected once for the skills-conformance gate (#313/#324): logic moved into a module, script deleted. + +This is orthogonal to the existing co-location ADL, [2026-07-08-test-file-colocation-multi-module.md](./2026-07-08-test-file-colocation-multi-module.md), which governs **where** a test file sits (root-of-call-chain co-location; one file per entry point for e2e). It does not say **what** should be a testable module versus a script, nor how scripts themselves may be verified. This ADL fills that gap; the two are complementary and non-overlapping. + +The repository owner stated the rule directly: "tested functionality lives in production code and is tested there; do NOT unit-test scripts" and, on how to verify a script/CLI when desired, "use a smoke test, not a unit test." + +## Decision + +**Functionality worth testing lives in a production module** — an importable module in the package that owns the concern, unit-tested white-box (tests import the module's exported functions and assert on them directly). + +**Scripts/CLIs are thin entrypoints** that call those modules. A repo-root gate delegates to the owning package rather than holding logic itself: + +```json +"docs:staleness": "pnpm --filter @pair/website docs:staleness" +``` + +and the package script runs the module through a TS runner (`ts-node`/`tsx`) behind a main-guard, e.g. `"docs:staleness": "tsx lib/docs-staleness-check.ts"`. + +**Scripts are never unit-tested.** No importing a script's functions into a test, and no black-box `spawnSync`/`exec` of a script inside a vitest unit test. Unit tests target the module's exported logic. When script/CLI-level (end-to-end) verification is wanted, it uses the **smoke-test suite** (`scripts/smoke-tests/`, `pnpm smoke-tests`), not vitest. + +Rationale: a gate is testable logic, not an opaque script; keeping the logic in an importable module removes duplication and orphan tests that reach into root `scripts/`; unit tests then cover module logic while smoke tests cover CLI wiring end-to-end. The module's public functions are the single tested surface; the CLI wrapper is a trivial, unit-test-exempt shell. + +## Alternatives Considered + +- **Keep logic in the script, test it via `spawnSync`/`exec`**: Rejected. Tests an opaque process instead of logic, is slow, forces fixture-tree scaffolding for cases a pure function test expresses in one line, and leaves the logic un-importable and un-refactorable. This is the exact pattern being removed. +- **Keep logic in the script, `require()`/`import` its functions into a unit test**: Rejected. Makes `scripts/` a de-facto source tree with orphan tests reaching across the repo into it, contradicting package ownership and the co-location ADL; a script is meant to be a thin entrypoint, not a module. +- **Move logic to a module but skip a runnable CLI (call only from tests/CI code)**: Rejected. The gate must stay runnable locally and in CI as a single command; a thin main-guarded CLI wrapper delegating to the module keeps that ergonomics without holding logic. + +## Consequences + +- Quality/integrity gates are authored as owning-package modules with white-box unit tests; the repo-root gate script becomes a one-line delegation (`pnpm --filter `), and CI's step is unchanged because it already calls the root script. +- Reviewers can reject a new gate that puts logic in `scripts/` or that tests a script by spawning it; the fix is "extract to a module + white-box test, delegate the script." +- Script/CLI end-to-end coverage, when needed, is added to `scripts/smoke-tests/` (surfaced as gate `pnpm smoke-tests`), keeping vitest free of script spawning. +- Applied on landing: docs-staleness → `apps/website/lib/docs-staleness-check.ts` + white-box test, `scripts/docs-staleness-check.js` deleted, root gate delegates (PR #315). Prior exemplar: skills-conformance → module in `packages/knowledge-hub/src/tools/` + sibling test, script deleted (#313/#324). Pre-existing exemplars that already followed this shape: knowledge-hub `check-broken-links.ts` and `transfer-dataset.ts` (package modules run via package scripts). + +## Adoption Impact + +- [way-of-working.md](../tech/way-of-working.md): Quality Gates section gains a short "Gate & tooling code" bullet pointing to this ADL (surfacing). +- No knowledge-base/dataset mirror: sibling ADLs in `adoption/decision-log/` are adoption-only records; the dataset (`packages/knowledge-hub/dataset/`) is a curated sample, not an auto-mirror of adoption, so this decision is not copied there. +- Complements (does not amend) [2026-07-08-test-file-colocation-multi-module.md](./2026-07-08-test-file-colocation-multi-module.md): that ADL owns test-file placement; this ADL owns module-vs-script boundaries and the no-unit-test-on-scripts rule. diff --git a/.pair/adoption/tech/way-of-working.md b/.pair/adoption/tech/way-of-working.md index 98c2ffa9..7a1392d1 100644 --- a/.pair/adoption/tech/way-of-working.md +++ b/.pair/adoption/tech/way-of-working.md @@ -22,6 +22,7 @@ - `pnpm quality-gate` is the adopted project-level quality gate command. - Quality gate includes: type checking (`ts:check`), testing (`test`), linting (`lint`), formatting (`prettier:fix`), markdown lint (`mdlint:fix`). +- **Gate & tooling code:** a gate's logic lives in a tested module in its owning package (white-box unit tests); scripts/CLIs are thin entrypoints and a root gate delegates (`pnpm --filter `). Scripts are never unit-tested — CLI-level checks go to smoke tests. See ADL [2026-07-13-gate-tooling-code-in-tested-modules.md](../decision-log/2026-07-13-gate-tooling-code-in-tested-modules.md). ### Custom Gate Registry diff --git a/apps/pair-cli/src/commands/kb-info/migration-url.test.ts b/apps/pair-cli/src/commands/kb-info/migration-url.test.ts index f0fdd8de..97581e7a 100644 --- a/apps/pair-cli/src/commands/kb-info/migration-url.test.ts +++ b/apps/pair-cli/src/commands/kb-info/migration-url.test.ts @@ -4,13 +4,13 @@ import { buildMigrationUrl, migrationsIndexUrl } from './migration-url' describe('buildMigrationUrl', () => { it('builds a docs URL for the version jump', () => { expect(buildMigrationUrl('1.1.0', '1.2.0')).toBe( - 'https://pair.foomakers.com/docs/guides/migrations/v1.1.0-to-v1.2.0', + 'https://pair.foomakers.com/docs/migrations/v1.1.0-to-v1.2.0', ) }) it('strips a leading v from either version', () => { expect(buildMigrationUrl('v1.1.0', 'v1.2.0')).toBe( - 'https://pair.foomakers.com/docs/guides/migrations/v1.1.0-to-v1.2.0', + 'https://pair.foomakers.com/docs/migrations/v1.1.0-to-v1.2.0', ) }) @@ -22,6 +22,6 @@ describe('buildMigrationUrl', () => { describe('migrationsIndexUrl', () => { it('returns the docs migrations index', () => { - expect(migrationsIndexUrl()).toBe('https://pair.foomakers.com/docs/guides/migrations') + expect(migrationsIndexUrl()).toBe('https://pair.foomakers.com/docs/migrations') }) }) diff --git a/apps/pair-cli/src/commands/kb-info/migration-url.ts b/apps/pair-cli/src/commands/kb-info/migration-url.ts index 27ff283f..a13395d2 100644 --- a/apps/pair-cli/src/commands/kb-info/migration-url.ts +++ b/apps/pair-cli/src/commands/kb-info/migration-url.ts @@ -4,7 +4,7 @@ * jump, no migration logic in the CLI) — kept isolated so a docs-structure * change only requires updating this file. */ -const DOCS_MIGRATIONS_BASE = 'https://pair.foomakers.com/docs/guides/migrations' +const DOCS_MIGRATIONS_BASE = 'https://pair.foomakers.com/docs/migrations' function stripLeadingV(version: string): string { return version.replace(/^v/i, '') diff --git a/apps/pair-cli/src/commands/kb-info/version-check-formatter.test.ts b/apps/pair-cli/src/commands/kb-info/version-check-formatter.test.ts index ae8732da..5909c13e 100644 --- a/apps/pair-cli/src/commands/kb-info/version-check-formatter.test.ts +++ b/apps/pair-cli/src/commands/kb-info/version-check-formatter.test.ts @@ -62,7 +62,7 @@ describe('formatVersionCheckHuman', () => { const output = formatVersionCheckHuman(result) expect(output).toContain('Current version unavailable') - expect(output).toContain('https://pair.foomakers.com/docs/guides/migrations') + expect(output).toContain('https://pair.foomakers.com/docs/migrations') }) it('labels non-stable current versions', () => { diff --git a/apps/pair-cli/src/commands/kb-info/version-check.test.ts b/apps/pair-cli/src/commands/kb-info/version-check.test.ts index 53b78257..090a9d67 100644 --- a/apps/pair-cli/src/commands/kb-info/version-check.test.ts +++ b/apps/pair-cli/src/commands/kb-info/version-check.test.ts @@ -29,7 +29,7 @@ describe('compareVersions', () => { expect(result.status).toBe('drift') expect(result.migrationUrl).toBe( - 'https://pair.foomakers.com/docs/guides/migrations/v1.1.0-to-v1.2.0', + 'https://pair.foomakers.com/docs/migrations/v1.1.0-to-v1.2.0', ) }) diff --git a/apps/pair-cli/src/commands/update-link/metadata.ts b/apps/pair-cli/src/commands/update-link/metadata.ts index 9eefab5b..2d7290aa 100644 --- a/apps/pair-cli/src/commands/update-link/metadata.ts +++ b/apps/pair-cli/src/commands/update-link/metadata.ts @@ -22,6 +22,6 @@ export const updateLinkMetadata = { 'Creates automatic backup before modifications', 'Skips external URLs and mailto links', 'Processes all markdown files in .pair/ directory', - 'See also: https://pair.foomakers.com/docs/guides/update-link', + 'See also: https://pair.foomakers.com/docs/reference/cli/update-link', ], } as const diff --git a/apps/website/content/docs/concepts/ai-assisted-sdlc.mdx b/apps/website/content/docs/concepts/ai-assisted-sdlc.mdx index bb309072..873496dc 100644 --- a/apps/website/content/docs/concepts/ai-assisted-sdlc.mdx +++ b/apps/website/content/docs/concepts/ai-assisted-sdlc.mdx @@ -13,7 +13,7 @@ pair solves this by providing **structured, persistent context** that AI assista This onboarding takes the form of a [Knowledge Base](/docs/concepts/knowledge-base) — a set of documents installed in your project that cover: -- **How to work** — 11 process guides defining every step from product requirements to code review +- **How to work** — 9 how-to guides defining every step from product requirements to code review - **What standards to follow** — guidelines for architecture, testing, code design, security - **What the team decided** — adoption files recording the chosen tech stack, architecture, and way of working diff --git a/apps/website/content/docs/concepts/index.mdx b/apps/website/content/docs/concepts/index.mdx new file mode 100644 index 00000000..5e10220b --- /dev/null +++ b/apps/website/content/docs/concepts/index.mdx @@ -0,0 +1,25 @@ +--- +title: Concepts +description: How pair works under the hood — the Knowledge Base, skills, adoption files, and the ideas that connect them. +--- + +These pages explain **how pair works** — read them when you want understanding, not step-by-step instructions. + +## Recommended reading order + +The core pages build on each other — if you're new to pair, read them top to bottom: + +1. **[AI-Assisted SDLC](/docs/concepts/ai-assisted-sdlc)** — Start here: the problem pair solves and how structured context changes AI collaboration end-to-end. +2. **[Knowledge Base](/docs/concepts/knowledge-base)** — The core artifact: the documents installed into `.pair/` that give your AI assistant persistent context. +3. **[Skills](/docs/concepts/skills)** — The actions layered on top: structured instructions your assistant discovers and executes. +4. **[Adoption Files](/docs/concepts/adoption-files)** — Your project-specific decisions, recorded so they never drift. + +## Deeper mechanics + +Read these when the topic comes up — they explain specific mechanisms rather than the big picture: + +- **[Canonical States](/docs/concepts/canonical-states)** — How skills reason about work-item state across any board vocabulary. +- **[Agent Integration](/docs/concepts/agent-integration)** — The bridge pattern that connects any AI coding tool to the single source of truth. +- **[llms.txt](/docs/concepts/llms-txt)** — The standard pair uses to make documentation and project context natively consumable by AI assistants. + +Ready to apply the concepts? The [Process Lifecycle](/docs/developer-journey) shows the nine-step workflow they enable, and the [tutorials](/docs/tutorials) walk it through hands-on. diff --git a/apps/website/content/docs/concepts/knowledge-base.mdx b/apps/website/content/docs/concepts/knowledge-base.mdx index 000bf88f..64e96f7c 100644 --- a/apps/website/content/docs/concepts/knowledge-base.mdx +++ b/apps/website/content/docs/concepts/knowledge-base.mdx @@ -16,7 +16,7 @@ When you run `pair-cli install`, the CLI creates a `.pair/` folder with two main knowledge/ # Reference material (the "how to") way-of-working.md # Development process definition getting-started.md # Onboarding guide - how-to/ # 11 step-by-step process guides + how-to/ # 9 step-by-step process guides guidelines/ # Technical standards architecture/ # Architecture patterns code-design/ # Code design principles diff --git a/apps/website/content/docs/customization/adopt.mdx b/apps/website/content/docs/customization/adopt.mdx index 3dc25e5a..de3be153 100644 --- a/apps/website/content/docs/customization/adopt.mdx +++ b/apps/website/content/docs/customization/adopt.mdx @@ -58,7 +58,7 @@ pair-cli install --source github --url https://github.com/org/my-kb/releases/lat pair-cli install --source local --url /path/to/kb ``` -See [CLI Commands](/docs/reference/cli/commands) for the full `install` reference and [Install from URL](/docs/guides/install-from-url) for advanced source options. +See [CLI Commands](/docs/reference/cli/commands) for the full `install` reference and [Install from URL](/docs/customization/install-from-url) for advanced source options. ## Verify the Installation @@ -101,7 +101,7 @@ As the upstream KB evolves, pull updates without losing your project-specific de pair-cli update ``` -The update replaces `.pair/knowledge/` (upstream content) but preserves `.pair/adoption/` (your decisions). See [Customize the Knowledge Base](/docs/guides/customize-kb) for details on how the layered architecture protects your customizations. +The update replaces `.pair/knowledge/` (upstream content) but preserves `.pair/adoption/` (your decisions). See [Customize the Knowledge Base](/docs/customization/team) for details on how the layered architecture protects your customizations. ## Next Steps diff --git a/apps/website/content/docs/customization/index.mdx b/apps/website/content/docs/customization/index.mdx index cefde3ef..8c3e8c3d 100644 --- a/apps/website/content/docs/customization/index.mdx +++ b/apps/website/content/docs/customization/index.mdx @@ -13,6 +13,8 @@ Install a published Knowledge Base and start using it as-is. This is the fastest [Adopt a Knowledge Base](/docs/customization/adopt) +Installing from a custom mirror, a local path, or an offline environment? See [Install from URL or Path](/docs/customization/install-from-url). + ## Customize Fork and adjust an adopted KB's guidelines, quality gates, and way-of-working for your team. Adoption files override KB defaults, so your customizations survive upstream updates. @@ -42,6 +44,7 @@ Override the built-in templates that control commit messages, PR descriptions, u | Stage | You want to... | Start here | | --- | --- | --- | | Adopt | Use someone else's KB | [Adopt](/docs/customization/adopt) | +| Adopt | Install from a mirror, local path, or offline | [Install from URL or Path](/docs/customization/install-from-url) | | Customize | Adjust a KB for your team | [Customize](/docs/customization/team) | | Templates | Change commit, PR, or story formats | [Templates](/docs/customization/templates) | | Publish | Create and distribute your own KB | [Publish](/docs/customization/organization) | @@ -51,5 +54,5 @@ Override the built-in templates that control commit messages, PR descriptions, u - [Tutorials](/docs/tutorials) — End-to-end walkthroughs for adopt → customize → publish - [Quickstart](/docs/getting-started/quickstart) — Install pair-cli and set up a project - [KB Structure Reference](/docs/reference/kb-structure) — Directory layout and file purposes -- [Customize the Knowledge Base](/docs/guides/customize-kb) — Quick guide to overriding defaults +- [Customize for Your Team](/docs/customization/team) — Override guidelines, quality gates, and defaults - [Brand Identity Guide](https://github.com/foomakers/pair/blob/main/packages/brand/BRAND.md) — Logo, colors, typography, and design tokens diff --git a/apps/website/content/docs/guides/install-from-url.mdx b/apps/website/content/docs/customization/install-from-url.mdx similarity index 98% rename from apps/website/content/docs/guides/install-from-url.mdx rename to apps/website/content/docs/customization/install-from-url.mdx index 31e50d89..edf49e49 100644 --- a/apps/website/content/docs/guides/install-from-url.mdx +++ b/apps/website/content/docs/customization/install-from-url.mdx @@ -172,4 +172,4 @@ pair update --offline --source ./updated-kb - [CLI Commands Reference](/docs/reference/cli/commands) — Full `install` and `update` syntax - [KB Source Resolution Spec](/docs/reference/specs/kb-source-resolution) — Technical spec for resolution algorithm -- [Troubleshooting](/docs/guides/troubleshooting) — Common installation issues +- [Troubleshooting](/docs/support/troubleshooting) — Common installation issues diff --git a/apps/website/content/docs/customization/meta.json b/apps/website/content/docs/customization/meta.json index d52fd7f2..c8ebd718 100644 --- a/apps/website/content/docs/customization/meta.json +++ b/apps/website/content/docs/customization/meta.json @@ -1,4 +1,4 @@ { "title": "Customization", - "pages": ["adopt", "team", "organization", "templates"] + "pages": ["adopt", "install-from-url", "team", "organization", "templates"] } diff --git a/apps/website/content/docs/customization/organization.mdx b/apps/website/content/docs/customization/organization.mdx index c408bff3..62e625f8 100644 --- a/apps/website/content/docs/customization/organization.mdx +++ b/apps/website/content/docs/customization/organization.mdx @@ -3,6 +3,8 @@ title: Publish a Knowledge Base description: Create, structure, package, and distribute a Knowledge Base for your organization. --- +import { Callout } from 'fumadocs-ui/components/callout' + You've customized pair for your team. Now you want to codify those practices into a distributable Knowledge Base — so every team in your organization starts from the same foundation. This guide covers the full workflow from creation to distribution. ## Plan Your KB @@ -113,18 +115,104 @@ Each `SKILL.md` contains structured instructions the AI discovers and executes. ## Package for Distribution -Package your KB for distribution: +The `pair-cli package` command creates a distributable ZIP containing your KB's knowledge, adoption templates, and skills, with metadata (name, version, description) for registry listing. 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 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-cli 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-cli install`. This is the default. ```bash -pair-cli package +pair-cli 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. + + +### 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) + +### Packaging walkthrough + +```bash +# 1. Validate with the same layout you'll package +pair-cli kb validate --layout source + +# 2. Package (with metadata) +pair-cli package --layout source \ + --name "My KB" \ + --version 1.0.0 \ + --description "Team knowledge base" \ + -o dist/my-kb-source.zip + +# 3. Inspect the ZIP contents +unzip -l dist/my-kb-source.zip + +# 4. Validate the packaged KB +pair-cli kb validate dist/my-kb-source.zip ``` -This creates a distributable archive containing your KB's knowledge, adoption templates, and skills. The package includes metadata (name, version, description) for registry listing. +### Organizational packaging -For organizational metadata: +For enterprise distribution, add organizational metadata: ```bash -pair-cli package --org "your-org-name" +pair-cli package --layout source \ + --org \ + --org-name "Acme Corp" \ + --team "Platform" \ + --compliance "SOC2,ISO27001" \ + --distribution restricted ``` See [CLI Commands](/docs/reference/cli/commands) for the full `package` reference. @@ -188,7 +276,7 @@ pair-cli install --source github --url https://github.com/your-org/your-kb/relea For organizations that restrict external package sources, host the package on an internal registry or file share. Adopters use the `--url` flag to point to the internal location. -See [Install from URL](/docs/guides/install-from-url) for all source options. +See [Install from URL](/docs/customization/install-from-url) for all source options. ## Version and Maintain diff --git a/apps/website/content/docs/customization/team.mdx b/apps/website/content/docs/customization/team.mdx index f7ab52da..795d0929 100644 --- a/apps/website/content/docs/customization/team.mdx +++ b/apps/website/content/docs/customization/team.mdx @@ -27,6 +27,53 @@ The `.pair/` directory has two layers: **Adoption always wins.** When both layers have an opinion on the same topic, the AI reads adoption files first. This means you can override any guideline by recording a decision in the adoption layer. +## Quick Recipe: Override a Guideline + +Need to change a single KB default? Three steps: + +### Step 1: Identify what to change + +Browse the knowledge layer to find the guideline: + +```bash +# List all guideline categories +ls .pair/knowledge/guidelines/ + +# Example: see testing guidelines +ls .pair/knowledge/guidelines/quality-assurance/ +``` + +### Step 2: Record your decision + +Create or update the corresponding adoption file. For example, to change the testing framework, edit `.pair/adoption/tech/tech-stack.md`: + +```markdown +## Testing + +- jest is adopted as the testing framework (jest v29.x). +``` + +Adoption files always override knowledge files — no need to touch `.pair/knowledge/`. + +### Step 3: Verify + +```bash +pair-cli kb validate +pair-cli update-link --dry-run +``` + +### Which file to edit + +| You want to change... | Edit this adoption file | +| ------------------------------------- | --------------------------------------- | +| Testing framework, language, library | `.pair/adoption/tech/tech-stack.md` | +| Architecture pattern | `.pair/adoption/tech/architecture.md` | +| Methodology, quality gates | `.pair/adoption/tech/way-of-working.md` | +| Add a new guideline | Create a new file in `.pair/adoption/` | +| Record a significant decision | Create an ADR in `.pair/adoption/tech/adr/` | + +The rest of this guide covers each of these in depth. + ## Customize Guidelines ### Override the Tech Stack @@ -149,4 +196,4 @@ pair-cli update-link --dry-run - **Want to distribute your customized KB?** [Publish a Knowledge Base](/docs/customization/organization) — package and distribute for other teams. - **Need the directory reference?** [KB Structure](/docs/reference/kb-structure) — every directory and file explained. -- **Quick override guide?** [Customize the Knowledge Base](/docs/guides/customize-kb) — concise override patterns. +- **Config options?** [Configuration Reference](/docs/reference/configuration) — config.json options. diff --git a/apps/website/content/docs/developer-journey/execution.mdx b/apps/website/content/docs/developer-journey/execution.mdx index 0f3349be..c4ff810b 100644 --- a/apps/website/content/docs/developer-journey/execution.mdx +++ b/apps/website/content/docs/developer-journey/execution.mdx @@ -120,6 +120,6 @@ This loop repeats for every story. `/pair-next` always knows what comes next. ## Reference -- [Skills Catalog](/docs/reference/skills-catalog) — all 32 skills with commands and composition +- [Skills Catalog](/docs/reference/skills-catalog) — all 35 skills with commands and composition - [Guidelines & How-To Catalog](/docs/reference/guidelines-catalog) — all 9 guideline categories and 9 how-to guides - [Adopt a Knowledge Base](/docs/customization/adopt) — set up pair if you haven't already diff --git a/apps/website/content/docs/developer-journey/index.mdx b/apps/website/content/docs/developer-journey/index.mdx index 3fb3683b..1e251671 100644 --- a/apps/website/content/docs/developer-journey/index.mdx +++ b/apps/website/content/docs/developer-journey/index.mdx @@ -40,8 +40,8 @@ Level 4: Execution (per story) You can follow this flow in two ways: -1. **Process Skills (recommended)** — invoke a slash command and the AI handles the structured process for you. See the [Skills Catalog](/docs/reference/skills-catalog) for all 32 skills. -2. **How-to Guides** — follow the manual step-by-step instructions from the Knowledge Base. See the [Guidelines & How-To Catalog](/docs/reference/guidelines-catalog) for all 11 guides. +1. **Process Skills (recommended)** — invoke a slash command and the AI handles the structured process for you. See the [Skills Catalog](/docs/reference/skills-catalog) for all 35 skills. +2. **How-to Guides** — follow the manual step-by-step instructions from the Knowledge Base. See the [Guidelines & How-To Catalog](/docs/reference/guidelines-catalog) for all 9 how-to guides. Skills compose capability skills internally, enforce adoption compliance, and update your PM tool automatically. The how-to guides give you the same process with full manual control. @@ -117,5 +117,5 @@ Feeling lost? Check the [FAQ](/docs/support/general-faq) — it answers common q ## Reference -- [Skills Catalog](/docs/reference/skills-catalog) — all 32 skills with commands and composition +- [Skills Catalog](/docs/reference/skills-catalog) — all 35 skills with commands and composition - [Guidelines & How-To Catalog](/docs/reference/guidelines-catalog) — all 9 guideline categories and 9 how-to guides diff --git a/apps/website/content/docs/developer-journey/meta.json b/apps/website/content/docs/developer-journey/meta.json index d8c9bcba..6e51a20e 100644 --- a/apps/website/content/docs/developer-journey/meta.json +++ b/apps/website/content/docs/developer-journey/meta.json @@ -1,4 +1,4 @@ { - "title": "Developer Journey", + "title": "Process Lifecycle", "pages": ["induction", "strategic-planning", "iteration", "execution"] } diff --git a/apps/website/content/docs/guides/adopter-checklist.mdx b/apps/website/content/docs/getting-started/checklist.mdx similarity index 96% rename from apps/website/content/docs/guides/adopter-checklist.mdx rename to apps/website/content/docs/getting-started/checklist.mdx index 56be0ee7..e9be4974 100644 --- a/apps/website/content/docs/guides/adopter-checklist.mdx +++ b/apps/website/content/docs/getting-started/checklist.mdx @@ -157,10 +157,10 @@ rm -rf /tmp/pair-test If everything worked: - Start using pair-cli in your actual projects -- Check [CLI Workflows](/docs/guides/cli-workflows) for advanced usage +- Check [CLI Workflows](/docs/reference/cli/workflows) for advanced usage - Join the community at [GitHub Discussions](https://github.com/foomakers/pair/discussions) If issues occurred: -- Review the [Troubleshooting Guide](/docs/guides/troubleshooting) +- Review the [Troubleshooting](/docs/support/troubleshooting) - File an issue at [GitHub Issues](https://github.com/foomakers/pair/issues) diff --git a/apps/website/content/docs/getting-started/index.mdx b/apps/website/content/docs/getting-started/index.mdx index de9a74b2..22f539d5 100644 --- a/apps/website/content/docs/getting-started/index.mdx +++ b/apps/website/content/docs/getting-started/index.mdx @@ -15,7 +15,7 @@ A structured set of documents — guidelines, how-tos, architecture decisions, t ## Skills -Structured instructions that AI assistants discover and execute, following the [Agent Skills](https://agentskills.io) open standard. Skills automate the full product lifecycle — from PRD creation to code review — with 30 composable skills covering process and capability concerns. +Structured instructions that AI assistants discover and execute, following the [Agent Skills](https://agentskills.io) open standard. Skills automate the full product lifecycle — from PRD creation to code review — with 35 composable skills covering process and capability concerns. [Learn more about Skills](/docs/concepts/skills) @@ -30,9 +30,9 @@ You don't write these files manually. Run `/pair-next` and the AI walks you thro ## Choose your path - **[Quickstart](/docs/getting-started/quickstart)** — Install pair-cli, set up the Knowledge Base, and start using it. -- **[Solo Setup](/docs/getting-started/quickstart-solo)** — Working alone? Minimal configuration, no team coordination. -- **[Team Setup](/docs/getting-started/quickstart-team)** — Shared standards and the agent bridge pattern for multi-developer teams. -- **[Organization Rollout](/docs/getting-started/quickstart-org)** — Custom KB packaging, distribution, and governance across multiple teams. +- **[Quickstart: Solo](/docs/getting-started/quickstart-solo)** — Working alone? Minimal configuration, no team coordination. +- **[Quickstart: Team](/docs/getting-started/quickstart-team)** — Shared standards and the agent bridge pattern for multi-developer teams. +- **[Quickstart: Organization](/docs/getting-started/quickstart-org)** — Custom KB packaging, distribution, and governance across multiple teams. ## Go deeper with tutorials diff --git a/apps/website/content/docs/getting-started/meta.json b/apps/website/content/docs/getting-started/meta.json index a0ef0f0e..2d1f91a1 100644 --- a/apps/website/content/docs/getting-started/meta.json +++ b/apps/website/content/docs/getting-started/meta.json @@ -1,4 +1,4 @@ { "title": "Getting Started", - "pages": ["quickstart", "quickstart-solo", "quickstart-team", "quickstart-org"] + "pages": ["quickstart", "quickstart-solo", "quickstart-team", "quickstart-org", "checklist"] } diff --git a/apps/website/content/docs/getting-started/quickstart-org.mdx b/apps/website/content/docs/getting-started/quickstart-org.mdx index 6b0485ba..071b05b4 100644 --- a/apps/website/content/docs/getting-started/quickstart-org.mdx +++ b/apps/website/content/docs/getting-started/quickstart-org.mdx @@ -1,5 +1,5 @@ --- -title: Organization Setup +title: "Quickstart: Organization" description: Roll out pair across multiple teams with custom KB packaging, distribution, and organization-wide standards. --- diff --git a/apps/website/content/docs/getting-started/quickstart-solo.mdx b/apps/website/content/docs/getting-started/quickstart-solo.mdx index 9bb71c4e..95c584b3 100644 --- a/apps/website/content/docs/getting-started/quickstart-solo.mdx +++ b/apps/website/content/docs/getting-started/quickstart-solo.mdx @@ -1,5 +1,5 @@ --- -title: Solo Setup +title: "Quickstart: Solo" description: Configure pair for solo development — minimal setup, no team coordination needed. --- diff --git a/apps/website/content/docs/getting-started/quickstart-team.mdx b/apps/website/content/docs/getting-started/quickstart-team.mdx index 8b9a4d75..8bb23b03 100644 --- a/apps/website/content/docs/getting-started/quickstart-team.mdx +++ b/apps/website/content/docs/getting-started/quickstart-team.mdx @@ -1,5 +1,5 @@ --- -title: Team Setup +title: "Quickstart: Team" description: Configure pair for a development team with shared standards, adoption files, and multi-agent support. --- diff --git a/apps/website/content/docs/getting-started/quickstart.mdx b/apps/website/content/docs/getting-started/quickstart.mdx index f87d7f3e..b8a28f44 100644 --- a/apps/website/content/docs/getting-started/quickstart.mdx +++ b/apps/website/content/docs/getting-started/quickstart.mdx @@ -62,7 +62,7 @@ Your project now has: ```text .pair/ knowledge/ # Reference material: process guides, standards - how-to/ # 11 step-by-step guides (PRD → code review) + how-to/ # 9 step-by-step guides (PRD → code review) guidelines/ # Technical standards (architecture, testing, code design) assets/ # Templates (PRD, checklist, commit, PR) adoption/ # Your decisions (filled in as you make them) @@ -127,9 +127,9 @@ Each step has a **how-to guide** in `.pair/knowledge/how-to/` that the AI follow The steps above work for everyone. For environment-specific configuration: -- **[Solo Setup](/docs/getting-started/quickstart-solo)** — Working alone? Minimal setup, no team coordination needed. -- **[Team Setup](/docs/getting-started/quickstart-team)** — Shared standards, adoption files, and the agent bridge pattern for multi-developer teams. -- **[Organization Rollout](/docs/getting-started/quickstart-org)** — Custom KB packaging, distribution, and governance across multiple teams. +- **[Quickstart: Solo](/docs/getting-started/quickstart-solo)** — Working alone? Minimal setup, no team coordination needed. +- **[Quickstart: Team](/docs/getting-started/quickstart-team)** — Shared standards, adoption files, and the agent bridge pattern for multi-developer teams. +- **[Quickstart: Organization](/docs/getting-started/quickstart-org)** — Custom KB packaging, distribution, and governance across multiple teams. Want a guided, end-to-end walkthrough? Pick a [tutorial](/docs/tutorials) that matches your context — solo developer (~30 min), team lead (~45 min), or enterprise architect (~60 min). diff --git a/apps/website/content/docs/guides/customize-kb.mdx b/apps/website/content/docs/guides/customize-kb.mdx deleted file mode 100644 index 68d9aaaf..00000000 --- a/apps/website/content/docs/guides/customize-kb.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: Customize the Knowledge Base -description: Quick recipe to override a KB guideline using adoption files — identify, override, verify. ---- - -Need to change a single KB default? This is the quick recipe. For the full customization journey — from adopting a KB to publishing your own — see the [Customization](/docs/customization) section. - -## Override a Guideline in 3 Steps - -### Step 1: Identify What to Change - -Browse the knowledge layer to find the guideline: - -```bash -# List all guideline categories -ls .pair/knowledge/guidelines/ - -# Example: see testing guidelines -ls .pair/knowledge/guidelines/quality-assurance/ -``` - -### Step 2: Record Your Decision - -Create or update the corresponding adoption file. For example, to change the testing framework, edit `.pair/adoption/tech/tech-stack.md`: - -```markdown -## Testing - -- jest is adopted as the testing framework (jest v29.x). -``` - -Adoption files always override knowledge files — no need to touch `.pair/knowledge/`. - -### Step 3: Verify - -```bash -pair-cli kb validate -pair-cli update-link --dry-run -``` - -## Quick Reference: Which File to Edit - -| You want to change... | Edit this adoption file | -| --- | --- | -| Testing framework, language, library | `.pair/adoption/tech/tech-stack.md` | -| Architecture pattern | `.pair/adoption/tech/architecture.md` | -| Methodology, quality gates | `.pair/adoption/tech/way-of-working.md` | -| Add a new guideline | Create a new file in `.pair/adoption/` | -| Record a significant decision | Create an ADR in `.pair/adoption/tech/adr/` | - -## What's Safe to Edit - -| Directory | On `pair-cli update` | Safe to edit? | -| --- | --- | --- | -| `.pair/knowledge/` | Overwritten | No — don't edit | -| `.pair/adoption/` | Preserved | Yes — your decisions stay | - -## Going Deeper - -- [Customize for Your Team](/docs/customization/team) — full guide to adapting guidelines, quality gates, and way-of-working -- [Publish a Knowledge Base](/docs/customization/organization) — package and distribute a KB for your organization -- [KB Structure Reference](/docs/reference/kb-structure) — directory layout and file purposes -- [Configuration Reference](/docs/reference/configuration) — config.json options diff --git a/apps/website/content/docs/guides/meta.json b/apps/website/content/docs/guides/meta.json deleted file mode 100644 index 418d5ab7..00000000 --- a/apps/website/content/docs/guides/meta.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title": "Guides", - "pages": [ - "cli-workflows", - "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 deleted file mode 100644 index 942e143b..00000000 --- a/apps/website/content/docs/guides/packaging.mdx +++ /dev/null @@ -1,125 +0,0 @@ ---- -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 deleted file mode 100644 index f34a8d29..00000000 --- a/apps/website/content/docs/guides/troubleshooting.mdx +++ /dev/null @@ -1,236 +0,0 @@ ---- -title: Troubleshooting -description: Common issues and solutions for pair-cli installation and usage. ---- - -Common issues and solutions for pair-cli installation and usage. - -## Installation Issues - -### Permission Errors - -**Problem:** `npm install -g @foomakers/pair-cli` fails with permission errors. - -**Solution:** Use a Node version manager or install locally. - -```bash -# Option 1: Use nvm (recommended) -curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash -nvm install 18 -nvm use 18 - -# Option 2: Install locally in project -pnpm add -D @foomakers/pair-cli -# Use with: pnpm dlx pair-cli - -# Option 3: Use sudo (not recommended) -sudo npm install -g @foomakers/pair-cli -``` - -### Command Not Found - -**Problem:** `pair-cli` command not found after installation. - -**Solutions:** - -```bash -# Check PATH -echo $PATH -which pair-cli - -# If using nvm, ensure Node version is active -nvm current -nvm use 18 - -# Restart terminal or source profile -source ~/.bashrc # or ~/.zshrc - -# For local install, use npx -npx pair-cli --version -``` - -### Node.js Version Issues - -**Problem:** Errors about unsupported Node version. - -**Solution:** Ensure Node 18+ is active. - -```bash -# Check version -node --version # Should be 18.0.0 or higher - -# Update with nvm -nvm install 18 -nvm use 18 -nvm alias default 18 -``` - -## Runtime Issues - -### Target Directory Permissions - -**Problem:** `pair-cli install` fails with "permission denied". - -**Solution:** Ensure write access to target directory. - -```bash -# Check permissions -ls -la . - -# Fix permissions if needed -chmod 755 . - -# Or run in a directory you own -mkdir ~/my-project -cd ~/my-project -pair-cli install -``` - -### Network/Proxy Issues - -**Problem:** Installation fails due to network restrictions. - -**Solutions:** - -```bash -# Use manual install for offline environments -# Download from: https://github.com/foomakers/pair/releases -# Extract and run directly - -# Configure npm proxy -npm config set proxy http://proxy.company.com:8080 -npm config set https-proxy http://proxy.company.com:8080 - -# Or use manual install -``` - -## Platform-Specific Issues - -### macOS - -**Problem:** "command not found" after global install. - -**Solution:** Check PATH and npm prefix. - -```bash -# Check npm global install location -npm config get prefix - -# Ensure it's in PATH -export PATH="$(npm config get prefix)/bin:$PATH" - -# Add to ~/.zshrc or ~/.bashrc -echo 'export PATH="$(npm config get prefix)/bin:$PATH"' >> ~/.zshrc -``` - -### Linux - -**Problem:** Native module compilation fails. - -**Solution:** Install build tools. - -```bash -# Ubuntu/Debian -sudo apt-get update -sudo apt-get install build-essential python3-dev - -# CentOS/RHEL -sudo yum groupinstall "Development Tools" -sudo yum install python3-devel -``` - -### Windows - -**Problem:** Line ending or path issues. - -**Solutions:** - -```bash -# Use Git Bash or WSL -# Ensure Node is in PATH -where node - -# Use manual install with .cmd files -# Download pair-cli-manual-vX.Y.Z.zip -# Run pair-cli.cmd instead of pair-cli -``` - -## Verification Issues - -### Assets Not Installing - -**Problem:** `pair-cli install` runs but no files are created. - -**Solutions:** - -```bash -# Check current directory -pwd - -# Ensure you have write permissions -touch test.txt && rm test.txt - -# Try with debug-level logging (if available) -pair-cli install --log-level debug - -# Check if assets are available -pair-cli install --list-targets -``` - -### Outdated Assets - -**Problem:** Installed assets seem outdated. - -**Solution:** Update to latest version. - -```bash -# Update all assets -pair-cli update - -# Or reinstall -pair-cli install --force # if available -``` - -## Getting Help - -### Diagnostic Information - -When reporting issues, include: - -```bash -# System info -uname -a -node --version -npm --version -pnpm --version - -# pair-cli info -pair-cli --version - -# Installation method -which pair-cli -npm list -g @foomakers/pair-cli -``` - -### Support Channels - -- **Documentation:** Check this guide and the [Installation FAQ](/docs/support/faq) -- **Issues:** File bugs at [GitHub Issues](https://github.com/foomakers/pair/issues) -- **Discussions:** Ask questions in [GitHub Discussions](https://github.com/foomakers/pair/discussions) - -## Quick Fixes - -| Issue | Quick Fix | -| ----------------- | ---------------------------- | -| Permission denied | `chmod 755 .` or use `sudo` | -| Command not found | `npx pair-cli` or check PATH | -| Node version | `nvm use 18` | -| Network blocked | Use manual install | -| Files not created | Check directory permissions | - -## Prevention - -- 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 @foomakers/pair-cli` diff --git a/apps/website/content/docs/index.mdx b/apps/website/content/docs/index.mdx index 0820d7bf..27e15ecd 100644 --- a/apps/website/content/docs/index.mdx +++ b/apps/website/content/docs/index.mdx @@ -1,15 +1,33 @@ --- title: Welcome -description: Getting started with pair +description: pair documentation — pick your journey, from first install to publishing your own Knowledge Base. ---
-

Welcome to pair

+ + Welcome to pair +
-**Code is the easy part.** pair helps development teams collaborate with AI assistants throughout the product lifecycle. +**Code is the easy part.** pair helps development teams collaborate with AI assistants throughout the product lifecycle — a Knowledge Base for persistent context, skills that automate the process, and adoption files that keep decisions from drifting. -## What is pair? +## Pick your journey -pair is a suite of resources and workflows designed to guide and enable collaboration between development teams and AI assistants. It provides tools for defining epics, stories, guidelines, and adoptions, integrating with major code assistants and project management tools. +- **[Getting Started](/docs/getting-started)** — What pair is, install pair-cli, and set up your first project in 5–10 minutes. +- **[Tutorials](/docs/tutorials)** — Guided end-to-end walkthroughs: first project, existing project, team setup, enterprise adoption (30–60 min). +- **[Customization](/docs/customization)** — Adapt pair to your context: adopt a Knowledge Base, customize it for your team, publish your own. +- **[Reference](/docs/reference)** — Look up details: CLI commands, skills and guidelines catalogs, KB structure, configuration. + +## Top tasks + +| I want to... | Go to | +| --- | --- | +| Install pair and run my first command | [Quickstart](/docs/getting-started/quickstart) | +| Understand how pair works | [Concepts](/docs/concepts) | +| Follow the development lifecycle | [Process Lifecycle](/docs/developer-journey) | +| Connect my AI coding assistant | [Integrations](/docs/integrations) | +| Connect my project management tool | [PM Tools](/docs/pm-tools) | +| Upgrade `.pair/` after a pair release | [Migrations](/docs/migrations) | +| Fix an install or setup problem | [Support](/docs/support) | +| Contribute to pair | [Contributing](/docs/contributing) | diff --git a/apps/website/content/docs/integrations/claude-code.mdx b/apps/website/content/docs/integrations/claude-code.mdx index 1b2a2b39..3cb2ada7 100644 --- a/apps/website/content/docs/integrations/claude-code.mdx +++ b/apps/website/content/docs/integrations/claude-code.mdx @@ -25,7 +25,7 @@ your-project/ │ ├── pair-next/ │ ├── pair-process-implement/ │ ├── pair-capability-verify-quality/ -│ └── ... ← 30+ skills +│ └── ... ← 35 skills ├── .pair/ │ ├── knowledge/ ← upstream guidelines (don't edit) │ └── adoption/ ← your project decisions diff --git a/apps/website/content/docs/meta.json b/apps/website/content/docs/meta.json index 9c93b854..2bb70b50 100644 --- a/apps/website/content/docs/meta.json +++ b/apps/website/content/docs/meta.json @@ -4,16 +4,15 @@ "pages": [ "index", "getting-started", - "developer-journey", + "tutorials", "concepts", + "developer-journey", "customization", "integrations", "pm-tools", "---", - "guides", - "migrations", - "tutorials", "reference", + "migrations", "support", "contributing" ] diff --git a/apps/website/content/docs/pm-tools/index.mdx b/apps/website/content/docs/pm-tools/index.mdx index 3c16c00d..b481b2af 100644 --- a/apps/website/content/docs/pm-tools/index.mdx +++ b/apps/website/content/docs/pm-tools/index.mdx @@ -59,5 +59,5 @@ All PM tool configuration lives in one place: `.pair/adoption/tech/way-of-workin ## Related - [AI Coding Tools](/docs/integrations) — set up your AI assistant -- [Developer Journey](/docs/developer-journey) — the full development lifecycle +- [Process Lifecycle](/docs/developer-journey) — the full development lifecycle - [Adoption Files concept](/docs/concepts/adoption-files) — how adoption files work diff --git a/apps/website/content/docs/reference/cli/commands.mdx b/apps/website/content/docs/reference/cli/commands.mdx index 90535724..a0d4e6c1 100644 --- a/apps/website/content/docs/reference/cli/commands.mdx +++ b/apps/website/content/docs/reference/cli/commands.mdx @@ -166,7 +166,7 @@ pair update-link --url https://github.com/org/repo/releases/download/v1.0/kb.zip pair update-link --url ./relative/path/to/kb ``` -For detailed usage, see the [Link Update Guide](/docs/guides/update-link). +For detailed usage, see the [Link Update Guide](/docs/reference/cli/update-link). --- diff --git a/apps/website/content/docs/reference/cli/examples.mdx b/apps/website/content/docs/reference/cli/examples.mdx index 9ff8d54f..1b94742b 100644 --- a/apps/website/content/docs/reference/cli/examples.mdx +++ b/apps/website/content/docs/reference/cli/examples.mdx @@ -293,5 +293,5 @@ pair install --source https://github.com/org/repo/releases/download/v1.2.0/kb.zi ## Related - [CLI Commands Reference](/docs/reference/cli/commands) — Complete command documentation -- [CLI Workflows Guide](/docs/guides/cli-workflows) — Common workflows +- [CLI Workflows Guide](/docs/reference/cli/workflows) — Common workflows - [KB Source Resolution Spec](/docs/reference/specs/kb-source-resolution) — Technical specification diff --git a/apps/website/content/docs/reference/cli/meta.json b/apps/website/content/docs/reference/cli/meta.json index 39f9b739..e6609b64 100644 --- a/apps/website/content/docs/reference/cli/meta.json +++ b/apps/website/content/docs/reference/cli/meta.json @@ -1,4 +1,4 @@ { "title": "CLI", - "pages": ["commands", "examples"] + "pages": ["commands", "examples", "workflows", "update-link"] } diff --git a/apps/website/content/docs/guides/update-link.mdx b/apps/website/content/docs/reference/cli/update-link.mdx similarity index 100% rename from apps/website/content/docs/guides/update-link.mdx rename to apps/website/content/docs/reference/cli/update-link.mdx diff --git a/apps/website/content/docs/guides/cli-workflows.mdx b/apps/website/content/docs/reference/cli/workflows.mdx similarity index 97% rename from apps/website/content/docs/guides/cli-workflows.mdx rename to apps/website/content/docs/reference/cli/workflows.mdx index ff67749e..4a7fe2ca 100644 --- a/apps/website/content/docs/guides/cli-workflows.mdx +++ b/apps/website/content/docs/reference/cli/workflows.mdx @@ -81,7 +81,7 @@ pair-cli update-link - **After project restructure**: `pair-cli update-link` to fix broken links - **Link health check**: `pair-cli update-link --dry-run --log-level debug` to inspect -See the [Link Update Guide](/docs/guides/update-link) for comprehensive documentation. +See the [Link Update Guide](/docs/reference/cli/update-link) for comprehensive documentation. ### 6. Package Distribution and Verification @@ -302,4 +302,4 @@ If commands fail, check: - Permissions: Try with `sudo` if installing globally - Network: For offline installs, use manual download -See the [Troubleshooting Guide](/docs/guides/troubleshooting) for detailed solutions. +See the [Troubleshooting](/docs/support/troubleshooting) for detailed solutions. diff --git a/apps/website/content/docs/reference/guidelines-catalog.mdx b/apps/website/content/docs/reference/guidelines-catalog.mdx index 871b6175..24324346 100644 --- a/apps/website/content/docs/reference/guidelines-catalog.mdx +++ b/apps/website/content/docs/reference/guidelines-catalog.mdx @@ -1,6 +1,6 @@ --- title: Guidelines & How-To Catalog -description: Complete catalog of all pair Knowledge Base guidelines (9 categories) and how-to guides (9 sequential steps). +description: Complete catalog of all pair Knowledge Base guidelines (9 categories) and the 9 sequential how-to guides. --- The pair Knowledge Base ships with opinionated guidelines organized into 9 categories and 9 sequential how-to guides covering the full AI-assisted development lifecycle. Guidelines live in `.pair/knowledge/guidelines/`, how-to guides in `.pair/knowledge/how-to/`. @@ -9,7 +9,7 @@ The pair Knowledge Base ships with opinionated guidelines organized into 9 categ ## How-To Guides -9 sequential guides that walk you through the complete development process — from product requirements to code review. Each guide can be followed manually or invoked as a skill (recommended). See the [Developer Journey](/docs/developer-journey) for the full step-by-step walkthrough. +9 sequential guides that walk you through the complete development process — from product requirements to code review. Each guide can be followed manually or invoked as a skill (recommended). See the [Process Lifecycle](/docs/developer-journey) for the full step-by-step walkthrough. | # | Guide | Tags | Skill (preferred) | | --- | --- | --- | --- | @@ -157,7 +157,7 @@ Design systems, research, and content strategy. ## Related - [Quality Model](/docs/reference/quality-model) — the single source of default quality rules (classification, tiers, tags) -- [Skills Catalog](/docs/reference/skills-catalog) — all 34 pair skills with commands and composition -- [Developer Journey](/docs/developer-journey) — step-by-step walkthrough of the full process +- [Skills Catalog](/docs/reference/skills-catalog) — all 35 pair skills with commands and composition +- [Process Lifecycle](/docs/developer-journey) — step-by-step walkthrough of the full process - [KB Structure](/docs/reference/kb-structure) — directory layout and file purposes - [Customize for Your Team](/docs/customization/team) — override guidelines via adoption files diff --git a/apps/website/content/docs/reference/index.mdx b/apps/website/content/docs/reference/index.mdx new file mode 100644 index 00000000..1a04f4e2 --- /dev/null +++ b/apps/website/content/docs/reference/index.mdx @@ -0,0 +1,28 @@ +--- +title: Reference +description: Complete specifications — CLI commands, catalogs of skills and guidelines, KB structure, and configuration. +--- + +Look-up material: complete, factual, no narrative. Come here when you know what you need and want the details. + +## CLI + +- [CLI Commands](/docs/reference/cli/commands) — Full syntax and options for every pair-cli command +- [CLI Help Examples](/docs/reference/cli/examples) — Copy-paste ready examples + +## Catalogs + +- [Skills Catalog](/docs/reference/skills-catalog) — Every pair skill with its command and composition +- [Guidelines & How-To Catalog](/docs/reference/guidelines-catalog) — Guideline categories and how-to guides + +## Structure & configuration + +- [KB Structure](/docs/reference/kb-structure) — Directory layout and file purposes of `.pair/` +- [Configuration](/docs/reference/configuration) — `config.json` options +- [Quality Model](/docs/reference/quality-model) — Default quality rules, tiers, and tags +- [Skill Management](/docs/reference/skill-management) — How skills are resolved, transformed, and installed + +## Specs + +- [CLI Contracts](/docs/reference/specs/cli-contracts) — Machine-readable CLI behavior contracts +- [KB Source Resolution](/docs/reference/specs/kb-source-resolution) — How KB sources are resolved diff --git a/apps/website/content/docs/reference/kb-structure.mdx b/apps/website/content/docs/reference/kb-structure.mdx index 3608c2f7..06d037b7 100644 --- a/apps/website/content/docs/reference/kb-structure.mdx +++ b/apps/website/content/docs/reference/kb-structure.mdx @@ -119,8 +119,9 @@ Neither subdirectory is scaffolded by `pair install` — a skill creates them on ## Related -- [Customize the Knowledge Base](/docs/guides/customize-kb) — How to override guidelines +- [Customize the Knowledge Base](/docs/customization/team) — How to override guidelines - [Configuration Reference](/docs/reference/configuration) — config.json options -- [Skills Catalog](/docs/reference/skills-catalog) — All 32 pair skills +- [Skills Catalog](/docs/reference/skills-catalog) — All 35 pair skills +- [llms.txt](/docs/concepts/llms-txt) — How pair makes docs and project context consumable by AI assistants - [knowledge-hub README](https://github.com/foomakers/pair/blob/main/packages/knowledge-hub/README.md) — KB package operations, scripts, and maintainer guide - [content-ops README](https://github.com/foomakers/pair/blob/main/packages/content-ops/README.md) — File operations API, copy behaviors, and transform configuration diff --git a/apps/website/content/docs/reference/skill-management.mdx b/apps/website/content/docs/reference/skill-management.mdx index e016efe2..8c349111 100644 --- a/apps/website/content/docs/reference/skill-management.mdx +++ b/apps/website/content/docs/reference/skill-management.mdx @@ -164,7 +164,7 @@ If two different source paths flatten to the same installed name (e.g. `a/b/` an | ------ | ----- | ------- | | `pair-` | 1 | Top-level skills (only `next`) | | `pair-process-` | 9 | Workflow orchestration skills | -| `pair-capability-` | 24 | Atomic operation skills | +| `pair-capability-` | 25 | Atomic operation skills | ### Transform Functions diff --git a/apps/website/content/docs/support/general-faq.mdx b/apps/website/content/docs/support/general-faq.mdx index fd7ac9c9..4d624912 100644 --- a/apps/website/content/docs/support/general-faq.mdx +++ b/apps/website/content/docs/support/general-faq.mdx @@ -57,7 +57,7 @@ No. Run `/pair-next` and the AI walks you through the full process — from PRD Use pair's default process. It's a structured 9-step lifecycle from product requirements to code review, and it works out of the box. Run `/pair-next` in your AI assistant — it reads your project state and tells you exactly what to do next, step by step. You don't need to plan anything in advance. -[Developer Journey](/docs/developer-journey) +[Process Lifecycle](/docs/developer-journey) ### I already have my own process — do I need to change it? @@ -135,7 +135,7 @@ Use the [Filesystem](/docs/pm-tools/filesystem) option — stories and tasks are Edit `.pair/adoption/tech/tech-stack.md` and declare your choices. The AI reads adoption files first, so your decisions override the KB defaults. -[Customize for Your Team](/docs/customization/team) · [Quick override recipe](/docs/guides/customize-kb) +[Customize for Your Team](/docs/customization/team) ### How do I customize commit message or PR formats? @@ -161,13 +161,13 @@ Yes. Create a KB from scratch or fork an existing one, structure it following th Check Node.js version (18+), permissions, and PATH. The Installation FAQ covers all common issues step by step. -[Installation FAQ](/docs/support/faq) +[Troubleshooting](/docs/support/troubleshooting) ### The AI ignores my adoption files — what's wrong? Verify your AI assistant can see the `.pair/` directory. Check that AGENTS.md or CLAUDE.md links to `.pair/`. Run `pair-cli kb validate` to verify structure. If using a bridge pattern, ensure the bridge files are in place. -[Agent Integration](/docs/concepts/agent-integration) · [Adopter Checklist](/docs/guides/adopter-checklist) +[Agent Integration](/docs/concepts/agent-integration) · [Adopter Checklist](/docs/getting-started/checklist) ### Where do I report bugs or request features? diff --git a/apps/website/content/docs/support/index.mdx b/apps/website/content/docs/support/index.mdx index fbd95787..97578544 100644 --- a/apps/website/content/docs/support/index.mdx +++ b/apps/website/content/docs/support/index.mdx @@ -8,7 +8,7 @@ Get help with pair installation, configuration, and usage. ## Quick Links - [FAQ](/docs/support/general-faq) — Common questions about pair, process, languages, and PM tools -- [Installation FAQ](/docs/support/faq) — Solutions for common install issues +- [Troubleshooting](/docs/support/troubleshooting) — Diagnostics and fixes for install and setup issues - [GitHub Issues](https://github.com/foomakers/pair/issues) — Report bugs and feature requests ## Support Scope @@ -30,7 +30,7 @@ We cannot help with: ## Before Reporting Issues -1. **Check the FAQ first**: The [Installation FAQ](/docs/support/faq) covers 80%+ of issues +1. **Check the Troubleshooting page first**: [Troubleshooting](/docs/support/troubleshooting) covers 80%+ of issues 2. **Search existing issues**: [GitHub Issues](https://github.com/foomakers/pair/issues) 3. **Test with clean environment**: Isolate the issue from other tools/configs @@ -38,7 +38,7 @@ We cannot help with: ### Step 1: Self-Service -- Review the [Installation FAQ](/docs/support/faq) +- Review the [Troubleshooting](/docs/support/troubleshooting) page - Search [existing GitHub issues](https://github.com/foomakers/pair/issues) ### Step 2: Community Support diff --git a/apps/website/content/docs/support/meta.json b/apps/website/content/docs/support/meta.json index 927c8d1d..d73c7753 100644 --- a/apps/website/content/docs/support/meta.json +++ b/apps/website/content/docs/support/meta.json @@ -1,4 +1,4 @@ { "title": "Support", - "pages": ["general-faq", "faq"] + "pages": ["general-faq", "troubleshooting"] } diff --git a/apps/website/content/docs/support/faq.mdx b/apps/website/content/docs/support/troubleshooting.mdx similarity index 65% rename from apps/website/content/docs/support/faq.mdx rename to apps/website/content/docs/support/troubleshooting.mdx index d9c6c27d..389b32ec 100644 --- a/apps/website/content/docs/support/faq.mdx +++ b/apps/website/content/docs/support/troubleshooting.mdx @@ -1,9 +1,9 @@ --- -title: Installation FAQ -description: Common installation and setup issues with step-by-step solutions for pair-cli. +title: Troubleshooting +description: One page for install and setup problems — quick diagnostics, common issues, and step-by-step fixes for pair-cli. --- -Common installation and setup issues with step-by-step solutions for pair-cli. +Install or setup problem? This page collects every known issue and its fix — from permission errors to corrupted installs. ## Quick Diagnostics @@ -20,28 +20,50 @@ which pair-cli # Should show installation path npm list -g @foomakers/pair-cli # Global npm installation check ``` +## Quick Fixes + +| Issue | Quick Fix | +| ----------------- | ------------------------------------ | +| Permission denied | Use nvm, or fix the npm prefix | +| Command not found | Check PATH, or use `npx pair-cli` | +| Node version | `nvm use 18` | +| Network blocked | Use manual install | +| Files not created | Check directory permissions | + ## Permission Issues ### Problem: `EACCES` or permission denied errors during installation -**On macOS/Linux:** +**Recommended fix — use a Node version manager (nvm).** It installs Node in your home directory, so global installs never need elevated permissions (and it fixes Node-version problems at the same time): + +```bash +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash +source ~/.bashrc +nvm install 18 +nvm use 18 +npm install -g @foomakers/pair-cli +``` + +**Alternatives** if you can't use nvm: ```bash -# Option 1: Use npm with correct permissions (recommended) +# Option 2: Point npm's global prefix at a user-writable directory npm config set prefix ~/.npm-global echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.profile source ~/.profile npm install -g @foomakers/pair-cli -# Option 2: Use pnpm (avoids permission issues) +# Option 3: Use pnpm (avoids permission issues) npm install -g pnpm 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 @foomakers/pair-cli +# Option 4: Install locally in the project +pnpm add -D @foomakers/pair-cli +# Use with: pnpm dlx pair-cli ``` +Avoid `sudo npm install -g` — it hides the underlying problem and creates root-owned files that break later installs. + **On Windows:** ```cmd @@ -51,14 +73,14 @@ npm install -g @foomakers/pair-cli ### Problem: Can't write to `/usr/local/lib/node_modules` +Same root cause: npm's global prefix points at a system directory. Use nvm (recommended, above) or move the prefix: + ```bash -# Change npm's default directory mkdir ~/.npm-global npm config set prefix '~/.npm-global' echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.profile source ~/.profile -# Now install npm install -g @foomakers/pair-cli ``` @@ -163,6 +185,85 @@ npm install -g pnpm pnpm add -g @foomakers/pair-cli ``` +## Command Not Found & PATH Issues + +### Problem: `pair-cli` command not found after installation + +**Verify installation location:** + +```bash +# Check if installed +npm list -g @foomakers/pair-cli +pnpm list -g @foomakers/pair-cli + +# Check PATH +echo $PATH +which pair-cli +``` + +**Fix PATH issues:** + +```bash +# Add npm global bin to PATH +npm config get prefix # Note the output path +echo 'export PATH="$(npm config get prefix)/bin:$PATH"' >> ~/.profile +source ~/.profile + +# If using nvm, ensure the right Node version is active +nvm current +nvm use 18 + +# Restart terminal or source profile +source ~/.bashrc # or ~/.zshrc + +# For local installs, use npx +npx pair-cli --version +``` + +### Problem: Command exists but fails to run + +**Clean reinstall steps:** + +```bash +# 1. Remove completely +npm uninstall -g @foomakers/pair-cli +# or +pnpm remove -g @foomakers/pair-cli + +# 2. Clear caches +npm cache clean --force +# or +pnpm store prune + +# 3. Clear global node_modules (careful!) +rm -rf ~/.npm/_global/node_modules/@pair +# or similar path for your system + +# 4. Reinstall fresh +npm install -g @foomakers/pair-cli +``` + +### Problem: Version conflicts or "command not found" after update + +**Deterministic cleanup:** + +```bash +# 1. Find all pair-cli installations +find /usr/local -name "*pair-cli*" 2>/dev/null +find ~/.npm -name "*pair-cli*" 2>/dev/null +find ~/.pnpm -name "*pair-cli*" 2>/dev/null + +# 2. Remove all found instances +# (Review the list first, then remove manually) + +# 3. Clean install +npm install -g @foomakers/pair-cli + +# 4. Verify +pair-cli --version +which pair-cli +``` + ## Native Build Failures ### Problem: `node-gyp` errors or native module build failures @@ -183,7 +284,7 @@ npm install -g @foomakers/pair-cli ```bash # Install build tools sudo apt update -sudo apt install build-essential python3-distutils +sudo apt install build-essential python3-dev python3-distutils # Clear and reinstall npm uninstall -g @foomakers/pair-cli @@ -236,6 +337,8 @@ echo 'export PATH=$HOME/pair-cli-manual-vX.Y.Z/bin:$PATH' >> ~/.profile source ~/.profile ``` +On Windows, run `pair-cli.cmd` from the extracted archive instead of `pair-cli` (use Git Bash or WSL if you hit line-ending or path issues). + ### Problem: Corporate proxy blocking installs ```bash @@ -251,78 +354,62 @@ npm config set proxy http://username:password@proxy.company.com:8080 npm install -g @foomakers/pair-cli ``` -## Corrupted Installs +## Runtime Issues -### Problem: `pair-cli` command not found after installation +### Problem: `pair-cli install` fails with "permission denied" -**Verify installation location:** +**Solution:** Ensure write access to the target directory. ```bash -# Check if installed -npm list -g @foomakers/pair-cli -pnpm list -g @foomakers/pair-cli +# Check permissions +ls -la . -# Check PATH -echo $PATH -which pair-cli -``` - -**Fix PATH issues:** +# Fix permissions if needed +chmod 755 . -```bash -# Add npm global bin to PATH -npm config get prefix # Note the output path -echo 'export PATH="$(npm config get prefix)/bin:$PATH"' >> ~/.profile -source ~/.profile +# Or run in a directory you own +mkdir ~/my-project +cd ~/my-project +pair-cli install ``` -### Problem: Command exists but fails to run +### Problem: `pair-cli install` runs but no files are created -**Clean reinstall steps:** +**Solutions:** ```bash -# 1. Remove completely -npm uninstall -g @foomakers/pair-cli -# or -pnpm remove -g @foomakers/pair-cli +# Check current directory +pwd -# 2. Clear caches -npm cache clean --force -# or -pnpm store prune +# Ensure you have write permissions +touch test.txt && rm test.txt -# 3. Clear global node_modules (careful!) -rm -rf ~/.npm/_global/node_modules/@pair -# or similar path for your system +# Try with debug-level logging (if available) +pair-cli install --log-level debug -# 4. Reinstall fresh -npm install -g @foomakers/pair-cli +# Check if assets are available +pair-cli install --list-targets ``` -### Problem: Version conflicts or "command not found" after update +### Problem: Installed assets seem outdated -**Deterministic cleanup:** +**Solution:** Update to the latest version. ```bash -# 1. Find all pair-cli installations -find /usr/local -name "*pair-cli*" 2>/dev/null -find ~/.npm -name "*pair-cli*" 2>/dev/null -find ~/.pnpm -name "*pair-cli*" 2>/dev/null +# Update all assets +pair-cli update -# 2. Remove all found instances -# (Review the list first, then remove manually) - -# 3. Clean install -npm install -g @foomakers/pair-cli - -# 4. Verify -pair-cli --version -which pair-cli +# Or reinstall +pair-cli install --force # if available ``` -## Diagnostic Commands +## Getting Additional Help + +If these solutions don't resolve your issue: -When reporting issues, include output from these commands: +1. **Check existing issues**: [GitHub Issues](https://github.com/foomakers/pair/issues) +2. **Ask the community**: [GitHub Discussions](https://github.com/foomakers/pair/discussions) +3. **Open a new issue**: Include output from these diagnostic commands: ```bash # System information @@ -342,20 +429,13 @@ npm config get prefix # npm global prefix npm config list # npm configuration ``` -## Getting Additional Help +**Include in your report**: operating system and version, Node.js and npm/pnpm versions, the complete error message, and the steps you've already tried. -If these solutions don't resolve your issue: +**Do not include**: personal paths with usernames, API keys or tokens, corporate proxy credentials. -1. **Check existing issues**: [GitHub Issues](https://github.com/foomakers/pair/issues) -2. **Open a new issue**: Include diagnostic output from commands above -3. **Include in your report**: - - Operating system and version - - Node.js and npm/pnpm versions - - Complete error message - - Steps you've already tried - -**Do not include** in reports: - -- Personal paths with usernames -- API keys or tokens -- Corporate proxy credentials +## Prevention + +- Always use Node 18+ (managed with nvm) and pnpm +- Install globally with nvm-managed Node, or locally with `pnpm dlx` +- Test in a clean directory first +- Keep pair-cli updated: `npm update -g @foomakers/pair-cli` diff --git a/apps/website/content/docs/tutorials/enterprise-adoption.mdx b/apps/website/content/docs/tutorials/enterprise-adoption.mdx index 9d1c2ab9..71cd5c01 100644 --- a/apps/website/content/docs/tutorials/enterprise-adoption.mdx +++ b/apps/website/content/docs/tutorials/enterprise-adoption.mdx @@ -299,4 +299,4 @@ Each team gets the same org foundation with their own customization layer. The A - **[Publish a Knowledge Base](/docs/customization/organization)** — Detailed reference for all packaging and distribution options - **[KB Structure Reference](/docs/reference/kb-structure)** — Full directory layout and file purposes - **[Contributing](/docs/contributing)** — Contribute improvements back to the pair project -- **[Guides](/docs/guides)** — Goal-oriented guides for specific tasks +- **[Support](/docs/support)** — FAQ and troubleshooting when something doesn't work diff --git a/apps/website/content/docs/tutorials/first-project.mdx b/apps/website/content/docs/tutorials/first-project.mdx index 7d215c30..c68aa812 100644 --- a/apps/website/content/docs/tutorials/first-project.mdx +++ b/apps/website/content/docs/tutorials/first-project.mdx @@ -255,4 +255,4 @@ The review skill checks adoption compliance, quality gates, Definition of Done, - **[Concepts](/docs/concepts)** — Understand how the Knowledge Base, Skills, and Adoption Files work together - **[Customization](/docs/customization)** — Adjust guidelines and quality gates for your specific needs - **[Team Setup tutorial](/docs/tutorials/team-setup)** — Ready to share pair with your team? Follow the team configuration tutorial -- **[Guides](/docs/guides)** — Goal-oriented guides for specific tasks +- **[Support](/docs/support)** — FAQ and troubleshooting when something doesn't work diff --git a/apps/website/content/docs/tutorials/index.mdx b/apps/website/content/docs/tutorials/index.mdx index 5dd72860..978560d3 100644 --- a/apps/website/content/docs/tutorials/index.mdx +++ b/apps/website/content/docs/tutorials/index.mdx @@ -33,6 +33,6 @@ Different from [Getting Started](/docs/getting-started) quickstarts (5–10 minu |---------|---------|--------| | [Getting Started](/docs/getting-started) | Quick setup — get running fast | 5–10 min | | **Tutorials** (you are here) | Learn by doing — full end-to-end workflow | 30–60 min | -| [Guides](/docs/guides) | Solve a specific problem — goal-oriented | 10–20 min | +| [Customization](/docs/customization) | Adapt pair to your context — goal-oriented | 10–20 min | | [Concepts](/docs/concepts) | Understand how things work — explanation | 5–15 min | | [Reference](/docs/reference) | Look up details — complete specifications | As needed | diff --git a/apps/website/content/docs/tutorials/managing-ai-artifacts.mdx b/apps/website/content/docs/tutorials/managing-ai-artifacts.mdx index 41002385..c25c0ae3 100644 --- a/apps/website/content/docs/tutorials/managing-ai-artifacts.mdx +++ b/apps/website/content/docs/tutorials/managing-ai-artifacts.mdx @@ -262,7 +262,7 @@ pair-cli update --source https://github.com/acme/ai-artifacts.git#v1.1.0 --confi The new locations are populated automatically. Existing assistants' skill directories remain untouched. - You can also host the package as a ZIP on any HTTP server, network share, or artifact registry. See [Install from URL or Path](/docs/guides/install-from-url). + You can also host the package as a ZIP on any HTTP server, network share, or artifact registry. See [Install from URL or Path](/docs/customization/install-from-url). ## What you've learned @@ -277,6 +277,6 @@ The new locations are populated automatically. Existing assistants' skill direct ## Next steps - [Team Setup](/docs/tutorials/team-setup) — Full team onboarding with pair -- [Install from URL or Path](/docs/guides/install-from-url) — Alternative distribution methods +- [Install from URL or Path](/docs/customization/install-from-url) — Alternative distribution methods - [Configuration Reference](/docs/reference/configuration) — Full `config.json` reference - [Skills Concept](/docs/concepts/skills) — How skills work diff --git a/apps/website/content/docs/tutorials/release-testing.mdx b/apps/website/content/docs/tutorials/release-testing.mdx index cd467ce3..c27a8a39 100644 --- a/apps/website/content/docs/tutorials/release-testing.mdx +++ b/apps/website/content/docs/tutorials/release-testing.mdx @@ -201,4 +201,4 @@ The `/pair-process-review` skill can optionally invoke `/pair-capability-execute - Read the [Skills Catalog](/docs/reference/skills-catalog) for the full list of pair skills - Check [Release Process](/docs/contributing/release-process) for how releases are automated -- Explore [CLI Workflows](/docs/guides/cli-workflows) for other CLI commands +- Explore [CLI Workflows](/docs/reference/cli/workflows) for other CLI commands diff --git a/apps/website/content/docs/tutorials/team-setup.mdx b/apps/website/content/docs/tutorials/team-setup.mdx index f32ae683..f32505f6 100644 --- a/apps/website/content/docs/tutorials/team-setup.mdx +++ b/apps/website/content/docs/tutorials/team-setup.mdx @@ -212,4 +212,4 @@ If everything works end-to-end, your team setup is complete. - **[Customize for Your Team](/docs/customization/team)** — Deep dive into all customization options - **[Publish a Knowledge Base](/docs/customization/organization)** — Package your team's KB for distribution - **[Enterprise Adoption tutorial](/docs/tutorials/enterprise-adoption)** — Rolling out pair across an organization -- **[Guides](/docs/guides)** — Goal-oriented guides for specific tasks +- **[Support](/docs/support)** — FAQ and troubleshooting when something doesn't work diff --git a/apps/website/e2e/docs.e2e.test.ts b/apps/website/e2e/docs.e2e.test.ts index 2e399555..a1cbd84b 100644 --- a/apps/website/e2e/docs.e2e.test.ts +++ b/apps/website/e2e/docs.e2e.test.ts @@ -59,7 +59,7 @@ test('solo setup journey: quickstart → solo setup with content verification', // Solo setup page renders with expected content const main = page.locator('main') - await expect(page.locator('main h1')).toContainText('Solo Setup') + await expect(page.locator('main h1')).toContainText('Quickstart: Solo') await expect(main).toContainText('Solo Workflow') await expect(main).toContainText('/pair-next') await expect(main).toContainText('pair-cli install --list-targets') @@ -83,7 +83,7 @@ test('team journey: overview → team setup with content verification', async ({ await page.goto('/docs/getting-started') // Navigate to Team Setup via sidebar - await page.locator('a', { hasText: 'Team Setup' }).first().click() + await page.locator('a', { hasText: 'Quickstart: Team' }).first().click() await expect(page).toHaveURL('/docs/getting-started/quickstart-team') // AC-4: shared KB, adoption files, bridge pattern @@ -103,7 +103,7 @@ test('org journey: overview → org setup with content verification', async ({ p await page.goto('/docs/getting-started') // Navigate to Org Setup via sidebar - await page.locator('a', { hasText: 'Organization Setup' }).first().click() + await page.locator('a', { hasText: 'Quickstart: Organization' }).first().click() await expect(page).toHaveURL('/docs/getting-started/quickstart-org') // AC-5: KB packaging, distribution, compliance @@ -160,9 +160,9 @@ test('smoke: all docs pages return 200 with correct titles', async ({ page }) => const pages = [ { url: '/docs/getting-started', title: 'What is pair?' }, { url: '/docs/getting-started/quickstart', title: 'Quickstart' }, - { url: '/docs/getting-started/quickstart-solo', title: 'Solo Setup' }, - { url: '/docs/getting-started/quickstart-team', title: 'Team Setup' }, - { url: '/docs/getting-started/quickstart-org', title: 'Organization Setup' }, + { url: '/docs/getting-started/quickstart-solo', title: 'Quickstart: Solo' }, + { url: '/docs/getting-started/quickstart-team', title: 'Quickstart: Team' }, + { url: '/docs/getting-started/quickstart-org', title: 'Quickstart: Organization' }, { url: '/docs/concepts/ai-assisted-sdlc', title: 'AI-Assisted SDLC' }, { url: '/docs/concepts/knowledge-base', title: 'Knowledge Base' }, { url: '/docs/concepts/skills', title: 'Skills' }, @@ -181,23 +181,52 @@ test('smoke: all docs pages return 200 with correct titles', async ({ page }) => // E2E: Docs — Guides, Reference, and Support sections (#124) // ============================================================ -test('guides section: navigate and verify content', async ({ page }) => { - await page.goto('/docs/guides/cli-workflows') +test('moved guides pages: render at their new locations', async ({ page }) => { + await page.goto('/docs/reference/cli/workflows') const main = page.locator('main') - // Guides page renders with expected content + // CLI Workflows now lives under Reference > CLI await expect(page.locator('main h1')).toContainText('CLI Workflows') await expect(main).toContainText('Common Workflows') await expect(main).toContainText('pair-cli install') - // Sidebar shows Guides section - await expect(page.locator('body')).toContainText('Guides') + await page.goto('/docs/customization/install-from-url') + await expect(page.locator('main h1')).toContainText('Install from URL') - // Navigate to another guide via sidebar - await page.locator('a', { hasText: 'Troubleshooting' }).first().click() - await expect(page).toHaveURL('/docs/guides/troubleshooting') + await page.goto('/docs/getting-started/checklist') + await expect(page.locator('main h1')).toContainText('Adopter Checklist') + + await page.goto('/docs/reference/cli/update-link') + await expect(page.locator('main h1')).toContainText('Link Update') +}) + +test('redirects: old FAQ/troubleshooting URLs land on support/troubleshooting', async ({ + page, +}) => { + await page.goto('/docs/support/faq') + await expect(page).toHaveURL('/docs/support/troubleshooting') + await expect(page.locator('main h1')).toContainText('Troubleshooting') + + await page.goto('/docs/guides/troubleshooting') + await expect(page).toHaveURL('/docs/support/troubleshooting') await expect(page.locator('main h1')).toContainText('Troubleshooting') - await expect(main).toContainText('Installation Issues') +}) + +test('redirects: dissolved guides URLs land on their new locations', async ({ page }) => { + const redirects = [ + { from: '/docs/guides/adopter-checklist', to: '/docs/getting-started/checklist' }, + { from: '/docs/guides/cli-workflows', to: '/docs/reference/cli/workflows' }, + { from: '/docs/guides/customize-kb', to: '/docs/customization/team' }, + { from: '/docs/guides/install-from-url', to: '/docs/customization/install-from-url' }, + { from: '/docs/guides/packaging', to: '/docs/customization/organization' }, + { from: '/docs/guides/update-link', to: '/docs/reference/cli/update-link' }, + { from: '/docs/guides', to: '/docs' }, + ] + for (const { from, to } of redirects) { + await page.goto(from) + await expect(page, `${from} should redirect to ${to}`).toHaveURL(to) + await expect(page.locator('main h1')).toBeVisible() + } }) test('reference section: navigate CLI, specs, and top-level pages', async ({ page }) => { @@ -263,22 +292,20 @@ test('support section: navigate and verify content', async ({ page }) => { await expect(main).toContainText('Project Management') await expect(main).toContainText('Customization') - // Navigate to Installation FAQ via sidebar - await page.locator('a', { hasText: 'Installation FAQ' }).first().click() - await expect(page).toHaveURL('/docs/support/faq') - await expect(page.locator('main h1')).toContainText('Installation FAQ') + // Navigate to Troubleshooting via sidebar (merged Installation FAQ + guides troubleshooting) + await page.locator('a', { hasText: 'Troubleshooting' }).first().click() + await expect(page).toHaveURL('/docs/support/troubleshooting') + await expect(page.locator('main h1')).toContainText('Troubleshooting') await expect(main).toContainText('Permission Issues') await expect(main).toContainText('Node Version Issues') }) test('smoke: all guides/reference/support pages return 200', async ({ page }) => { const pages = [ - { url: '/docs/guides/cli-workflows', title: 'CLI Workflows' }, - { url: '/docs/guides/install-from-url', title: 'Install from URL' }, - { url: '/docs/guides/customize-kb', title: 'Customize the Knowledge Base' }, - { url: '/docs/guides/adopter-checklist', title: 'Adopter Checklist' }, - { url: '/docs/guides/troubleshooting', title: 'Troubleshooting' }, - { url: '/docs/guides/update-link', title: 'Link Update' }, + { url: '/docs/reference/cli/workflows', title: 'CLI Workflows' }, + { url: '/docs/customization/install-from-url', title: 'Install from URL' }, + { url: '/docs/getting-started/checklist', title: 'Adopter Checklist' }, + { url: '/docs/reference/cli/update-link', title: 'Link Update' }, { url: '/docs/reference/cli/commands', title: 'CLI Commands' }, { url: '/docs/reference/cli/examples', title: 'CLI Help Examples' }, { url: '/docs/reference/specs/cli-contracts', title: 'CLI Contracts' }, @@ -290,7 +317,7 @@ test('smoke: all guides/reference/support pages return 200', async ({ page }) => { url: '/docs/reference/configuration', title: 'Configuration' }, { url: '/docs/support', title: 'Support' }, { url: '/docs/support/general-faq', title: 'FAQ' }, - { url: '/docs/support/faq', title: 'Installation FAQ' }, + { url: '/docs/support/troubleshooting', title: 'Troubleshooting' }, ] for (const { url, title } of pages) { const response = await page.goto(url) @@ -302,7 +329,7 @@ test('smoke: all guides/reference/support pages return 200', async ({ page }) => test('no broken .md links in guides/reference/support sections', async ({ page }) => { const sections = [ - '/docs/guides/cli-workflows', + '/docs/reference/cli/workflows', '/docs/reference/cli/commands', '/docs/reference/skills-catalog', '/docs/support', @@ -332,8 +359,8 @@ test('developer journey: navigate through section pages', async ({ page }) => { await expect(main).toContainText('The Four Levels') await expect(main).toContainText('Entry Points') - // Sidebar shows Developer Journey section - await expect(page.locator('body')).toContainText('Developer Journey') + // Sidebar shows Process Lifecycle section (label unified with index title) + await expect(page.locator('body')).toContainText('Process Lifecycle') // Navigate to Induction await page @@ -785,6 +812,37 @@ test('smoke: all tutorials pages return 200 with correct titles', async ({ page } }) +// ============================================================ +// E2E: Docs — Hub + section indexes (#312) +// ============================================================ + +test('smoke: docs hub returns 200 with journey links and top tasks', async ({ page }) => { + const response = await page.goto('/docs') + expect(response?.status(), '/docs should return 200').toBe(200) + await expect(page).toHaveTitle(/Welcome/) + await expect(page.locator('main h1')).toContainText('Welcome') + + const main = page.locator('main') + await expect(main).toContainText('Pick your journey') + await expect(main.locator('a[href="/docs/getting-started"]').first()).toBeVisible() + await expect(main.locator('a[href="/docs/tutorials"]').first()).toBeVisible() + await expect(main.locator('a[href="/docs/reference"]').first()).toBeVisible() + await expect(main).toContainText('Top tasks') +}) + +test('smoke: concepts and reference section indexes return 200 with headings', async ({ page }) => { + const pages = [ + { url: '/docs/concepts', title: 'Concepts' }, + { url: '/docs/reference', title: 'Reference' }, + ] + for (const { url, title } of pages) { + const response = await page.goto(url) + expect(response?.status(), `${url} should return 200`).toBe(200) + await expect(page.locator('main h1')).toContainText(title) + await expect(page).toHaveTitle(new RegExp(title)) + } +}) + // ============================================================ // E2E: Search — Orama client-side search // ============================================================ @@ -880,12 +938,10 @@ test('no circular prev/next footer links on any docs page', async ({ page }) => '/docs/pm-tools/filesystem', '/docs/pm-tools/github-projects', '/docs/pm-tools/linear', - '/docs/guides/cli-workflows', - '/docs/guides/install-from-url', - '/docs/guides/customize-kb', - '/docs/guides/adopter-checklist', - '/docs/guides/troubleshooting', - '/docs/guides/update-link', + '/docs/getting-started/checklist', + '/docs/customization/install-from-url', + '/docs/reference/cli/workflows', + '/docs/reference/cli/update-link', '/docs/reference/cli/commands', '/docs/reference/cli/examples', '/docs/reference/specs/cli-contracts', @@ -897,7 +953,7 @@ test('no circular prev/next footer links on any docs page', async ({ page }) => '/docs/reference/configuration', '/docs/support', '/docs/support/general-faq', - '/docs/support/faq', + '/docs/support/troubleshooting', '/docs/tutorials', '/docs/tutorials/first-project', '/docs/tutorials/existing-project', diff --git a/apps/website/e2e/landing.e2e.test.ts b/apps/website/e2e/landing.e2e.test.ts index 05aefa54..66fe236b 100644 --- a/apps/website/e2e/landing.e2e.test.ts +++ b/apps/website/e2e/landing.e2e.test.ts @@ -48,8 +48,10 @@ test('docs sidebar navigates to Concepts', async ({ page }) => { .locator('section[aria-label="Call to action"] a') .filter({ hasText: 'Read the docs' }) .click() - await page.locator('button', { hasText: 'Concepts' }).click() - await page.locator('a', { hasText: 'AI-Assisted SDLC' }).click() + // Concepts now has an index page: the sidebar folder renders as a link, not a button + await page.locator('a', { hasText: 'Concepts' }).first().click() + await expect(page).toHaveURL('/docs/concepts') + await page.locator('a', { hasText: 'AI-Assisted SDLC' }).first().click() await expect(page).toHaveURL('/docs/concepts/ai-assisted-sdlc') await expect(page.locator('h1')).toContainText('AI-Assisted SDLC') }) @@ -70,7 +72,7 @@ test('full flow: landing → quickstart → concept page via Next link', async ( .click() await expect(page).toHaveURL('/docs/getting-started/quickstart') await expect(page.locator('h1')).toContainText('Quickstart') - // Use prev/next to go to Solo Setup - await page.locator('a', { hasText: 'Solo Setup' }).last().click() + // Use prev/next to go to Quickstart: Solo + await page.locator('a', { hasText: 'Quickstart: Solo' }).last().click() await expect(page).toHaveURL('/docs/getting-started/quickstart-solo') }) diff --git a/apps/website/lib/docs-staleness-check.test.ts b/apps/website/lib/docs-staleness-check.test.ts new file mode 100644 index 00000000..977ecbf9 --- /dev/null +++ b/apps/website/lib/docs-staleness-check.test.ts @@ -0,0 +1,177 @@ +import { describe, it, expect } from 'vitest' +import { resolve } from 'node:path' +import { + findSkillCountMismatches, + findGuideCountMismatches, + findDeadLinks, + checkCatalogSync, + checkCommandAnchors, + checkTutorialCommands, + countHowToGuides, + buildValidRoutes, + runAllChecks, +} from './docs-staleness-check' + +// White-box unit tests for the docs-staleness gate LOGIC. Exported functions are +// tested directly — no spawning of any CLI/script. The thin `tsx` CLI wrapper is +// out of scope here (its logic is these functions); parity with the real docs +// tree is asserted in-process via runAllChecks() below. + +const REPO_ROOT = resolve(__dirname, '../../..') + +describe('findSkillCountMismatches', () => { + it('flags a wrong bare "N skills"', () => { + expect(findSkillCountMismatches('has 5 skills', 'a.mdx', 35)).toHaveLength(1) + }) + + it('passes a matching "N skills"', () => { + expect(findSkillCountMismatches('has 35 skills', 'a.mdx', 35)).toHaveLength(0) + }) + + it('flags a wrong "N composable skills" (adjective between number and skills)', () => { + const errs = findSkillCountMismatches('7 composable skills', 'a.mdx', 1) + expect(errs).toHaveLength(1) + expect(errs[0]).toContain('7 composable skills') + }) + + it('flags a wrong "N+ skills" (trailing plus)', () => { + expect(findSkillCountMismatches('30+ skills', 'a.mdx', 35)).toHaveLength(1) + }) + + it('flags a wrong "N agent skills"', () => { + const errs = findSkillCountMismatches('exposes 7 agent skills', 'a.mdx', 1) + expect(errs).toHaveLength(1) + expect(errs[0]).toContain('7 agent skills') + }) + + it('passes matching "N composable skills" and "N+ skills"', () => { + expect(findSkillCountMismatches('1 composable skills and 1+ skills', 'a.mdx', 1)).toHaveLength( + 0, + ) + }) + + it('ignores subset counts ("N process skills")', () => { + expect(findSkillCountMismatches('9 process skills', 'a.mdx', 35)).toHaveLength(0) + }) +}) + +describe('findGuideCountMismatches', () => { + it('flags a wrong "N how-to guides"', () => { + expect(findGuideCountMismatches('11 how-to guides', 'a.mdx', 9)).toHaveLength(1) + }) + + it('passes a matching "N how-to guides"', () => { + expect(findGuideCountMismatches('9 how-to guides', 'a.mdx', 9)).toHaveLength(0) + }) + + it('flags wrong counts in adjective phrasings ("N sequential/step-by-step guides")', () => { + const errs = findGuideCountMismatches( + '11 sequential guides and 11 step-by-step guides', + 'a.mdx', + 9, + ) + expect(errs).toHaveLength(2) + expect(errs[0]).toContain('11 sequential guides') + expect(errs[1]).toContain('11 step-by-step guides') + }) + + it('flags "N step-by-step process guides" and "N process guides"', () => { + expect(findGuideCountMismatches('11 step-by-step process guides', 'a.mdx', 9)).toHaveLength(1) + expect(findGuideCountMismatches('11 process guides', 'a.mdx', 9)).toHaveLength(1) + }) + + it('does NOT false-positive on bare "N guides" prose (no how-to qualifier)', () => { + expect( + findGuideCountMismatches('5 guides at the museum and 3 tour guides', 'a.mdx', 9), + ).toHaveLength(0) + }) +}) + +describe('countHowToGuides', () => { + it('returns null when the how-to dir is missing (drives the loud gate failure)', () => { + expect(countHowToGuides(resolve(REPO_ROOT, 'does/not/exist'))).toBeNull() + }) + + it('counts NN-how-to-*.md files in the real dataset (ignoring README)', () => { + const n = countHowToGuides( + resolve(REPO_ROOT, 'packages/knowledge-hub/dataset/.pair/knowledge/how-to'), + ) + expect(n).toBe(9) + }) +}) + +describe('findDeadLinks', () => { + const routes = new Set(['/docs', '/docs/reference/skills-catalog', '/docs/tutorials']) + + it('flags a dead markdown link', () => { + expect(findDeadLinks('see [x](/docs/nope)', 'a.mdx', routes)).toHaveLength(1) + }) + + it('flags a dead JSX href="/docs/..." card link', () => { + const errs = findDeadLinks('x', 'a.mdx', routes) + expect(errs).toHaveLength(1) + expect(errs[0]).toContain('/docs/does-not-exist') + }) + + it('passes a valid JSX href and a valid markdown link (incl. anchors)', () => { + const ok = 'x and [t](/docs/tutorials#top)' + expect(findDeadLinks(ok, 'a.mdx', routes)).toHaveLength(0) + }) +}) + +describe('checkCatalogSync', () => { + it('flags a skill dir missing from the catalog', () => { + expect(checkCatalogSync(['implement'], 'no rows here')).toHaveLength(1) + }) + + it('flags a catalog row with no matching dir', () => { + expect(checkCatalogSync([], '| **ghost** | row |')).toHaveLength(1) + }) + + it('passes when both directions agree', () => { + expect(checkCatalogSync(['implement'], '| **implement** | row |')).toHaveLength(0) + }) +}) + +describe('checkCommandAnchors', () => { + it('flags a command dir with no anchor', () => { + expect(checkCommandAnchors(['install'], 'no anchors')).toHaveLength(1) + }) + + it('passes when the anchor exists', () => { + expect(checkCommandAnchors(['install'], '## install (#install)')).toHaveLength(0) + }) +}) + +describe('checkTutorialCommands', () => { + it('flags an unknown pair-cli command reference', () => { + expect(checkTutorialCommands(['run pair-cli bogus'], ['install'])).toHaveLength(1) + }) + + it('ignores builtins and prose words after pair-cli', () => { + expect( + checkTutorialCommands(['pair-cli --version', 'pair-cli is installed'], ['install']), + ).toHaveLength(0) + }) +}) + +describe('buildValidRoutes', () => { + it('maps index.mdx to /docs and folder index to the folder route', () => { + const docsDir = '/x/docs' + const routes = buildValidRoutes( + ['/x/docs/index.mdx', '/x/docs/reference/index.mdx', '/x/docs/tutorials/first.mdx'], + docsDir, + ) + expect(routes.has('/docs')).toBe(true) + expect(routes.has('/docs/reference')).toBe(true) + expect(routes.has('/docs/tutorials/first')).toBe(true) + }) +}) + +describe('runAllChecks (in-process, real docs tree)', () => { + it('reports zero drift and 35 skills against the actual repo', () => { + const { errors, skillCount } = runAllChecks(REPO_ROOT) + expect(errors, errors.join('\n')).toHaveLength(0) + expect(skillCount).toBe(35) + }) +}) diff --git a/apps/website/lib/docs-staleness-check.ts b/apps/website/lib/docs-staleness-check.ts new file mode 100644 index 00000000..c2889c85 --- /dev/null +++ b/apps/website/lib/docs-staleness-check.ts @@ -0,0 +1,287 @@ +/** + * Docs Staleness Check — verifies the published docs site matches source-of-truth + * code artifacts (skills corpus, CLI commands, how-to guides) and has no dead + * internal links. + * + * This is the website-docs integrity gate. The LOGIC lives here as individually + * exported, unit-tested functions (see docs-staleness-check.test.ts, white-box). + * The `main()` block is a thin CLI wrapper run via `tsx lib/docs-staleness-check.ts` + * (package script `docs:staleness`); it prints the same output and exit codes as + * before. Exit 0 = in sync, Exit 1 = drift detected. + * + * DOCS_STALENESS_ROOT overrides the repo root (used to point the checks at a + * fixture tree). Absent, the repo root is resolved from this file's location: + * apps/website/lib -> apps/website -> apps -> (up 3). + */ +import { existsSync, readFileSync, readdirSync } from 'node:fs' +import { basename, join, relative, resolve, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' + +const MODULE_DIR = dirname(fileURLToPath(import.meta.url)) + +/** Resolve the repo root, honouring the DOCS_STALENESS_ROOT override. */ +export function resolveRoot(): string { + const override = process.env['DOCS_STALENESS_ROOT'] + return override ? resolve(override) : resolve(MODULE_DIR, '../../..') +} + +// --- Regexes (exported so their intent is documented and directly testable) --- + +// Skill total-count phrasings across docs. Narrow to avoid prose false positives +// but covers "N skills", "N+ skills" (trailing plus), and an optional total-count +// adjective — "N pair/composable/agent/idempotent skills". Subset counts +// ("9 process skills") do NOT match: the adjective, when present, must be one of +// the whitelisted total-count words. +export const SKILL_COUNT_RE = /(\d+)\+?\s+(?:pair\s+|composable\s+|agent\s+|idempotent\s+)?skills/g + +// How-to guide count phrasings. Requires a how-to qualifier so arbitrary +// "N guides" prose ("5 guides at the museum") never false-positives: a match +// needs EITHER a recognized adjective (sequential/step-by-step) OR a +// how-to/process word. Bare "N guides" does not match. Covers "9 how-to guides", +// "9 process guides", "9 sequential guides", "9 step-by-step guides", +// "9 sequential how-to guides", "9 step-by-step process guides". +export const GUIDE_COUNT_RE = + /(\d+)\s+(?:(?:sequential|step-by-step)\s+(?:how-to\s+|process\s+)?|(?:how-to|process)\s+)guides/g + +// Internal /docs targets: markdown links `](/docs/...)` and JSX card +// `href="/docs/..."` attributes (Fumadocs /). +export const LINK_RE = /\]\((\/docs[^)\s]*)\)/g +export const HREF_RE = /href="(\/docs[^"]*)"/g + +// --- Filesystem helpers --- + +/** Skill names under a category dir: its subdirs, or the category itself if it's a meta skill (SKILL.md at the category root). */ +export function getSkillNames(categoryDir: string): string[] { + const entries = readdirSync(categoryDir, { withFileTypes: true }) + const subdirs = entries.filter(d => d.isDirectory()).map(d => d.name) + if (subdirs.length > 0) return subdirs + if (existsSync(join(categoryDir, 'SKILL.md'))) return [basename(categoryDir)] + return [] +} + +/** Every skill name across all category dirs under skillsDir. */ +export function collectSkills(skillsDir: string): string[] { + const categories = readdirSync(skillsDir, { withFileTypes: true }).filter(d => d.isDirectory()) + const all: string[] = [] + for (const cat of categories) all.push(...getSkillNames(join(skillsDir, cat.name))) + return all +} + +/** All .mdx files under dir, recursively. */ +export function walkMdx(dir: string): string[] { + const out: string[] = [] + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const full = join(dir, entry.name) + if (entry.isDirectory()) out.push(...walkMdx(full)) + else if (entry.name.endsWith('.mdx')) out.push(full) + } + return out +} + +/** Count of how-to guide files (NN-how-to-*.md) in a KB how-to dir. `null` if the dir is missing. */ +export function countHowToGuides(howToDir: string): number | null { + if (!existsSync(howToDir)) return null + return readdirSync(howToDir).filter(f => /^\d+-how-to-.*\.md$/.test(f)).length +} + +// --- Pure per-content checks (return error strings; no I/O) --- + +/** Check 1: every "N skills" phrasing in content matches the actual skill count. */ +export function findSkillCountMismatches(content: string, rel: string, actual: number): string[] { + const errors: string[] = [] + for (const m of content.matchAll(SKILL_COUNT_RE)) { + const n = m[1] + if (n !== undefined && parseInt(n, 10) !== actual) { + errors.push(`Skill count mismatch in ${rel}: docs say "${m[0]}", actual count is ${actual}`) + } + } + return errors +} + +/** Check 2b: every "N how-to guides" phrasing in content matches the actual guide count. */ +export function findGuideCountMismatches(content: string, rel: string, actual: number): string[] { + const errors: string[] = [] + for (const m of content.matchAll(GUIDE_COUNT_RE)) { + const n = m[1] + if (n !== undefined && parseInt(n, 10) !== actual) { + errors.push( + `How-to guide count mismatch in ${rel}: docs say "${m[0]}", actual count is ${actual}`, + ) + } + } + return errors +} + +/** Check 5: every /docs link/href in content resolves to a known route. */ +export function findDeadLinks(content: string, rel: string, validRoutes: Set): string[] { + const errors: string[] = [] + for (const re of [LINK_RE, HREF_RE]) { + for (const m of content.matchAll(re)) { + const raw = m[1] + if (raw === undefined) continue + const head = (raw.split('#')[0] ?? '').split('?')[0] ?? '' + const target = head.replace(/\/$/, '') || '/docs' + if (!validRoutes.has(target)) { + errors.push(`Dead internal link in ${rel}: ${raw} does not resolve to a docs page`) + } + } + } + return errors +} + +/** Check 2: catalog lists every skill dir, and no catalog row lacks a dir (both directions). */ +export function checkCatalogSync(allSkills: string[], catalog: string): string[] { + const errors: string[] = [] + for (const skill of allSkills) { + if (!catalog.includes(`**${skill}**`)) { + errors.push(`Skill "${skill}" exists in .skills/ but missing from skills-catalog.mdx`) + } + } + const catalogSkills = [...catalog.matchAll(/\| \*\*([a-z0-9-]+)\*\* \|/g)] + .map(m => m[1]) + .filter((s): s is string => s !== undefined) + for (const docSkill of catalogSkills) { + if (!allSkills.includes(docSkill)) { + errors.push(`Skill "${docSkill}" in skills-catalog.mdx but no matching dir in .skills/`) + } + } + return errors +} + +/** Check 3: every command dir has an anchor in commands.mdx. */ +export function checkCommandAnchors(commandDirs: string[], commandsDoc: string): string[] { + const errors: string[] = [] + for (const cmd of commandDirs) { + if (!commandsDoc.includes(`(#${cmd})`)) { + errors.push(`CLI command "${cmd}" has a dir in commands/ but missing from commands.mdx`) + } + } + return errors +} + +const CLI_BUILTINS = new Set(['--version', '--help']) +const PROSE_WORDS = new Set(['as', 'is', 'on', 'to', 'installed', 'and', 'or', 'in', 'for', 'the']) + +/** Check 4: every `pair-cli ` referenced in tutorial content maps to a command dir. */ +export function checkTutorialCommands(tutorialContents: string[], commandDirs: string[]): string[] { + const errors: string[] = [] + const referenced = new Set() + for (const content of tutorialContents) { + for (const m of content.matchAll(/pair-cli\s+([a-z][a-z0-9-]*)/g)) { + if (m[1] !== undefined) referenced.add(m[1]) + } + } + for (const cmd of referenced) { + if (CLI_BUILTINS.has(`--${cmd}`)) continue + if (PROSE_WORDS.has(cmd)) continue + if (!commandDirs.includes(cmd)) { + errors.push(`Tutorial references "pair-cli ${cmd}" but no matching command dir in commands/`) + } + } + return errors +} + +/** Build the set of valid /docs routes from the docs .mdx file list. */ +export function buildValidRoutes(docsFiles: string[], docsDir: string): Set { + const routes = new Set() + for (const file of docsFiles) { + const rel = relative(docsDir, file) + .replace(/\\/g, '/') + .replace(/\.mdx$/, '') + routes.add(rel === 'index' ? '/docs' : `/docs/${rel.replace(/\/index$/, '')}`) + } + return routes +} + +export interface RunResult { + errors: string[] + skillCount: number + commandCount: number +} + +/** Checks 3 & 4: command anchors in commands.mdx, and tutorial `pair-cli ` references. */ +export function checkCliCommands( + commandsDir: string, + commandsFile: string, + tutorialsDir: string, +): { errors: string[]; commandCount: number } { + const errors: string[] = [] + const commandDirs = readdirSync(commandsDir, { withFileTypes: true }) + .filter(d => d.isDirectory()) + .map(d => d.name) + errors.push(...checkCommandAnchors(commandDirs, readFileSync(commandsFile, 'utf-8'))) + if (existsSync(tutorialsDir)) { + const tutorialContents = readdirSync(tutorialsDir) + .filter(f => f.endsWith('.mdx')) + .map(f => readFileSync(join(tutorialsDir, f), 'utf-8')) + errors.push(...checkTutorialCommands(tutorialContents, commandDirs)) + } + return { errors, commandCount: commandDirs.length } +} + +/** Run every check against a repo root and collect all drift errors. */ +export function runAllChecks(root: string): RunResult { + const SKILLS_DIR = join(root, 'packages/knowledge-hub/dataset/.skills') + const COMMANDS_DIR = join(root, 'apps/pair-cli/src/commands') + const DOCS_DIR = join(root, 'apps/website/content/docs') + const CATALOG_FILE = join(DOCS_DIR, 'reference/skills-catalog.mdx') + const COMMANDS_FILE = join(DOCS_DIR, 'reference/cli/commands.mdx') + const HOW_TO_DIR = join(root, 'packages/knowledge-hub/dataset/.pair/knowledge/how-to') + const TUTORIALS_DIR = join(DOCS_DIR, 'tutorials') + + const errors: string[] = [] + const docsFiles = walkMdx(DOCS_DIR) + const allSkills = collectSkills(SKILLS_DIR) + const skillCount = allSkills.length + const validRoutes = buildValidRoutes(docsFiles, DOCS_DIR) + const howToCount = countHowToGuides(HOW_TO_DIR) + + // Check 2b (loud failure if the how-to dataset dir moved) + if (howToCount === null) { + errors.push(`How-to guides dir not found: ${HOW_TO_DIR} — guide-count check cannot run`) + } + + // Per-file checks — read each doc once and run all content-level checks: + // 1 (skill counts), 2b (guide counts), 5 (dead links, markdown + JSX href). + for (const file of docsFiles) { + const content = readFileSync(file, 'utf-8') + const rel = relative(DOCS_DIR, file) + errors.push(...findSkillCountMismatches(content, rel, skillCount)) + if (howToCount !== null) errors.push(...findGuideCountMismatches(content, rel, howToCount)) + errors.push(...findDeadLinks(content, rel, validRoutes)) + } + + // Check 2: catalog sync (both directions) + errors.push(...checkCatalogSync(allSkills, readFileSync(CATALOG_FILE, 'utf-8'))) + + // Checks 3 & 4: CLI command anchors + tutorial references + const cli = checkCliCommands(COMMANDS_DIR, COMMANDS_FILE, TUTORIALS_DIR) + errors.push(...cli.errors) + + // NOTE: repo-root README.md is intentionally OUT of this gate's scope — the gate + // governs the published docs site (apps/website/content/docs) only. README's own + // literal skill/guide counts are tracked and fixed by PR #325. + + return { errors, skillCount, commandCount: cli.commandCount } +} + +/** Thin CLI wrapper: print the report and set the exit code. */ +export function main(): void { + const { errors, skillCount, commandCount } = runAllChecks(resolveRoot()) + console.log('Docs Staleness Check') + console.log('====================') + if (errors.length === 0) { + console.log(`PASS — ${skillCount} skills, ${commandCount} commands in sync`) + process.exit(0) + } + console.log(`FAIL — ${errors.length} issue${errors.length > 1 ? 's' : ''}\n`) + for (const e of errors) console.log(` • ${e}`) + console.log() + process.exit(1) +} + +// Main-guard: run only when invoked directly (tsx lib/docs-staleness-check.ts), +// not when imported by the unit tests. ESM equivalent of `require.main === module`. +if (import.meta.url === `file://${process.argv[1]}`) { + main() +} diff --git a/apps/website/next.config.mjs b/apps/website/next.config.mjs index 67e0062b..50c21117 100644 --- a/apps/website/next.config.mjs +++ b/apps/website/next.config.mjs @@ -5,6 +5,56 @@ const withMDX = createMDX() /** @type {import('next').NextConfig} */ const nextConfig = { transpilePackages: ['@pair/brand'], + async redirects() { + // Permanent redirects for docs IA restructuring (#312) + return [ + { + source: '/docs/support/faq', + destination: '/docs/support/troubleshooting', + permanent: true, + }, + { + source: '/docs/guides/troubleshooting', + destination: '/docs/support/troubleshooting', + permanent: true, + }, + { + source: '/docs/guides/adopter-checklist', + destination: '/docs/getting-started/checklist', + permanent: true, + }, + { + source: '/docs/guides/cli-workflows', + destination: '/docs/reference/cli/workflows', + permanent: true, + }, + { + source: '/docs/guides/customize-kb', + destination: '/docs/customization/team', + permanent: true, + }, + { + source: '/docs/guides/install-from-url', + destination: '/docs/customization/install-from-url', + permanent: true, + }, + { + source: '/docs/guides/packaging', + destination: '/docs/customization/organization', + permanent: true, + }, + { + source: '/docs/guides/update-link', + destination: '/docs/reference/cli/update-link', + permanent: true, + }, + { + source: '/docs/guides', + destination: '/docs', + permanent: true, + }, + ] + }, } export default withMDX(nextConfig) diff --git a/apps/website/package.json b/apps/website/package.json index fbb4e0b3..41c93c1f 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -12,7 +12,7 @@ "ct": "playwright test -c playwright-ct.config.ts --grep-invert @a11y-report", "e2e": "pnpm clean:server && playwright test -c playwright.e2e.config.ts", "a11y:report": "playwright test -c playwright-ct.config.ts --grep @a11y-report --reporter=list", - "a11y:report:html": "playwright test -c playwright-ct.config.ts --grep @a11y-report --reporter=html && echo '\n\u2192 Report: playwright-report/index.html\n Open: npx playwright show-report'", + "a11y:report:html": "playwright test -c playwright-ct.config.ts --grep @a11y-report --reporter=html && echo '\n→ Report: playwright-report/index.html\n Open: npx playwright show-report'", "test": "vitest run && pnpm ct", "test:coverage": "vitest run --coverage", "postinstall": "fumadocs-mdx", @@ -22,7 +22,8 @@ "prettier:fix": "prettier-fix", "prettier:check": "prettier-check", "mdlint:check": "markdownlint-check", - "mdlint:fix": "markdownlint-fix" + "mdlint:fix": "markdownlint-fix", + "docs:staleness": "tsx lib/docs-staleness-check.ts" }, "dependencies": { "@pair/brand": "workspace:*", @@ -54,6 +55,7 @@ "jsdom": "catalog:", "postcss": "catalog:", "tailwindcss": "catalog:", + "tsx": "catalog:", "typescript": "catalog:", "vitest": "catalog:", "vitest-axe": "catalog:" diff --git a/package.json b/package.json index b215bc1d..0709ab22 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "a11y:report": "turbo a11y:report", "a11y:report:html": "turbo a11y:report:html", "hygiene:check": "node scripts/code-hygiene-check.js", - "docs:staleness": "node scripts/docs-staleness-check.js", + "docs:staleness": "pnpm --filter @pair/website docs:staleness", "dup:check": "jscpd apps packages", "quality-gate": "turbo ts:check test lint && turbo prettier:fix mdlint:fix && ./tools/markdownlint-config/bin/markdownlint-fix.sh '*.md' && pnpm hygiene:check && pnpm docs:staleness && pnpm dup:check", "e2e": "pnpm --filter @pair/website e2e", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c9d6808..e721878a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,6 +150,9 @@ catalogs: ts-node: specifier: 10.9.2 version: 10.9.2 + tsx: + specifier: 4.23.1 + version: 4.23.1 turbo: specifier: 2.5.6 version: 2.5.6 @@ -239,7 +242,7 @@ importers: version: 24.3.0 '@vitest/coverage-v8': specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2)) ts-node: specifier: 'catalog:' version: 10.9.2(@types/node@24.3.0)(typescript@5.4.5) @@ -248,10 +251,10 @@ importers: version: 5.4.5 vite-tsconfig-paths: specifier: 'catalog:' - version: 5.1.4(typescript@5.4.5)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2)) + version: 5.1.4(typescript@5.4.5)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2)) vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2) apps/website: dependencies: @@ -266,7 +269,7 @@ importers: version: 14.7.7(@types/react@19.0.6)(next@15.5.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) fumadocs-mdx: specifier: 'catalog:' - version: 11.10.1(fumadocs-core@14.7.7(@types/react@19.0.6)(next@15.5.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(next@15.5.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2)) + version: 11.10.1(fumadocs-core@14.7.7(@types/react@19.0.6)(next@15.5.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(next@15.5.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2)) fumadocs-ui: specifier: 'catalog:' version: 14.7.7(@types/react-dom@19.0.6(@types/react@19.0.6))(@types/react@19.0.6)(fumadocs-core@14.7.7(@types/react@19.0.6)(next@15.5.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(next@15.5.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@24.3.0)(typescript@5.4.5))) @@ -303,7 +306,7 @@ importers: version: link:../../tools/ts-config '@playwright/experimental-ct-react': specifier: 'catalog:' - version: 1.58.2(@types/node@24.3.0)(jiti@1.21.7)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2))(yaml@2.8.2) + version: 1.58.2(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2))(yaml@2.8.2) '@playwright/test': specifier: 'catalog:' version: 1.58.2 @@ -321,10 +324,10 @@ importers: version: 19.0.6(@types/react@19.0.6) '@vitejs/plugin-react': specifier: 'catalog:' - version: 4.3.4(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2)) + version: 4.3.4(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2)) '@vitest/coverage-v8': specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2)) autoprefixer: specifier: 'catalog:' version: 10.4.20(postcss@8.4.49) @@ -337,15 +340,18 @@ importers: tailwindcss: specifier: 'catalog:' version: 3.4.17(ts-node@10.9.2(@types/node@24.3.0)(typescript@5.4.5)) + tsx: + specifier: 'catalog:' + version: 4.23.1 typescript: specifier: 'catalog:' version: 5.4.5 vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2) vitest-axe: specifier: 'catalog:' - version: 1.0.0-pre.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2)) + version: 1.0.0-pre.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2)) packages/brand: devDependencies: @@ -366,7 +372,7 @@ importers: version: link:../../tools/ts-config '@playwright/experimental-ct-react': specifier: 'catalog:' - version: 1.58.2(@types/node@24.3.0)(jiti@1.21.7)(vite@6.0.7(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2))(yaml@2.8.2) + version: 1.58.2(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(vite@6.0.7(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2))(yaml@2.8.2) '@testing-library/jest-dom': specifier: 'catalog:' version: 6.6.3 @@ -381,10 +387,10 @@ importers: version: 19.0.6(@types/react@19.0.6) '@vitejs/plugin-react': specifier: 'catalog:' - version: 4.3.4(vite@6.0.7(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2)) + version: 4.3.4(vite@6.0.7(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2)) '@vitest/coverage-v8': specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2)) autoprefixer: specifier: 'catalog:' version: 10.4.20(postcss@8.4.49) @@ -411,13 +417,13 @@ importers: version: 5.4.5 vite: specifier: 'catalog:' - version: 6.0.7(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2) + version: 6.0.7(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2) vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2) vitest-axe: specifier: 'catalog:' - version: 1.0.0-pre.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2)) + version: 1.0.0-pre.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2)) packages/content-ops: dependencies: @@ -463,7 +469,7 @@ importers: version: 24.3.0 '@vitest/coverage-v8': specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2)) ts-node: specifier: 'catalog:' version: 10.9.2(@types/node@24.3.0)(typescript@5.4.5) @@ -472,10 +478,10 @@ importers: version: 5.4.5 vite-tsconfig-paths: specifier: 'catalog:' - version: 5.1.4(typescript@5.4.5)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2)) + version: 5.1.4(typescript@5.4.5)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2)) vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2) packages/knowledge-hub: devDependencies: @@ -499,7 +505,7 @@ importers: version: 24.3.0 '@vitest/coverage-v8': specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2)) ts-node: specifier: 'catalog:' version: 10.9.2(@types/node@24.3.0)(typescript@5.4.5) @@ -508,10 +514,10 @@ importers: version: 5.4.5 vite-tsconfig-paths: specifier: 'catalog:' - version: 5.1.4(typescript@5.4.5)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2)) + version: 5.1.4(typescript@5.4.5)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2)) vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2) tools/eslint-config: devDependencies: @@ -768,6 +774,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.24.2': resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} engines: {node: '>=18'} @@ -780,6 +792,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.24.2': resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} engines: {node: '>=18'} @@ -792,6 +810,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.24.2': resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} engines: {node: '>=18'} @@ -804,6 +828,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.24.2': resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} engines: {node: '>=18'} @@ -816,6 +846,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.24.2': resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} engines: {node: '>=18'} @@ -828,6 +864,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.24.2': resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} engines: {node: '>=18'} @@ -840,6 +882,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.24.2': resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} engines: {node: '>=18'} @@ -852,6 +900,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.24.2': resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} engines: {node: '>=18'} @@ -864,6 +918,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.24.2': resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} engines: {node: '>=18'} @@ -876,6 +936,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.24.2': resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} engines: {node: '>=18'} @@ -888,6 +954,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.24.2': resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} engines: {node: '>=18'} @@ -900,6 +972,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.24.2': resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} engines: {node: '>=18'} @@ -912,6 +990,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.24.2': resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} engines: {node: '>=18'} @@ -924,6 +1008,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.24.2': resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} engines: {node: '>=18'} @@ -936,6 +1026,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.24.2': resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} engines: {node: '>=18'} @@ -948,6 +1044,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.24.2': resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} engines: {node: '>=18'} @@ -960,6 +1062,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.24.2': resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} engines: {node: '>=18'} @@ -972,6 +1080,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.24.2': resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} engines: {node: '>=18'} @@ -984,6 +1098,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.24.2': resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} engines: {node: '>=18'} @@ -996,6 +1116,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.24.2': resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} engines: {node: '>=18'} @@ -1008,12 +1134,24 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.24.2': resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} engines: {node: '>=18'} @@ -1026,6 +1164,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.24.2': resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} engines: {node: '>=18'} @@ -1038,6 +1182,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.24.2': resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} engines: {node: '>=18'} @@ -1050,6 +1200,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.24.2': resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} engines: {node: '>=18'} @@ -1062,6 +1218,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2908,6 +3070,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -4759,6 +4926,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.23.1: + resolution: {integrity: sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==} + engines: {node: '>=18.0.0'} + hasBin: true + turbo-darwin-64@2.5.6: resolution: {integrity: sha512-3C1xEdo4aFwMJAPvtlPqz1Sw/+cddWIOmsalHFMrsqqydcptwBfu26WW2cDm3u93bUzMbBJ8k3zNKFqxJ9ei2A==} cpu: [x64] @@ -5490,153 +5662,231 @@ snapshots: '@esbuild/aix-ppc64@0.25.12': optional: true + '@esbuild/aix-ppc64@0.28.1': + optional: true + '@esbuild/android-arm64@0.24.2': optional: true '@esbuild/android-arm64@0.25.12': optional: true + '@esbuild/android-arm64@0.28.1': + optional: true + '@esbuild/android-arm@0.24.2': optional: true '@esbuild/android-arm@0.25.12': optional: true + '@esbuild/android-arm@0.28.1': + optional: true + '@esbuild/android-x64@0.24.2': optional: true '@esbuild/android-x64@0.25.12': optional: true + '@esbuild/android-x64@0.28.1': + optional: true + '@esbuild/darwin-arm64@0.24.2': optional: true '@esbuild/darwin-arm64@0.25.12': optional: true + '@esbuild/darwin-arm64@0.28.1': + optional: true + '@esbuild/darwin-x64@0.24.2': optional: true '@esbuild/darwin-x64@0.25.12': optional: true + '@esbuild/darwin-x64@0.28.1': + optional: true + '@esbuild/freebsd-arm64@0.24.2': optional: true '@esbuild/freebsd-arm64@0.25.12': optional: true + '@esbuild/freebsd-arm64@0.28.1': + optional: true + '@esbuild/freebsd-x64@0.24.2': optional: true '@esbuild/freebsd-x64@0.25.12': optional: true + '@esbuild/freebsd-x64@0.28.1': + optional: true + '@esbuild/linux-arm64@0.24.2': optional: true '@esbuild/linux-arm64@0.25.12': optional: true + '@esbuild/linux-arm64@0.28.1': + optional: true + '@esbuild/linux-arm@0.24.2': optional: true '@esbuild/linux-arm@0.25.12': optional: true + '@esbuild/linux-arm@0.28.1': + optional: true + '@esbuild/linux-ia32@0.24.2': optional: true '@esbuild/linux-ia32@0.25.12': optional: true + '@esbuild/linux-ia32@0.28.1': + optional: true + '@esbuild/linux-loong64@0.24.2': optional: true '@esbuild/linux-loong64@0.25.12': optional: true + '@esbuild/linux-loong64@0.28.1': + optional: true + '@esbuild/linux-mips64el@0.24.2': optional: true '@esbuild/linux-mips64el@0.25.12': optional: true + '@esbuild/linux-mips64el@0.28.1': + optional: true + '@esbuild/linux-ppc64@0.24.2': optional: true '@esbuild/linux-ppc64@0.25.12': optional: true + '@esbuild/linux-ppc64@0.28.1': + optional: true + '@esbuild/linux-riscv64@0.24.2': optional: true '@esbuild/linux-riscv64@0.25.12': optional: true + '@esbuild/linux-riscv64@0.28.1': + optional: true + '@esbuild/linux-s390x@0.24.2': optional: true '@esbuild/linux-s390x@0.25.12': optional: true + '@esbuild/linux-s390x@0.28.1': + optional: true + '@esbuild/linux-x64@0.24.2': optional: true '@esbuild/linux-x64@0.25.12': optional: true + '@esbuild/linux-x64@0.28.1': + optional: true + '@esbuild/netbsd-arm64@0.24.2': optional: true '@esbuild/netbsd-arm64@0.25.12': optional: true + '@esbuild/netbsd-arm64@0.28.1': + optional: true + '@esbuild/netbsd-x64@0.24.2': optional: true '@esbuild/netbsd-x64@0.25.12': optional: true + '@esbuild/netbsd-x64@0.28.1': + optional: true + '@esbuild/openbsd-arm64@0.24.2': optional: true '@esbuild/openbsd-arm64@0.25.12': optional: true + '@esbuild/openbsd-arm64@0.28.1': + optional: true + '@esbuild/openbsd-x64@0.24.2': optional: true '@esbuild/openbsd-x64@0.25.12': optional: true + '@esbuild/openbsd-x64@0.28.1': + optional: true + '@esbuild/openharmony-arm64@0.25.12': optional: true + '@esbuild/openharmony-arm64@0.28.1': + optional: true + '@esbuild/sunos-x64@0.24.2': optional: true '@esbuild/sunos-x64@0.25.12': optional: true + '@esbuild/sunos-x64@0.28.1': + optional: true + '@esbuild/win32-arm64@0.24.2': optional: true '@esbuild/win32-arm64@0.25.12': optional: true + '@esbuild/win32-arm64@0.28.1': + optional: true + '@esbuild/win32-ia32@0.24.2': optional: true '@esbuild/win32-ia32@0.25.12': optional: true + '@esbuild/win32-ia32@0.28.1': + optional: true + '@esbuild/win32-x64@0.24.2': optional: true '@esbuild/win32-x64@0.25.12': optional: true + '@esbuild/win32-x64@0.28.1': + optional: true + '@eslint-community/eslint-utils@4.9.0(eslint@9.34.0(jiti@1.21.7))': dependencies: eslint: 9.34.0(jiti@1.21.7) @@ -6141,11 +6391,11 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/experimental-ct-core@1.58.2(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2)': + '@playwright/experimental-ct-core@1.58.2(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2)': dependencies: playwright: 1.58.2 playwright-core: 1.58.2 - vite: 6.4.1(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2) + vite: 6.4.1(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2) transitivePeerDependencies: - '@types/node' - jiti @@ -6159,10 +6409,10 @@ snapshots: - tsx - yaml - '@playwright/experimental-ct-react@1.58.2(@types/node@24.3.0)(jiti@1.21.7)(vite@6.0.7(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2))(yaml@2.8.2)': + '@playwright/experimental-ct-react@1.58.2(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(vite@6.0.7(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2))(yaml@2.8.2)': dependencies: - '@playwright/experimental-ct-core': 1.58.2(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2) - '@vitejs/plugin-react': 4.3.4(vite@6.0.7(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2)) + '@playwright/experimental-ct-core': 1.58.2(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2) + '@vitejs/plugin-react': 4.3.4(vite@6.0.7(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2)) transitivePeerDependencies: - '@types/node' - jiti @@ -6178,10 +6428,10 @@ snapshots: - vite - yaml - '@playwright/experimental-ct-react@1.58.2(@types/node@24.3.0)(jiti@1.21.7)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2))(yaml@2.8.2)': + '@playwright/experimental-ct-react@1.58.2(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2))(yaml@2.8.2)': dependencies: - '@playwright/experimental-ct-core': 1.58.2(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2) - '@vitejs/plugin-react': 4.3.4(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2)) + '@playwright/experimental-ct-core': 1.58.2(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2) + '@vitejs/plugin-react': 4.3.4(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2)) transitivePeerDependencies: - '@types/node' - jiti @@ -6935,29 +7185,29 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react@4.3.4(vite@6.0.7(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2))': + '@vitejs/plugin-react@4.3.4(vite@6.0.7(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 6.0.7(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2) + vite: 6.0.7(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.3.4(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2))': + '@vitejs/plugin-react@4.3.4(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2))': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -6972,7 +7222,7 @@ snapshots: std-env: 3.10.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2) transitivePeerDependencies: - supports-color @@ -6984,13 +7234,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2))': + '@vitest/mocker@3.2.4(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2) '@vitest/pretty-format@3.2.4': dependencies: @@ -7633,6 +7883,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.12 '@esbuild/win32-x64': 0.25.12 + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -7909,7 +8188,7 @@ snapshots: - '@types/react' - supports-color - fumadocs-mdx@11.10.1(fumadocs-core@14.7.7(@types/react@19.0.6)(next@15.5.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(next@15.5.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2)): + fumadocs-mdx@11.10.1(fumadocs-core@14.7.7(@types/react@19.0.6)(next@15.5.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(next@15.5.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2)): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.1.0 @@ -7930,7 +8209,7 @@ snapshots: optionalDependencies: next: 15.5.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 - vite: 7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2) transitivePeerDependencies: - supports-color @@ -10065,6 +10344,12 @@ snapshots: tslib@2.8.1: {} + tsx@4.23.1: + dependencies: + esbuild: 0.28.1 + optionalDependencies: + fsevents: 2.3.3 + turbo-darwin-64@2.5.6: optional: true @@ -10220,13 +10505,13 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-node@3.2.4(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2): + vite-node@3.2.4(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2) transitivePeerDependencies: - '@types/node' - jiti @@ -10241,18 +10526,18 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@5.1.4(typescript@5.4.5)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2)): + vite-tsconfig-paths@5.1.4(typescript@5.4.5)(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.4.5) optionalDependencies: - vite: 7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2) transitivePeerDependencies: - supports-color - typescript - vite@6.0.7(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2): + vite@6.0.7(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2): dependencies: esbuild: 0.24.2 postcss: 8.5.6 @@ -10261,9 +10546,10 @@ snapshots: '@types/node': 24.3.0 fsevents: 2.3.3 jiti: 1.21.7 + tsx: 4.23.1 yaml: 2.8.2 - vite@6.4.1(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2): + vite@6.4.1(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -10275,9 +10561,10 @@ snapshots: '@types/node': 24.3.0 fsevents: 2.3.3 jiti: 1.21.7 + tsx: 4.23.1 yaml: 2.8.2 - vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2): + vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -10289,21 +10576,22 @@ snapshots: '@types/node': 24.3.0 fsevents: 2.3.3 jiti: 1.21.7 + tsx: 4.23.1 yaml: 2.8.2 - vitest-axe@1.0.0-pre.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2)): + vitest-axe@1.0.0-pre.5(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2)): dependencies: '@vitest/pretty-format': 3.2.4 axe-core: 4.11.1 chalk: 5.6.2 lodash-es: 4.17.23 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2) - vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(yaml@2.8.2): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@1.21.7)(jsdom@25.0.1)(tsx@4.23.1)(yaml@2.8.2): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2)) + '@vitest/mocker': 3.2.4(vite@7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -10321,8 +10609,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.2.6(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2) - vite-node: 3.2.4(@types/node@24.3.0)(jiti@1.21.7)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2) + vite-node: 3.2.4(@types/node@24.3.0)(jiti@1.21.7)(tsx@4.23.1)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 58d4ccda..6ae7daeb 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -52,6 +52,7 @@ catalog: remark-parse: 11.0.0 tailwindcss: 3.4.17 ts-node: 10.9.2 + tsx: 4.23.1 turbo: 2.5.6 typescript: 5.4.5 unified: 11.0.5 diff --git a/qa/release-validation/CP5-website-docs-completeness.md b/qa/release-validation/CP5-website-docs-completeness.md index 498c1899..e5e34634 100644 --- a/qa/release-validation/CP5-website-docs-completeness.md +++ b/qa/release-validation/CP5-website-docs-completeness.md @@ -16,14 +16,16 @@ 1. For each URL below, issue an HTTP request and check status code -**Getting Started** (5 pages): +**Getting Started** (6 pages): - `$BASE_URL/docs/getting-started` - `$BASE_URL/docs/getting-started/quickstart` - `$BASE_URL/docs/getting-started/quickstart-solo` - `$BASE_URL/docs/getting-started/quickstart-team` - `$BASE_URL/docs/getting-started/quickstart-org` +- `$BASE_URL/docs/getting-started/checklist` -**Concepts** (6 pages): +**Concepts** (7 pages): +- `$BASE_URL/docs/concepts` - `$BASE_URL/docs/concepts/ai-assisted-sdlc` - `$BASE_URL/docs/concepts/knowledge-base` - `$BASE_URL/docs/concepts/skills` @@ -31,16 +33,17 @@ - `$BASE_URL/docs/concepts/agent-integration` - `$BASE_URL/docs/concepts/llms-txt` -**Developer Journey** (5 pages): +**Process Lifecycle** (5 pages): - `$BASE_URL/docs/developer-journey` - `$BASE_URL/docs/developer-journey/induction` - `$BASE_URL/docs/developer-journey/strategic-planning` - `$BASE_URL/docs/developer-journey/iteration` - `$BASE_URL/docs/developer-journey/execution` -**Customization** (5 pages): +**Customization** (6 pages): - `$BASE_URL/docs/customization` - `$BASE_URL/docs/customization/adopt` +- `$BASE_URL/docs/customization/install-from-url` - `$BASE_URL/docs/customization/team` - `$BASE_URL/docs/customization/templates` - `$BASE_URL/docs/customization/organization` @@ -59,17 +62,12 @@ - `$BASE_URL/docs/pm-tools/filesystem` - `$BASE_URL/docs/pm-tools/linear` -**Guides** (6 pages): -- `$BASE_URL/docs/guides/cli-workflows` -- `$BASE_URL/docs/guides/install-from-url` -- `$BASE_URL/docs/guides/customize-kb` -- `$BASE_URL/docs/guides/adopter-checklist` -- `$BASE_URL/docs/guides/troubleshooting` -- `$BASE_URL/docs/guides/update-link` - -**Reference** (9 pages): +**Reference** (12 pages): +- `$BASE_URL/docs/reference` - `$BASE_URL/docs/reference/cli/commands` - `$BASE_URL/docs/reference/cli/examples` +- `$BASE_URL/docs/reference/cli/workflows` +- `$BASE_URL/docs/reference/cli/update-link` - `$BASE_URL/docs/reference/specs/cli-contracts` - `$BASE_URL/docs/reference/specs/kb-source-resolution` - `$BASE_URL/docs/reference/skills-catalog` @@ -81,7 +79,7 @@ **Support** (3 pages): - `$BASE_URL/docs/support` - `$BASE_URL/docs/support/general-faq` -- `$BASE_URL/docs/support/faq` +- `$BASE_URL/docs/support/troubleshooting` **Tutorials** (5 pages): - `$BASE_URL/docs/tutorials` diff --git a/scripts/docs-staleness-check.js b/scripts/docs-staleness-check.js deleted file mode 100644 index aac5d3d7..00000000 --- a/scripts/docs-staleness-check.js +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env node -/** - * Docs Staleness Check — verifies docs match source-of-truth code artifacts. - * Exit 0 = in sync, Exit 1 = drift detected. - */ -const fs = require('fs') -const path = require('path') - -const ROOT = path.resolve(__dirname, '..') -const SKILLS_DIR = path.join(ROOT, 'packages/knowledge-hub/dataset/.skills') -const COMMANDS_DIR = path.join(ROOT, 'apps/pair-cli/src/commands') -const CATALOG_FILE = path.join(ROOT, 'apps/website/content/docs/reference/skills-catalog.mdx') -const COMMANDS_FILE = path.join(ROOT, 'apps/website/content/docs/reference/cli/commands.mdx') - -const errors = [] - -// --- Helpers --- - -function getSkillNames(categoryDir) { - const entries = fs.readdirSync(categoryDir, { withFileTypes: true }) - const subdirs = entries.filter((d) => d.isDirectory()).map((d) => d.name) - if (subdirs.length > 0) return subdirs - // Meta skill: category dir itself contains SKILL.md - if (fs.existsSync(path.join(categoryDir, 'SKILL.md'))) { - return [path.basename(categoryDir)] - } - return [] -} - -// --- Check 1 & 2: Skills --- - -const categories = fs.readdirSync(SKILLS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory()) - -const allSkills = [] -for (const cat of categories) { - allSkills.push(...getSkillNames(path.join(SKILLS_DIR, cat.name))) -} -const skillCount = allSkills.length - -const catalog = fs.readFileSync(CATALOG_FILE, 'utf-8') - -// Check 1: every occurrence of "N skills" matches actual count -const countMatches = [...catalog.matchAll(/(\d+)\s+(?:pair\s+)?skills/g)] -for (const m of countMatches) { - const docCount = parseInt(m[1], 10) - if (docCount !== skillCount) { - errors.push(`Skill count mismatch: docs say "${m[0]}", actual count is ${skillCount}`) - } -} - -// Check 2: every skill dir has a table row in the catalog -for (const skill of allSkills) { - if (!catalog.includes(`**${skill}**`)) { - errors.push(`Skill "${skill}" exists in .skills/ but missing from skills-catalog.mdx`) - } -} - -// Reverse check: catalog entries that no longer exist as dirs -const catalogSkills = [...catalog.matchAll(/\| \*\*([a-z0-9-]+)\*\* \|/g)].map((m) => m[1]) -for (const docSkill of catalogSkills) { - if (!allSkills.includes(docSkill)) { - errors.push(`Skill "${docSkill}" in skills-catalog.mdx but no matching dir in .skills/`) - } -} - -// --- Check 3: CLI commands --- - -const commandDirs = fs - .readdirSync(COMMANDS_DIR, { withFileTypes: true }) - .filter((d) => d.isDirectory()) - .map((d) => d.name) - -const commandsDoc = fs.readFileSync(COMMANDS_FILE, 'utf-8') - -for (const cmd of commandDirs) { - if (!commandsDoc.includes(`(#${cmd})`)) { - errors.push(`CLI command "${cmd}" has a dir in commands/ but missing from commands.mdx`) - } -} - -// --- Check 4: Tutorial CLI command references --- - -const TUTORIALS_DIR = path.join(ROOT, 'apps/website/content/docs/tutorials') -const CLI_BUILTINS = new Set(['--version', '--help']) - -if (fs.existsSync(TUTORIALS_DIR)) { - const tutorialFiles = fs - .readdirSync(TUTORIALS_DIR) - .filter((f) => f.endsWith('.mdx')) - - const referencedCommands = new Set() - for (const file of tutorialFiles) { - const content = fs.readFileSync(path.join(TUTORIALS_DIR, file), 'utf-8') - // Match pair-cli in code blocks (``` or backtick-inline) - const matches = [...content.matchAll(/pair-cli\s+([a-z][a-z0-9-]*)/g)] - for (const m of matches) { - referencedCommands.add(m[1]) - } - } - - // Filter to likely commands: must exist as a command dir or be a known builtin - // Ignore English prose words that follow "pair-cli" in non-code context - const proseWords = new Set(['as', 'is', 'on', 'to', 'installed', 'and', 'or', 'in', 'for', 'the']) - for (const cmd of referencedCommands) { - if (CLI_BUILTINS.has(`--${cmd}`)) continue - if (proseWords.has(cmd)) continue - if (!commandDirs.includes(cmd)) { - errors.push( - `Tutorial references "pair-cli ${cmd}" but no matching command dir in commands/`, - ) - } - } -} - -// --- Output --- - -console.log('Docs Staleness Check') -console.log('====================') - -if (errors.length === 0) { - console.log(`PASS — ${skillCount} skills, ${commandDirs.length} commands in sync`) - process.exit(0) -} else { - console.log(`FAIL — ${errors.length} issue${errors.length > 1 ? 's' : ''}\n`) - for (const e of errors) { - console.log(` \u2022 ${e}`) - } - console.log() - process.exit(1) -}