diff --git a/README.md b/README.md index 10c593a9..606cba43 100644 --- a/README.md +++ b/README.md @@ -1,294 +1,132 @@ # SpecFact CLI -> **SpecFact is the validation and alignment layer for software delivery.** -> It adds the missing validation layer that keeps backlog intent, specifications, tests, and code -> from drifting apart across AI-assisted coding, brownfield systems, and governed delivery. -> Use it to move fast without losing rigor. - -**No API keys required. Works offline. Zero vendor lock-in.** - -SpecFact CLI does **not** include built-in AI. It is a deterministic local CLI -that can be paired with IDE slash-command prompts so your chosen AI copilot can -invoke SpecFact as part of a command chain. +> Review AI-assisted code against your own contracts. +> Catch drift before it reaches PR or main. [![PyPI version](https://img.shields.io/pypi/v/specfact-cli.svg?color=22c55e)](https://pypi.org/project/specfact-cli/) [![Python versions](https://img.shields.io/pypi/pyversions/specfact-cli.svg)](https://pypi.org/project/specfact-cli/) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) [![Status](https://img.shields.io/badge/status-beta-F59E0B.svg)](https://github.com/nold-ai/specfact-cli) +**No API keys required. Works offline. Zero vendor lock-in.** +
-**[specfact.com](https://specfact.com)** • **[specfact.io](https://specfact.io)** • **[specfact.dev](https://specfact.dev)** • **[Documentation](https://docs.specfact.io/)** • **[Support](mailto:hello@noldai.com)** +**[Documentation](https://docs.specfact.io/)** • **[Modules](https://modules.specfact.io/)** • **[Support](mailto:hello@noldai.com)**
---- - -## What is SpecFact? - -SpecFact is the validation and alignment layer for software delivery. - -It is a local CLI that helps you keep the intent behind a change aligned from -backlog or idea through specifications, implementation, and checks. The “Swiss-knife CLI” metaphor -fits because SpecFact gives you a set of focused tools for specific delivery problems, not a vague -bag of features. - -In practice, SpecFact helps you: -- add guardrails to AI-assisted and fast-moving greenfield work -- reverse-engineer large brownfield codebases into trustworthy structured understanding -- reduce the “I wanted X but got Y” drift between backlog, spec, and implementation -- move from local rigor toward team and enterprise policy enforcement - -## Why does it exist? - -Modern delivery drifts in predictable ways: -- AI-generated quick wins often lack the validation layer needed for mid- and long-term reliability -- brownfield systems often have missing or drifted specs, so teams need to reverse-engineer reality -- backlog intent gets reinterpreted into something else before it reaches code -- teams working with different skill levels, opinions, and AI IDE setups need consistent review and - policy enforcement - -SpecFact exists to reduce that drift. It helps teams understand what is really there, express what -should happen more accurately, and validate that the result still matches the original intent. - -## Why should I use it? - -Use SpecFact if you want one of these outcomes: -- ship AI-assisted changes faster without accepting fragile “looks fine to me” quality -- understand a legacy or unfamiliar codebase before changing it -- hand brownfield insight into OpenSpec, Spec-Kit, or other spec-first workflows -- keep backlog expectations, specifications, and implementation from silently diverging -- enforce shared rules consistently across developers and CI/CD - -## What do I get? - -With SpecFact, you get: -- a deterministic local CLI instead of another opaque SaaS dependency -- a validation layer around fast-moving implementation work -- codebase analysis and sidecar flows for brownfield understanding -- stronger backlog/spec/code alignment for real delivery workflows -- a path from individual rigor to organization-level policy enforcement - -## How do I get started? - -### Start Here (about 2 minutes): scored code review — no `pip install` - -**Point SpecFact at your code.** From a **git repository** (any branch), run two commands: +## Try it in 60 seconds ```bash +# Zero-install, no API key, works offline uvx specfact-cli init --profile solo-developer uvx specfact-cli code review run --path . --scope full ``` -You should see a **Verdict** (PASS/FAIL), a **Score**, and categorized **findings** — the fastest way to see SpecFact on real code before you dive into backlog, specs, or CI. +**Sample output:** -- **Command 1** installs the `solo-developer` bundles (including `specfact-codebase` and `specfact-code-review`) into your user module store so `code review` and related commands are available on the next invocation. -- **Command 2** runs the clean-code review on the repo at `.`. Use **`--scope full`** on the first run so review does not depend on having local git changes. +```text +SpecFact CLI - v0.45.1 -**Already installed the CLI?** Use the same flow with `specfact` instead of `uvx specfact-cli`: +Running Ruff checks... +Running Radon complexity checks... +Running Semgrep rules... +Running AST clean-code checks... +Running basedpyright type checks... +Running pylint checks... +Running contract checks... +Running targeted tests and coverage... -```bash -specfact init --profile solo-developer -specfact code review run --path . --scope full -``` - -**Read the canonical walkthrough:** **[Documentation — Quickstart](https://docs.specfact.io/getting-started/quickstart/)** · **[Installation](https://docs.specfact.io/getting-started/installation/)** (uvx-first, then persistent install). +Verdict: FAIL | CI exit: 1 | Score: 0 | Reward delta: -80 -### Install (persistent CLI for daily use) +Findings: + - specfact_demo/enforcement.py:96 Cyclomatic complexity for run_enforcement is 16. + - specfact_demo/main.py:81 Avoid print() in source files; use structured logging instead. + - examples/buggy_math.py:4 Public function divide is missing @require/@ensure decorators. -```bash -pip install -U specfact-cli +Evidence bundle: docs/_support/readme-first-contact/sample-output/ ``` -You can still use **`uvx specfact-cli@latest ...`** anytime without installing; it always fetches the latest published CLI. +⭐ **Star this repo if the output is useful.** Open an issue if you want the workflow adapted for your stack. -### After the wow path: deeper workflows - -When you want analysis, snapshots, or sidecar validation on top of the review layer: +**Already installed the CLI?** Use: ```bash -# Analyze a codebase you care about -specfact code import my-project --repo . - -# Snapshot the project state for follow-up workflows -specfact project snapshot --bundle my-project - -# Validate external code without modifying the target repo -specfact code validate sidecar init my-project /path/to/repo -specfact code validate sidecar run my-project /path/to/repo -``` - -### AI IDE setup - -```bash -specfact init ide -specfact init ide --ide cursor -specfact init ide --ide vscode +specfact init --profile solo-developer +specfact code review run --path . --scope full ``` -`specfact init ide` discovers prompt resources from installed workflow modules and exports them to -your IDE. If module prompt payloads are not installed yet, the CLI uses packaged fallback resources. - -## Choose Your Path - -### Greenfield and AI-assisted delivery - -Use SpecFact as the validation layer around fast-moving implementation work. - -Start with: -- `uvx specfact-cli init --profile solo-developer` then `uvx specfact-cli code review run --path . --scope full` (see **Start Here** above) -- `specfact code validate sidecar init /path/to/repo` -- `specfact code validate sidecar run /path/to/repo` +The sample output comes from a pinned capture against `nold-ai/specfact-demo-repo`. Reproduce it with `docs/_support/readme-first-contact/capture-readme-output.sh`; capture metadata lives alongside the raw logs in `docs/_support/readme-first-contact/sample-output/`. -### Brownfield and reverse engineering +## What SpecFact does -Use SpecFact to understand an existing system before you change it, then hand that understanding -into spec-first tools such as OpenSpec or Spec-Kit. +- **Reviews AI-assisted changes deterministically** — compare code against contracts, clean-code rules, and policy gates +- **Extracts structure from existing code** — reverse-engineer brownfield repos before you change them +- **Blocks drift before merge** — use the same checks locally, in pre-commit, and in CI +- **Links backlog intent to code reality** — connect backlog, specs, validation, and implementation +- **Stays local-first** — no cloud account, no vendor lock-in, no built-in model dependency -Start with: -- `specfact code import my-project --repo .` -- `specfact project snapshot --bundle my-project` -- `specfact code validate sidecar init my-project /path/to/repo` -- `specfact code validate sidecar run my-project /path/to/repo` - -### Backlog to code alignment - -Use SpecFact when the problem is not only code quality, but drift between expectations and delivery. -Backlog commands require a backlog-enabled profile or installed backlog bundle before the workflow -commands are available. - -Start with: -- `specfact init --profile backlog-team` -- `specfact backlog ceremony standup ...` -- `specfact backlog ceremony refinement ...` -- `specfact backlog verify-readiness --bundle ` - -### Team and policy enforcement - -Use SpecFact when multiple developers and AI IDEs need consistent checks and review behavior. - -Start with: -- `specfact backlog verify-readiness --bundle ` -- `specfact govern ...` -- CI validation flows that keep the same rules active outside local development - -## How do I get started if I want more? - -**Next steps** - -- **[Core CLI docs](docs/index.md)** for the core runtime, bootstrap, validation, and command topology -- **[Reference: command topology](docs/reference/commands.md)** for grouped command surfaces -- **[Canonical modules docs site](https://modules.specfact.io/)** for bundle-deep workflows - -## Documentation Topology - -`docs.specfact.io` is the canonical starting point for SpecFact. - -- Core CLI/runtime/platform documentation remains owned by `specfact-cli`. -- Module-specific deep docs are canonically owned by `specfact-cli-modules`. -- The live modules docs site is published at `https://modules.specfact.io/`. - -Use this repository's docs for the overall product story, runtime lifecycle, command topology, -trust model, and getting-started flow. Use the modules docs site when you want deeper workflow, -adapter, and module-authoring guidance. - -### Migration Note (Flat Commands Removed) - -As of `0.40.0`, flat root commands are removed. Use grouped commands: +## What is SpecFact? -- `specfact validate ...` -> `specfact code validate ...` -- `specfact plan ...` -> removed; use `specfact project devops-flow` or `specfact project snapshot` -- `specfact policy ...` -> removed; use `specfact backlog verify-readiness` +SpecFact is a local CLI for catching backlog/spec/code drift before it becomes expensive. It gives solo developers, legacy maintainers, and teams a validation layer around AI-assisted delivery, brownfield reverse engineering, and contract-first reviews. -### Backlog Bridge (60 seconds) +It exists because delivery drifts in predictable ways: -SpecFact's USP is closing the drift gap between **backlog -> specs -> code**. -These commands require the backlog bundle to be installed first, for example via -`specfact init --profile backlog-team` or `specfact init --install backlog`. +- AI-assisted code lands faster than validation catches up +- brownfield systems rarely have trustworthy up-to-date specs +- backlog intent gets reinterpreted before it reaches code +- teams need the same review rules across IDEs, CI, and pull requests -```bash -# 1) Initialize backlog config + field mapping -specfact backlog init-config --force -specfact backlog map-fields --provider ado --ado-org --ado-project "" +## Add SpecFact to your workflow -# 2) Run ceremony workflows on real backlog scope -specfact backlog ceremony standup ado --ado-org --ado-project "" --state any --assignee any --limit 5 -specfact backlog ceremony refinement ado --ado-org --ado-project "" --id --preview +**Pre-commit hook** -# 3) Keep backlog + spec intent aligned (avoid silent drift) -specfact backlog verify-readiness --bundle +```yaml +- repo: https://github.com/nold-ai/specfact-cli + rev: v0.45.1 + hooks: + - id: specfact-smart-checks ``` -Compatibility note: `specfact backlog daily ...` and `specfact backlog refine ...` still exist, but the preferred entrypoints are `backlog ceremony standup` and `backlog ceremony refinement`. - -For GitHub, replace adapter/org/project with: -`specfact backlog ceremony standup github --repo-owner --repo-name --state any --assignee any --limit 5` +**GitHub Actions** -**AI IDE quick start** - -```bash -# In your IDE chat (Cursor, VS Code, Copilot, etc.) -/specfact.01-import my-project --repo . +```yaml +- name: SpecFact Gate + run: uvx specfact-cli@latest govern enforce stage --preset minimal ``` ---- +## How SpecFact is built -## Who It Is For +SpecFact uses the same discipline it asks you to trust: -- **Vibe coders and new builders** who want to ship fast with guardrails and confidence. -- **Legacy professionals** who want AI speed without lowering standards. -- **DevOps and engineering leaders** who need evidence and repeatable workflows. +1. Outside-in research on the workflow or drift problem +2. Public **OpenSpec** proposal and spec deltas +3. TDD evidence before implementation +4. Dogfooding with `specfact code review` +5. Format, type-check, contract-test, and docs quality gates +6. PR review with evidence artifacts +7. Release through the same reproducible CLI paths ---- +## For teams and organizations -## The Missing Link (Coder + DevOps Bridge) +SpecFact still scales beyond the solo-developer entry path: -Most tools help **either** coders **or** agile teams. SpecFact does both: +- **Backlog + ceremony workflows** for GitHub, Azure DevOps, Jira, and Linear +- **DoR/DoD and policy enforcement** for teams that need repeatable gates +- **Evidence-backed PR review** with the same checks used locally +- **CI/CD adoption path** that keeps validation deterministic instead of model-driven -- **Backlog sync that is actually strong**: round-trip sync + refinement with GitHub, Azure DevOps, Jira, Linear. -- **Ceremony support teams can run**: standup, refinement, sprint planning, flow metrics (Scrum/Kanban/SAFe). -- **Policy + validation**: DoR/DoD/flow checks plus contract enforcement for production-grade stability. +Start with: -Recommended command entrypoints: - `specfact backlog ceremony standup ...` - `specfact backlog ceremony refinement ...` - `specfact backlog verify-readiness --bundle ` -- `specfact backlog analyze-deps --bundle ` - -What the backlog readiness and ceremony commands do in practice: -- Turns team agreements (DoR, DoD, flow checks) into executable checks against your real backlog data. -- Shows exactly what is missing per item (for example missing acceptance criteria or definition of done). -- Runs structured ceremony workflows directly from the CLI. - -Start with: -- `specfact backlog ceremony standup --help` -- `specfact backlog verify-readiness --bundle ` -- `specfact backlog refine --help` - ---- - -## Core CLI Features - -The `specfact-cli` repository owns the platform-level features that every workflow bundle depends on: - -- `specfact init` for first-run bootstrap and IDE setup. -- `specfact module` for install/list/show/search/enable/disable/uninstall/upgrade lifecycle flows. -- `specfact upgrade` for CLI upgrades. -- Runtime contracts, registry bootstrapping, trust checks, logging, and shared orchestration. -- The grouped command surface that mounts installed bundle families under `project`, `backlog`, `code`, `spec`, and `govern`. - -## Official Modules Integration +- `specfact govern ...` -Official workflow behavior now ships from `nold-ai/specfact-cli-modules`. -The core CLI discovers those bundle packages, mounts their command groups, and enforces compatibility, trust, and lifecycle rules. +## Module system -Installed official bundles expose the current grouped surfaces: - -- `specfact project ...` -- `specfact backlog ...` -- `specfact code ...` -- `specfact spec ...` -- `specfact govern ...` +Official workflow behavior ships from `nold-ai/specfact-cli-modules`. The core CLI owns bootstrap, runtime contracts, trust checks, logging, and the grouped command surface. Installed modules add families such as `project`, `backlog`, `code`, `spec`, and `govern`. Install examples: @@ -296,34 +134,33 @@ Install examples: specfact module install nold-ai/specfact-project specfact module install nold-ai/specfact-backlog specfact module install nold-ai/specfact-codebase -specfact module install nold-ai/specfact-spec +specfact module install nold-ai/specfact-code-review specfact module install nold-ai/specfact-govern ``` -If startup warns that bundled modules are missing or outdated, run: +If startup warns that modules are missing or outdated, run: ```bash specfact module init --scope project specfact module init ``` -Use this repo's docs for the current CLI/runtime release branch and the overall process of how official modules plug into the core platform. -Use `https://modules.specfact.io/` for the in-depth backlog, project, spec, govern, adapter, and module-authoring guides. +## Documentation topology ---- +`docs.specfact.io` is the canonical starting point for SpecFact. -## How It Works (High Level) +- Core CLI/runtime/platform documentation remains owned by `specfact-cli` +- Module-specific deep docs are canonically owned by `specfact-cli-modules` +- The live modules docs site is published at `https://modules.specfact.io/` -1. **Bootstrap**: use **uvx** or **pip**, then `init --profile` to install the bundles you need (for example `solo-developer` for a scored **code review** first). -2. **Review or analyze**: run **`code review run`** on a repo, or import code and snapshot state for deeper workflows. -3. **Sync**: connect backlog systems or sync external artifacts into project bundles when you are ready. -4. **Validate**: run spec, governance, and sidecar validation flows before implementation or release. -5. **Iterate safely**: use module-provided workflows while the core runtime keeps command mounting, trust, and lifecycle consistent. +Use this repository's docs for the product story, runtime lifecycle, command topology, trust model, and getting-started flow. Use the modules docs site when you want deeper workflow, adapter, and module-authoring guidance. -## Where SpecFact Fits +## How do I get started if I want more? -SpecFact complements your stack rather than replacing it. +Next steps: -- **Spec-Kit/OpenSpec** for authoring and change tracking -- **Backlog tools** for planning and delivery -- **CI/CD** for enforcement and regression prevention +- **[Core CLI docs](docs/index.md)** for the core runtime, bootstrap, and validation flow +- **[Installation guide](https://docs.specfact.io/getting-started/installation/)** for uvx-first and persistent CLI setup +- **[Quickstart](https://docs.specfact.io/getting-started/quickstart/)** for the first repo review path +- **[Reference: command topology](docs/reference/commands.md)** for grouped command surfaces +- **[Canonical modules docs site](https://modules.specfact.io/)** for bundle-deep workflows diff --git a/docs/_support/readme-first-contact/capture-readme-output.sh b/docs/_support/readme-first-contact/capture-readme-output.sh new file mode 100755 index 00000000..90987874 --- /dev/null +++ b/docs/_support/readme-first-contact/capture-readme-output.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_VERSION="${CLI_VERSION:-0.45.1}" +REPO_SLUG="${REPO_SLUG:-nold-ai/specfact-demo-repo}" +CAPTURE_REF="${CAPTURE_REF:-${CAPTURE_COMMIT:-2b5ba8cd57d16c1a1f24463a297fdb28fbede123}}" +WORK_DIR="${WORK_DIR:-/tmp/specfact-demo-repo}" +CAPTURE_HOME="${CAPTURE_HOME:-/tmp/specfact-readme-capture-home}" +OUTPUT_DIR="${OUTPUT_DIR:-$REPO_ROOT/docs/_support/readme-first-contact/sample-output}" +RAW_OUTPUT_PATH="${RAW_OUTPUT_PATH:-$OUTPUT_DIR/review-output.txt}" +SUMMARY_PATH="${SUMMARY_PATH:-$OUTPUT_DIR/capture-metadata.txt}" +INIT_OUTPUT_PATH="${INIT_OUTPUT_PATH:-$OUTPUT_DIR/init-output.txt}" + +mkdir -p "$OUTPUT_DIR" +rm -rf "$CAPTURE_HOME" +mkdir -p "$CAPTURE_HOME" + +if [[ ! -d "$WORK_DIR/.git" ]]; then + rm -rf "$WORK_DIR" + gh repo clone "$REPO_SLUG" "$WORK_DIR" +fi + +git -C "$WORK_DIR" fetch --all --tags --prune +git -C "$WORK_DIR" checkout --force "$CAPTURE_REF" +git -C "$WORK_DIR" reset --hard "$CAPTURE_REF" + +export HOME="$CAPTURE_HOME" + +uvx --from "specfact-cli==$CLI_VERSION" specfact init --profile solo-developer \ + >"$INIT_OUTPUT_PATH" 2>&1 + +pushd "$WORK_DIR" >/dev/null +uvx \ + --from "specfact-cli==$CLI_VERSION" \ + --with ruff \ + --with radon \ + --with semgrep \ + --with basedpyright \ + --with pylint \ + --with crosshair-tool \ + specfact code review run --path . --scope full \ + >"$RAW_OUTPUT_PATH" 2>&1 +REVIEW_EXIT_CODE=$? +popd >/dev/null + +cat >"$SUMMARY_PATH" < │ +│ │ │ │ │ │ (Unknown | │ +│ │ │ │ │ │ None), (key: │ +│ │ │ │ │ │ Unknown, │ +│ │ │ │ │ │ default: │ +│ │ │ │ │ │ Unknown, /) │ +│ │ │ │ │ │ -> Unknown, │ +│ │ │ │ │ │ (key: │ +│ │ │ │ │ │ Unknown, │ +│ │ │ │ │ │ default: │ +│ │ │ │ │ │ _T@get, /) │ +│ │ │ │ │ │ -> (Unknown │ +│ │ │ │ │ │ | _T@get)]" │ +│ /tmp/specfa… │ 68 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 69 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 70 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 75 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "key" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "setdefault" │ +│ /tmp/specfa… │ 75 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "object" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "append" │ +│ /tmp/specfa… │ 78 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "item" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 79 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "story_id" │ +│ │ │ │ │ │ is Any │ +│ /tmp/specfa… │ 79 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "get" is Any │ +│ /tmp/specfa… │ 80 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "key" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "get" │ +│ /tmp/specfa… │ 83 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "map" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "__init__" │ +│ /tmp/specfa… │ 86 │ basedpyright │ reportUnknow… │ warning │ Type of │ +│ │ │ │ │ │ "append" is │ +│ │ │ │ │ │ partially │ +│ │ │ │ │ │ unknown │ +│ │ │ │ │ │   Type of │ +│ │ │ │ │ │ "append" is │ +│ │ │ │ │ │ "(object: │ +│ │ │ │ │ │ Unknown, /) │ +│ │ │ │ │ │ -> None" │ +│ /tmp/specfa… │ 89 │ basedpyright │ reportUnknow… │ warning │ Argument │ +│ │ │ │ │ │ type is │ +│ │ │ │ │ │ partially │ +│ │ │ │ │ │ unknown │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "obj" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "len" │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ type is │ +│ │ │ │ │ │ "list[Unknow │ +│ │ │ │ │ │ n]" │ +│ /tmp/specfa… │ 90 │ basedpyright │ reportUnknow… │ warning │ Argument │ +│ │ │ │ │ │ type is │ +│ │ │ │ │ │ partially │ +│ │ │ │ │ │ unknown │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "obj" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "len" │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ type is │ +│ │ │ │ │ │ "list[Unknow │ +│ │ │ │ │ │ n]" │ +│ /tmp/specfa… │ 92 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "obj" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "len" │ +│ /tmp/specfa… │ 92 │ basedpyright │ reportUnknow… │ warning │ Argument │ +│ │ │ │ │ │ type is │ +│ │ │ │ │ │ partially │ +│ │ │ │ │ │ unknown │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "obj" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "len" │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ type is │ +│ │ │ │ │ │ "list[Unknow │ +│ │ │ │ │ │ n]" │ +│ /tmp/specfa… │ 100 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 106 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 114 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "stories_dat │ +│ │ │ │ │ │ a" is Any │ +│ /tmp/specfa… │ 115 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "backlog_dat │ +│ │ │ │ │ │ a" is Any │ +│ /tmp/specfa… │ 116 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "policy_data │ +│ │ │ │ │ │ " is Any │ +│ /tmp/specfa… │ 117 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "stories" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 117 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "get" is Any │ +│ /tmp/specfa… │ 128 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "required_ap │ +│ │ │ │ │ │ i_version" │ +│ │ │ │ │ │ is Any │ +│ /tmp/specfa… │ 128 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "get" is Any │ +│ /tmp/specfa… │ 129 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 133 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "story" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 133 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "iterable" │ +│ │ │ │ │ │ in function │ +│ │ │ │ │ │ "__new__" │ +│ /tmp/specfa… │ 134 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "story_id" │ +│ │ │ │ │ │ is Any │ +│ /tmp/specfa… │ 136 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "get" is Any │ +│ /tmp/specfa… │ 148 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "contract_re │ +│ │ │ │ │ │ l" is Any │ +│ /tmp/specfa… │ 148 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "get" is Any │ +│ /tmp/specfa… │ 161 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "contract_pa │ +│ │ │ │ │ │ th" is Any │ +│ /tmp/specfa… │ 162 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "exists" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 174 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "contract_pa │ +│ │ │ │ │ │ yload" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 174 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "path" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "_read_json" │ +│ /tmp/specfa… │ 175 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "contract" │ +│ │ │ │ │ │ in function │ +│ │ │ │ │ │ "_validate_c │ +│ │ │ │ │ │ ontract" │ +│ /tmp/specfa… │ 175 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "required_ap │ +│ │ │ │ │ │ i_version" │ +│ │ │ │ │ │ in function │ +│ │ │ │ │ │ "_validate_c │ +│ │ │ │ │ │ ontract" │ +│ /tmp/specfa… │ 204 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "evidence_pa │ +│ │ │ │ │ │ yload" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 205 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "get" is Any │ +│ /tmp/specfa… │ 223 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "backlog_dat │ +│ │ │ │ │ │ a" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "_sync_backl │ +│ │ │ │ │ │ og" │ +│ /tmp/specfa… │ 227 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "obj" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "len" │ +│ /tmp/specfa… │ 235 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "get" is Any │ +│ /tmp/specfa… │ 235 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "x" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "__new__" │ +│ /tmp/specfa… │ 297 │ basedpyright │ reportImplic… │ warning │ Implicit │ +│ │ │ │ │ │ string │ +│ │ │ │ │ │ concatenatio │ +│ │ │ │ │ │ n not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 303 │ basedpyright │ reportUnused… │ warning │ Result of │ +│ │ │ │ │ │ call │ +│ │ │ │ │ │ expression │ +│ │ │ │ │ │ is of type │ +│ │ │ │ │ │ "int" and is │ +│ │ │ │ │ │ not used; │ +│ │ │ │ │ │ assign to │ +│ │ │ │ │ │ variable "_" │ +│ │ │ │ │ │ if this is │ +│ │ │ │ │ │ intentional │ +│ /tmp/specfa… │ 24 │ basedpyright │ reportUnanno… │ warning │ Type │ +│ │ │ │ │ │ annotation │ +│ │ │ │ │ │ for │ +│ │ │ │ │ │ attribute │ +│ │ │ │ │ │ `name` is │ +│ │ │ │ │ │ required │ +│ │ │ │ │ │ because this │ +│ │ │ │ │ │ class is not │ +│ │ │ │ │ │ decorated │ +│ │ │ │ │ │ with │ +│ │ │ │ │ │ `@final` │ +│ /tmp/specfa… │ 25 │ basedpyright │ reportUnanno… │ warning │ Type │ +│ │ │ │ │ │ annotation │ +│ │ │ │ │ │ for │ +│ │ │ │ │ │ attribute │ +│ │ │ │ │ │ `version` is │ +│ │ │ │ │ │ required │ +│ │ │ │ │ │ because this │ +│ │ │ │ │ │ class is not │ +│ │ │ │ │ │ decorated │ +│ │ │ │ │ │ with │ +│ │ │ │ │ │ `@final` │ +│ /tmp/specfa… │ 26 │ basedpyright │ reportUnanno… │ warning │ Type │ +│ │ │ │ │ │ annotation │ +│ │ │ │ │ │ for │ +│ │ │ │ │ │ attribute │ +│ │ │ │ │ │ `api_version │ +│ │ │ │ │ │ ` is │ +│ │ │ │ │ │ required │ +│ │ │ │ │ │ because this │ +│ │ │ │ │ │ class is not │ +│ │ │ │ │ │ decorated │ +│ │ │ │ │ │ with │ +│ │ │ │ │ │ `@final` │ +│ /tmp/specfa… │ 27 │ basedpyright │ reportUnanno… │ warning │ Type │ +│ │ │ │ │ │ annotation │ +│ │ │ │ │ │ for │ +│ │ │ │ │ │ attribute │ +│ │ │ │ │ │ `scope` is │ +│ │ │ │ │ │ required │ +│ │ │ │ │ │ because this │ +│ │ │ │ │ │ class is not │ +│ │ │ │ │ │ decorated │ +│ │ │ │ │ │ with │ +│ │ │ │ │ │ `@final` │ +│ /tmp/specfa… │ 28 │ basedpyright │ reportUnanno… │ warning │ Type │ +│ │ │ │ │ │ annotation │ +│ │ │ │ │ │ for │ +│ │ │ │ │ │ attribute │ +│ │ │ │ │ │ `invariants_ │ +│ │ │ │ │ │ touched` is │ +│ │ │ │ │ │ required │ +│ │ │ │ │ │ because this │ +│ │ │ │ │ │ class is not │ +│ │ │ │ │ │ decorated │ +│ │ │ │ │ │ with │ +│ │ │ │ │ │ `@final` │ +│ /tmp/specfa… │ 29 │ basedpyright │ reportUnanno… │ warning │ Type │ +│ │ │ │ │ │ annotation │ +│ │ │ │ │ │ for │ +│ │ │ │ │ │ attribute │ +│ │ │ │ │ │ `side_effect │ +│ │ │ │ │ │ s` is │ +│ │ │ │ │ │ required │ +│ │ │ │ │ │ because this │ +│ │ │ │ │ │ class is not │ +│ │ │ │ │ │ decorated │ +│ │ │ │ │ │ with │ +│ │ │ │ │ │ `@final` │ +│ /tmp/specfa… │ 30 │ basedpyright │ reportUnanno… │ warning │ Type │ +│ │ │ │ │ │ annotation │ +│ │ │ │ │ │ for │ +│ │ │ │ │ │ attribute │ +│ │ │ │ │ │ `attestation │ +│ │ │ │ │ │ _hash` is │ +│ │ │ │ │ │ required │ +│ │ │ │ │ │ because this │ +│ │ │ │ │ │ class is not │ +│ │ │ │ │ │ decorated │ +│ │ │ │ │ │ with │ +│ │ │ │ │ │ `@final` │ +│ /tmp/specfa… │ 32 │ basedpyright │ reportUnused… │ warning │ "context" is │ +│ │ │ │ │ │ not accessed │ +│ /tmp/specfa… │ 32 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 35 │ basedpyright │ reportUnused… │ warning │ "context" is │ +│ │ │ │ │ │ not accessed │ +│ /tmp/specfa… │ 35 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 38 │ basedpyright │ reportUnused… │ warning │ "context" is │ +│ │ │ │ │ │ not accessed │ +│ /tmp/specfa… │ 38 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 41 │ basedpyright │ reportUnused… │ warning │ "context" is │ +│ │ │ │ │ │ not accessed │ +│ /tmp/specfa… │ 41 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 44 │ basedpyright │ reportUnused… │ warning │ "context" is │ +│ │ │ │ │ │ not accessed │ +│ /tmp/specfa… │ 44 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 47 │ basedpyright │ reportUnused… │ warning │ "context" is │ +│ │ │ │ │ │ not accessed │ +│ /tmp/specfa… │ 47 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 10 │ basedpyright │ reportDeprec… │ warning │ This type is │ +│ │ │ │ │ │ deprecated │ +│ │ │ │ │ │ as of Python │ +│ │ │ │ │ │ 3.9; use │ +│ │ │ │ │ │ "collections │ +│ │ │ │ │ │ .abc.Iterabl │ +│ │ │ │ │ │ e" instead │ +│ /tmp/specfa… │ 15 │ basedpyright │ reportAny │ warning │ Return type │ +│ │ │ │ │ │ is Any │ +│ /tmp/specfa… │ 15 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 17 │ basedpyright │ reportAny │ warning │ Return type │ +│ │ │ │ │ │ is Any │ +│ /tmp/specfa… │ 34 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "value" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 35 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "object" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "isclass" │ +│ /tmp/specfa… │ 36 │ basedpyright │ reportUnknow… │ warning │ Type of │ +│ │ │ │ │ │ "append" is │ +│ │ │ │ │ │ partially │ +│ │ │ │ │ │ unknown │ +│ │ │ │ │ │   Type of │ +│ │ │ │ │ │ "append" is │ +│ │ │ │ │ │ "(object: │ +│ │ │ │ │ │ Unknown, /) │ +│ │ │ │ │ │ -> None" │ +│ /tmp/specfa… │ 41 │ basedpyright │ reportUnknow… │ warning │ Argument │ +│ │ │ │ │ │ type is │ +│ │ │ │ │ │ partially │ +│ │ │ │ │ │ unknown │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "obj" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "len" │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ type is │ +│ │ │ │ │ │ "list[Unknow │ +│ │ │ │ │ │ n]" │ +│ /tmp/specfa… │ 42 │ basedpyright │ reportUnknow… │ warning │ Type of │ +│ │ │ │ │ │ "sort" is │ +│ │ │ │ │ │ partially │ +│ │ │ │ │ │ unknown │ +│ │ │ │ │ │   Type of │ +│ │ │ │ │ │ "sort" is │ +│ │ │ │ │ │ "Overload[(* │ +│ │ │ │ │ │ , key: None │ +│ │ │ │ │ │ = None, │ +│ │ │ │ │ │ reverse: │ +│ │ │ │ │ │ bool = │ +│ │ │ │ │ │ False) -> │ +│ │ │ │ │ │ None, (*, │ +│ │ │ │ │ │ key: │ +│ │ │ │ │ │ (Unknown) -> │ +│ │ │ │ │ │ (SupportsDun │ +│ │ │ │ │ │ derLT[Any] | │ +│ │ │ │ │ │ SupportsDund │ +│ │ │ │ │ │ erGT[Any]), │ +│ │ │ │ │ │ reverse: │ +│ │ │ │ │ │ bool = │ +│ │ │ │ │ │ False) -> │ +│ │ │ │ │ │ None]" │ +│ /tmp/specfa… │ 42 │ basedpyright │ reportUnknow… │ warning │ Type of │ +│ │ │ │ │ │ parameter │ +│ │ │ │ │ │ "cls" is │ +│ │ │ │ │ │ unknown │ +│ /tmp/specfa… │ 42 │ basedpyright │ reportUnknow… │ warning │ Type of │ +│ │ │ │ │ │ "__name__" │ +│ │ │ │ │ │ is unknown │ +│ /tmp/specfa… │ 42 │ basedpyright │ reportUnknow… │ warning │ Return type │ +│ │ │ │ │ │ of lambda is │ +│ │ │ │ │ │ unknown │ +│ /tmp/specfa… │ 44 │ basedpyright │ reportUnknow… │ warning │ Return type │ +│ │ │ │ │ │ is unknown │ +│ /tmp/specfa… │ 51 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 55 │ basedpyright │ reportUnused… │ warning │ Result of │ +│ │ │ │ │ │ call │ +│ │ │ │ │ │ expression │ +│ │ │ │ │ │ is of type │ +│ │ │ │ │ │ "object" and │ +│ │ │ │ │ │ is not used; │ +│ │ │ │ │ │ assign to │ +│ │ │ │ │ │ variable "_" │ +│ │ │ │ │ │ if this is │ +│ │ │ │ │ │ intentional │ +│ /tmp/specfa… │ 67 │ basedpyright │ reportImplic… │ warning │ Implicit │ +│ │ │ │ │ │ string │ +│ │ │ │ │ │ concatenatio │ +│ │ │ │ │ │ n not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 70 │ basedpyright │ reportUnnece… │ warning │ Unnecessary │ +│ │ │ │ │ │ isinstance │ +│ │ │ │ │ │ call; "list" │ +│ │ │ │ │ │ is always an │ +│ │ │ │ │ │ instance of │ +│ │ │ │ │ │ "list[Unknow │ +│ │ │ │ │ │ n]" │ +│ /tmp/specfa… │ 72 │ basedpyright │ reportUnnece… │ warning │ Unnecessary │ +│ │ │ │ │ │ isinstance │ +│ │ │ │ │ │ call; │ +│ │ │ │ │ │ "list[Unknow │ +│ │ │ │ │ │ n]" is │ +│ │ │ │ │ │ always an │ +│ │ │ │ │ │ instance of │ +│ │ │ │ │ │ "list[Unknow │ +│ │ │ │ │ │ n]" │ +│ /tmp/specfa… │ 72 │ basedpyright │ reportUnknow… │ warning │ Type of │ +│ │ │ │ │ │ "invariants_ │ +│ │ │ │ │ │ touched" is │ +│ │ │ │ │ │ partially │ +│ │ │ │ │ │ unknown │ +│ │ │ │ │ │   Type of │ +│ │ │ │ │ │ "invariants_ │ +│ │ │ │ │ │ touched" is │ +│ │ │ │ │ │ "list[Unknow │ +│ │ │ │ │ │ n]" │ +│ /tmp/specfa… │ 74 │ basedpyright │ reportUnnece… │ warning │ Unnecessary │ +│ │ │ │ │ │ isinstance │ +│ │ │ │ │ │ call; │ +│ │ │ │ │ │ "list[Unknow │ +│ │ │ │ │ │ n]" is │ +│ │ │ │ │ │ always an │ +│ │ │ │ │ │ instance of │ +│ │ │ │ │ │ "list[Unknow │ +│ │ │ │ │ │ n]" │ +│ /tmp/specfa… │ 74 │ basedpyright │ reportUnknow… │ warning │ Type of │ +│ │ │ │ │ │ "side_effect │ +│ │ │ │ │ │ s" is │ +│ │ │ │ │ │ partially │ +│ │ │ │ │ │ unknown │ +│ │ │ │ │ │   Type of │ +│ │ │ │ │ │ "side_effect │ +│ │ │ │ │ │ s" is │ +│ │ │ │ │ │ "list[Unknow │ +│ │ │ │ │ │ n]" │ +│ /tmp/specfa… │ 77 │ basedpyright │ reportUnnece… │ warning │ Unnecessary │ +│ │ │ │ │ │ isinstance │ +│ │ │ │ │ │ call; "list" │ +│ │ │ │ │ │ is always an │ +│ │ │ │ │ │ instance of │ +│ │ │ │ │ │ "list[Unknow │ +│ │ │ │ │ │ n]" │ +│ /tmp/specfa… │ 88 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 91 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "policy" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 92 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "allowed_sco │ +│ │ │ │ │ │ pes" is Any │ +│ /tmp/specfa… │ 92 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "get" is Any │ +│ /tmp/specfa… │ 95 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "allowed_sco │ +│ │ │ │ │ │ pes" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "validate_pl │ +│ │ │ │ │ │ ugin_metadat │ +│ │ │ │ │ │ a" │ +│ /tmp/specfa… │ 97 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 108 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "append" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 113 │ basedpyright │ reportUnused… │ warning │ Result of │ +│ │ │ │ │ │ call │ +│ │ │ │ │ │ expression │ +│ │ │ │ │ │ is of type │ +│ │ │ │ │ │ "object" and │ +│ │ │ │ │ │ is not used; │ +│ │ │ │ │ │ assign to │ +│ │ │ │ │ │ variable "_" │ +│ │ │ │ │ │ if this is │ +│ │ │ │ │ │ intentional │ +│ /tmp/specfa… │ 133 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 209 │ basedpyright │ reportUnused… │ warning │ Result of │ +│ │ │ │ │ │ call │ +│ │ │ │ │ │ expression │ +│ │ │ │ │ │ is of type │ +│ │ │ │ │ │ "int" and is │ +│ │ │ │ │ │ not used; │ +│ │ │ │ │ │ assign to │ +│ │ │ │ │ │ variable "_" │ +│ │ │ │ │ │ if this is │ +│ │ │ │ │ │ intentional │ +│ /tmp/specfa… │ 20 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "report" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 24 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "first" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "assertAlmos │ +│ │ │ │ │ │ tEqual" │ +│ /tmp/specfa… │ 29 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "first" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "assertAlmos │ +│ │ │ │ │ │ tEqual" │ +│ /tmp/specfa… │ 39 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "backlog_syn │ +│ │ │ │ │ │ c" is Any │ +│ /tmp/specfa… │ 43 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "item" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 44 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "label" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 44 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "get" is Any │ +│ /tmp/specfa… │ 56 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "report" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 60 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "first" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "assertAlmos │ +│ │ │ │ │ │ tEqual" │ +│ /tmp/specfa… │ 65 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "first" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "assertAlmos │ +│ │ │ │ │ │ tEqual" │ +│ /tmp/specfa… │ 72 │ basedpyright │ reportUnused… │ warning │ Result of │ +│ │ │ │ │ │ call │ +│ │ │ │ │ │ expression │ +│ │ │ │ │ │ is of type │ +│ │ │ │ │ │ "main" and │ +│ │ │ │ │ │ is not used; │ +│ │ │ │ │ │ assign to │ +│ │ │ │ │ │ variable "_" │ +│ │ │ │ │ │ if this is │ +│ │ │ │ │ │ intentional │ +│ /tmp/specfa… │ 23 │ basedpyright │ reportUnused… │ warning │ Result of │ +│ │ │ │ │ │ call │ +│ │ │ │ │ │ expression │ +│ │ │ │ │ │ is of type │ +│ │ │ │ │ │ "int" and is │ +│ │ │ │ │ │ not used; │ +│ │ │ │ │ │ assign to │ +│ │ │ │ │ │ variable "_" │ +│ │ │ │ │ │ if this is │ +│ │ │ │ │ │ intentional │ +│ /tmp/specfa… │ 45 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "error" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 49 │ basedpyright │ reportUnused… │ warning │ Result of │ +│ │ │ │ │ │ call │ +│ │ │ │ │ │ expression │ +│ │ │ │ │ │ is of type │ +│ │ │ │ │ │ "main" and │ +│ │ │ │ │ │ is not used; │ +│ │ │ │ │ │ assign to │ +│ │ │ │ │ │ variable "_" │ +│ │ │ │ │ │ if this is │ +│ │ │ │ │ │ intentional │ +└──────────────┴──────┴──────────────┴───────────────┴──────────┴──────────────┘ +Verdict: FAIL | CI exit: 1 | Score: 0 | Reward delta: -80 +Review completed with 217 findings (2 blocking). + +✓ Finished: 2026-04-03 20:55:34 | Duration: 5.98s diff --git a/docs/index.md b/docs/index.md index d8d81fb9..fa4a1063 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,14 +17,19 @@ exempt_reason: "" # SpecFact CLI Documentation -**Point SpecFact at your code. Get a score and a list of what to fix.** No week-long setup — start with two commands, then add IDE prompts, backlog workflows, or CI gates when you want more. +**Review AI-assisted code against your own contracts.** +**Catch drift before it reaches PR or main.** + +Point SpecFact at your repo, get a scored review with file-level findings, then go deeper into +backlog, specs, and CI when you need more control. ```bash uvx specfact-cli init --profile solo-developer uvx specfact-cli code review run --path . --scope full ``` -You should see a **Verdict** (PASS/FAIL), a **Score**, and a list of findings (for example dozens of categorized items on a real repo). That is the fastest way to see SpecFact on an existing project. [Read the full quickstart →](/getting-started/quickstart/) +You should see a **Verdict**, a **Score**, and a list of findings on a real repo. That is the +fastest way to see SpecFact on existing code. [Read the full quickstart →](/getting-started/quickstart/) SpecFact does **not** include built-in AI. It pairs deterministic CLI commands with your chosen IDE and copilot so fast-moving work has a stronger validation and alignment layer around it. diff --git a/openspec/CHANGE_ORDER.md b/openspec/CHANGE_ORDER.md index c7e71951..2564bf55 100644 --- a/openspec/CHANGE_ORDER.md +++ b/openspec/CHANGE_ORDER.md @@ -127,6 +127,7 @@ The 2026-03-22 clean-code plan adds one new cross-repo change pair and re-sequen | docs | 09 | docs-13-core-nav-search-theme-roles | [#458](https://github.com/nold-ai/specfact-cli/issues/458) | docs-05-core-site-ia-restructure; docs-07-core-handoff-conversion; docs-12-docs-validation-ci; modules-repo/docs-13-nav-search-theme-roles (design parity only, no content ownership coupling) | | docs | 10 | docs-14-first-contact-story-and-onboarding (in progress) | [#466](https://github.com/nold-ai/specfact-cli/issues/466) | docs-05-core-site-ia-restructure ✅; docs-07-core-handoff-conversion ✅; docs-12-docs-validation-ci ✅; docs-13-core-nav-search-theme-roles ✅; Parent Feature: [#356](https://github.com/nold-ai/specfact-cli/issues/356) | | docs | 11 | docs-new-user-onboarding | [#476](https://github.com/nold-ai/specfact-cli/issues/476) | Parent Feature: [#356](https://github.com/nold-ai/specfact-cli/issues/356); related [#466](https://github.com/nold-ai/specfact-cli/issues/466); vibe-coder uvx hero + CLI wow-path fixes | +| docs | 12 | readme-star-conversion-01 | pending | blocked by `docs-new-user-onboarding` [#476](https://github.com/nold-ai/specfact-cli/issues/476); README-first proof and adoption restructuring | ### Docs refactoring plan addendum (2026-03-23) diff --git a/openspec/changes/readme-star-conversion-01/.openspec.yaml b/openspec/changes/readme-star-conversion-01/.openspec.yaml new file mode 100644 index 00000000..c430c5fa --- /dev/null +++ b/openspec/changes/readme-star-conversion-01/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-04-03 diff --git a/openspec/changes/readme-star-conversion-01/TDD_EVIDENCE.md b/openspec/changes/readme-star-conversion-01/TDD_EVIDENCE.md new file mode 100644 index 00000000..183b1e39 --- /dev/null +++ b/openspec/changes/readme-star-conversion-01/TDD_EVIDENCE.md @@ -0,0 +1,74 @@ +# TDD evidence — readme-star-conversion-01 + +## 2026-04-03 (pre-implementation failing run) + +### Timestamp + +- `2026-04-03 20:08:28 +0000` + +### Command run + +- `hatch run pytest tests/unit/docs/test_wow_entrypoint_contract.py tests/unit/docs/test_first_contact_story.py tests/unit/docs/test_release_docs_parity.py -q --no-cov` + +### Result + +- **Failed as expected** before README changes + +### Failure summary + +- README still opens with the older platform-first "validation and alignment layer" framing instead + of the new proof-first developer hook +- README does not yet include the required sample output block, CTA, trust section, or reordered + top-level sections +- `docs/_support/readme-first-contact/capture-readme-output.sh` and + `docs/_support/readme-first-contact/sample-output/` do not exist yet +- `docs/index.md` has not yet been aligned to the new README hook / subhook wording + +### Evidence notes + +- Failing test output captured from the command above and used as the pre-implementation baseline +- Post-implementation passing evidence will be appended after the README, docs, and evidence assets + are updated + +## 2026-04-03 (post-implementation passing run) + +### Timestamp + +- `2026-04-03 20:20:08 +0000` + +### Commands run + +- `hatch run pytest tests/unit/docs/test_wow_entrypoint_contract.py tests/unit/docs/test_first_contact_story.py tests/unit/docs/test_release_docs_parity.py -q --no-cov` +- `hatch run yaml-lint` +- `hatch run format` +- `hatch run type-check` +- `hatch run contract-test` +- `hatch run specfact code review run --json --out .specfact/code-review.json` +- `hatch run specfact code review run tests/unit/docs/test_wow_entrypoint_contract.py tests/unit/docs/test_first_contact_story.py tests/unit/docs/test_release_docs_parity.py --json --out .specfact/code-review-docs-tests.json` +- `docs/_support/readme-first-contact/capture-readme-output.sh` + +### Result + +- **Passed** for the README/docs proof-first contract and supporting quality gates + +### Passing summary + +- `README.md` now leads with the proof-first hook, runnable quickstart, real sample-output block, CTA, + workflow snippet, trust section, team section, and deferred module/documentation detail +- `docs/index.md` now shares the same first-contact hook and runnable quickstart ordering +- Reproducible capture assets exist at `docs/_support/readme-first-contact/sample-output/` and are + generated by `docs/_support/readme-first-contact/capture-readme-output.sh` +- The capture script now pins `nold-ai/specfact-demo-repo` to commit + `2b5ba8cd57d16c1a1f24463a297fdb28fbede123` so README evidence stays deterministic across reruns +- Focused docs contract tests and focused SpecFact review checks for the changed docs test files pass + +### Environment notes + +- `hatch run smart-test` still fails in this environment for an unrelated repository baseline issue: + `tests/integration/test_command_package_runtime_validation.py` raises `IndexError: 2` during test + collection before executing this change's paths +- `hatch run specfact code review run --json --out .specfact/code-review.json --scope full` writes the + required `.specfact/code-review.json` artifact but exits non-zero because the repository-wide review + surface already contains unrelated findings; a focused review of the changed docs test files passes and + was recorded above +- `openspec validate readme-star-conversion-01 --strict` now passes after installing the OpenSpec CLI diff --git a/openspec/changes/readme-star-conversion-01/proposal.md b/openspec/changes/readme-star-conversion-01/proposal.md new file mode 100644 index 00000000..c62c7e07 --- /dev/null +++ b/openspec/changes/readme-star-conversion-01/proposal.md @@ -0,0 +1,93 @@ +# Change: readme-star-conversion-01 + +## Why + +The repository README still opens with platform and governance language before showing a runnable +command or a visible proof point. First-time visitors have to scroll past conceptual explanation +before they see what SpecFact actually does on a real repository. + +That is a conversion problem for the audience most likely to star or try the project: + +- Python developers who want a concrete CLI outcome fast +- solo engineers and vibe coders who evaluate tools by running one command +- OSS visitors who decide in seconds whether a repo looks useful or too enterprise-heavy + +The current README already contains the uvx-first review path, but it appears only after multiple +sections of framing. The README also lacks a real terminal output block that proves what the tool +returns. A first-contact surface that does not show input -> output wastes the strongest available +trust signal for an OSS CLI. + +This change restructures the README so the first screen answers four questions immediately: + +1. What does this do? +2. What does the output look like? +3. How do I try it right now? +4. Why should I care? + +## What Changes + +- Rewrite the README top section around a proof-first hero: + - concrete one-line value proposition for developers + - badges directly below the title + - uvx quickstart within the first screen + - real sample output block directly after the quickstart + - visible CTA to star the repo if the output is useful +- Add a reproducible capture script and checked-in evidence bundle for the README sample output +- Reorder README sections so developer-first outcomes appear above enterprise and module-system + detail +- Add a short "How SpecFact is built" section that turns the repo's OpenSpec + TDD workflow into a + trust signal +- Add copy-pasteable pre-commit and GitHub Actions snippets near the top-half of the README +- Keep team / enterprise content, module system detail, and documentation topology, but move them + below the proof-first onboarding flow + +## Capabilities + +### New Capabilities + +- `readme-first-contact`: The repository README acts as a proof-first OSS landing page that leads + with a concrete CLI outcome, a runnable command, and a visible output example +- `readme-output-evidence`: The README sample output is backed by a reproducible capture script and + stored evidence files, not hand-written terminal copy + +### Modified Capabilities + +- `first-contact-story`: README messaging shifts from platform-internal framing toward developer + outcomes while preserving truthful claims and deeper product context below the fold +- `entrypoint-onboarding`: The README must surface the runnable uvx entry path before architecture, + backlog, governance, or module-system sections + +## Impact + +**Files expected to change** + +- `README.md` +- `docs/index.md` if needed to preserve first-contact parity with the README +- `tests/unit/docs/test_wow_entrypoint_contract.py` +- `tests/unit/docs/test_first_contact_story.py` +- `docs/_support/readme-first-contact/capture-readme-output.sh` +- `docs/_support/readme-first-contact/sample-output/` +- `openspec/CHANGE_ORDER.md` + +**Behavior / documentation impact** + +- The repo's first screen becomes developer-first and proof-first +- Enterprise and governance content is preserved but moved below the fold +- The README gains a reproducible evidence path for the sample output block +- Docs parity is maintained so README and docs landing do not drift on the first-contact story + +**Rollback** + +- Restore the previous README structure and delete the evidence bundle references if the new layout + proves less clear or creates maintenance burden + + +--- + +## Source Tracking + + +- **GitHub Issue**: #481 +- **Issue URL**: +- **Last Synced Status**: proposed +- **Sanitized**: false \ No newline at end of file diff --git a/openspec/changes/readme-star-conversion-01/specs/readme-first-contact/spec.md b/openspec/changes/readme-star-conversion-01/specs/readme-first-contact/spec.md new file mode 100644 index 00000000..48a051fe --- /dev/null +++ b/openspec/changes/readme-star-conversion-01/specs/readme-first-contact/spec.md @@ -0,0 +1,65 @@ +## ADDED Requirements + +### Requirement: README hero leads with developer outcome and runnable proof + +The repository README SHALL lead with a developer-facing value proposition and a runnable quickstart +before platform, module-system, or governance explanation. A first-time visitor must be able to +understand what SpecFact does and how to try it without scrolling through architecture prose. + +#### Scenario: Visitor reads only the first screen of the README + +- **WHEN** a first-time visitor opens `README.md` +- **THEN** the title SHALL be followed by a short, concrete value statement for developers +- **AND** badges SHALL appear directly under the title / tagline block +- **AND** the README SHALL show a uvx-based quickstart in the first screenful +- **AND** the README SHALL include a visible CTA inviting the user to star the repo if the output is + useful + +#### Scenario: README defers enterprise framing + +- **WHEN** a user scans the README from the top +- **THEN** enterprise, governance, module-marketplace, and architecture framing SHALL appear only + after the quickstart, sample output, and concrete feature summary +- **AND** the README SHALL still preserve those deeper sections further down the page + +### Requirement: README includes real sample output with reproducible evidence + +The README SHALL include a sample output block derived from a real `specfact code review run` +capture, not hand-written illustrative output. The proof block SHALL help the reader mentally +simulate running the command. + +#### Scenario: Visitor evaluates whether the tool feels real + +- **WHEN** a visitor reads the quickstart section +- **THEN** the README SHALL display a sample output block that includes a verdict, a score or status, + file-level findings, and an evidence bundle path +- **AND** the output SHALL be sourced from a checked-in capture artifact under + `docs/_support/readme-first-contact/sample-output/` +- **AND** the repo SHALL include a capture script that reproduces or refreshes the stored output + +### Requirement: README explains value before deep product detail + +The README SHALL include an explicit "what it does" summary for developers before it explains +organizational workflows, module ownership, or documentation topology. + +#### Scenario: Visitor wants concrete reasons to care + +- **WHEN** the visitor reaches the section immediately after the quickstart and proof block +- **THEN** the README SHALL summarize the product in concrete outcome bullets +- **AND** those bullets SHALL focus on developer-visible outcomes such as review, validation, drift + detection, offline use, and backlog/code alignment +- **AND** deeper sections for pipeline story, team use, module system, and documentation topology + SHALL appear later on the page + +### Requirement: README shows adoption path beyond the hero + +The README SHALL provide copy-pasteable adoption examples for local hooks and CI without forcing the +reader into the full documentation set first. + +#### Scenario: Visitor wants to move from trial to workflow + +- **WHEN** a developer finds the quickstart useful +- **THEN** the README SHALL include a short pre-commit or GitHub Actions snippet near the upper half + of the document +- **AND** the README SHALL include a brief "How SpecFact is built" or equivalent trust section that + explains the repo's OpenSpec -> TDD -> quality-gate workflow in plain language diff --git a/openspec/changes/readme-star-conversion-01/tasks.md b/openspec/changes/readme-star-conversion-01/tasks.md new file mode 100644 index 00000000..535a3936 --- /dev/null +++ b/openspec/changes/readme-star-conversion-01/tasks.md @@ -0,0 +1,58 @@ +## 1. Branch and change setup + +- [x] 1.1 Work on managed cloud branch `cursor/readme-star-conversion-a583` +- [ ] 1.2 Create worktree from `origin/dev` for `cursor/readme-star-conversion-a583` + - cloud-managed branch is already active in this session, but AGENTS.md requires the worktree step to be tracked explicitly +- [ ] 1.3 Run `hatch env create` in the active worktree +- [ ] 1.4 Run pre-flight checks in the active worktree: + - `hatch run smart-test-status` + - `hatch run contract-test-status` +- [x] 1.5 Create OpenSpec change folder `openspec/changes/readme-star-conversion-01/` +- [x] 1.6 Add proposal and README-focused spec delta +- [x] 1.7 Validate change with OpenSpec CLI + +## 2. Tests first (strict TDD order) + +- [ ] 2.1 Update README contract tests to encode the new proof-first structure +- [ ] 2.2 Run targeted docs tests and capture a failing result before editing `README.md` +- [ ] 2.3 Record the failing command, timestamp, and failure summary in + `openspec/changes/readme-star-conversion-01/TDD_EVIDENCE.md` + +## 3. Evidence capture + +- [ ] 3.1 Add `docs/_support/readme-first-contact/capture-readme-output.sh` +- [ ] 3.2 Capture raw `specfact code review run` output against `specfact-demo-repo` +- [ ] 3.3 Store raw output and run metadata under `docs/_support/readme-first-contact/sample-output/` +- [ ] 3.4 Pin the CLI version used by the capture and document rerun steps in the evidence folder + +## 4. README restructure + +- [ ] 4.1 Rewrite the README top screen around a concrete hook, badges, quickstart, sample output, + and CTA +- [ ] 4.2 Add a concrete "What SpecFact does" summary near the top +- [ ] 4.3 Add pre-commit and GitHub Actions snippets in the upper half of the README +- [ ] 4.4 Add a short "How SpecFact is built" trust section +- [ ] 4.5 Move team / enterprise, module-system, and documentation-topology sections below the fold +- [ ] 4.6 Update `docs/index.md` only if needed to preserve first-contact parity with the README + +## 5. Verify and document + +- [ ] 5.1 Re-run the targeted docs tests and record passing evidence in + `openspec/changes/readme-star-conversion-01/TDD_EVIDENCE.md` +- [ ] 5.2 Run required quality gates for the changed files: + - `hatch run format` + - `hatch run type-check` + - `hatch run lint` + - `hatch run yaml-lint` + - `hatch run contract-test` + - `hatch run smart-test` +- [ ] 5.3 Refresh `.specfact/code-review.json` and resolve any findings +- [ ] 5.4 Run `hatch run ./scripts/verify-modules-signature.py --require-signature` +- [ ] 5.5 Update `openspec/CHANGE_ORDER.md` with this change entry + +## 6. Git and PR + +- [ ] 6.1 Commit the OpenSpec + README conversion change with a conventional commit message +- [ ] 6.2 Push branch `cursor/readme-star-conversion-a583` +- [ ] 6.3 Create or update the PR against `dev` +- [ ] 6.4 After merge, remove/prune the worktree per AGENTS.md cleanup policy diff --git a/tests/unit/docs/docs_test_constants.py b/tests/unit/docs/docs_test_constants.py new file mode 100644 index 00000000..2fb7859f --- /dev/null +++ b/tests/unit/docs/docs_test_constants.py @@ -0,0 +1,4 @@ +"""Shared constants for README/docs first-contact contract tests.""" + +HOOK = "Review AI-assisted code against your own contracts." +SUBHOOK = "Catch drift before it reaches PR or main." diff --git a/tests/unit/docs/test_first_contact_story.py b/tests/unit/docs/test_first_contact_story.py index 2dab1870..159a2d32 100644 --- a/tests/unit/docs/test_first_contact_story.py +++ b/tests/unit/docs/test_first_contact_story.py @@ -1,8 +1,6 @@ -"""Validate first-contact messaging across the core repo entry points. +"""Validate the README-first first-contact story across entrypoint surfaces.""" -These tests ensure the README, docs landing page, and contributor guidance all -present the same canonical product story and onboarding order. -""" +from __future__ import annotations import re from pathlib import Path @@ -10,6 +8,8 @@ import pytest +from tests.unit.docs.docs_test_constants import HOOK, SUBHOOK + REPO_ROOT = Path(__file__).resolve().parents[3] README = REPO_ROOT / "README.md" @@ -21,8 +21,6 @@ @pytest.fixture(scope="module", autouse=True) def _require_entrypoint_files() -> None: - """Skip the module if the docs entrypoint files are not present.""" - if not REPO_ROOT.exists(): pytest.skip(f"Repository root missing: expected at {REPO_ROOT}", allow_module_level=True) if not README.is_file(): @@ -34,173 +32,56 @@ def _require_entrypoint_files() -> None: def _read(path: Path) -> str: - """Return the UTF-8 text contents of a repository file. - - Args: - path: Path to the file to read. - - Returns: - File contents as a string. - """ - return path.read_text(encoding="utf-8") -def _assert_question_order(content: str, questions: list[str], surface: str) -> None: - """Assert that the first-contact questions appear in increasing order. - - Args: - content: Text content to inspect. - questions: Ordered question strings that must appear in sequence. - surface: Human-readable name of the file or surface being inspected. - """ - - indices: list[int] = [] - for question in questions: - index = content.find(question) - assert index != -1, f"Missing question {question!r} in {surface}" - indices.append(index) - - assert indices == sorted(indices), f"Questions are out of order in {surface}: {questions}" - - def _assert_contains_url_host(content: str, host: str, surface: str) -> None: - """Assert that a surface contains at least one absolute URL for the expected host. - - Args: - content: Text content to inspect. - host: Expected URL host name. - surface: Human-readable name of the file or surface being inspected. - """ - found_hosts = {urlparse(match.group(0).rstrip(".,;:")).netloc for match in ABSOLUTE_URL_RE.finditer(content)} assert host in found_hosts, f"Missing URL host {host!r} in {surface}; found hosts: {sorted(found_hosts)}" -def _assert_contains_any_phrase(content: str, phrases: tuple[str, ...], message: str) -> None: - """Assert that at least one of the candidate phrases appears in the content.""" +def test_readme_hero_uses_proof_first_story() -> None: + readme = _read(README) + try_it_idx = readme.lower().find("## try it in 60 seconds") + assert try_it_idx != -1 + first_screen = readme[:try_it_idx].lower() - lowered = content.lower() - assert any(phrase in lowered for phrase in phrases), message + assert HOOK in readme + assert SUBHOOK in readme + assert "## Try it in 60 seconds" in readme + assert "validation and alignment layer" not in first_screen + assert "swiss knife" not in first_screen -def test_readme_leads_with_validation_and_alignment_story() -> None: +def test_readme_prioritizes_quickstart_before_deep_sections() -> None: readme = _read(README) - readme_lower = readme.lower() - questions = [ - "What is SpecFact?", - "Why does it exist?", - "Why should I use it?", - "What do I get?", - "How do I get started?", - ] - - assert "validation and alignment layer" in readme - _assert_question_order(readme, questions, "README.md") - _assert_contains_any_phrase( - readme_lower, - ("ai-assisted", "vibe-coded", "ai-generated"), - "README.md must explain AI-assisted validation pressure in the why-story.", - ) - _assert_contains_any_phrase( - readme_lower, - ("brownfield", "reverse-engineer", "reverse-engineered"), - "README.md must explain the brownfield reverse-engineering pressure.", - ) - _assert_contains_any_phrase( - readme_lower, - ("i wanted x but got y", "backlog/spec/code drift", "drift between backlog"), - "README.md must explain backlog/spec/code drift as a reason SpecFact exists.", - ) - _assert_contains_any_phrase( - readme_lower, - ("policy enforcement", "enterprise policy", "ci/cd", "shared rules"), - "README.md must explain team and enterprise policy consistency pressure.", - ) - - -def test_readme_prioritizes_fast_start_over_docs_topology() -> None: - readme = _read(README) - - start_match = re.search(r"^#+\s*Start Here", readme, re.MULTILINE) - topology_match = re.search(r"^#+\s*Documentation Topology", readme, re.MULTILINE) - assert start_match is not None, "Missing Start Here heading in README.md" - assert topology_match is not None, "Missing Documentation Topology heading in README.md" - start_idx = start_match.start() - topology_idx = topology_match.start() - assert start_idx < topology_idx + try_it = readme.find("## Try it in 60 seconds") + teams = readme.find("## For teams and organizations") + modules = readme.find("## Module system") + topology = readme.find("## Documentation topology") + assert min(try_it, teams, modules, topology) != -1 + assert try_it < teams < modules < topology -def test_readme_routes_users_by_outcome() -> None: +def test_readme_includes_trust_and_pipeline_sections() -> None: readme = _read(README) - readme_lower = readme.lower() + assert "## How SpecFact is built" in readme + assert "OpenSpec" in readme + assert "TDD" in readme + assert "quality gates" in readme.lower() - assert "## Choose Your Path" in readme - assert "Greenfield and AI-assisted delivery" in readme - assert "Brownfield and reverse engineering" in readme - assert "Backlog to code alignment" in readme - _assert_contains_any_phrase( - readme_lower, - ("govern", "policy enforcement", "team and policy enforcement"), - "README.md must route users toward team and enterprise policy enforcement outcomes.", - ) - -def test_docs_index_matches_first_contact_story() -> None: +def test_docs_index_matches_proof_first_story() -> None: docs_index = _read(DOCS_INDEX) - docs_index_lower = docs_index.lower() - questions = [ - "What is SpecFact?", - "Why does it exist?", - "Why should I use it?", - "What do I get?", - "How to get started", - ] - - assert "validation and alignment layer" in docs_index - _assert_question_order(docs_index, questions, "docs/index.md") + assert HOOK in docs_index + assert SUBHOOK in docs_index + assert "## What is SpecFact?" in docs_index _assert_contains_url_host(docs_index, "modules.specfact.io", "docs/index.md") - _assert_contains_any_phrase( - docs_index_lower, - ("brownfield", "legacy code", "existing systems"), - "docs/index.md must describe the brownfield path.", - ) - _assert_contains_any_phrase( - docs_index_lower, - ("spec-first", "openspec", "spec-kit"), - "docs/index.md must explain the spec-first handoff for brownfield workflows.", - ) - _assert_contains_any_phrase( - docs_index_lower, - ("default starting point", "start here before jumping", "start here before"), - "docs/index.md must orient users that core docs are the default starting point.", - ) - _assert_contains_any_phrase( - docs_index_lower, - ("ai-assisted", "vibe-coded", "validation layer"), - "docs/index.md must explain AI-assisted validation pressure in the why-story.", - ) - _assert_contains_any_phrase( - docs_index_lower, - ("i wanted x but got y", "backlog language", "backlog/spec/code"), - "docs/index.md must explain backlog/spec/code drift as a reason SpecFact exists.", - ) - _assert_contains_any_phrase( - docs_index_lower, - ("policy enforcement", "organizations need a path", "developers, ai ides, and ci/cd"), - "docs/index.md must explain team and enterprise policy consistency pressure.", - ) + assert "default starting point" in docs_index.lower() def test_contributing_guidance_mentions_entrypoint_story_hierarchy() -> None: contributing = _read(CONTRIBUTING) - questions = [ - "What is SpecFact?", - "Why does it exist?", - "Why should I use it?", - "What do I get?", - "How do I get started?", - ] - assert "first-contact" in contributing - _assert_question_order(contributing, questions, "CONTRIBUTING.md") + assert "What is SpecFact?" in contributing + assert "How do I get started?" in contributing diff --git a/tests/unit/docs/test_release_docs_parity.py b/tests/unit/docs/test_release_docs_parity.py index 698979dd..febbf7dc 100644 --- a/tests/unit/docs/test_release_docs_parity.py +++ b/tests/unit/docs/test_release_docs_parity.py @@ -348,10 +348,11 @@ def test_module_contracts_reference_external_bundle_boundary() -> None: def test_readme_and_docs_index_define_core_and_modules_split() -> None: readme = _repo_file("README.md").read_text(encoding="utf-8") docs_index = _repo_file("docs/index.md").read_text(encoding="utf-8") - assert "validation and alignment layer for software delivery" in readme - assert "docs.specfact.io` is the canonical starting point for SpecFact" in readme + assert "Review AI-assisted code against your own contracts." in readme + assert "## Documentation topology" in readme assert "Module-specific deep docs are canonically owned by `specfact-cli-modules`" in readme _assert_mentions_modules_docs_site(readme) + assert "Review AI-assisted code against your own contracts." in docs_index assert "canonical starting point for the core CLI story" in docs_index assert "docs.specfact.io` is the default starting point" in docs_index _assert_mentions_modules_docs_site(docs_index) diff --git a/tests/unit/docs/test_wow_entrypoint_contract.py b/tests/unit/docs/test_wow_entrypoint_contract.py index d4a9f026..307ccdc8 100644 --- a/tests/unit/docs/test_wow_entrypoint_contract.py +++ b/tests/unit/docs/test_wow_entrypoint_contract.py @@ -1,7 +1,4 @@ -"""Contract tests: README and docs landing must match the canonical uvx \"wow\" entry path. - -The wow path is the primary onboarding surface (init + code review with --scope full). -""" +"""Contract tests for the README proof-first onboarding surface.""" from __future__ import annotations @@ -9,12 +6,15 @@ import pytest +from tests.unit.docs.docs_test_constants import HOOK + REPO_ROOT = Path(__file__).resolve().parents[3] README = REPO_ROOT / "README.md" DOCS_INDEX = REPO_ROOT / "docs" / "index.md" - -# Canonical strings — keep in sync with docs/index.md hero and README "Start Here". +CAPTURE_SCRIPT = REPO_ROOT / "docs" / "_support" / "readme-first-contact" / "capture-readme-output.sh" +EVIDENCE_DIR = REPO_ROOT / "docs" / "_support" / "readme-first-contact" / "sample-output" +CTA = "Star this repo if the output is useful." UVX_INIT = "uvx specfact-cli init --profile solo-developer" UVX_REVIEW = "uvx specfact-cli code review run --path . --scope full" INSTALLED_INIT = "specfact init --profile solo-developer" @@ -29,12 +29,12 @@ def _require_files() -> None: pytest.skip(f"docs/index.md missing at {DOCS_INDEX}", allow_module_level=True) -def _read(p: Path) -> str: - return p.read_text(encoding="utf-8") +def _read(path: Path) -> str: + return path.read_text(encoding="utf-8") def test_readme_and_docs_index_include_identical_uvx_wow_commands() -> None: - """Hero commands in README and docs/index.md must not drift.""" + """README and docs/index must share the canonical uvx wow commands.""" readme = _read(README) docs = _read(DOCS_INDEX) for needle in (UVX_INIT, UVX_REVIEW): @@ -42,36 +42,40 @@ def test_readme_and_docs_index_include_identical_uvx_wow_commands() -> None: assert needle in docs, f"docs/index.md must contain {needle!r}" -def test_readme_documents_pip_free_alternate_and_scope_full_rationale() -> None: - """README explains --scope full and the installed-CLI equivalent.""" +def test_readme_documents_proof_block_cta_and_installed_equivalent() -> None: + """README keeps the hook, proof block, CTA, and installed CLI path together.""" readme = _read(README) - assert "--scope full" in readme + assert HOOK in readme + assert "Sample output:" in readme + assert "Evidence bundle:" in readme + assert CTA in readme assert INSTALLED_INIT in readme and INSTALLED_REVIEW in readme - assert "Verdict" in readme and "Score" in readme and "findings" in readme.lower() + assert "Verdict:" in readme and "Findings:" in readme and "works offline" in readme.lower() -def test_readme_wow_section_appears_before_choose_your_path() -> None: - """Primary entry content must appear before outcome routing.""" +def test_readme_proof_first_sections_precede_deeper_sections() -> None: + """Proof and workflow sections must appear before org and module detail.""" readme = _read(README) - wow = readme.find("uvx specfact-cli init --profile solo-developer") - choose = readme.find("## Choose Your Path") - assert wow != -1 and choose != -1 - assert wow < choose + try_it = readme.find("## Try it in 60 seconds") + what_it_does = readme.find("## What SpecFact does") + workflow = readme.find("## Add SpecFact to your workflow") + teams = readme.find("## For teams and organizations") + assert min(try_it, what_it_does, workflow, teams) != -1 + assert try_it < what_it_does < workflow < teams -def test_docs_index_wow_block_precedes_what_is_specfact() -> None: - """Landing page leads with the runnable block before deep product copy.""" +def test_docs_index_shares_readme_hook_and_wow_block() -> None: + """Docs landing keeps the same first-contact identity and ordering.""" docs = _read(DOCS_INDEX) - block = docs.find(UVX_INIT) - heading = docs.find("## What is SpecFact?") - assert block != -1 and heading != -1 - assert block < heading - - -def test_readme_start_here_precedes_documentation_topology() -> None: - """Fast-start remains above internal docs topology (existing contract).""" - readme = _read(README) - start = readme.find("### Start Here") - topo = readme.find("## Documentation Topology") - assert start != -1 and topo != -1 - assert start < topo + assert HOOK in docs + assert docs.find(UVX_INIT) != -1 + assert docs.find(UVX_INIT) < docs.find("## What is SpecFact?") + + +def test_readme_capture_script_and_evidence_folder_exist() -> None: + """README sample output must be backed by reproducible evidence artifacts.""" + assert CAPTURE_SCRIPT.is_file(), "Missing docs/_support/readme-first-contact/capture-readme-output.sh" + assert EVIDENCE_DIR.is_dir(), "Missing docs/_support/readme-first-contact/sample-output/" + evidence_files = [path for path in EVIDENCE_DIR.iterdir() if path.is_file()] + assert evidence_files, "Expected at least one sample-output artifact file" + assert any(path.stat().st_size > 0 for path in evidence_files), "Expected a non-empty sample-output artifact"