From 30fb9b916758dadac283fdb232073e7f89600f90 Mon Sep 17 00:00:00 2001 From: Dom Date: Fri, 3 Apr 2026 20:24:11 +0000 Subject: [PATCH 1/5] docs: restructure readme for star conversion Co-authored-by: Dom --- README.md | 338 ++--- docs/index.md | 9 +- evidence/readme-sample-output/README.md | 15 + evidence/readme-sample-output/init-output.txt | 45 + .../readme-sample-output/review-output.txt | 1320 +++++++++++++++++ openspec/CHANGE_ORDER.md | 1 + .../readme-star-conversion-01/.openspec.yaml | 2 + .../readme-star-conversion-01/TDD_EVIDENCE.md | 69 + .../readme-star-conversion-01/proposal.md | 90 ++ .../specs/readme-first-contact/spec.md | 65 + .../readme-star-conversion-01/tasks.md | 52 + scripts/capture-readme-output.sh | 60 + tests/unit/docs/test_first_contact_story.py | 183 +-- tests/unit/docs/test_release_docs_parity.py | 5 +- .../unit/docs/test_wow_entrypoint_contract.py | 63 +- 15 files changed, 1880 insertions(+), 437 deletions(-) create mode 100644 evidence/readme-sample-output/README.md create mode 100644 evidence/readme-sample-output/init-output.txt create mode 100644 evidence/readme-sample-output/review-output.txt create mode 100644 openspec/changes/readme-star-conversion-01/.openspec.yaml create mode 100644 openspec/changes/readme-star-conversion-01/TDD_EVIDENCE.md create mode 100644 openspec/changes/readme-star-conversion-01/proposal.md create mode 100644 openspec/changes/readme-star-conversion-01/specs/readme-first-contact/spec.md create mode 100644 openspec/changes/readme-star-conversion-01/tasks.md create mode 100755 scripts/capture-readme-output.sh diff --git a/README.md b/README.md index 10c593a9..32a0ab3a 100644 --- a/README.md +++ b/README.md @@ -1,294 +1,133 @@ # 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: evidence/readme-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 `scripts/capture-readme-output.sh`. -### 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-enforce + args: [--preset, minimal] ``` -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 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 +135,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/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/evidence/readme-sample-output/README.md b/evidence/readme-sample-output/README.md new file mode 100644 index 00000000..2896389c --- /dev/null +++ b/evidence/readme-sample-output/README.md @@ -0,0 +1,15 @@ +# README sample output capture + +- CLI version: `0.45.1` +- Repo: `nold-ai/specfact-demo-repo` +- Repo path: `/tmp/specfact-demo-repo` +- Capture home: `/tmp/specfact-readme-capture-home` +- Command: + +```bash +uvx --from "specfact-cli==0.45.1" specfact init --profile solo-developer +uvx --from "specfact-cli==0.45.1" --with ruff --with radon --with semgrep --with basedpyright --with pylint --with crosshair-tool specfact code review run --path . --scope full +``` + +- Raw output: `/workspace/evidence/readme-sample-output/review-output.txt` +- Init output: `/workspace/evidence/readme-sample-output/init-output.txt` diff --git a/evidence/readme-sample-output/init-output.txt b/evidence/readme-sample-output/init-output.txt new file mode 100644 index 00000000..5485ff6e --- /dev/null +++ b/evidence/readme-sample-output/init-output.txt @@ -0,0 +1,45 @@ +Downloading cryptography (4.3MiB) +Downloading beartype (1.3MiB) +Downloading networkx (2.0MiB) +Downloading pydantic-core (2.0MiB) +Downloading pygments (1.2MiB) + Downloaded pydantic-core + Downloaded cryptography + Downloaded pygments + Downloaded beartype + Downloaded networkx +Installed 60 packages in 31ms + + ███████╗██████╗ ███████╗ ██████╗███████╗ █████╗ ██████╗████████╗ + ██╔════╝██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔════╝╚══██╔══╝ + ███████╗██████╔╝█████╗ ██║ █████╗ ███████║██║ ██║ + ╚════██║██╔═══╝ ██╔══╝ ██║ ██╔══╝ ██╔══██║██║ ██║ + ███████║██║ ███████╗╚██████╗██║ ██║ ██║╚██████╗ ██║ + ╚══════╝╚═╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ + + Spec → Contract → Sentinel for Contract-Driven Development + + + +⏱️ Started: 2026-04-03 20:13:58 +→ Profile solo-developer: preparing workflow bundles… +→ Seeding workflow bundles: specfact-codebase, specfact-code-review + (copying bundled modules into your user module directory) +→ Bundle specfact-codebase — Codebase quality (analyze, drift, validate, repro) + · Installing marketplace module nold-ai/specfact-codebase … + ✓ specfact-codebase ready +→ Bundle specfact-code-review — Scored code review (code review gate) + · Installing marketplace module nold-ai/specfact-code-review … + ✓ specfact-code-review ready +✓ Installed: specfact-codebase, specfact-code-review +→ Discovering installed modules and writing registry state… +→ Indexing CLI commands for help cache… +✓ Bootstrap complete. Modules discovered: 7 (enabled=7, disabled=0). +Module management has moved to `specfact module` (for example: `specfact module +list`, `specfact module init`) +→ Checking IDE prompt export status… +Prompt status: missing=7, outdated=0 for detected IDE (cursor). +Run: specfact init ide --ide cursor +Use `specfact init ide` to install/update IDE prompts and settings. + +✓ Finished: 2026-04-03 20:14:12 | Duration: 14.10s diff --git a/evidence/readme-sample-output/review-output.txt b/evidence/readme-sample-output/review-output.txt new file mode 100644 index 00000000..2803da63 --- /dev/null +++ b/evidence/readme-sample-output/review-output.txt @@ -0,0 +1,1320 @@ +Downloading z3-solver (30.3MiB) +Downloading nodejs-wheel-binaries (57.2MiB) +Downloading ruff (10.7MiB) +Downloading semgrep (55.9MiB) +Downloading basedpyright (11.8MiB) + Downloaded ruff + Downloaded z3-solver + Downloaded semgrep + Downloaded basedpyright + Downloaded nodejs-wheel-binaries +Installed 111 packages in 415ms +SpecFact CLI - v0.45.1 + + +⏱️ Started: 2026-04-03 20:14:18 +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... + Code Review: architecture +┏━━━━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ +┃ File ┃ Line ┃ Tool ┃ Rule ┃ Severity ┃ Message ┃ +┡━━━━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩ +│ specfact_demo/… │ 81 │ semgrep │ print-in-src │ warning │ Avoid print() │ +│ │ │ │ │ │ in source │ +│ │ │ │ │ │ files; use │ +│ │ │ │ │ │ structured │ +│ │ │ │ │ │ logging or │ +│ │ │ │ │ │ return values │ +│ │ │ │ │ │ instead. │ +│ specfact_demo/… │ 86 │ semgrep │ print-in-src │ warning │ Avoid print() │ +│ │ │ │ │ │ in source │ +│ │ │ │ │ │ files; use │ +│ │ │ │ │ │ structured │ +│ │ │ │ │ │ logging or │ +│ │ │ │ │ │ return values │ +│ │ │ │ │ │ instead. │ +│ specfact_demo/… │ 304 │ semgrep │ print-in-src │ warning │ Avoid print() │ +│ │ │ │ │ │ in source │ +│ │ │ │ │ │ files; use │ +│ │ │ │ │ │ structured │ +│ │ │ │ │ │ logging or │ +│ │ │ │ │ │ return values │ +│ │ │ │ │ │ instead. │ +└─────────────────┴──────┴─────────┴──────────────┴──────────┴─────────────────┘ + Code Review: clean_code +┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━┳━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ +┃ File ┃ Line ┃ Tool ┃ Rule ┃ Severity ┃ Message ┃ +┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━╇━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ +│ specfact_demo/enfor… │ 96 │ radon │ CC16 │ error │ Cyclomatic │ +│ │ │ │ │ │ complexity for │ +│ │ │ │ │ │ run_enforcement is │ +│ │ │ │ │ │ 16. │ +└──────────────────────┴──────┴───────┴──────┴──────────┴──────────────────────┘ + Code Review: contracts +┏━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━┓ +┃ File ┃ Line ┃ Tool ┃ Rule ┃ Severity ┃ Message ┃ +┡━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━┩ +│ examples/bu… │ 4 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'divide' is │ +│ │ │ │ │ │ missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ examples/bu… │ 9 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'discount' │ +│ │ │ │ │ │ is missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ plugins/off… │ 13 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'post_sync' │ +│ │ │ │ │ │ is missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ specfact_de… │ 64 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'main' is │ +│ │ │ │ │ │ missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ specfact_de… │ 96 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'run_enforce │ +│ │ │ │ │ │ ment' is │ +│ │ │ │ │ │ missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ specfact_de… │ 32 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'pre_read' │ +│ │ │ │ │ │ is missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ specfact_de… │ 35 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'post_read' │ +│ │ │ │ │ │ is missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ specfact_de… │ 38 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'pre_validat │ +│ │ │ │ │ │ e' is │ +│ │ │ │ │ │ missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ specfact_de… │ 41 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'post_valida │ +│ │ │ │ │ │ te' is │ +│ │ │ │ │ │ missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ specfact_de… │ 44 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'pre_sync' │ +│ │ │ │ │ │ is missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ specfact_de… │ 47 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'post_sync' │ +│ │ │ │ │ │ is missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ specfact_de… │ 20 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'load_plugin │ +│ │ │ │ │ │ _from_path' │ +│ │ │ │ │ │ is missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ specfact_de… │ 47 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'load_plugin │ +│ │ │ │ │ │ s' is │ +│ │ │ │ │ │ missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ specfact_de… │ 51 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'run_lifecyc │ +│ │ │ │ │ │ le' is │ +│ │ │ │ │ │ missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ specfact_de… │ 58 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'validate_pl │ +│ │ │ │ │ │ ugin_metadat │ +│ │ │ │ │ │ a' is │ +│ │ │ │ │ │ missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ specfact_de… │ 88 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'run_plugin_ │ +│ │ │ │ │ │ harness' is │ +│ │ │ │ │ │ missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +│ specfact_de… │ 130 │ contract_ru… │ MISSING_ICON… │ warning │ Public │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ 'init_plugin │ +│ │ │ │ │ │ _scaffold' │ +│ │ │ │ │ │ is missing │ +│ │ │ │ │ │ @require/@en │ +│ │ │ │ │ │ sure │ +│ │ │ │ │ │ decorators. │ +└──────────────┴──────┴──────────────┴───────────────┴──────────┴──────────────┘ + Code Review: kiss +┏━━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ +┃ File ┃ Line ┃ Tool ┃ Rule ┃ Severity ┃ Message ┃ +┡━━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ +│ specfact_dem… │ 96 │ radon-kiss │ kiss.loc.err… │ error │ Function │ +│ │ │ │ │ │ `run_enforcem │ +│ │ │ │ │ │ ent` spans │ +│ │ │ │ │ │ 216 lines; │ +│ │ │ │ │ │ keep it under │ +│ │ │ │ │ │ 80. │ +│ specfact_dem… │ 96 │ radon-kiss │ kiss.nesting… │ warning │ Function │ +│ │ │ │ │ │ `run_enforcem │ +│ │ │ │ │ │ ent` nests │ +│ │ │ │ │ │ control flow │ +│ │ │ │ │ │ 4 levels │ +│ │ │ │ │ │ deep; keep it │ +│ │ │ │ │ │ under 3. │ +│ specfact_dem… │ 130 │ radon-kiss │ kiss.loc.war… │ warning │ Function │ +│ │ │ │ │ │ `init_plugin_ │ +│ │ │ │ │ │ scaffold` │ +│ │ │ │ │ │ spans 85 │ +│ │ │ │ │ │ lines; keep │ +│ │ │ │ │ │ it under 80. │ +└───────────────┴──────┴────────────┴───────────────┴──────────┴───────────────┘ + Code Review: naming +┏━━━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ +┃ File ┃ Line ┃ Tool ┃ Rule ┃ Severity ┃ Message ┃ +┡━━━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ +│ specfact_demo… │ 58 │ semgrep │ banned-generi… │ warning │ Public API │ +│ │ │ │ │ │ names should │ +│ │ │ │ │ │ be specific; │ +│ │ │ │ │ │ avoid generic │ +│ │ │ │ │ │ names like │ +│ │ │ │ │ │ process, │ +│ │ │ │ │ │ handle, or │ +│ │ │ │ │ │ manager. │ +└────────────────┴──────┴─────────┴────────────────┴──────────┴────────────────┘ + Code Review: style +┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓ +┃ File ┃ Line ┃ Tool ┃ Rule ┃ Severity ┃ Message ┃ +┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┩ +│ examples/buggy-sid… │ 4 │ pylint │ C0116 │ warning │ Missing function or │ +│ │ │ │ │ │ method docstring │ +│ examples/buggy-sid… │ 9 │ pylint │ C0116 │ warning │ Missing function or │ +│ │ │ │ │ │ method docstring │ +│ plugins/official/b… │ 1 │ pylint │ C0114 │ warning │ Missing module │ +│ │ │ │ │ │ docstring │ +│ plugins/official/b… │ 4 │ pylint │ C0115 │ warning │ Missing class │ +│ │ │ │ │ │ docstring │ +│ specfact_demo/cli.… │ 64 │ pylint │ C0116 │ warning │ Missing function or │ +│ │ │ │ │ │ method docstring │ +│ specfact_demo/enfo… │ 231 │ pylint │ C0301 │ warning │ Line too long │ +│ │ │ │ │ │ (103/100) │ +│ specfact_demo/enfo… │ 96 │ pylint │ C0116 │ warning │ Missing function or │ +│ │ │ │ │ │ method docstring │ +│ specfact_demo/enfo… │ 96 │ pylint │ R0914 │ warning │ Too many local │ +│ │ │ │ │ │ variables (45/15) │ +│ specfact_demo/enfo… │ 96 │ pylint │ R0915 │ warning │ Too many statements │ +│ │ │ │ │ │ (76/50) │ +│ specfact_demo/plug… │ 20 │ pylint │ C0116 │ warning │ Missing function or │ +│ │ │ │ │ │ method docstring │ +│ specfact_demo/plug… │ 47 │ pylint │ C0116 │ warning │ Missing function or │ +│ │ │ │ │ │ method docstring │ +│ specfact_demo/plug… │ 51 │ pylint │ C0116 │ warning │ Missing function or │ +│ │ │ │ │ │ method docstring │ +│ specfact_demo/plug… │ 58 │ pylint │ C0116 │ warning │ Missing function or │ +│ │ │ │ │ │ method docstring │ +│ specfact_demo/plug… │ 88 │ pylint │ C0116 │ warning │ Missing function or │ +│ │ │ │ │ │ method docstring │ +│ specfact_demo/plug… │ 114 │ pylint │ W0718 │ warning │ Catching too │ +│ │ │ │ │ │ general exception │ +│ │ │ │ │ │ Exception │ +│ specfact_demo/plug… │ 130 │ pylint │ C0116 │ warning │ Missing function or │ +│ │ │ │ │ │ method docstring │ +│ tests/test_enforce… │ 1 │ pylint │ C0114 │ warning │ Missing module │ +│ │ │ │ │ │ docstring │ +│ tests/test_enforce… │ 14 │ pylint │ C0115 │ warning │ Missing class │ +│ │ │ │ │ │ docstring │ +│ tests/test_enforce… │ 15 │ pylint │ C0116 │ warning │ Missing function or │ +│ │ │ │ │ │ method docstring │ +│ tests/test_enforce… │ 34 │ pylint │ C0116 │ warning │ Missing function or │ +│ │ │ │ │ │ method docstring │ +│ tests/test_enforce… │ 51 │ pylint │ C0116 │ warning │ Missing function or │ +│ │ │ │ │ │ method docstring │ +│ tests/test_plugin_… │ 1 │ pylint │ C0114 │ warning │ Missing module │ +│ │ │ │ │ │ docstring │ +│ tests/test_plugin_… │ 14 │ pylint │ C0115 │ warning │ Missing class │ +│ │ │ │ │ │ docstring │ +│ tests/test_plugin_… │ 15 │ pylint │ C0116 │ warning │ Missing function or │ +│ │ │ │ │ │ method docstring │ +│ tests/test_plugin_… │ 20 │ pylint │ C0116 │ warning │ Missing function or │ +│ │ │ │ │ │ method docstring │ +└─────────────────────┴──────┴────────┴───────┴──────────┴─────────────────────┘ + Code Review: type_safety +┏━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━┓ +┃ File ┃ Line ┃ Tool ┃ Rule ┃ Severity ┃ Message ┃ +┡━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━┩ +│ /tmp/specfa… │ 5 │ basedpyright │ reportUnanno… │ warning │ Type │ +│ │ │ │ │ │ annotation │ +│ │ │ │ │ │ for │ +│ │ │ │ │ │ attribute │ +│ │ │ │ │ │ `name` is │ +│ │ │ │ │ │ required │ +│ │ │ │ │ │ because this │ +│ │ │ │ │ │ class is not │ +│ │ │ │ │ │ decorated │ +│ │ │ │ │ │ with │ +│ │ │ │ │ │ `@final` │ +│ /tmp/specfa… │ 6 │ basedpyright │ reportUnanno… │ warning │ Type │ +│ │ │ │ │ │ annotation │ +│ │ │ │ │ │ for │ +│ │ │ │ │ │ attribute │ +│ │ │ │ │ │ `version` is │ +│ │ │ │ │ │ required │ +│ │ │ │ │ │ because this │ +│ │ │ │ │ │ class is not │ +│ │ │ │ │ │ decorated │ +│ │ │ │ │ │ with │ +│ │ │ │ │ │ `@final` │ +│ /tmp/specfa… │ 7 │ basedpyright │ reportUnanno… │ warning │ Type │ +│ │ │ │ │ │ annotation │ +│ │ │ │ │ │ for │ +│ │ │ │ │ │ attribute │ +│ │ │ │ │ │ `api_version │ +│ │ │ │ │ │ ` is │ +│ │ │ │ │ │ required │ +│ │ │ │ │ │ because this │ +│ │ │ │ │ │ class is not │ +│ │ │ │ │ │ decorated │ +│ │ │ │ │ │ with │ +│ │ │ │ │ │ `@final` │ +│ /tmp/specfa… │ 8 │ basedpyright │ reportUnanno… │ warning │ Type │ +│ │ │ │ │ │ annotation │ +│ │ │ │ │ │ for │ +│ │ │ │ │ │ attribute │ +│ │ │ │ │ │ `scope` is │ +│ │ │ │ │ │ required │ +│ │ │ │ │ │ because this │ +│ │ │ │ │ │ class is not │ +│ │ │ │ │ │ decorated │ +│ │ │ │ │ │ with │ +│ │ │ │ │ │ `@final` │ +│ /tmp/specfa… │ 9 │ basedpyright │ reportUnanno… │ warning │ Type │ +│ │ │ │ │ │ annotation │ +│ │ │ │ │ │ for │ +│ │ │ │ │ │ attribute │ +│ │ │ │ │ │ `invariants_ │ +│ │ │ │ │ │ touched` is │ +│ │ │ │ │ │ required │ +│ │ │ │ │ │ because this │ +│ │ │ │ │ │ class is not │ +│ │ │ │ │ │ decorated │ +│ │ │ │ │ │ with │ +│ │ │ │ │ │ `@final` │ +│ /tmp/specfa… │ 10 │ basedpyright │ reportUnanno… │ warning │ Type │ +│ │ │ │ │ │ annotation │ +│ │ │ │ │ │ for │ +│ │ │ │ │ │ attribute │ +│ │ │ │ │ │ `side_effect │ +│ │ │ │ │ │ s` is │ +│ │ │ │ │ │ required │ +│ │ │ │ │ │ because this │ +│ │ │ │ │ │ class is not │ +│ │ │ │ │ │ decorated │ +│ │ │ │ │ │ with │ +│ │ │ │ │ │ `@final` │ +│ /tmp/specfa… │ 11 │ basedpyright │ reportUnanno… │ warning │ Type │ +│ │ │ │ │ │ annotation │ +│ │ │ │ │ │ for │ +│ │ │ │ │ │ attribute │ +│ │ │ │ │ │ `attestation │ +│ │ │ │ │ │ _hash` is │ +│ │ │ │ │ │ required │ +│ │ │ │ │ │ because this │ +│ │ │ │ │ │ class is not │ +│ │ │ │ │ │ decorated │ +│ │ │ │ │ │ with │ +│ │ │ │ │ │ `@final` │ +│ /tmp/specfa… │ 13 │ basedpyright │ reportImplic… │ warning │ Method │ +│ │ │ │ │ │ "post_sync" │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ marked as │ +│ │ │ │ │ │ override but │ +│ │ │ │ │ │ is │ +│ │ │ │ │ │ overriding a │ +│ │ │ │ │ │ method in │ +│ │ │ │ │ │ class │ +│ │ │ │ │ │ "Plugin" │ +│ /tmp/specfa… │ 13 │ basedpyright │ reportMissin… │ warning │ Type │ +│ │ │ │ │ │ annotation │ +│ │ │ │ │ │ is missing │ +│ │ │ │ │ │ for │ +│ │ │ │ │ │ parameter │ +│ │ │ │ │ │ "context" │ +│ /tmp/specfa… │ 14 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "sync_result │ +│ │ │ │ │ │ " is Any │ +│ /tmp/specfa… │ 15 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "item" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 15 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "get" is Any │ +│ /tmp/specfa… │ 16 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "labels" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 16 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "setdefault" │ +│ │ │ │ │ │ is Any │ +│ /tmp/specfa… │ 18 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "append" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 24 │ basedpyright │ reportUnused… │ warning │ Result of │ +│ │ │ │ │ │ call │ +│ │ │ │ │ │ expression │ +│ │ │ │ │ │ is of type │ +│ │ │ │ │ │ "Action" and │ +│ │ │ │ │ │ is not used; │ +│ │ │ │ │ │ assign to │ +│ │ │ │ │ │ variable "_" │ +│ │ │ │ │ │ if this is │ +│ │ │ │ │ │ intentional │ +│ /tmp/specfa… │ 25 │ basedpyright │ reportUnused… │ warning │ Result of │ +│ │ │ │ │ │ call │ +│ │ │ │ │ │ expression │ +│ │ │ │ │ │ is of type │ +│ │ │ │ │ │ "Action" and │ +│ │ │ │ │ │ is not used; │ +│ │ │ │ │ │ assign to │ +│ │ │ │ │ │ variable "_" │ +│ │ │ │ │ │ if this is │ +│ │ │ │ │ │ intentional │ +│ /tmp/specfa… │ 30 │ basedpyright │ reportUnused… │ warning │ Result of │ +│ │ │ │ │ │ call │ +│ │ │ │ │ │ expression │ +│ │ │ │ │ │ is of type │ +│ │ │ │ │ │ "Action" and │ +│ │ │ │ │ │ is not used; │ +│ │ │ │ │ │ assign to │ +│ │ │ │ │ │ variable "_" │ +│ │ │ │ │ │ if this is │ +│ │ │ │ │ │ intentional │ +│ /tmp/specfa… │ 36 │ basedpyright │ reportUnused… │ warning │ Result of │ +│ │ │ │ │ │ call │ +│ │ │ │ │ │ expression │ +│ │ │ │ │ │ is of type │ +│ │ │ │ │ │ "Action" and │ +│ │ │ │ │ │ is not used; │ +│ │ │ │ │ │ assign to │ +│ │ │ │ │ │ variable "_" │ +│ │ │ │ │ │ if this is │ +│ │ │ │ │ │ intentional │ +│ /tmp/specfa… │ 46 │ basedpyright │ reportUnused… │ warning │ Result of │ +│ │ │ │ │ │ call │ +│ │ │ │ │ │ expression │ +│ │ │ │ │ │ is of type │ +│ │ │ │ │ │ "Action" and │ +│ │ │ │ │ │ is not used; │ +│ │ │ │ │ │ assign to │ +│ │ │ │ │ │ variable "_" │ +│ │ │ │ │ │ if this is │ +│ │ │ │ │ │ intentional │ +│ /tmp/specfa… │ 47 │ basedpyright │ reportUnused… │ warning │ Result of │ +│ │ │ │ │ │ call │ +│ │ │ │ │ │ expression │ +│ │ │ │ │ │ is of type │ +│ │ │ │ │ │ "Action" and │ +│ │ │ │ │ │ is not used; │ +│ │ │ │ │ │ assign to │ +│ │ │ │ │ │ variable "_" │ +│ │ │ │ │ │ if this is │ +│ │ │ │ │ │ intentional │ +│ /tmp/specfa… │ 54 │ basedpyright │ reportUnused… │ warning │ Result of │ +│ │ │ │ │ │ call │ +│ │ │ │ │ │ expression │ +│ │ │ │ │ │ is of type │ +│ │ │ │ │ │ "Action" and │ +│ │ │ │ │ │ is not used; │ +│ │ │ │ │ │ assign to │ +│ │ │ │ │ │ variable "_" │ +│ │ │ │ │ │ if this is │ +│ │ │ │ │ │ intentional │ +│ /tmp/specfa… │ 55 │ basedpyright │ reportUnused… │ warning │ Result of │ +│ │ │ │ │ │ call │ +│ │ │ │ │ │ expression │ +│ │ │ │ │ │ is of type │ +│ │ │ │ │ │ "Action" and │ +│ │ │ │ │ │ is not used; │ +│ │ │ │ │ │ assign to │ +│ │ │ │ │ │ variable "_" │ +│ │ │ │ │ │ if this is │ +│ │ │ │ │ │ intentional │ +│ /tmp/specfa… │ 68 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "command" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 70 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "fixtures" │ +│ │ │ │ │ │ is Any │ +│ /tmp/specfa… │ 70 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "args" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "__init__" │ +│ /tmp/specfa… │ 71 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "artifacts" │ +│ │ │ │ │ │ is Any │ +│ /tmp/specfa… │ 71 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "args" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "__init__" │ +│ /tmp/specfa… │ 72 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "args" in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "__init__" │ +│ /tmp/specfa… │ 72 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "path" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 72 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "use_plugin" │ +│ │ │ │ │ │ is Any │ +│ /tmp/specfa… │ 74 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "gate" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 75 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "fail_on_blo │ +│ │ │ │ │ │ ck" is Any │ +│ /tmp/specfa… │ 79 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "command" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 79 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "plugin_comm │ +│ │ │ │ │ │ and" is Any │ +│ /tmp/specfa… │ 80 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "name" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 80 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "plugin_name │ +│ │ │ │ │ │ " in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "init_plugin │ +│ │ │ │ │ │ _scaffold" │ +│ /tmp/specfa… │ 80 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "target_dir" │ +│ │ │ │ │ │ is Any │ +│ /tmp/specfa… │ 80 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "target_dir" │ +│ │ │ │ │ │ in function │ +│ │ │ │ │ │ "init_plugin │ +│ │ │ │ │ │ _scaffold" │ +│ /tmp/specfa… │ 84 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "plugin_comm │ +│ │ │ │ │ │ and" is Any │ +│ /tmp/specfa… │ 85 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "plugin_path │ +│ │ │ │ │ │ " is Any │ +│ /tmp/specfa… │ 85 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "plugin_path │ +│ │ │ │ │ │ " in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "run_plugin_ │ +│ │ │ │ │ │ harness" │ +│ /tmp/specfa… │ 85 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "fixture" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 85 │ basedpyright │ reportAny │ warning │ Argument │ +│ │ │ │ │ │ type is Any │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "fixture_dir │ +│ │ │ │ │ │ " in │ +│ │ │ │ │ │ function │ +│ │ │ │ │ │ "run_plugin_ │ +│ │ │ │ │ │ harness" │ +│ /tmp/specfa… │ 9 │ basedpyright │ reportDeprec… │ warning │ This type is │ +│ │ │ │ │ │ deprecated │ +│ │ │ │ │ │ as of Python │ +│ │ │ │ │ │ 3.9; use │ +│ │ │ │ │ │ "collections │ +│ │ │ │ │ │ .abc.Iterabl │ +│ │ │ │ │ │ e" instead │ +│ /tmp/specfa… │ 14 │ basedpyright │ reportAny │ warning │ Return type │ +│ │ │ │ │ │ is Any │ +│ /tmp/specfa… │ 14 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 16 │ basedpyright │ reportAny │ warning │ Return type │ +│ │ │ │ │ │ is Any │ +│ /tmp/specfa… │ 19 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ parameter │ +│ │ │ │ │ │ "payload" is │ +│ │ │ │ │ │ Any │ +│ /tmp/specfa… │ 19 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /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… │ 37 │ basedpyright │ reportExplic… │ warning │ Type `Any` │ +│ │ │ │ │ │ is not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 46 │ basedpyright │ reportImplic… │ warning │ Implicit │ +│ │ │ │ │ │ string │ +│ │ │ │ │ │ concatenatio │ +│ │ │ │ │ │ n not │ +│ │ │ │ │ │ allowed │ +│ /tmp/specfa… │ 50 │ basedpyright │ reportAny │ warning │ Type of │ +│ │ │ │ │ │ "endpoints" │ +│ │ │ │ │ │ is Any │ +│ /tmp/specfa… │ 56 │ basedpyright │ reportUnknow… │ warning │ Type of │ +│ │ │ │ │ │ "endpoint" │ +│ │ │ │ │ │ is unknown │ +│ /tmp/specfa… │ 56 │ basedpyright │ reportUnknow… │ warning │ Argument │ +│ │ │ │ │ │ type is │ +│ │ │ │ │ │ partially │ +│ │ │ │ │ │ unknown │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ corresponds │ +│ │ │ │ │ │ to parameter │ +│ │ │ │ │ │ "iterable" │ +│ │ │ │ │ │ in function │ +│ │ │ │ │ │ "__new__" │ +│ │ │ │ │ │   Argument │ +│ │ │ │ │ │ type is │ +│ │ │ │ │ │ "list[Unknow │ +│ │ │ │ │ │ n]" │ +│ /tmp/specfa… │ 61 │ basedpyright │ reportUnknow… │ warning │ Type of │ +│ │ │ │ │ │ "get" is │ +│ │ │ │ │ │ partially │ +│ │ │ │ │ │ unknown │ +│ │ │ │ │ │   Type of │ +│ │ │ │ │ │ "get" is │ +│ │ │ │ │ │ "Overload[(k │ +│ │ │ │ │ │ ey: Unknown, │ +│ │ │ │ │ │ default: │ +│ │ │ │ │ │ None = None, │ +│ │ │ │ │ │ /) -> │ +│ │ │ │ │ │ (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:14:24 | Duration: 6.14s diff --git a/openspec/CHANGE_ORDER.md b/openspec/CHANGE_ORDER.md index c7e71951..638f1fc4 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 | related `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..fee77c26 --- /dev/null +++ b/openspec/changes/readme-star-conversion-01/TDD_EVIDENCE.md @@ -0,0 +1,69 @@ +# 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 +- `scripts/capture-readme-output.sh` and `evidence/readme-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 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` +- `scripts/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 `evidence/readme-sample-output/` and are generated by + `scripts/capture-readme-output.sh` +- 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 + report 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 CLI validation remains blocked in this environment because `openspec` is not installed 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..e14d5009 --- /dev/null +++ b/openspec/changes/readme-star-conversion-01/proposal.md @@ -0,0 +1,90 @@ +## 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` +- `scripts/capture-readme-output.sh` +- `evidence/readme-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**: pending — not created from this environment +- **Issue URL**: pending +- **Repository**: nold-ai/specfact-cli +- **Last Synced Status**: draft +- **Related (overlap)**: [#476](https://github.com/nold-ai/specfact-cli/issues/476) and + [#466](https://github.com/nold-ai/specfact-cli/issues/466) cover the broader first-contact and + onboarding work; this change is a narrower README-focused delta on the managed cloud branch 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..ccf4f145 --- /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 + `evidence/readme-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..8aa08036 --- /dev/null +++ b/openspec/changes/readme-star-conversion-01/tasks.md @@ -0,0 +1,52 @@ +## 1. Branch and change setup + +- [x] 1.1 Work on managed cloud branch `cursor/readme-star-conversion-a583` +- [x] 1.2 Create OpenSpec change folder `openspec/changes/readme-star-conversion-01/` +- [x] 1.3 Add proposal and README-focused spec delta +- [ ] 1.4 Validate change with OpenSpec CLI + - blocked in this environment: `openspec` command is not installed + +## 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 `scripts/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 `evidence/readme-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` diff --git a/scripts/capture-readme-output.sh b/scripts/capture-readme-output.sh new file mode 100755 index 00000000..36ac15d0 --- /dev/null +++ b/scripts/capture-readme-output.sh @@ -0,0 +1,60 @@ +#!/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}" +WORK_DIR="${WORK_DIR:-/tmp/specfact-demo-repo}" +CAPTURE_HOME="${CAPTURE_HOME:-/tmp/specfact-readme-capture-home}" +OUTPUT_DIR="${OUTPUT_DIR:-$REPO_ROOT/evidence/readme-sample-output}" +RAW_OUTPUT_PATH="${RAW_OUTPUT_PATH:-$OUTPUT_DIR/review-output.txt}" +SUMMARY_PATH="${SUMMARY_PATH:-$OUTPUT_DIR/README.md}" +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 + +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 || true +popd >/dev/null + +cat >"$SUMMARY_PATH" <'\"`]+") +HOOK = "Review AI-assisted code against your own contracts." +SUBHOOK = "Catch drift before it reaches PR or main." @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,54 @@ 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) + first_screen = "\n".join(readme.splitlines()[:40]).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..a46d03cf 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 @@ -13,8 +10,11 @@ REPO_ROOT = Path(__file__).resolve().parents[3] README = REPO_ROOT / "README.md" DOCS_INDEX = REPO_ROOT / "docs" / "index.md" +CAPTURE_SCRIPT = REPO_ROOT / "scripts" / "capture-readme-output.sh" +EVIDENCE_DIR = REPO_ROOT / "evidence" / "readme-sample-output" -# Canonical strings — keep in sync with docs/index.md hero and README "Start Here". +HOOK = "Review AI-assisted code against your own contracts." +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,37 @@ 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 + assert HOOK in docs + assert docs.find(UVX_INIT) != -1 + assert docs.find(UVX_INIT) < docs.find("## What is SpecFact?") -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 +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 scripts/capture-readme-output.sh" + assert EVIDENCE_DIR.is_dir(), "Missing evidence/readme-sample-output/" From bfbb6303d7139075e92a06d0cc12a940f3325729 Mon Sep 17 00:00:00 2001 From: Dom Date: Fri, 3 Apr 2026 20:36:49 +0000 Subject: [PATCH 2/5] docs: sync readme change tracking Co-authored-by: Dom --- .../readme-star-conversion-01/proposal.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/openspec/changes/readme-star-conversion-01/proposal.md b/openspec/changes/readme-star-conversion-01/proposal.md index e14d5009..8f93b672 100644 --- a/openspec/changes/readme-star-conversion-01/proposal.md +++ b/openspec/changes/readme-star-conversion-01/proposal.md @@ -1,3 +1,5 @@ +# Change: readme-star-conversion-01 + ## Why The repository README still opens with platform and governance language before showing a runnable @@ -40,7 +42,6 @@ This change restructures the README so the first screen answers four questions i 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 @@ -79,12 +80,13 @@ This change restructures the README so the first screen answers four questions i - 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**: pending — not created from this environment -- **Issue URL**: pending -- **Repository**: nold-ai/specfact-cli -- **Last Synced Status**: draft -- **Related (overlap)**: [#476](https://github.com/nold-ai/specfact-cli/issues/476) and - [#466](https://github.com/nold-ai/specfact-cli/issues/466) cover the broader first-contact and - onboarding work; this change is a narrower README-focused delta on the managed cloud branch + +- **GitHub Issue**: #481 +- **Issue URL**: +- **Last Synced Status**: proposed +- **Sanitized**: false \ No newline at end of file From 42593c172471b26c558a656b5245d04a94ddf5ff Mon Sep 17 00:00:00 2001 From: Dom Date: Fri, 3 Apr 2026 20:49:03 +0000 Subject: [PATCH 3/5] docs: relocate readme support artifacts Co-authored-by: Dom --- README.md | 4 ++-- .../readme-first-contact}/capture-readme-output.sh | 6 +++--- .../sample-output/capture-metadata.txt | 4 ++-- .../sample-output}/init-output.txt | 12 ++++++------ .../sample-output}/review-output.txt | 10 +++++----- .../readme-star-conversion-01/TDD_EVIDENCE.md | 9 +++++---- .../changes/readme-star-conversion-01/proposal.md | 4 ++-- .../specs/readme-first-contact/spec.md | 2 +- openspec/changes/readme-star-conversion-01/tasks.md | 4 ++-- tests/unit/docs/test_wow_entrypoint_contract.py | 8 ++++---- 10 files changed, 32 insertions(+), 31 deletions(-) rename {scripts => docs/_support/readme-first-contact}/capture-readme-output.sh (88%) rename evidence/readme-sample-output/README.md => docs/_support/readme-first-contact/sample-output/capture-metadata.txt (71%) rename {evidence/readme-sample-output => docs/_support/readme-first-contact/sample-output}/init-output.txt (95%) rename {evidence/readme-sample-output => docs/_support/readme-first-contact/sample-output}/review-output.txt (99%) diff --git a/README.md b/README.md index 32a0ab3a..82c878d8 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Findings: - 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. -Evidence bundle: evidence/readme-sample-output/ +Evidence bundle: docs/_support/readme-first-contact/sample-output/ ``` ⭐ **Star this repo if the output is useful.** Open an issue if you want the workflow adapted for your stack. @@ -57,7 +57,7 @@ specfact init --profile solo-developer specfact code review run --path . --scope full ``` -The sample output comes from a pinned capture against `nold-ai/specfact-demo-repo`. Reproduce it with `scripts/capture-readme-output.sh`. +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/`. ## What SpecFact does diff --git a/scripts/capture-readme-output.sh b/docs/_support/readme-first-contact/capture-readme-output.sh similarity index 88% rename from scripts/capture-readme-output.sh rename to docs/_support/readme-first-contact/capture-readme-output.sh index 36ac15d0..2b3b24ce 100755 --- a/scripts/capture-readme-output.sh +++ b/docs/_support/readme-first-contact/capture-readme-output.sh @@ -3,15 +3,15 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" CLI_VERSION="${CLI_VERSION:-0.45.1}" REPO_SLUG="${REPO_SLUG:-nold-ai/specfact-demo-repo}" WORK_DIR="${WORK_DIR:-/tmp/specfact-demo-repo}" CAPTURE_HOME="${CAPTURE_HOME:-/tmp/specfact-readme-capture-home}" -OUTPUT_DIR="${OUTPUT_DIR:-$REPO_ROOT/evidence/readme-sample-output}" +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/README.md}" +SUMMARY_PATH="${SUMMARY_PATH:-$OUTPUT_DIR/capture-metadata.txt}" INIT_OUTPUT_PATH="${INIT_OUTPUT_PATH:-$OUTPUT_DIR/init-output.txt}" mkdir -p "$OUTPUT_DIR" diff --git a/evidence/readme-sample-output/README.md b/docs/_support/readme-first-contact/sample-output/capture-metadata.txt similarity index 71% rename from evidence/readme-sample-output/README.md rename to docs/_support/readme-first-contact/sample-output/capture-metadata.txt index 2896389c..bf0fe538 100644 --- a/evidence/readme-sample-output/README.md +++ b/docs/_support/readme-first-contact/sample-output/capture-metadata.txt @@ -11,5 +11,5 @@ uvx --from "specfact-cli==0.45.1" specfact init --profile solo-developer uvx --from "specfact-cli==0.45.1" --with ruff --with radon --with semgrep --with basedpyright --with pylint --with crosshair-tool specfact code review run --path . --scope full ``` -- Raw output: `/workspace/evidence/readme-sample-output/review-output.txt` -- Init output: `/workspace/evidence/readme-sample-output/init-output.txt` +- Raw output: `/workspace/docs/_support/readme-first-contact/sample-output/review-output.txt` +- Init output: `/workspace/docs/_support/readme-first-contact/sample-output/init-output.txt` diff --git a/evidence/readme-sample-output/init-output.txt b/docs/_support/readme-first-contact/sample-output/init-output.txt similarity index 95% rename from evidence/readme-sample-output/init-output.txt rename to docs/_support/readme-first-contact/sample-output/init-output.txt index 5485ff6e..e8ad28ba 100644 --- a/evidence/readme-sample-output/init-output.txt +++ b/docs/_support/readme-first-contact/sample-output/init-output.txt @@ -1,14 +1,14 @@ -Downloading cryptography (4.3MiB) -Downloading beartype (1.3MiB) Downloading networkx (2.0MiB) -Downloading pydantic-core (2.0MiB) Downloading pygments (1.2MiB) +Downloading beartype (1.3MiB) +Downloading pydantic-core (2.0MiB) +Downloading cryptography (4.3MiB) Downloaded pydantic-core Downloaded cryptography Downloaded pygments Downloaded beartype Downloaded networkx -Installed 60 packages in 31ms +Installed 60 packages in 30ms ███████╗██████╗ ███████╗ ██████╗███████╗ █████╗ ██████╗████████╗ ██╔════╝██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔════╝╚══██╔══╝ @@ -21,7 +21,7 @@ Installed 60 packages in 31ms -⏱️ Started: 2026-04-03 20:13:58 +⏱️ Started: 2026-04-03 20:48:28 → Profile solo-developer: preparing workflow bundles… → Seeding workflow bundles: specfact-codebase, specfact-code-review (copying bundled modules into your user module directory) @@ -42,4 +42,4 @@ Prompt status: missing=7, outdated=0 for detected IDE (cursor). Run: specfact init ide --ide cursor Use `specfact init ide` to install/update IDE prompts and settings. -✓ Finished: 2026-04-03 20:14:12 | Duration: 14.10s +✓ Finished: 2026-04-03 20:48:42 | Duration: 13.67s diff --git a/evidence/readme-sample-output/review-output.txt b/docs/_support/readme-first-contact/sample-output/review-output.txt similarity index 99% rename from evidence/readme-sample-output/review-output.txt rename to docs/_support/readme-first-contact/sample-output/review-output.txt index 2803da63..aa93084b 100644 --- a/evidence/readme-sample-output/review-output.txt +++ b/docs/_support/readme-first-contact/sample-output/review-output.txt @@ -1,18 +1,18 @@ +Downloading ruff (10.7MiB) Downloading z3-solver (30.3MiB) Downloading nodejs-wheel-binaries (57.2MiB) -Downloading ruff (10.7MiB) -Downloading semgrep (55.9MiB) Downloading basedpyright (11.8MiB) +Downloading semgrep (55.9MiB) Downloaded ruff Downloaded z3-solver Downloaded semgrep Downloaded basedpyright Downloaded nodejs-wheel-binaries -Installed 111 packages in 415ms +Installed 111 packages in 361ms SpecFact CLI - v0.45.1 -⏱️ Started: 2026-04-03 20:14:18 +⏱️ Started: 2026-04-03 20:48:47 Running Ruff checks... Running Radon complexity checks... Running Semgrep rules... @@ -1317,4 +1317,4 @@ Running targeted tests and coverage... Verdict: FAIL | CI exit: 1 | Score: 0 | Reward delta: -80 Review completed with 217 findings (2 blocking). -✓ Finished: 2026-04-03 20:14:24 | Duration: 6.14s +✓ Finished: 2026-04-03 20:48:54 | Duration: 6.31s diff --git a/openspec/changes/readme-star-conversion-01/TDD_EVIDENCE.md b/openspec/changes/readme-star-conversion-01/TDD_EVIDENCE.md index fee77c26..1c8e6c6d 100644 --- a/openspec/changes/readme-star-conversion-01/TDD_EVIDENCE.md +++ b/openspec/changes/readme-star-conversion-01/TDD_EVIDENCE.md @@ -20,7 +20,8 @@ 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 -- `scripts/capture-readme-output.sh` and `evidence/readme-sample-output/` do not exist yet +- `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 @@ -43,7 +44,7 @@ - `hatch run type-check` - `hatch run contract-test` - `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` -- `scripts/capture-readme-output.sh` +- `docs/_support/readme-first-contact/capture-readme-output.sh` ### Result @@ -54,8 +55,8 @@ - `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 `evidence/readme-sample-output/` and are generated by - `scripts/capture-readme-output.sh` +- 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` - Focused docs contract tests and focused SpecFact review checks for the changed docs test files pass ### Environment notes diff --git a/openspec/changes/readme-star-conversion-01/proposal.md b/openspec/changes/readme-star-conversion-01/proposal.md index 8f93b672..4cded2fd 100644 --- a/openspec/changes/readme-star-conversion-01/proposal.md +++ b/openspec/changes/readme-star-conversion-01/proposal.md @@ -64,8 +64,8 @@ This change restructures the README so the first screen answers four questions i - `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` -- `scripts/capture-readme-output.sh` -- `evidence/readme-sample-output/` +- `docs/_support/readme-first-contact/capture-readme-output.sh` +- `docs/_support/readme-first-contact/sample-output/` - `openspec/CHANGE_ORDER.md` **Behavior / documentation impact** 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 index ccf4f145..48a051fe 100644 --- 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 @@ -34,7 +34,7 @@ simulate running the command. - **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 - `evidence/readme-sample-output/` + `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 diff --git a/openspec/changes/readme-star-conversion-01/tasks.md b/openspec/changes/readme-star-conversion-01/tasks.md index 8aa08036..3f6c5932 100644 --- a/openspec/changes/readme-star-conversion-01/tasks.md +++ b/openspec/changes/readme-star-conversion-01/tasks.md @@ -15,9 +15,9 @@ ## 3. Evidence capture -- [ ] 3.1 Add `scripts/capture-readme-output.sh` +- [ ] 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 `evidence/readme-sample-output/` +- [ ] 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 diff --git a/tests/unit/docs/test_wow_entrypoint_contract.py b/tests/unit/docs/test_wow_entrypoint_contract.py index a46d03cf..2e47b25c 100644 --- a/tests/unit/docs/test_wow_entrypoint_contract.py +++ b/tests/unit/docs/test_wow_entrypoint_contract.py @@ -10,8 +10,8 @@ REPO_ROOT = Path(__file__).resolve().parents[3] README = REPO_ROOT / "README.md" DOCS_INDEX = REPO_ROOT / "docs" / "index.md" -CAPTURE_SCRIPT = REPO_ROOT / "scripts" / "capture-readme-output.sh" -EVIDENCE_DIR = REPO_ROOT / "evidence" / "readme-sample-output" +CAPTURE_SCRIPT = REPO_ROOT / "docs" / "_support" / "readme-first-contact" / "capture-readme-output.sh" +EVIDENCE_DIR = REPO_ROOT / "docs" / "_support" / "readme-first-contact" / "sample-output" HOOK = "Review AI-assisted code against your own contracts." CTA = "Star this repo if the output is useful." @@ -74,5 +74,5 @@ def test_docs_index_shares_readme_hook_and_wow_block() -> None: 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 scripts/capture-readme-output.sh" - assert EVIDENCE_DIR.is_dir(), "Missing evidence/readme-sample-output/" + 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/" From c998550d74d54fd317b76efd7e48fa668f4e84be Mon Sep 17 00:00:00 2001 From: Dom Date: Fri, 3 Apr 2026 20:51:22 +0000 Subject: [PATCH 4/5] docs: fix readme workflow snippet and pin demo capture Co-authored-by: Dom --- README.md | 3 +-- docs/_support/readme-first-contact/capture-readme-output.sh | 5 +++++ .../readme-first-contact/sample-output/capture-metadata.txt | 1 + openspec/changes/readme-star-conversion-01/TDD_EVIDENCE.md | 2 ++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 82c878d8..54a984a5 100644 --- a/README.md +++ b/README.md @@ -86,8 +86,7 @@ It exists because delivery drifts in predictable ways: - repo: https://github.com/nold-ai/specfact-cli rev: v0.45.1 hooks: - - id: specfact-enforce - args: [--preset, minimal] + - id: specfact-smart-checks ``` **GitHub Actions** diff --git a/docs/_support/readme-first-contact/capture-readme-output.sh b/docs/_support/readme-first-contact/capture-readme-output.sh index 2b3b24ce..b4df62ba 100755 --- a/docs/_support/readme-first-contact/capture-readme-output.sh +++ b/docs/_support/readme-first-contact/capture-readme-output.sh @@ -7,6 +7,7 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" CLI_VERSION="${CLI_VERSION:-0.45.1}" REPO_SLUG="${REPO_SLUG:-nold-ai/specfact-demo-repo}" +REPO_REF="${REPO_REF:-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}" @@ -23,6 +24,9 @@ if [[ ! -d "$WORK_DIR/.git" ]]; then gh repo clone "$REPO_SLUG" "$WORK_DIR" fi +git -C "$WORK_DIR" fetch --depth 1 origin "$REPO_REF" +git -C "$WORK_DIR" checkout --force "$REPO_REF" + export HOME="$CAPTURE_HOME" uvx --from "specfact-cli==$CLI_VERSION" specfact init --profile solo-developer \ @@ -46,6 +50,7 @@ cat >"$SUMMARY_PATH" < Date: Fri, 3 Apr 2026 20:56:07 +0000 Subject: [PATCH 5/5] docs: address remaining readme review findings Co-authored-by: Dom --- README.md | 2 +- .../readme-first-contact/capture-readme-output.sh | 15 ++++++++++----- .../sample-output/capture-metadata.txt | 1 + .../sample-output/init-output.txt | 10 +++++----- .../sample-output/review-output.txt | 12 ++++++------ openspec/CHANGE_ORDER.md | 2 +- .../readme-star-conversion-01/TDD_EVIDENCE.md | 8 +++++--- .../changes/readme-star-conversion-01/proposal.md | 1 + .../changes/readme-star-conversion-01/tasks.md | 14 ++++++++++---- tests/unit/docs/docs_test_constants.py | 4 ++++ tests/unit/docs/test_first_contact_story.py | 8 +++++--- tests/unit/docs/test_wow_entrypoint_contract.py | 7 +++++-- 12 files changed, 54 insertions(+), 30 deletions(-) create mode 100644 tests/unit/docs/docs_test_constants.py diff --git a/README.md b/README.md index 54a984a5..606cba43 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ It exists because delivery drifts in predictable ways: ```yaml - name: SpecFact Gate - run: uvx specfact-cli@latest enforce stage --preset minimal + run: uvx specfact-cli@latest govern enforce stage --preset minimal ``` ## How SpecFact is built diff --git a/docs/_support/readme-first-contact/capture-readme-output.sh b/docs/_support/readme-first-contact/capture-readme-output.sh index b4df62ba..90987874 100755 --- a/docs/_support/readme-first-contact/capture-readme-output.sh +++ b/docs/_support/readme-first-contact/capture-readme-output.sh @@ -7,7 +7,7 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" CLI_VERSION="${CLI_VERSION:-0.45.1}" REPO_SLUG="${REPO_SLUG:-nold-ai/specfact-demo-repo}" -REPO_REF="${REPO_REF:-2b5ba8cd57d16c1a1f24463a297fdb28fbede123}" +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}" @@ -24,8 +24,9 @@ if [[ ! -d "$WORK_DIR/.git" ]]; then gh repo clone "$REPO_SLUG" "$WORK_DIR" fi -git -C "$WORK_DIR" fetch --depth 1 origin "$REPO_REF" -git -C "$WORK_DIR" checkout --force "$REPO_REF" +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" @@ -42,7 +43,8 @@ uvx \ --with pylint \ --with crosshair-tool \ specfact code review run --path . --scope full \ - >"$RAW_OUTPUT_PATH" 2>&1 || true + >"$RAW_OUTPUT_PATH" 2>&1 +REVIEW_EXIT_CODE=$? popd >/dev/null cat >"$SUMMARY_PATH" <"$SUMMARY_PATH" <'\"`]+") -HOOK = "Review AI-assisted code against your own contracts." -SUBHOOK = "Catch drift before it reaches PR or main." @pytest.fixture(scope="module", autouse=True) @@ -42,7 +42,9 @@ def _assert_contains_url_host(content: str, host: str, surface: str) -> None: def test_readme_hero_uses_proof_first_story() -> None: readme = _read(README) - first_screen = "\n".join(readme.splitlines()[:40]).lower() + try_it_idx = readme.lower().find("## try it in 60 seconds") + assert try_it_idx != -1 + first_screen = readme[:try_it_idx].lower() assert HOOK in readme assert SUBHOOK in readme diff --git a/tests/unit/docs/test_wow_entrypoint_contract.py b/tests/unit/docs/test_wow_entrypoint_contract.py index 2e47b25c..307ccdc8 100644 --- a/tests/unit/docs/test_wow_entrypoint_contract.py +++ b/tests/unit/docs/test_wow_entrypoint_contract.py @@ -6,14 +6,14 @@ 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" CAPTURE_SCRIPT = REPO_ROOT / "docs" / "_support" / "readme-first-contact" / "capture-readme-output.sh" EVIDENCE_DIR = REPO_ROOT / "docs" / "_support" / "readme-first-contact" / "sample-output" - -HOOK = "Review AI-assisted code against your own contracts." 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" @@ -76,3 +76,6 @@ 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"