artie-cli 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. artie_cli-0.7.0/.artie.toml.example +32 -0
  2. artie_cli-0.7.0/.claude/settings.local.json +25 -0
  3. artie_cli-0.7.0/.github/workflows/ci.yml +37 -0
  4. artie_cli-0.7.0/.gitignore +23 -0
  5. artie_cli-0.7.0/ARCHITECTURE.md +83 -0
  6. artie_cli-0.7.0/CONTRIBUTING.md +142 -0
  7. artie_cli-0.7.0/PKG-INFO +169 -0
  8. artie_cli-0.7.0/README.md +139 -0
  9. artie_cli-0.7.0/config.py +184 -0
  10. artie_cli-0.7.0/doc-eval.py +393 -0
  11. artie_cli-0.7.0/doc_eval.py +435 -0
  12. artie_cli-0.7.0/docs/scoring.md +230 -0
  13. artie_cli-0.7.0/examples/bookclub-openapi.yaml +160 -0
  14. artie_cli-0.7.0/examples/broken-openapi.yaml +48 -0
  15. artie_cli-0.7.0/examples/sample-openapi.yaml +55 -0
  16. artie_cli-0.7.0/pyproject.toml +61 -0
  17. artie_cli-0.7.0/src/artie/__init__.py +11 -0
  18. artie_cli-0.7.0/src/artie/__main__.py +6 -0
  19. artie_cli-0.7.0/src/artie/baseline.py +71 -0
  20. artie_cli-0.7.0/src/artie/checks/__init__.py +23 -0
  21. artie_cli-0.7.0/src/artie/checks/auth_clarity.py +173 -0
  22. artie_cli-0.7.0/src/artie/checks/base.py +93 -0
  23. artie_cli-0.7.0/src/artie/checks/endpoint_completeness.py +138 -0
  24. artie_cli-0.7.0/src/artie/checks/error_documentation.py +180 -0
  25. artie_cli-0.7.0/src/artie/checks/example_coverage.py +157 -0
  26. artie_cli-0.7.0/src/artie/checks/format_efficiency.py +164 -0
  27. artie_cli-0.7.0/src/artie/checks/generation_quality.py +604 -0
  28. artie_cli-0.7.0/src/artie/checks/parameter_naming.py +227 -0
  29. artie_cli-0.7.0/src/artie/checks/schema_complexity.py +178 -0
  30. artie_cli-0.7.0/src/artie/cli.py +280 -0
  31. artie_cli-0.7.0/src/artie/config.py +180 -0
  32. artie_cli-0.7.0/src/artie/fetcher.py +117 -0
  33. artie_cli-0.7.0/src/artie/generator.py +186 -0
  34. artie_cli-0.7.0/src/artie/parsers/__init__.py +75 -0
  35. artie_cli-0.7.0/src/artie/parsers/openapi.py +325 -0
  36. artie_cli-0.7.0/src/artie/parsers/types.py +60 -0
  37. artie_cli-0.7.0/src/artie/reporters/__init__.py +1 -0
  38. artie_cli-0.7.0/src/artie/reporters/json_report.py +66 -0
  39. artie_cli-0.7.0/src/artie/reporters/terminal.py +186 -0
  40. artie_cli-0.7.0/tests/helpers.py +23 -0
  41. artie_cli-0.7.0/tests/test_baseline.py +76 -0
  42. artie_cli-0.7.0/tests/test_checks_scoring.py +375 -0
  43. artie_cli-0.7.0/tests/test_config.py +127 -0
  44. artie_cli-0.7.0/tests/test_format_detector.py +108 -0
  45. artie_cli-0.7.0/tests/test_gate.py +64 -0
  46. artie_cli-0.7.0/tests/test_generation_eval.py +143 -0
  47. artie_cli-0.7.0/tests/test_parser.py +202 -0
  48. artie_cli-0.7.0/tests/test_sample_generation.py +113 -0
  49. artie_cli-0.7.0/tests/test_scoring_version.py +51 -0
@@ -0,0 +1,32 @@
1
+ # .artie.toml — per-project configuration for artie-cli.
2
+ #
3
+ # Copy this file to `.artie.toml` in your repository root. artie discovers it
4
+ # automatically by searching the working directory and its parents; you can
5
+ # also point at one explicitly with `artie check --config path/to/.artie.toml`.
6
+ #
7
+ # Command-line flags always override what is set here. Every section is
8
+ # optional. See docs/scoring.md for what the thresholds mean.
9
+
10
+ # Checks excluded from the pass/fail gate. They still run and appear in the
11
+ # report — they just cannot fail the build. Use this for checks that don't
12
+ # apply to your API rather than disabling the gate entirely.
13
+ ignore = ["Schema Complexity"]
14
+
15
+ [defaults]
16
+ # Default values for CLI flags, so CI doesn't need a long command line.
17
+ with_generation = false # run the opt-in Sample Generation check
18
+ differential = false # differential mode (implies with_generation)
19
+ model = "claude-sonnet-4-6" # model for Sample Generation
20
+ fail_under = 7 # global gate threshold for checks without
21
+ # a per-check threshold below (0-10)
22
+
23
+ [thresholds]
24
+ # Per-check minimum scores. A check scoring below its threshold fails the
25
+ # run (exit code 1). These take precedence over defaults.fail_under and over
26
+ # the --fail-under CLI flag. Names must match the check names exactly.
27
+ "Format Efficiency" = 9
28
+ "Endpoint Completeness" = 8
29
+ "Example Coverage" = 7
30
+ "Error Documentation" = 7
31
+ "Auth Clarity" = 7
32
+ "Parameter Naming" = 8
@@ -0,0 +1,25 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(python3 -c \"import sys; sys.path.insert\\(0,'src'\\); import tomllib\")",
5
+ "Bash(python3 -c ' *)",
6
+ "Bash(python3 -m venv .venv)",
7
+ "Bash(.venv/bin/pip install *)",
8
+ "Bash(.venv/bin/python -c \"from artie import SCORING_VERSION; print\\('scoring', SCORING_VERSION\\)\")",
9
+ "Bash(.venv/bin/artie check *)",
10
+ "Read(//tmp/**)",
11
+ "Bash(/home/edgrz/lib/artie-cli/.venv/bin/artie check *)",
12
+ "Bash(echo \"exit=$?\")",
13
+ "Bash(python3 -c \"import json; d=json.load\\(open\\('/tmp/sample.json'\\)\\); print\\('scoring_version:', d.get\\('scoring_version'\\)\\); print\\('checks:', [\\(c['name'],c['score'],c.get\\('informational'\\)\\) for c in d['checks']]\\)\")",
14
+ "Bash(echo \"gate exit=$?\")",
15
+ "Bash(rm /tmp/.artie.toml)",
16
+ "Bash(.venv/bin/python -m pytest -q)",
17
+ "Bash(.venv/bin/artie --version)",
18
+ "Bash(env -u ANTHROPIC_API_KEY .venv/bin/artie check examples/sample-openapi.yaml --with-generation)",
19
+ "Bash(python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); sg=[c for c in d['checks'] if c['name']=='Sample Generation'][0]; print\\('top keys:', sorted\\(d.keys\\(\\)\\)\\); print\\('sample-gen informational/evaluable/score:', sg['informational'], sg['evaluable'], sg['score']\\)\")",
20
+ "Bash(git checkout *)",
21
+ "Bash(git add *)",
22
+ "Bash(.venv/bin/python -m pytest --co -q)"
23
+ ]
24
+ }
25
+ }
@@ -0,0 +1,37 @@
1
+ name: CI
2
+
3
+ # Run the test suite on every push and pull request. The suite is fully
4
+ # offline — no check here calls the Anthropic API.
5
+ on:
6
+ push:
7
+ pull_request:
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ python-version: ["3.10", "3.11", "3.12"]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Set up Python ${{ matrix.python-version }}
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+
25
+ - name: Install package and dev dependencies
26
+ run: |
27
+ python -m pip install --upgrade pip
28
+ pip install -e ".[dev]"
29
+
30
+ - name: Run tests
31
+ run: pytest
32
+
33
+ - name: Smoke-check the CLI against the sample specs
34
+ run: |
35
+ artie check examples/bookclub-openapi.yaml
36
+ artie check examples/sample-openapi.yaml
37
+ artie check examples/broken-openapi.yaml
@@ -0,0 +1,23 @@
1
+ # Python bytecode
2
+ __pycache__/
3
+ *.py[cod]
4
+
5
+ # Virtual environments
6
+ .venv/
7
+ venv/
8
+
9
+ # Packaging / build
10
+ build/
11
+ dist/
12
+ *.egg-info/
13
+
14
+ # Test and tooling caches
15
+ .pytest_cache/
16
+ .coverage
17
+ htmlcov/
18
+
19
+ # Local artie config (the example template is tracked; a real one is not)
20
+ .artie.toml
21
+
22
+ # Editor noise
23
+ .DS_Store
@@ -0,0 +1,83 @@
1
+ # Architecture
2
+
3
+ artie-cli scores API documentation for AI readiness. It accepts either a local file or an HTTP URL, detects the format, parses what it can, runs a set of checks against the parsed content, and renders a report. Each check is an independent measurement; there is no aggregate score.
4
+
5
+ ## Layout
6
+
7
+ ```
8
+ src/artie/
9
+ ├── __init__.py Package version and SCORING_VERSION.
10
+ ├── __main__.py Lets `python -m artie` run the CLI.
11
+ ├── cli.py Typer-based CLI. Owns the request flow and the gate.
12
+ ├── config.py Loads .artie.toml: per-check thresholds, ignored
13
+ │ checks, default flags.
14
+ ├── baseline.py Loads a saved JSON report for run-over-run deltas.
15
+ ├── fetcher.py HTTP fetcher using stdlib urllib. Returns raw content
16
+ │ plus a format hint derived from Content-Type and URL.
17
+ ├── generator.py Minimal Anthropic Messages API client (stdlib urllib).
18
+ │ Used by the Sample Generation check. Implements retry
19
+ │ logic for transient errors (429, 503, 529).
20
+ ├── parsers/
21
+ │ ├── __init__.py Format detection (detect_format) and the public
22
+ │ │ parse() entry point.
23
+ │ ├── openapi.py OpenAPI YAML and JSON parsing. Also extracts embedded
24
+ │ │ OpenAPI specs from markdown code fences.
25
+ │ └── types.py Endpoint and ParsedDocs dataclasses. Shared by all
26
+ │ checks that need a structured view of the spec.
27
+ ├── checks/
28
+ │ ├── __init__.py The ALL_CHECKS registry.
29
+ │ ├── base.py BaseCheck, CheckResult, Severity. The check protocol.
30
+ │ ├── format_efficiency.py
31
+ │ ├── endpoint_completeness.py
32
+ │ ├── example_coverage.py
33
+ │ ├── error_documentation.py
34
+ │ ├── auth_clarity.py
35
+ │ ├── parameter_naming.py
36
+ │ ├── schema_complexity.py
37
+ │ └── generation_quality.py SampleGenerationCheck (opt-in, unscored).
38
+ └── reporters/
39
+ ├── __init__.py
40
+ ├── terminal.py Rich-based human-readable report.
41
+ └── json_report.py Machine-readable JSON for CI integration.
42
+ ```
43
+
44
+ ## Request flow
45
+
46
+ The CLI does the same thing in every invocation:
47
+
48
+ 1. Parse arguments. Resolve target as either a file path or an HTTP URL.
49
+ 2. Load configuration. `config.load_config` reads a `.artie.toml` (an explicit `--config` path, or auto-discovered from the working directory upward). CLI flags override config defaults; a `None` flag means "not passed". If `--baseline` is given, `baseline.load_baseline` reads the prior JSON report.
50
+ 3. Read content. For files, read directly. For URLs, call `fetcher.fetch`, which sends an Accept header preferring structured formats and returns raw bytes, a Content-Type, and a format hint.
51
+ 4. Detect format. `parsers.detect_format` combines the URL suffix, the Content-Type hint, and content inspection to return one of: `openapi-yaml`, `openapi-json`, `yaml`, `json`, `markdown`, `html`, `unknown`.
52
+ 5. Parse. `parsers.parse` produces a `ParsedDocs`. For OpenAPI inputs this contains the raw dict, the extracted endpoints, and the components map. For markdown, parse scans code fences for an embedded OpenAPI spec; if found, the ParsedDocs is fully populated and `extracted_from` is set to `"markdown"`. For HTML or other unstructured inputs, raw is None and `parse_error` explains why.
53
+ 6. Run checks. The CLI instantiates each check in `ALL_CHECKS`, plus `SampleGenerationCheck` constructed with the resolved flags, and calls `check.run(content, format_type, parsed)` on each. Checks decide for themselves whether they can run. If they cannot, they return a not-evaluable result.
54
+ 7. Render. Either terminal output via Rich or JSON via `json_report.render`, both passed the baseline for delta reporting.
55
+ 8. Apply the gate. `_gate_failed` exits non-zero if any scored, non-ignored check is below its threshold (per-check from config, else the global `--fail-under` or `defaults.fail_under`).
56
+
57
+ ## The check protocol
58
+
59
+ Every check subclasses `BaseCheck` and implements `run(content, format_type, parsed) -> CheckResult`. A check returns one of three things:
60
+
61
+ - An evaluable result with a score 0-10 and a severity computed from the score.
62
+ - A not-evaluable result, created by calling `self.not_evaluable(reason)`. Use this when the input does not provide enough information to score honestly. Examples: schema complexity has no `components.schemas` section, endpoint completeness was given markdown with no embedded spec, generation quality has no API key in the environment.
63
+ - An evaluable result with score 0 if the check ran but found nothing worth scoring above zero. Used sparingly. Prefer not-evaluable when the issue is missing information rather than missing quality.
64
+
65
+ `CheckResult.metadata` is a free-form dict. Checks use it to carry machine-readable data that does not belong in findings or recommendations: token counts, evaluation criteria booleans, generated code blocks, baseline scores in differential mode, and so on. The JSON reporter passes metadata through verbatim. The terminal reporter has special-case rendering for `metadata.code`, which it shows as a syntax-highlighted Python block.
66
+
67
+ ## Notable design decisions
68
+
69
+ **No aggregate score.** Each check stands alone. We do not currently have empirical evidence for the right weights to combine them. See `docs/scoring.md` for every threshold, its evidence basis, and the rationale for not aggregating.
70
+
71
+ **Stdlib urllib instead of the anthropic SDK.** The generator does one kind of call: a single Messages request, no streaming, no batching. Adding httpx, pydantic, and the SDK would roughly double cold-start time under uvx. The fetcher uses the same approach for the same reason.
72
+
73
+ **Embedded OpenAPI extraction.** Modern docs sites (Mintlify, Fern, ReadMe, Stainless, Scalar) embed the OpenAPI spec inside markdown code fences alongside prose and SDK examples. When the input is markdown, `parsers.openapi._parse_markdown` scans for the first fenced block whose contents parse as OpenAPI and uses that as the spec. The structured checks then run against the embedded spec while Format Efficiency still scores against the full markdown.
74
+
75
+ **Sample Generation is a separate kind of check, and unscored.** It calls an external API, costs money, and produces results that depend partly on the model rather than entirely on the docs. Because of that it is opt-in (`--with-generation`) and deliberately produces no 0-10 score: its `CheckResult` has `informational=True` and `score=None`. The generated code is the deliverable; the five structural criteria are reported as commentary. It accepts a `console` parameter so it can render a spinner during the LLM call, and a `differential` flag that triggers a second baseline call. When that baseline is structurally strong it emits a contamination warning. It is the most complex check and the least empirically grounded. Treat it accordingly.
76
+
77
+ **Informational is a third result state.** Alongside scored and not-evaluable, a `CheckResult` can be `informational`: the check ran and has findings but assigns no score. It is excluded from `is_evaluable` and from the pass/fail gate. Only Sample Generation uses it.
78
+
79
+ **Not-evaluable is a first-class state, not an error.** A check returning N/A is honest: this measurement does not apply to this input. The terminal reporter renders N/A with its own symbol and severity color. The JSON reporter exposes `evaluable: false`. Both are intentional design choices to avoid pressuring checks into producing fake scores when they cannot honestly produce real ones.
80
+
81
+ ## Adding a check
82
+
83
+ See `CONTRIBUTING.md`.
@@ -0,0 +1,142 @@
1
+ # Contributing
2
+
3
+ ## Development setup
4
+
5
+ ```bash
6
+ git clone https://github.com/grzetich/artie-cli
7
+ cd artie-cli
8
+ python3 -m venv .venv
9
+ source .venv/bin/activate
10
+ pip install -e ".[dev]"
11
+ artie check examples/bookclub-openapi.yaml
12
+ pytest
13
+ ```
14
+
15
+ The static checks and the test suite are fully offline. If you want to exercise the opt-in Sample Generation check, export an Anthropic API key and pass `--with-generation`:
16
+
17
+ ```bash
18
+ export ANTHROPIC_API_KEY=sk-ant-...
19
+ artie check examples/bookclub-openapi.yaml --with-generation
20
+ ```
21
+
22
+ Read `ARCHITECTURE.md` before touching the code. It explains the request flow, the check protocol, and the design decisions that constrain implementation choices.
23
+
24
+ ## Running examples
25
+
26
+ Three samples live in `examples/`:
27
+
28
+ - `bookclub-openapi.yaml` is the gold standard. Every check should score 10/10 against it. If a change makes it score lower, the change is suspect.
29
+ - `sample-openapi.yaml` is a minimal spec. Endpoint Completeness scores well, Example Coverage and Auth Clarity do not. Useful for checking middle-of-the-road behavior.
30
+ - `broken-openapi.yaml` is deliberately bad. Most checks score zero or near zero, with named operations and properties in the recommendations. Useful for verifying that low scores produce actionable findings.
31
+
32
+ Run each before and after any scoring change to make sure the change does what you expect.
33
+
34
+ ## Writing a new check
35
+
36
+ Subclass `BaseCheck`, implement `run`, register in `checks/__init__.py`.
37
+
38
+ ```python
39
+ from typing import Any
40
+
41
+ from artie.checks.base import BaseCheck, CheckResult
42
+ from artie.parsers.types import ParsedDocs
43
+
44
+
45
+ class MyNewCheck(BaseCheck):
46
+ name = "My New Check"
47
+ description = "What this check measures."
48
+
49
+ def run(
50
+ self, content: str, format_type: str, parsed: Any = None
51
+ ) -> CheckResult:
52
+ if not isinstance(parsed, ParsedDocs) or not parsed.is_openapi:
53
+ return self.not_evaluable("This check needs a parsed OpenAPI spec.")
54
+
55
+ # Compute the score on a 0-10 scale.
56
+ score = self._compute(parsed)
57
+ severity = self.severity_for(score)
58
+
59
+ return CheckResult(
60
+ name=self.name,
61
+ description=self.description,
62
+ score=score,
63
+ max_score=self.max_score,
64
+ severity=severity,
65
+ findings=["What we observed."],
66
+ recommendations=["What to do about it."],
67
+ metadata={"raw_data": "for JSON consumers"},
68
+ )
69
+
70
+ def _compute(self, parsed: ParsedDocs) -> int:
71
+ ...
72
+ ```
73
+
74
+ Then add it to `ALL_CHECKS` in `checks/__init__.py`. Order matters because it determines display order in the report.
75
+
76
+ Things to honor in your check:
77
+
78
+ - Return `not_evaluable` when the check cannot honestly score the input. Do not return a fake zero or a fake ten just to have a number on the report.
79
+ - Use `self.severity_for(score)` to map the score to a severity bucket. Do not invent your own mapping.
80
+ - Findings are observations. Recommendations are advice. Keep them distinct.
81
+ - Findings should be specific to the input being scored. "5 of 14 endpoints" beats "many endpoints." When pointing at specific problems, name the operations or properties (up to about five examples, truncated with an ellipsis).
82
+ - Metadata is for machine consumers. Put structured data there instead of synthesizing it from finding strings.
83
+
84
+ ## Style notes
85
+
86
+ Write the way the existing code reads. Concrete observations, named items, no hedging. Avoid these:
87
+
88
+ - Em or en dashes. Use a comma or split the sentence.
89
+ - "Leverage," "seamless," "robust," "fundamentally," "paradigm," "ecosystem" as metaphor, and the rest of the AI-tells list.
90
+ - Guided-tour phrasings like "Let's look at." Just look at it.
91
+ - Long bolded thesis sentences that summarize a paragraph. Let the paragraph carry its own weight.
92
+ - Sentences clustered on "This." More than two in a row signals laziness.
93
+
94
+ For docstrings, one sentence is usually enough. Longer when the function does something non-obvious. Module docstrings should explain why the module exists and what it produces, not narrate every function.
95
+
96
+ ## Scoring conventions
97
+
98
+ Scores are 0-10 integers. The mapping to severity, in `BaseCheck.severity_for`:
99
+
100
+ - 9-10: excellent
101
+ - 7-8: good
102
+ - 4-6: needs work
103
+ - 0-3: poor
104
+ - None: not evaluable
105
+
106
+ If you find yourself wanting a different bucket size or a different scale, that is a sign to add a new check rather than redefine the scale. The convention is shared across all checks so users can read across them.
107
+
108
+ When a check has multiple sub-signals, average them and scale the result to 0-10. When sub-signals do not apply (no path parameters, no request bodies), drop them from the average rather than scoring them zero.
109
+
110
+ Document threshold choices in code comments at the top of the check. Future calibration work needs to know what each threshold currently corresponds to.
111
+
112
+ ## Tests
113
+
114
+ Tests live under `tests/` and use pytest. Run them with `pytest` (the
115
+ configuration in `pyproject.toml` handles the source path). The suite is
116
+ fully offline — no test calls the Anthropic API.
117
+
118
+ Current coverage:
119
+
120
+ - Each check's scoring math and the Schema Complexity threshold buckets, including not-evaluable edge cases (`test_checks_scoring.py`).
121
+ - The format detector across all supported formats (`test_format_detector.py`).
122
+ - The OpenAPI parser including embedded extraction from markdown (`test_parser.py`).
123
+ - Sample Generation internals: the gap-marker regex, the identifier builder, and code evaluation (`test_generation_eval.py`, `test_sample_generation.py`).
124
+ - Config loading, baseline deltas, the pass/fail gate, and scoring-version sync (`test_config.py`, `test_baseline.py`, `test_gate.py`, `test_scoring_version.py`).
125
+
126
+ Still uncovered and worth adding: the fetcher's content-type hint logic and
127
+ the generator's retry behavior, both with mocked HTTP responses.
128
+
129
+ `.github/workflows/ci.yml` runs the suite on every push and pull request
130
+ across Python 3.10–3.12.
131
+
132
+ ## Versioning
133
+
134
+ Bump the version in `pyproject.toml` and `src/artie/__init__.py` together. They must match. Use semver. A change to the scoring rubric is a minor version bump because it changes user-visible behavior even when code APIs do not change.
135
+
136
+ The scoring rubric is versioned separately from the package. `artie.SCORING_VERSION` is the runtime source of truth; `pyproject.toml [tool.artie] scoring_version` mirrors it, and `tests/test_scoring_version.py` fails if the two drift. Bump it whenever a threshold, bucket, or weighting changes, and update `docs/scoring.md` in the same commit. It is emitted in JSON output so downstream tooling can pin against it.
137
+
138
+ ## Pull requests
139
+
140
+ Keep them focused. One check, one bug fix, one CI feature per PR. If you find yourself wanting to do unrelated cleanups, open a separate PR for those.
141
+
142
+ Run the three example specs before and after your change. Include the diff in the PR description when scores change. If a sample's score moves, explain why the new score is more correct than the old one.
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.4
2
+ Name: artie-cli
3
+ Version: 0.7.0
4
+ Summary: Score your API documentation for AI-readiness. Based on the Tokens Not Jokin' research.
5
+ Project-URL: Homepage, https://artie.fm
6
+ Project-URL: Repository, https://github.com/grzetich/artie-cli
7
+ Project-URL: Research, https://leanpub.com/tokensnotjokin
8
+ Author-email: Ed Grzetich <ed@grzeti.ch>
9
+ License: MIT
10
+ Keywords: ai,api,developer-tools,documentation,llm,openapi
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Documentation
20
+ Classifier: Topic :: Software Development :: Documentation
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: pyyaml>=6.0
23
+ Requires-Dist: rich>=13.0.0
24
+ Requires-Dist: tiktoken>=0.7.0
25
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
26
+ Requires-Dist: typer>=0.12.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=8.0; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # artie-cli
32
+
33
+ Score your API documentation for AI-readiness.
34
+
35
+ artie reads your API docs the way an AI agent would, then reports how well your documentation actually supports code generation. The checks are derived from [*Tokens Not Jokin'*](https://leanpub.com/tokensnotjokin), a 21,462-test empirical study comparing four AI models against four documentation formats.
36
+
37
+ artie measures and reports. It does not convert, clean, or modify your documentation.
38
+
39
+ ## Quickstart
40
+
41
+ ```bash
42
+ # No install required
43
+ uvx artie-cli check ./openapi.yaml
44
+ uvx artie-cli check https://api.example.com/openapi.yaml
45
+
46
+ # Install with pipx
47
+ pipx install artie-cli
48
+ artie check ./openapi.yaml
49
+ ```
50
+
51
+ By default artie runs the seven static checks, entirely offline. The optional Sample Generation check is opt-in: pass `--with-generation` and set an Anthropic API key.
52
+
53
+ ```bash
54
+ export ANTHROPIC_API_KEY=sk-ant-...
55
+ artie check ./openapi.yaml --with-generation
56
+ ```
57
+
58
+ Without `--with-generation`, Sample Generation shows as N/A with a note on how to enable it.
59
+
60
+ ## What it checks
61
+
62
+ **Seven static checks**, derived from the TNJ research:
63
+
64
+ - **Format Efficiency**: how token-efficient your documentation format is
65
+ - **Endpoint Completeness**: descriptions, operationIds, and documented path parameters
66
+ - **Example Coverage**: request and response body examples
67
+ - **Error Documentation**: 4xx and 5xx responses with meaningful descriptions and content schemas
68
+ - **Auth Clarity**: security schemes defined, described, and applied to operations
69
+ - **Parameter Naming**: consistent naming convention across parameters and schema properties
70
+ - **Schema Complexity**: nesting depth across component schemas
71
+
72
+ Each static check scores 0–10. Every threshold and weighting is documented in [docs/scoring.md](docs/scoring.md), along with which numbers are empirically grounded and which are heuristics awaiting calibration. artie deliberately reports no aggregate score.
73
+
74
+ **One optional, opt-in check**, applied to any input format:
75
+
76
+ - **Sample Generation**: an AI model is given your docs and asked to write a Python function that calls the API. The generated code is the deliverable — artie includes it in the report so you can see exactly what an agent writes from your docs, and comments on its structure (valid syntax, HTTP client import, error handling, request construction, response handling). It is **not scored**: one generation against one model is a sample, not a measurement. Enable it with `--with-generation`.
77
+
78
+ The static checks tell you what specifically to fix. Sample Generation shows you a concrete example of what an agent produces. Static checks return N/A when the input isn't OpenAPI; Sample Generation runs against anything.
79
+
80
+ ## Inputs
81
+
82
+ artie accepts either a local file or an HTTP/HTTPS URL:
83
+
84
+ ```bash
85
+ artie check ./openapi.yaml
86
+ artie check https://api.example.com/openapi.yaml
87
+ artie check https://docs.example.com/getting-started
88
+ ```
89
+
90
+ Supported formats: OpenAPI YAML, OpenAPI JSON, plain YAML, plain JSON, Markdown, HTML.
91
+
92
+ When the input is markdown, artie scans for an OpenAPI spec embedded in a code fence and runs the structured checks against that spec. Modern docs sites (Mintlify, Fern, ReadMe, Stainless) embed the spec inline alongside prose and SDK examples, and artie picks it up automatically.
93
+
94
+ When fetching URLs, artie sends an `Accept` header that requests structured formats first. If the server honors content negotiation (some major docs sites do, including parts of AWS and most Mintlify-hosted sites), you may receive a different format than the URL suggests. The report calls this out.
95
+
96
+ ## Output formats
97
+
98
+ ```bash
99
+ # Pretty terminal output (default); static checks only, fully offline
100
+ artie check ./openapi.yaml
101
+
102
+ # JSON for CI pipelines (includes scoring_version)
103
+ artie check ./openapi.yaml --output json
104
+
105
+ # Fail the build if any check scores below 7
106
+ artie check ./openapi.yaml --fail-under 7
107
+
108
+ # Compare against a previously saved JSON report and show per-check deltas
109
+ artie check ./openapi.yaml --baseline previous.json
110
+
111
+ # Run the opt-in Sample Generation check (uses the Anthropic API)
112
+ artie check ./openapi.yaml --with-generation
113
+
114
+ # Differential mode: flag training-data contamination (implies --with-generation, doubles cost)
115
+ artie check ./openapi.yaml --differential
116
+
117
+ # Use a different model for Sample Generation
118
+ artie check ./openapi.yaml --with-generation --model claude-opus-4-7
119
+ ```
120
+
121
+ ## Configuration
122
+
123
+ Drop a `.artie.toml` in your repository root to pin per-check pass/fail thresholds, exclude checks from the gate, and set default flags. artie discovers it automatically from the working directory upward. See [`.artie.toml.example`](.artie.toml.example) for the full schema and [docs/scoring.md](docs/scoring.md) for what the thresholds mean.
124
+
125
+ ## About Sample Generation and training contamination
126
+
127
+ Sample Generation has a real limitation worth understanding. When the model has seen an API during training (which is the case for AWS, Stripe, GitHub, and most public APIs with Python SDKs on PyPI), it can write working code from training alone, regardless of how complete the docs you're testing are. Good-looking code for a famous API tells you the model knows the API, not that the docs are good.
128
+
129
+ This is exactly why the check is unscored: a grade would imply a measurement the method can't honestly deliver. Instead artie shows you the generated code and comments on its structure.
130
+
131
+ The prompt instructs the model to use only information from the docs and to flag gaps in inline code comments. artie detects those gap comments automatically and reports them as evidence of real documentation deficiencies.
132
+
133
+ `--differential` mode adds a second API call with no docs body, measuring what the model produces from training alone. When that baseline is already strong, artie prints a contamination warning: the docs-informed sample reflects model capability, not docs quality. Differential mode is most informative for novel or internal APIs the model has not seen.
134
+
135
+ ## Cost
136
+
137
+ Sample Generation makes one Anthropic API call per run (two with `--differential`). On Claude Sonnet 4.6 (the default), a typical docs page costs roughly $0.02 to $0.05 per run at retail pricing. Because the check is opt-in, CI runs stay free and offline unless you explicitly ask for it.
138
+
139
+ ## Examples
140
+
141
+ ```bash
142
+ artie check examples/bookclub-openapi.yaml
143
+ artie check examples/broken-openapi.yaml
144
+ artie check examples/sample-openapi.yaml
145
+ ```
146
+
147
+ ## Privacy
148
+
149
+ The static checks run entirely locally. Sample Generation sends your documentation content to Anthropic's API — but it is opt-in, so nothing leaves your machine unless you pass `--with-generation`.
150
+
151
+ ## Why a CLI
152
+
153
+ artie is a checker, not a converter. CLI tools live where the docs do: in the repo, in the pipeline, next to the spec. You get a score, you act on it, the static-check data never leaves your machine.
154
+
155
+ ## Research
156
+
157
+ artie's checks are grounded in empirical findings published in *Tokens Not Jokin'*. Key results:
158
+
159
+ - YAML uses up to 80% fewer tokens than OpenAPI 3.0 JSON
160
+ - Documentation format explains more than 10x the variance in generated code quality than model choice
161
+ - Disciplined error documentation produces dramatically better error handling in generated code
162
+
163
+ The Sample Generation check uses the same methodology as TNJ, applied per-spec: ask an AI to write code from these docs, then show what it produced.
164
+
165
+ Buy the book: [leanpub.com/tokensnotjokin](https://leanpub.com/tokensnotjokin)
166
+
167
+ ## License
168
+
169
+ MIT
@@ -0,0 +1,139 @@
1
+ # artie-cli
2
+
3
+ Score your API documentation for AI-readiness.
4
+
5
+ artie reads your API docs the way an AI agent would, then reports how well your documentation actually supports code generation. The checks are derived from [*Tokens Not Jokin'*](https://leanpub.com/tokensnotjokin), a 21,462-test empirical study comparing four AI models against four documentation formats.
6
+
7
+ artie measures and reports. It does not convert, clean, or modify your documentation.
8
+
9
+ ## Quickstart
10
+
11
+ ```bash
12
+ # No install required
13
+ uvx artie-cli check ./openapi.yaml
14
+ uvx artie-cli check https://api.example.com/openapi.yaml
15
+
16
+ # Install with pipx
17
+ pipx install artie-cli
18
+ artie check ./openapi.yaml
19
+ ```
20
+
21
+ By default artie runs the seven static checks, entirely offline. The optional Sample Generation check is opt-in: pass `--with-generation` and set an Anthropic API key.
22
+
23
+ ```bash
24
+ export ANTHROPIC_API_KEY=sk-ant-...
25
+ artie check ./openapi.yaml --with-generation
26
+ ```
27
+
28
+ Without `--with-generation`, Sample Generation shows as N/A with a note on how to enable it.
29
+
30
+ ## What it checks
31
+
32
+ **Seven static checks**, derived from the TNJ research:
33
+
34
+ - **Format Efficiency**: how token-efficient your documentation format is
35
+ - **Endpoint Completeness**: descriptions, operationIds, and documented path parameters
36
+ - **Example Coverage**: request and response body examples
37
+ - **Error Documentation**: 4xx and 5xx responses with meaningful descriptions and content schemas
38
+ - **Auth Clarity**: security schemes defined, described, and applied to operations
39
+ - **Parameter Naming**: consistent naming convention across parameters and schema properties
40
+ - **Schema Complexity**: nesting depth across component schemas
41
+
42
+ Each static check scores 0–10. Every threshold and weighting is documented in [docs/scoring.md](docs/scoring.md), along with which numbers are empirically grounded and which are heuristics awaiting calibration. artie deliberately reports no aggregate score.
43
+
44
+ **One optional, opt-in check**, applied to any input format:
45
+
46
+ - **Sample Generation**: an AI model is given your docs and asked to write a Python function that calls the API. The generated code is the deliverable — artie includes it in the report so you can see exactly what an agent writes from your docs, and comments on its structure (valid syntax, HTTP client import, error handling, request construction, response handling). It is **not scored**: one generation against one model is a sample, not a measurement. Enable it with `--with-generation`.
47
+
48
+ The static checks tell you what specifically to fix. Sample Generation shows you a concrete example of what an agent produces. Static checks return N/A when the input isn't OpenAPI; Sample Generation runs against anything.
49
+
50
+ ## Inputs
51
+
52
+ artie accepts either a local file or an HTTP/HTTPS URL:
53
+
54
+ ```bash
55
+ artie check ./openapi.yaml
56
+ artie check https://api.example.com/openapi.yaml
57
+ artie check https://docs.example.com/getting-started
58
+ ```
59
+
60
+ Supported formats: OpenAPI YAML, OpenAPI JSON, plain YAML, plain JSON, Markdown, HTML.
61
+
62
+ When the input is markdown, artie scans for an OpenAPI spec embedded in a code fence and runs the structured checks against that spec. Modern docs sites (Mintlify, Fern, ReadMe, Stainless) embed the spec inline alongside prose and SDK examples, and artie picks it up automatically.
63
+
64
+ When fetching URLs, artie sends an `Accept` header that requests structured formats first. If the server honors content negotiation (some major docs sites do, including parts of AWS and most Mintlify-hosted sites), you may receive a different format than the URL suggests. The report calls this out.
65
+
66
+ ## Output formats
67
+
68
+ ```bash
69
+ # Pretty terminal output (default); static checks only, fully offline
70
+ artie check ./openapi.yaml
71
+
72
+ # JSON for CI pipelines (includes scoring_version)
73
+ artie check ./openapi.yaml --output json
74
+
75
+ # Fail the build if any check scores below 7
76
+ artie check ./openapi.yaml --fail-under 7
77
+
78
+ # Compare against a previously saved JSON report and show per-check deltas
79
+ artie check ./openapi.yaml --baseline previous.json
80
+
81
+ # Run the opt-in Sample Generation check (uses the Anthropic API)
82
+ artie check ./openapi.yaml --with-generation
83
+
84
+ # Differential mode: flag training-data contamination (implies --with-generation, doubles cost)
85
+ artie check ./openapi.yaml --differential
86
+
87
+ # Use a different model for Sample Generation
88
+ artie check ./openapi.yaml --with-generation --model claude-opus-4-7
89
+ ```
90
+
91
+ ## Configuration
92
+
93
+ Drop a `.artie.toml` in your repository root to pin per-check pass/fail thresholds, exclude checks from the gate, and set default flags. artie discovers it automatically from the working directory upward. See [`.artie.toml.example`](.artie.toml.example) for the full schema and [docs/scoring.md](docs/scoring.md) for what the thresholds mean.
94
+
95
+ ## About Sample Generation and training contamination
96
+
97
+ Sample Generation has a real limitation worth understanding. When the model has seen an API during training (which is the case for AWS, Stripe, GitHub, and most public APIs with Python SDKs on PyPI), it can write working code from training alone, regardless of how complete the docs you're testing are. Good-looking code for a famous API tells you the model knows the API, not that the docs are good.
98
+
99
+ This is exactly why the check is unscored: a grade would imply a measurement the method can't honestly deliver. Instead artie shows you the generated code and comments on its structure.
100
+
101
+ The prompt instructs the model to use only information from the docs and to flag gaps in inline code comments. artie detects those gap comments automatically and reports them as evidence of real documentation deficiencies.
102
+
103
+ `--differential` mode adds a second API call with no docs body, measuring what the model produces from training alone. When that baseline is already strong, artie prints a contamination warning: the docs-informed sample reflects model capability, not docs quality. Differential mode is most informative for novel or internal APIs the model has not seen.
104
+
105
+ ## Cost
106
+
107
+ Sample Generation makes one Anthropic API call per run (two with `--differential`). On Claude Sonnet 4.6 (the default), a typical docs page costs roughly $0.02 to $0.05 per run at retail pricing. Because the check is opt-in, CI runs stay free and offline unless you explicitly ask for it.
108
+
109
+ ## Examples
110
+
111
+ ```bash
112
+ artie check examples/bookclub-openapi.yaml
113
+ artie check examples/broken-openapi.yaml
114
+ artie check examples/sample-openapi.yaml
115
+ ```
116
+
117
+ ## Privacy
118
+
119
+ The static checks run entirely locally. Sample Generation sends your documentation content to Anthropic's API — but it is opt-in, so nothing leaves your machine unless you pass `--with-generation`.
120
+
121
+ ## Why a CLI
122
+
123
+ artie is a checker, not a converter. CLI tools live where the docs do: in the repo, in the pipeline, next to the spec. You get a score, you act on it, the static-check data never leaves your machine.
124
+
125
+ ## Research
126
+
127
+ artie's checks are grounded in empirical findings published in *Tokens Not Jokin'*. Key results:
128
+
129
+ - YAML uses up to 80% fewer tokens than OpenAPI 3.0 JSON
130
+ - Documentation format explains more than 10x the variance in generated code quality than model choice
131
+ - Disciplined error documentation produces dramatically better error handling in generated code
132
+
133
+ The Sample Generation check uses the same methodology as TNJ, applied per-spec: ask an AI to write code from these docs, then show what it produced.
134
+
135
+ Buy the book: [leanpub.com/tokensnotjokin](https://leanpub.com/tokensnotjokin)
136
+
137
+ ## License
138
+
139
+ MIT