diff --git a/.gitignore b/.gitignore index 2974ef4..c117fa2 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,12 @@ logs/ .vibe/skills/openspec-*/ !.vibe/skills/openspec-workflows/ + +# Jekyll docs — local Bundler state and build output +docs/.bundle/ +docs/_site/ +docs/.jekyll-cache/ + +# local bundle installs (bundle config path vendor/bundle) +docs/vendor/ + diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 972dd41..582f1a5 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,21 +1,21 @@ GEM remote: https://rubygems.org/ specs: - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) + addressable (2.8.9) + public_suffix (>= 2.0.2, < 8.0) base64 (0.3.0) colorator (1.1.0) - concurrent-ruby (1.3.5) + concurrent-ruby (1.3.6) csv (3.3.5) em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) eventmachine (1.2.7) - ffi (1.17.2) + ffi (1.17.3) forwardable-extended (2.6.0) google-protobuf (3.25.8-x86_64-linux) - http_parser.rb (0.8.0) - i18n (1.14.7) + http_parser.rb (0.8.1) + i18n (1.14.8) concurrent-ruby (~> 1.0) jekyll (4.4.1) addressable (~> 2.4) @@ -50,15 +50,17 @@ GEM jekyll (>= 3.7, < 5.0) jekyll-watch (2.2.1) listen (~> 3.0) - json (2.17.1.2) - kramdown (2.5.1) - rexml (>= 3.3.9) + json (2.19.3) + kramdown (2.5.2) + rexml (>= 3.4.4) kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) liquid (4.0.4) - listen (3.9.0) + listen (3.10.0) + logger rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) mercenary (0.4.0) minima (2.5.2) jekyll (>= 3.5, < 5.0) @@ -72,7 +74,7 @@ GEM rb-inotify (0.11.1) ffi (~> 1.0) rexml (3.4.4) - rouge (4.6.1) + rouge (4.7.0) safe_yaml (1.0.5) sass-embedded (1.69.5) google-protobuf (~> 3.23) @@ -80,7 +82,7 @@ GEM terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) unicode-display_width (2.6.0) - webrick (1.9.1) + webrick (1.9.2) PLATFORMS x86_64-linux diff --git a/docs/_config.yml b/docs/_config.yml index bb74f62..7096267 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -81,6 +81,10 @@ defaults: path: "authoring" values: layout: default + - scope: + path: "modules" + values: + layout: default theme: minima minima: social: diff --git a/docs/bundles/backlog/overview.md b/docs/bundles/backlog/overview.md new file mode 100644 index 0000000..7b252c9 --- /dev/null +++ b/docs/bundles/backlog/overview.md @@ -0,0 +1,114 @@ +--- +layout: default +title: Backlog bundle overview +nav_order: 2 +permalink: /bundles/backlog/overview/ +--- + +# Backlog bundle overview + +The **Backlog** bundle (`nold-ai/specfact-backlog`) connects SpecFact to external backlog tools (GitHub Issues, Azure DevOps, and other adapters), adds ceremony-oriented workflows, dependency analysis, delta tracking, and deterministic backlog policy checks. + +## Prerequisites + +- [SpecFact CLI](https://docs.specfact.io) installed +- Bundle installed: `specfact module install nold-ai/specfact-backlog` (or include it via `specfact init --profile …`) +- For provider access: configure tokens or use `specfact backlog auth` where your adapter requires it + +## Command surface + +After installation, `specfact backlog --help` lists the backlog command group. The backlog category also mounts **policy** as `specfact backlog policy` (see below). + +### Ceremony (`specfact backlog ceremony`) + +| Command | Purpose | +|--------|---------| +| `standup` | Alias for daily standup-style views (`backlog daily`) | +| `refinement` | Alias for AI-assisted refinement (`backlog refine`) | +| `planning` | Delegates to sprint-summary style flows when available | +| `flow` | Kanban-style flow views when available | +| `pi-summary` | PI summary views when available | + +### Core workflows + +| Command | Purpose | +|--------|---------| +| `daily` | Daily standup table: filtered items, optional interactive mode, summarize prompts | +| `refine` | AI-assisted template-driven refinement (preview/write, filters, DoR checks) | +| `add` | Create backlog items with hierarchy and Definition-of-Ready validation | +| `sync` | Sync backlog graph with stored baseline and export delta outputs | +| `verify-readiness` | Verify release readiness for selected backlog items | +| `analyze-deps` | Analyze backlog dependencies for a project | +| `diff` | Show changes since baseline sync | +| `promote` | Validate promotion impact and print promotion intent | +| `init-config` | Scaffold `.specfact/backlog-config.yaml` structure | +| `map-fields` | Interactive mapping of ADO fields to canonical names | + +### Delta (`specfact backlog delta`) + +| Command | Purpose | +|--------|---------| +| `status` | Compare current graph to baseline; show delta summary | +| `impact` | Downstream dependency impact for an item | +| `cost-estimate` | Rough effort points from delta volume | +| `rollback-analysis` | Rollback risk from current delta | + +### Policy (`specfact backlog policy`) + +Deterministic policy validation against backlog snapshots (bundled with this package; not core CLI-owned). + +| Command | Purpose | +|--------|---------| +| `init` | Scaffold `.specfact/policy.yaml` from a template | +| `validate` | Run policy checks (JSON/Markdown/both) | +| `suggest` | Patch-ready suggestions (no automatic writes) | + +See [Policy engine](policy-engine/) for details. + +### Auth (`specfact backlog auth`) + +Backlog provider authentication for **GitHub** and **Azure DevOps**: store tokens, run OAuth / device-code flows, inspect status, and clear credentials for setup and troubleshooting. + +| Subcommand | Purpose | +|------------|---------| +| `azure-devops` | Authenticate to Azure DevOps via PAT (`--pat`) or OAuth (interactive browser or `--use-device-code`) | +| `github` | Authenticate to GitHub (or GitHub Enterprise) via RFC 8628 device code flow; optional `--client-id`, `--base-url`, `--scopes` | +| `status` | Show stored auth state for supported providers (valid vs expired tokens) | +| `clear` | Remove stored tokens; optional `--provider` (`azure-devops` or `github`) or omit to clear all | + +## Bundle-owned prompts and templates + +Refinement and ceremony flows **emit prompts and instructions** for your IDE copilot. Those assets ship with the backlog bundle (and related payloads); they are **not** maintained as core CLI-owned files. Install or refresh IDE resources with `specfact init ide` (and your team’s bundle publishing workflow) so the CLI and bundles stay aligned. + +## Quick examples + +Validate the exact flags for your adapter: + +```bash +specfact backlog daily --help +specfact backlog refine --help +specfact backlog delta status --help +specfact backlog policy validate --help +specfact backlog auth --help +specfact backlog auth status --help +specfact backlog auth github --help +``` + +GitHub refinement preview (typical entry point): + +```bash +specfact backlog refine github --preview --labels feature +``` + +Daily standup scope: + +```bash +specfact backlog daily github --state open --limit 20 +``` + +## Deep dives + +- [Refinement](refinement/) +- [Dependency analysis](dependency-analysis/) +- [Delta](delta/) +- [Policy engine](policy-engine/) diff --git a/docs/bundles/code-review/overview.md b/docs/bundles/code-review/overview.md new file mode 100644 index 0000000..0d2b2fa --- /dev/null +++ b/docs/bundles/code-review/overview.md @@ -0,0 +1,58 @@ +--- +layout: default +title: Code Review bundle overview +nav_order: 2 +permalink: /bundles/code-review/overview/ +--- + +# Code Review bundle overview + +The **Code Review** bundle (`nold-ai/specfact-code-review`) extends the shared **`specfact code`** command group with **`review`** workflows: governed review runs, **reward ledger** history, and **house-rules** skill management. + +Use it together with the [Codebase](../codebase/overview/) bundle (`import`, `analyze`, `drift`, `validate`, `repro`) on the same `code` surface. + +## Prerequisites + +- `specfact module install nold-ai/specfact-code-review` +- Optional tool installs (Ruff, Radon, Semgrep, Pyright, etc.) as described in command help + +## `specfact code review` — nested commands + +| Command | Purpose | +|--------|---------| +| `run` | Execute a governed review (scope, JSON output, `--fix`, TDD gate, etc.) | +| `ledger` | Inspect and update review reward history | +| `rules` | Manage the house-rules skill (`show`, `init`, `update`) | + +### `ledger` subcommands + +| Subcommand | Purpose | +|------------|---------| +| `update` | Update ledger entries | +| `status` | Show ledger status | +| `reset` | Reset ledger state | + +### `rules` subcommands + +| Subcommand | Purpose | +|------------|---------| +| `show` | Show current rules configuration | +| `init` | Initialize rules/skill assets | +| `update` | Update rules content | + +## Bundle-owned skills and policy packs + +House rules and review payloads ship **inside the bundle** (for example Semgrep packs and skill metadata). They are **not** core CLI-owned resources. Install or refresh IDE-side assets with `specfact init ide` after upgrading the bundle. + +## Quick examples + +```bash +specfact code review run --help +specfact code review ledger status --help +specfact code review rules show --help +``` + +## See also + +- [Code review module](../../modules/code-review/) +- [Codebase bundle overview](../codebase/overview/) — import, drift, validation, repro diff --git a/docs/bundles/codebase/overview.md b/docs/bundles/codebase/overview.md new file mode 100644 index 0000000..dc49d37 --- /dev/null +++ b/docs/bundles/codebase/overview.md @@ -0,0 +1,74 @@ +--- +layout: default +title: Codebase bundle overview +nav_order: 2 +permalink: /bundles/codebase/overview/ +--- + +# Codebase bundle overview + +The **Codebase** bundle (`nold-ai/specfact-codebase`) mounts under `specfact code` alongside the Code Review bundle. It focuses on **brownfield import**, **contract coverage analysis**, **drift detection**, **sidecar validation**, and **reproducible validation suites**. + +For automated review runs (Ruff, Semgrep, ledger, rules), see [Code Review](../code-review/overview/) — also on the `code` command group. + +## Prerequisites + +- `specfact module install nold-ai/specfact-codebase` (often together with `nold-ai/specfact-project`) +- Python repos for import/sidecar workflows; optional tool installs (CrossHair, Specmatic, Ruff, etc.) per command help + +## Command groups (`specfact code …`) + +### `import` — brownfield intake + +| Entry | Purpose | +|-------|---------| +| `specfact code import` (default) | Import a repository into a project bundle (`from-code` behavior; see `--help`) | +| `specfact code import from-bridge` | Import from an external bridge/export flow | + +Advanced import topics: [Project import command features](../project/import-migration/) (cross-bundle). + +### `analyze` — structure and contracts + +| Command | Purpose | +|--------|---------| +| `contracts` | Analyze codebase for OpenAPI contract coverage and related signals | + +### `drift` + +| Command | Purpose | +|--------|---------| +| `detect` | Detect drift between implementation and specifications | + +### `validate` — sidecar + +| Command | Purpose | +|--------|---------| +| `sidecar init` | Initialize a sidecar workspace for external-repo validation | +| `sidecar run` | Run the sidecar validation pipeline | + +### `repro` — reproducibility + +| Command | Purpose | +|--------|---------| +| `repro` (default) | Run the full validation suite | +| `setup` | Prepare repro validation setup | + +Use `specfact code repro --help` for the default invocation flags (`--repo`, `--verbose`, `--sidecar`, …). + +## Bundle-owned prompts for import/generation + +Import and enrichment flows may ship **prompts or helper templates** with the bundle. They are **bundle payload**, not core-owned assets. Align your IDE with `specfact init ide` after bundle upgrades. + +## Quick examples + +```bash +specfact code import --help +specfact code analyze contracts --help +specfact code drift detect --help +specfact code validate sidecar init my-bundle /path/to/repo +specfact code repro --verbose --repo . +``` + +## Deep dives + +- [Sidecar validation](sidecar-validation/) diff --git a/docs/bundles/govern/overview.md b/docs/bundles/govern/overview.md new file mode 100644 index 0000000..7c1edd6 --- /dev/null +++ b/docs/bundles/govern/overview.md @@ -0,0 +1,45 @@ +--- +layout: default +title: Govern bundle overview +nav_order: 2 +permalink: /bundles/govern/overview/ +--- + +# Govern bundle overview + +The **Govern** bundle (`nold-ai/specfact-govern`) adds **enforcement** and **patch-mode** workflows: configure quality gates tied to SDD plans, and preview/apply patches with explicit write controls. + +## Prerequisites + +- `specfact module install nold-ai/specfact-govern` +- Project bundles with SDD manifests when using SDD enforcement paths + +## `specfact govern enforce` + +| Command | Purpose | +|--------|---------| +| `stage` | Configure enforcement stages for bundles and plans | +| `sdd` | Enforce SDD quality gates against manifests and plans | + +## `specfact govern patch` + +| Command | Purpose | +|--------|---------| +| `apply` | Preview and apply patches (local or upstream with `--write`) | + +## Bundle-owned policy packs + +Enforcement may ship **bundle-local policy packs or presets** with the package. Treat them as **bundle payload** referenced by the govern module, not as core CLI-owned configuration. Refresh IDE-facing resources with `specfact init ide` (core IDE prompts from installed modules). The **Code Review** bundle (`nold-ai/specfact-code-review`) provides `specfact code review rules init --ide` and `specfact code review rules update --ide` (e.g. `--ide cursor`, `--ide claude`, `--ide codex`, or `--ide vibe`; see `--help`) for the house-rules skill—install that bundle separately when you need those commands. + +## Quick examples + +```bash +specfact govern --help +specfact govern enforce stage --help +specfact govern enforce sdd --help +specfact govern patch apply --help +``` + +## See also + +- [Command reference](../../reference/commands/) — nested `govern` commands diff --git a/docs/bundles/project/overview.md b/docs/bundles/project/overview.md new file mode 100644 index 0000000..d02b226 --- /dev/null +++ b/docs/bundles/project/overview.md @@ -0,0 +1,94 @@ +--- +layout: default +title: Project bundle overview +nav_order: 2 +permalink: /bundles/project/overview/ +--- + +# Project bundle overview + +The **Project** bundle (`nold-ai/specfact-project`) manages SpecFact **project bundles** (personas, locks, roadmap export), links them to backlog providers, coordinates **development plans** (features, stories, SDD alignment), **syncs** external tools (Spec-Kit, OpenSpec, GitHub, Linear, Jira, …), and **migrates** legacy layouts to bundle-centric structure. + +## Prerequisites + +- SpecFact CLI and a repository with `.specfact/` layout +- Bundle installed: `specfact module install nold-ai/specfact-project` +- For backlog-linked flows: install [Backlog](../backlog/overview/) and link a provider + +## Command families + +The project lifecycle surface loads from this bundle across **`specfact project`**, **`specfact plan`**, top-level **`specfact sync`**, and **`specfact migrate`** (see each group’s `--help` after install). The `project` group also nests a `sync` Typer; prefer the **top-level** `specfact sync …` entry when documented in the command reference. + +### `specfact project` — bundles and personas + +| Command | Purpose | +|--------|---------| +| `link-backlog` | Link a project bundle to a backlog adapter and project id | +| `health-check` | Project health including backlog graph checks | +| `devops-flow` | Integrated DevOps stage actions for a linked bundle | +| `snapshot` | Save the linked backlog graph as a baseline snapshot | +| `regenerate` | Re-derive plan state from bundle + backlog graph | +| `export-roadmap` | Export roadmap milestones from dependency critical path | +| `export` | Export persona-owned sections to Markdown | +| `import` | Import persona-edited Markdown back into the bundle | +| `lock` / `unlock` / `locks` | Section locks for collaborative editing | +| `init-personas` | Initialize persona mappings in the manifest | +| `merge` | Three-way merge with persona-aware conflict handling | +| `resolve-conflict` | Resolve a specific merge conflict | +| `version` | Subcommands for bundle versioning | +| `sync` | Same sync Typer as top-level `specfact sync` (see below) | + +### `specfact plan` — plans, stories, and reviews + +| Command | Purpose | +|--------|---------| +| `init` | Initialize or adopt a development plan for a bundle | +| `add-feature` / `add-story` | Add plan items | +| `update-idea` / `update-feature` / `update-story` | Update plan content | +| `compare` | Compare manual vs automatic plan inputs | +| `select` | Select active plan context | +| `upgrade` | Upgrade plan artifacts | +| `sync` | Sync plan artifacts with repo state | +| `promote` | Promotion workflow for plan readiness | +| `review` | Plan review workflows | +| `harden` | Harden SDD and related artifacts | + +### `specfact sync` — bridges and automation + +Use the top-level group (`specfact sync --help`). + +| Command | Purpose | +|--------|---------| +| `bridge` | Import/export bridge for external tools and adapters | +| `repository` | Repository-scoped sync operations | +| `intelligent` | Higher-level orchestrated sync | + +### `specfact migrate` — structure migrations + +| Command | Purpose | +|--------|---------| +| `cleanup-legacy` | Remove empty legacy top-level directories under `.specfact/` | +| `to-contracts` | Migrate verbose bundles toward contract-oriented layouts | +| `artifacts` | Migrate plan and artifact layouts | + +## Related: codebase import + +Brownfield **code import** (`specfact code import`, `specfact import …`) lives in the [Codebase](../codebase/overview/) bundle; it often feeds project bundles. See [Import command features](import-migration/) for behavior that spans both bundles. + +## Bundle-owned prompts and plan templates + +Plan and review flows may ship **prompts or templates** with the bundle. Treat them as **bundle payload**, not core CLI sources of truth. Refresh IDE-facing resources with `specfact init ide` after upgrades so editors receive the same artifacts the CLI expects. + +## Quick examples + +```bash +specfact project link-backlog --adapter github --project-id owner/repo --bundle my-bundle --repo . +specfact plan init --help +specfact sync bridge --help +specfact migrate artifacts --repo . +``` + +## See also + +- [DevOps flow](devops-flow/) +- [Import command features](import-migration/) diff --git a/docs/bundles/spec/overview.md b/docs/bundles/spec/overview.md new file mode 100644 index 0000000..54f1b82 --- /dev/null +++ b/docs/bundles/spec/overview.md @@ -0,0 +1,81 @@ +--- +layout: default +title: Spec bundle overview +nav_order: 2 +permalink: /bundles/spec/overview/ +--- + +# Spec bundle overview + +The **Spec** bundle (`nold-ai/specfact-spec`) mounts under the **`specfact spec`** command group. That group aggregates **OpenAPI contract lifecycle** commands, **Specmatic** integration (mounted as the `api` subgroup), **SDD** manifest utilities, and **generate** workflows for contracts and prompts. + +Install the bundle, then confirm the mounted tree with `specfact spec --help`. + +## Prerequisites + +- `specfact module install nold-ai/specfact-spec` +- Optional tooling per workflow (Specmatic, OpenAPI files, etc.) + +## `specfact spec contract` — OpenAPI contracts + +| Command | Purpose | +|--------|---------| +| `init` | Initialize contract artifacts for a bundle | +| `validate` | Validate OpenAPI/AsyncAPI specs | +| `coverage` | Report contract coverage signals | +| `serve` | Serve specs for local testing | +| `verify` | Verify contract consistency | +| `test` | Run contract-oriented tests | + +## `specfact spec api` — Specmatic (API spec testing) + +| Command | Purpose | +|--------|---------| +| `validate` | Validate specs via Specmatic | +| `backward-compat` | Compare two spec versions for compatibility | +| `generate-tests` | Generate Specmatic test suites | +| `mock` | Run a Specmatic mock server | + +## `specfact spec sdd` — SDD manifests + +| Command | Purpose | +|--------|---------| +| `list` | List SDD manifests in the repo | + +### `constitution` subcommands + +| Subcommand | Purpose | +|------------|---------| +| `bootstrap` | Bootstrap constitution markdown for Spec-Kit compatibility | +| `enrich` | Enrich constitution content | +| `validate` | Validate constitution structure | + +## `specfact spec generate` — generation and prompts + +| Command | Purpose | +|--------|---------| +| `contracts` | Generate contract artifacts from plans | +| `contracts-prompt` | Emit prompts for contract work | +| `contracts-apply` | Apply generated contract changes | +| `fix-prompt` | Prompt-driven fix flows | +| `test-prompt` | Prompt-driven test flows | + +## Bundle-owned prompts + +Generate and contract flows emit **prompts** shipped with the bundle. They are **bundle resources**, not core CLI files. Use `specfact init ide` to refresh IDE exports after bundle upgrades. + +## Quick examples + +```bash +specfact spec --help +specfact spec contract validate --help +specfact spec api validate --help +specfact spec sdd list --repo . +specfact spec sdd constitution validate --help +specfact spec generate contracts --help +``` + +## See also + +- [Command reference](../../reference/commands/) — bundle-to-command mapping +- [Contract testing workflow](../../guides/contract-testing-workflow/) diff --git a/docs/index.md b/docs/index.md index 06a0328..4fae584 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,14 +13,14 @@ The modules site owns all bundle-specific deep guidance. Core CLI platform docs ## Official Bundles -| Bundle | Description | -|--------|-------------| -| [Backlog](bundles/backlog/refinement/) | AI-assisted refinement, delta commands, dependency analysis, policy engine | -| [Project](bundles/project/devops-flow/) | DevOps flow, import/migration, project setup | -| [Codebase](bundles/codebase/sidecar-validation/) | Sidecar validation, codebase analysis | -| [Spec](reference/commands/) | Specification management | -| [Govern](reference/commands/) | Governance and compliance | -| [Code Review](reference/commands/) | Automated code review with ruff, radon, semgrep | +| Bundle | Overview | Deep dives | +|--------|----------|------------| +| Backlog | [Overview](bundles/backlog/overview/) | [Refinement](bundles/backlog/refinement/), [delta](bundles/backlog/delta/), [policy](bundles/backlog/policy-engine/) | +| Project | [Overview](bundles/project/overview/) | [DevOps flow](bundles/project/devops-flow/), [import features](bundles/project/import-migration/) | +| Codebase | [Overview](bundles/codebase/overview/) | [Sidecar validation](bundles/codebase/sidecar-validation/) | +| Spec | [Overview](bundles/spec/overview/) | [Command reference](reference/commands/) | +| Govern | [Overview](bundles/govern/overview/) | [Command reference](reference/commands/) | +| Code Review | [Overview](bundles/code-review/overview/) | [Module guide](modules/code-review/) | ## Getting Started diff --git a/docs/modules/code-review.md b/docs/modules/code-review.md index b91715a..77038e7 100644 --- a/docs/modules/code-review.md +++ b/docs/modules/code-review.md @@ -1,3 +1,10 @@ +--- +layout: default +title: Code review module +nav_order: 10 +permalink: /modules/code-review/ +--- + # specfact-code-review module notes ## Review run command diff --git a/openspec/specs/bundle-overview-pages/spec.md b/openspec/specs/bundle-overview-pages/spec.md new file mode 100644 index 0000000..5950e80 --- /dev/null +++ b/openspec/specs/bundle-overview-pages/spec.md @@ -0,0 +1,38 @@ +# bundle-overview-pages Specification + +## Purpose + +Define requirements for official bundle overview pages on the modules documentation site: each official bundle has a single landing page that lists commands, prerequisites, quick examples, and bundle-owned resource setup guidance aligned with the mounted SpecFact CLI surface. + +## Requirements + +### Requirement: Bundle overview pages SHALL provide complete bundle entry points + +Each official bundle SHALL have a single overview page that lists its commands, prerequisites, examples, and relevant bundle-owned resource setup guidance. + +#### Scenario: Overview page lists all bundle commands + +- **GIVEN** a bundle overview page such as `bundles/backlog/overview.md` +- **WHEN** a user reads the page +- **THEN** every registered command and subcommand for that bundle is listed +- **AND** each command has a brief description + +#### Scenario: Overview page includes quick examples + +- **GIVEN** a bundle overview page +- **WHEN** a user reads the page +- **THEN** at least one practical example is shown for each major command group + +#### Scenario: Overview page explains bundle-owned resource setup when relevant + +- **GIVEN** a bundle overview page for a bundle that ships prompts or workspace templates +- **WHEN** a user reads the page +- **THEN** the page explains which resources are bundled with that package +- **AND** it points to the supported setup flow such as `specfact init ide` or bundle-specific template/bootstrap commands + +#### Scenario: Command examples match actual CLI + +- **GIVEN** a command example in an overview page +- **WHEN** compared against the actual `specfact --help` output +- **THEN** the command name, arguments, and key options match +- **AND** `tests/unit/docs/test_bundle_overview_cli_examples.py::test_validate_bundle_overview_cli_help_examples` exercises each quick-example line by invoking the corresponding bundle Typer app with `--help` (or an explicit `--help` normalization for lines that include runnable flags), failing when help output cannot be produced diff --git a/tests/unit/docs/test_bundle_overview_cli_examples.py b/tests/unit/docs/test_bundle_overview_cli_examples.py new file mode 100644 index 0000000..0fb7b4a --- /dev/null +++ b/tests/unit/docs/test_bundle_overview_cli_examples.py @@ -0,0 +1,241 @@ +"""Validate bundle overview quick examples against bundle Typer apps (`--help`). + +Implements the bundle-overview-pages spec scenario: command examples stay aligned with +`specfact --help`. The root `specfact` CLI may omit nested members in some dev +setups, so we invoke the same Typer apps the CLI mounts from each official bundle. +""" + +from __future__ import annotations + +import importlib +import logging +import re +import shlex +from pathlib import Path +from typing import Any + +from typer.main import get_command as typer_get_command +from typer.testing import CliRunner + + +_REPO_ROOT = Path(__file__).resolve().parents[3] +_BUNDLE_OVERVIEWS = sorted(_REPO_ROOT.glob("docs/bundles/*/overview.md")) + +# Runnable examples (not plain `--help`); map exact line → tokens after `specfact`. +_OVERVIEW_LINE_TO_TOKENS_AFTER_SPECFACT: dict[str, list[str]] = { + "specfact code validate sidecar init my-bundle /path/to/repo": [ + "code", + "validate", + "sidecar", + "init", + "--help", + ], + "specfact code repro --verbose --repo .": ["code", "repro", "--help"], + "specfact project link-backlog --adapter github --project-id owner/repo --bundle my-bundle --repo .": [ + "project", + "link-backlog", + "--help", + ], + "specfact migrate artifacts --repo .": ["migrate", "artifacts", "--help"], + "specfact backlog refine github --preview --labels feature": [ + "backlog", + "refine", + "github", + "--help", + ], + "specfact backlog daily github --state open --limit 20": ["backlog", "daily", "--help"], + "specfact spec sdd list --repo .": ["spec", "sdd", "list", "--help"], +} + +_OPEN_BASH_FENCE_RE = re.compile(r"^```bash\s*$") +_CLOSE_FENCE_RE = re.compile(r"^```\s*$") + + +def _iter_bash_block_lines(markdown: str) -> list[str]: + lines_out: list[str] = [] + in_bash = False + for raw in markdown.splitlines(): + stripped = raw.strip() + if _OPEN_BASH_FENCE_RE.match(stripped): + in_bash = True + continue + if _CLOSE_FENCE_RE.match(stripped): + in_bash = False + continue + if in_bash: + lines_out.append(raw.rstrip("\n")) + return lines_out + + +def _tokens_for_specfact_line(line: str) -> list[str] | None: + s = line.strip() + if not s or s.startswith("#"): + return None + if "&&" in s or "|" in s: + return None + if s in _OVERVIEW_LINE_TO_TOKENS_AFTER_SPECFACT: + return ["specfact", *_OVERVIEW_LINE_TO_TOKENS_AFTER_SPECFACT[s]] + try: + tokens = shlex.split(s) + except ValueError: + return None + if not tokens or tokens[0] != "specfact": + return None + return tokens + + +def _route_backlog(t: list[str]) -> tuple[Any, list[str]] | None: + if not t or t[0] != "backlog": + return None + if len(t) > 1 and t[1] == "policy": + mod = importlib.import_module("specfact_backlog.policy_engine.commands") + return mod.app, t[2:] + mod = importlib.import_module("specfact_backlog.backlog.commands") + return mod.app, t[1:] + + +_TOP_LEVEL_MODULE_BY_PREFIX: dict[str, str] = { + "govern": "specfact_govern.govern.commands", + "project": "specfact_project.project.commands", + "plan": "specfact_project.plan.commands", + "sync": "specfact_project.sync.commands", + "migrate": "specfact_project.migrate.commands", +} + + +def _route_top_level(t: list[str]) -> tuple[Any, list[str]] | None: + if not t: + return None + mod_path = _TOP_LEVEL_MODULE_BY_PREFIX.get(t[0]) + if mod_path is None: + return None + mod = importlib.import_module(mod_path) + return mod.app, t[1:] + + +# Subcommand under `specfact code …` → Typer module. `review` keeps `review` in argv (nested subgroup). +_CODE_SUB_TO_MODULE: dict[str, tuple[str, bool]] = { + "review": ("specfact_code_review.review.commands", True), + "import": ("specfact_codebase.import_cmd.commands", False), + "analyze": ("specfact_codebase.analyze.commands", False), + "drift": ("specfact_codebase.drift.commands", False), + "validate": ("specfact_codebase.validate.commands", False), + "repro": ("specfact_codebase.repro.commands", False), +} + + +def _route_code(t: list[str]) -> tuple[Any, list[str]] | None: + if len(t) < 2 or t[0] != "code": + return None + entry = _CODE_SUB_TO_MODULE.get(t[1]) + if entry is None: + return None + mod_path, keep_review_prefix = entry + mod = importlib.import_module(mod_path) + argv = t[1:] if keep_review_prefix else t[2:] + return mod.app, argv + + +_SPEC_SUB_TO_MODULE = { + "contract": "specfact_spec.contract.commands", + "api": "specfact_spec.spec.commands", + "sdd": "specfact_spec.sdd.commands", + "generate": "specfact_spec.generate.commands", +} + + +def _spec_group_help_app_or_contract_fallback() -> tuple[Any, list[str]]: + """`specfact spec --help` uses the spec category group when CommandRegistry mounts members. + + In CI the group Typer can be empty (no bundle modules registered yet); Typer then raises + when building a Click command. Fall back to a representative bundle sub-app so the test + still validates a real `--help` surface. + """ + spec_group_mod = importlib.import_module("specfact_cli.groups.spec_group") + app = spec_group_mod.build_app() + try: + typer_get_command(app) + except RuntimeError: + contract_mod = importlib.import_module("specfact_spec.contract.commands") + return contract_mod.app, ["--help"] + return app, ["--help"] + + +def _route_spec(t: list[str]) -> tuple[Any, list[str]] | None: + if not t or t[0] != "spec": + return None + if len(t) == 2 and t[1] == "--help": + return _spec_group_help_app_or_contract_fallback() + if len(t) < 2: + return None + sub = t[1] + mod_path = _SPEC_SUB_TO_MODULE.get(sub) + if mod_path is None: + logging.getLogger(__name__).warning("Unrecognized spec subcommand: %s - tokens: %s", sub, t) + msg = f"Unrecognized spec subcommand: {sub!r} (tokens: {t!r})" + raise ValueError(msg) + mod = importlib.import_module(mod_path) + return mod.app, t[2:] + + +def _route_to_bundle_app_and_argv(tokens: list[str]) -> tuple[Any, list[str]] | None: + """Map `specfact …` tokens to (Typer app, argv for CliRunner).""" + if len(tokens) < 2 or tokens[0] != "specfact": + return None + t = tokens[1:] + if not t: + return None + for router in (_route_backlog, _route_top_level, _route_code, _route_spec): + routed = router(t) + if routed is not None: + return routed + return None + + +def test_validate_bundle_overview_cli_help_examples() -> None: + """Invoke bundle Typer apps with argv derived from each overview quick-example line.""" + runner = CliRunner() + seen: set[tuple[str, ...]] = set() + failures: list[str] = [] + + assert _BUNDLE_OVERVIEWS, "no bundle overviews discovered" + for overview in _BUNDLE_OVERVIEWS: + text = overview.read_text(encoding="utf-8") + for raw_line in _iter_bash_block_lines(text): + tokens = _tokens_for_specfact_line(raw_line) + if tokens is None: + continue + if "--help" not in tokens: + failures.append( + f"{overview.relative_to(_REPO_ROOT)}: {raw_line.strip()!r} " + "(add --help to the example or an entry in _OVERVIEW_LINE_TO_TOKENS_AFTER_SPECFACT)" + ) + continue + + dedupe_key = tuple(tokens) + display_key = " ".join(tokens) + if dedupe_key in seen: + continue + seen.add(dedupe_key) + + routed = _route_to_bundle_app_and_argv(tokens) + if routed is None: + failures.append(f"{overview.relative_to(_REPO_ROOT)}: no route for {display_key!r}") + continue + app, argv = routed + # We only assert Typer accepted argv and exited 0; we do not diff full --help text or + # every option name against the markdown (that would be brittle and duplicate Typer). + # Optional smoke check below ensures something help-like was printed when --help is used. + result = runner.invoke(app, argv, prog_name="specfact") + if result.exit_code != 0: + failures.append(f"{overview.relative_to(_REPO_ROOT)}: {raw_line.strip()!r} -> exit {result.exit_code}") + continue + if "--help" in argv: + # CliRunner may expose only `output` (stdout+stderr) unless mix_stderr=False. + combined = result.output or "" + if "Usage" not in combined and "usage:" not in combined.lower(): + failures.append( + f"{overview.relative_to(_REPO_ROOT)}: {raw_line.strip()!r} -> help output missing Usage banner" + ) + + assert not failures, "Overview CLI --help mismatches:\n" + "\n".join(failures) diff --git a/tests/unit/docs/test_docs_review.py b/tests/unit/docs/test_docs_review.py index 71d94f0..d0977bd 100644 --- a/tests/unit/docs/test_docs_review.py +++ b/tests/unit/docs/test_docs_review.py @@ -33,8 +33,11 @@ def _docs_root() -> Path: return _repo_root() / "docs" +_SKIP_DOCS_TREE_PARTS = frozenset({"_site", "vendor", ".bundle", ".jekyll-cache"}) + + def _is_docs_markdown(path: Path) -> bool: - return path.suffix == ".md" and "_site" not in path.parts and "vendor" not in path.parts + return path.suffix == ".md" and not _SKIP_DOCS_TREE_PARTS.intersection(path.parts) def _is_publishable_page(path: Path) -> bool: @@ -401,6 +404,10 @@ def test_moved_files_have_redirect_from_entries() -> None: rel = path.relative_to(_docs_root()) if not any(str(rel).startswith(d) for d in moved_dirs): continue + # New per-bundle landing pages (not migrated from guides/) have no legacy URL. + parts = rel.parts + if len(parts) >= 3 and parts[0] == "bundles" and path.name == "overview.md": + continue metadata, _ = _split_front_matter(_read_text(path)) if not metadata: continue diff --git a/tests/unit/test_modules_docs_site_contract.py b/tests/unit/test_modules_docs_site_contract.py index 9519d1b..02d709e 100644 --- a/tests/unit/test_modules_docs_site_contract.py +++ b/tests/unit/test_modules_docs_site_contract.py @@ -56,9 +56,12 @@ def test_modules_layout_keeps_sidebar_module_focused() -> None: def test_docs_tree_does_not_reference_retired_public_hosts() -> None: + skip_parts = frozenset({"vendor", "_site", ".bundle", ".jekyll-cache"}) for path in REPO_ROOT.joinpath("docs").rglob("*"): if not path.is_file(): continue + if skip_parts.intersection(path.parts): + continue text = _read(path) for host in OUTDATED_DOCS_HOSTS: assert host not in text, f"{path} still references retired host {host}"