askround 0.1.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.
- askround-0.1.0/LICENSE +201 -0
- askround-0.1.0/PKG-INFO +452 -0
- askround-0.1.0/README.md +422 -0
- askround-0.1.0/plugin/src/askround/__init__.py +3 -0
- askround-0.1.0/plugin/src/askround/__main__.py +3 -0
- askround-0.1.0/plugin/src/askround/budget.py +108 -0
- askround-0.1.0/plugin/src/askround/citations.py +172 -0
- askround-0.1.0/plugin/src/askround/claims.py +61 -0
- askround-0.1.0/plugin/src/askround/cli.py +1179 -0
- askround-0.1.0/plugin/src/askround/costs.py +206 -0
- askround-0.1.0/plugin/src/askround/diff.py +170 -0
- askround-0.1.0/plugin/src/askround/discover.py +124 -0
- askround-0.1.0/plugin/src/askround/doctor.py +65 -0
- askround-0.1.0/plugin/src/askround/export.py +113 -0
- askround-0.1.0/plugin/src/askround/extract.py +288 -0
- askround-0.1.0/plugin/src/askround/gaps.py +117 -0
- askround-0.1.0/plugin/src/askround/http.py +94 -0
- askround-0.1.0/plugin/src/askround/jobs.py +142 -0
- askround-0.1.0/plugin/src/askround/leading.py +71 -0
- askround-0.1.0/plugin/src/askround/links.py +153 -0
- askround-0.1.0/plugin/src/askround/lookup.py +246 -0
- askround-0.1.0/plugin/src/askround/mcp.py +538 -0
- askround-0.1.0/plugin/src/askround/mention.py +112 -0
- askround-0.1.0/plugin/src/askround/private.py +67 -0
- askround-0.1.0/plugin/src/askround/product.py +114 -0
- askround-0.1.0/plugin/src/askround/provenance.py +44 -0
- askround-0.1.0/plugin/src/askround/providers.py +454 -0
- askround-0.1.0/plugin/src/askround/refusals.py +104 -0
- askround-0.1.0/plugin/src/askround/report.py +531 -0
- askround-0.1.0/plugin/src/askround/research.py +296 -0
- askround-0.1.0/plugin/src/askround/reuse.py +96 -0
- askround-0.1.0/plugin/src/askround/rivals.py +79 -0
- askround-0.1.0/plugin/src/askround/round.py +467 -0
- askround-0.1.0/plugin/src/askround/series.py +142 -0
- askround-0.1.0/plugin/src/askround/sets.py +122 -0
- askround-0.1.0/plugin/src/askround/wiring.py +162 -0
- askround-0.1.0/plugin/src/askround.egg-info/PKG-INFO +452 -0
- askround-0.1.0/plugin/src/askround.egg-info/SOURCES.txt +79 -0
- askround-0.1.0/plugin/src/askround.egg-info/dependency_links.txt +1 -0
- askround-0.1.0/plugin/src/askround.egg-info/entry_points.txt +2 -0
- askround-0.1.0/plugin/src/askround.egg-info/requires.txt +4 -0
- askround-0.1.0/plugin/src/askround.egg-info/top_level.txt +1 -0
- askround-0.1.0/pyproject.toml +94 -0
- askround-0.1.0/setup.cfg +4 -0
- askround-0.1.0/tests/test_brief.py +110 -0
- askround-0.1.0/tests/test_budget.py +107 -0
- askround-0.1.0/tests/test_citations.py +137 -0
- askround-0.1.0/tests/test_claims.py +117 -0
- askround-0.1.0/tests/test_cli.py +127 -0
- askround-0.1.0/tests/test_contract.py +164 -0
- askround-0.1.0/tests/test_costs.py +101 -0
- askround-0.1.0/tests/test_dependencies.py +42 -0
- askround-0.1.0/tests/test_diff.py +100 -0
- askround-0.1.0/tests/test_discover.py +120 -0
- askround-0.1.0/tests/test_doctor.py +76 -0
- askround-0.1.0/tests/test_export.py +94 -0
- askround-0.1.0/tests/test_extract.py +192 -0
- askround-0.1.0/tests/test_gaps.py +178 -0
- askround-0.1.0/tests/test_jobs.py +129 -0
- askround-0.1.0/tests/test_leading.py +103 -0
- askround-0.1.0/tests/test_links.py +120 -0
- askround-0.1.0/tests/test_live.py +39 -0
- askround-0.1.0/tests/test_locale.py +111 -0
- askround-0.1.0/tests/test_lookup.py +172 -0
- askround-0.1.0/tests/test_mcp.py +138 -0
- askround-0.1.0/tests/test_mention.py +65 -0
- askround-0.1.0/tests/test_private.py +78 -0
- askround-0.1.0/tests/test_providers.py +129 -0
- askround-0.1.0/tests/test_recorded.py +72 -0
- askround-0.1.0/tests/test_refusals.py +103 -0
- askround-0.1.0/tests/test_report.py +119 -0
- askround-0.1.0/tests/test_research.py +206 -0
- askround-0.1.0/tests/test_retries.py +112 -0
- askround-0.1.0/tests/test_reuse.py +107 -0
- askround-0.1.0/tests/test_rivals.py +91 -0
- askround-0.1.0/tests/test_round.py +199 -0
- askround-0.1.0/tests/test_series.py +124 -0
- askround-0.1.0/tests/test_sets.py +94 -0
- askround-0.1.0/tests/test_site.py +96 -0
- askround-0.1.0/tests/test_tokens.py +127 -0
- askround-0.1.0/tests/test_wiring.py +133 -0
askround-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 Alexandre Oliveira
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
askround-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: askround
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: See what ChatGPT, Gemini, Claude and Perplexity recommend: ask them all the same question with web search and read whether your product is named, where, beside which rivals, and what they claim about it.
|
|
5
|
+
Author: Alexandre Oliveira
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/alegauss/askround
|
|
8
|
+
Project-URL: Repository, https://github.com/alegauss/askround
|
|
9
|
+
Project-URL: Issues, https://github.com/alegauss/askround/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/alegauss/askround/blob/main/docs/CHANGELOG.md
|
|
11
|
+
Keywords: ai-visibility,llm-visibility,generative-engine-optimization,chatgpt,gemini,claude,perplexity,mcp,cli
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# askround
|
|
32
|
+
|
|
33
|
+
**Site:** [alegauss.com/askround](https://alegauss.com/askround/), with a real
|
|
34
|
+
recorded round. **Docs:** [install, a first session, prompt sets, budget, reports, the MCP
|
|
35
|
+
tools and the CLI](https://alegauss.com/askround/docs/).
|
|
36
|
+
|
|
37
|
+
People now ask ChatGPT or Gemini which app to use, and you cannot see the answer they
|
|
38
|
+
get. askround asks the assistants for you: the same question to OpenAI, Gemini,
|
|
39
|
+
Anthropic and Perplexity, with web search on, several times each. It tells you whether
|
|
40
|
+
your product is named and in which position, which rivals sit around it, what the
|
|
41
|
+
answers claim about it, and which of your pages they cite.
|
|
42
|
+
|
|
43
|
+
A real run, from 12/11/2026: four questions about Modelado, a Windows photo-to-3D app,
|
|
44
|
+
one run each on two providers (`askround run examples/modelado.toml --runs 1`):
|
|
45
|
+
|
|
46
|
+
| # | question | purpose | openai | gemini |
|
|
47
|
+
|---|---|---|---|---|
|
|
48
|
+
| 1 | What is a free offline app to turn a photo into a 3D model on Windows? | generic | 0/1 | 0/1 |
|
|
49
|
+
| 2 | What app can turn a photo into a 3D model on Windows without an NVIDIA GPU (AMD or CPU only)? | constraint | 1/1 | 0/1 |
|
|
50
|
+
| 3 | What Windows app can turn a photo into a relief STL for 3D printing? | use case | 0/1 | 0/1 |
|
|
51
|
+
| 4 | Qual app de Windows transforma uma foto em modelo 3D usando só a CPU, sem placa NVIDIA? | language | 1/1, #1 | 0/1 |
|
|
52
|
+
|
|
53
|
+
Each cell is the number of runs that named the app, then its position. ChatGPT finds
|
|
54
|
+
Modelado when the asker has no NVIDIA card, and Gemini doesn't find it at all. That is
|
|
55
|
+
who the app is for, and what its first screen has to say.
|
|
56
|
+
|
|
57
|
+
**askround measures the APIs, not the apps.** An API answer with web search is close to
|
|
58
|
+
what the chat apps say but not the same page: the apps add memory, personalisation and
|
|
59
|
+
their own choice of model. askround records what produced each answer (the model asked
|
|
60
|
+
for and the one that answered, the search tool, the settings, the time), and every
|
|
61
|
+
report is about the API.
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
pip install askround # from a checkout until the first release: pip install .
|
|
65
|
+
askround doctor # which providers have a key
|
|
66
|
+
askround ask "What app can turn a photo into a 3D model on Windows without an NVIDIA GPU?" \
|
|
67
|
+
--product "Modelado,alegauss.com/modelado" --extract
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Python 3.11 or later and the standard library only, so nothing in it is specific to one
|
|
71
|
+
system; it is tested on Windows.
|
|
72
|
+
|
|
73
|
+
Every provider whose API key is in the environment is asked in parallel:
|
|
74
|
+
`OPENAI_API_KEY`, `GEMINI_API_KEY` (or `GOOGLE_API_KEY`), `ANTHROPIC_API_KEY` and
|
|
75
|
+
`PERPLEXITY_API_KEY`. askround reads the keys at call time and never stores them.
|
|
76
|
+
`askround doctor` lists which providers can run here: the key variable (never its
|
|
77
|
+
value), the model, the search tool and a free check call; `--no-check` makes no call.
|
|
78
|
+
|
|
79
|
+
Web search is on, through each provider's own search tool, because that is how
|
|
80
|
+
assistants find and recommend products. Each answer is printed with the sources it
|
|
81
|
+
cited. `--no-search` asks the same question without search, to compare.
|
|
82
|
+
|
|
83
|
+
Each round is saved under `.askround/rounds/<date>/<time>-<question>/` (change it with
|
|
84
|
+
`--out` or `ASKROUND_DIR`): `round.json` holds the question and each provider's answer,
|
|
85
|
+
search queries and sources, and `<provider>-<run>.json` the raw response as the provider
|
|
86
|
+
sent it. Assistants answer differently from one run to the next, so a product is
|
|
87
|
+
reported as "named in 2/3 runs, positions #1, #3, -", never from a single answer.
|
|
88
|
+
|
|
89
|
+
| Option | What it does |
|
|
90
|
+
| ------------------- | --------------------------------------------------- |
|
|
91
|
+
| `--provider NAME` | ask only this provider (`openai`, `gemini`, `anthropic`, `perplexity`), repeatable |
|
|
92
|
+
| `--model PROVIDER=MODEL` | pin the model one provider uses, e.g. `openai=gpt-5-2025-08-07`, repeatable |
|
|
93
|
+
| `--no-search` | ask without web search |
|
|
94
|
+
| `--runs N` | ask each provider N times, in fresh conversations (default 3) |
|
|
95
|
+
| `--product NAME[,ALIAS...]` | report how many runs named the product and its position in each, repeatable |
|
|
96
|
+
| `--extract [PROVIDER]` | read each answer's recommendations in order with a model (one extra call per answer), so "Best fit" in a table or "my first choice" in prose ranks the product first |
|
|
97
|
+
| `--locale COUNTRY[:LANG]` | ask as someone in this country, e.g. `BR:pt-BR`; repeat to compare countries side by side |
|
|
98
|
+
| `--out DIR` | where the round is saved |
|
|
99
|
+
| `--json` | print the round as JSON |
|
|
100
|
+
|
|
101
|
+
Without `--extract`, a product's position is read from the answer's text: its place
|
|
102
|
+
among the top-level list items, numbered headings and table rows. With it, a small model
|
|
103
|
+
(gpt-5-mini, gemini-2.5-flash or claude-haiku-5-5, whichever has a key; override with
|
|
104
|
+
`ASKROUND_EXTRACT_MODEL`) lists what the answer recommends in its own order, each with a
|
|
105
|
+
quote. The text stays the check: a product counts as named only if its name is in the
|
|
106
|
+
answer, and a quote not found in the answer is marked `(?)`. Positions ranked this way
|
|
107
|
+
show a `*`.
|
|
108
|
+
|
|
109
|
+
Give the product's own places among its names, anything with a dot: a domain or a URL
|
|
110
|
+
prefix (`--product "Modelado,alegauss.com/modelado,github.com/alegauss/modelado"`).
|
|
111
|
+
The report then says, per provider, how many answers cited one of its pages and which
|
|
112
|
+
kind (site, repository, store, docs), and `round.json` lists every page and the
|
|
113
|
+
third-party domains cited. Tracking parameters are dropped; Gemini's redirect links are
|
|
114
|
+
counted by the domain in their title.
|
|
115
|
+
|
|
116
|
+
`--check-links` follows every link an answer writes beside the product's name (in its
|
|
117
|
+
table row or list item, or close to it in prose), images included, and flags those that
|
|
118
|
+
land on someone else's page, such as another app's Store page shown for yours, or that
|
|
119
|
+
are broken. A page counts as the product's when it is on one of its places or its
|
|
120
|
+
title names it.
|
|
121
|
+
|
|
122
|
+
With `--extract`, the report also lists what the answers claim about the product, from
|
|
123
|
+
tables and prose: `CPU only: Check build`, `Cost: Free, open-source`, each with the
|
|
124
|
+
providers that said it and the passage it came from. A claim whose quote is not in the
|
|
125
|
+
answer is counted apart and never shown as a finding.
|
|
126
|
+
|
|
127
|
+
With `--extract`, the report also lists the rivals: every other product the answers
|
|
128
|
+
recommend, with how many answers named it, its rank in each and which providers did
|
|
129
|
+
(the first ten in the report, all of them in `round.json`, each with the quote that
|
|
130
|
+
recommends it).
|
|
131
|
+
|
|
132
|
+
Each API takes a different part of a locale, and only for its search: OpenAI,
|
|
133
|
+
Anthropic and Perplexity an approximate country, Gemini a language. What a provider
|
|
134
|
+
cannot take is recorded on the answer and shown as "ignored by the API". Write the
|
|
135
|
+
question in the language you are testing: that is still the strongest signal.
|
|
136
|
+
|
|
137
|
+
A rate limit, a server error, a timeout or a dropped connection is retried up to three
|
|
138
|
+
times with backoff (honouring `Retry-After`); a rejected key or a missing model is not.
|
|
139
|
+
An answer that still fails is recorded with its error, the rest of the round carries
|
|
140
|
+
on, and the report lists the failures at the end.
|
|
141
|
+
|
|
142
|
+
The model per provider is overridden with `ASKROUND_OPENAI_MODEL`,
|
|
143
|
+
`ASKROUND_GEMINI_MODEL`, `ASKROUND_ANTHROPIC_MODEL` and `ASKROUND_PERPLEXITY_MODEL`.
|
|
144
|
+
|
|
145
|
+
## A product file, and the facts the answers get wrong
|
|
146
|
+
|
|
147
|
+
```toml
|
|
148
|
+
# modelado.toml
|
|
149
|
+
[product]
|
|
150
|
+
name = "Modelado"
|
|
151
|
+
places = ["alegauss.com/modelado", "github.com/alegauss/modelado"]
|
|
152
|
+
|
|
153
|
+
[[fact]]
|
|
154
|
+
attribute = "Runs without an NVIDIA GPU"
|
|
155
|
+
value = "Yes: any DirectX 12 GPU through DirectML, or the CPU"
|
|
156
|
+
page = "https://alegauss.com/modelado/" # where it should be said plainly
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`askround ask "..." --product-file modelado.toml --extract` asks about the product the
|
|
160
|
+
file describes and compares what the answers claim with its facts, in one more call: a
|
|
161
|
+
claim that contradicts a fact, or doubts it ('CPU only: Check build'), is reported as a
|
|
162
|
+
fact gap with the page that should state the fact plainly and the product's pages the
|
|
163
|
+
answers did cite.
|
|
164
|
+
|
|
165
|
+
## What a round costs
|
|
166
|
+
|
|
167
|
+
Before a round, askround prints an estimate from the number of calls, the tokens a
|
|
168
|
+
searched answer usually takes and each provider's prices, and refuses a round above the
|
|
169
|
+
cap you set (`--max-cost 0.50`, or `ASKROUND_MAX_COST`) before anything is sent. After
|
|
170
|
+
it, the report gives the actual cost per provider from the usage each API reported,
|
|
171
|
+
the extraction pass included.
|
|
172
|
+
|
|
173
|
+
The prices are a dated table in `plugin/src/askround/costs.py`: Anthropic's from its model table
|
|
174
|
+
of 2026-11-03, the others the last known list prices, to be checked against the
|
|
175
|
+
providers' pages. `ASKROUND_PRICES` names a JSON file that replaces any of them:
|
|
176
|
+
`{"models": {"gpt-5": {"input": 1.25, "output": 10.0}}, "search": {"openai": 0.01}}`.
|
|
177
|
+
|
|
178
|
+
### A budget an agent cannot raise
|
|
179
|
+
|
|
180
|
+
Set what askround may spend in its config file, `%APPDATA%\askround\config.toml` on
|
|
181
|
+
Windows, `~/.config/askround/config.toml` elsewhere (or the file `ASKROUND_CONFIG`
|
|
182
|
+
names):
|
|
183
|
+
|
|
184
|
+
```toml
|
|
185
|
+
[budget]
|
|
186
|
+
day = 1.00
|
|
187
|
+
month = 10.00
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Every saved round adds its actual cost to `spent.jsonl` beside the rounds. A run whose
|
|
191
|
+
estimate would pass the day's or the month's budget is refused before any call
|
|
192
|
+
(`budget.over`): it waits for you. You approve it by running it with `--approve`, or
|
|
193
|
+
raise the budget; an agent using the MCP tools cannot do either, and every paid result
|
|
194
|
+
it gets says what has been spent against the budget. `askround budget` shows it.
|
|
195
|
+
|
|
196
|
+
### The same question twice costs once
|
|
197
|
+
|
|
198
|
+
A question asked again the same way within a day (the same text, providers, models,
|
|
199
|
+
locales and search, as many runs, extraction if wanted) returns the stored round, marked
|
|
200
|
+
with its age, and calls no provider. A round where some answers failed is asked again.
|
|
201
|
+
`--fresh` (or `fresh: true` over MCP) asks anew; the window is set in the config file:
|
|
202
|
+
|
|
203
|
+
```toml
|
|
204
|
+
[reuse]
|
|
205
|
+
max_age_hours = 24 # 0 turns reuse off
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Prompt sets: the same questions every week
|
|
209
|
+
|
|
210
|
+
Add `[[question]]` entries to the product file and it becomes a prompt set:
|
|
211
|
+
|
|
212
|
+
```toml
|
|
213
|
+
[[question]]
|
|
214
|
+
text = "What app can turn a photo into a 3D model on Windows without an NVIDIA GPU?"
|
|
215
|
+
purpose = "constraint" # generic, constraint, use case, language
|
|
216
|
+
locale = "BR:pt-BR" # optional
|
|
217
|
+
runs = 3 # optional
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
A question that names the product it tests (its name, an alias, one of its places, or
|
|
221
|
+
a phrase listed under `phrases`) measures nothing, so it is refused before anything is
|
|
222
|
+
paid for. A question that asks about the product on purpose is marked `brand = true`
|
|
223
|
+
(or `--brand` on `ask`); it runs, and is reported apart.
|
|
224
|
+
|
|
225
|
+
A round sends the same text to every provider, so a question that carries personal data
|
|
226
|
+
(an email address, a phone number, a CPF, an IP address, an API key, or a name on your
|
|
227
|
+
own list in `ASKROUND_PRIVATE` or the file `ASKROUND_PRIVATE_FILE` names) stops before
|
|
228
|
+
anything is sent, naming the question and what it found.
|
|
229
|
+
|
|
230
|
+
`askround run modelado.toml` asks every question as its own round, kept together in
|
|
231
|
+
one set directory, and prints one table of question by provider before each question's
|
|
232
|
+
report. `askround report` on the set's directory prints it again.
|
|
233
|
+
[`examples/modelado.toml`](examples/modelado.toml) is the set this tool was built from.
|
|
234
|
+
|
|
235
|
+
## Which question leads to the product?
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
askround discover modelado.toml --seed "free offline app photo to 3D model on Windows"
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
From the seed and the product file's facts, a model writes variants (a constraint the
|
|
242
|
+
product meets, a use case, another language) that never name the product; askround
|
|
243
|
+
runs them with the seed and ranks them by how often and how high the product appears.
|
|
244
|
+
It runs once per question by default, which is a sketch: add the promising questions
|
|
245
|
+
to the product file and `askround run` them with more runs.
|
|
246
|
+
|
|
247
|
+
## Does a recommended product exist?
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
askround lookup "MeshForge3D" "Modly" --context "a Windows photo-to-3D app"
|
|
251
|
+
askround ask "..." --extract --check-rivals # the top 10 rivals of a round
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Each name is looked up on GitHub first (free; a repository of that name with at least
|
|
255
|
+
10 stars), then with one searched call on OpenAI, Anthropic or Gemini, which answers
|
|
256
|
+
found, ambiguous or not found with the page. A "found" whose page is not among the
|
|
257
|
+
pages that search returned is downgraded to ambiguous. `--no-web` keeps it to GitHub.
|
|
258
|
+
The report marks each checked rival, and names the ones found nowhere.
|
|
259
|
+
|
|
260
|
+
## The series
|
|
261
|
+
|
|
262
|
+
Every round saved also appends one line per answer to `series.jsonl` beside the rounds:
|
|
263
|
+
when, which question and set, which provider and model, and what was read (each
|
|
264
|
+
tracked product's mention and position, the recommendations, the cited pages, the
|
|
265
|
+
claims). It is an index of the rounds, so `--rebuild` writes it again from them.
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
askround series --product Modelado # answers naming it, per week
|
|
269
|
+
askround series --product Modelado --by day --question nvidia
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
`askround diff` compares the latest two runs of the same questions (or two you name)
|
|
273
|
+
and lists only what changed, per question and provider: the product's share and best
|
|
274
|
+
position, rivals that appeared or left, its pages newly cited or no longer cited, and
|
|
275
|
+
claims made or dropped.
|
|
276
|
+
|
|
277
|
+
## Export, and a weekly round
|
|
278
|
+
|
|
279
|
+
`askround export --product Modelado` writes the product's numbers from the series, one
|
|
280
|
+
row per day, provider and question (answers, named, frequency, best and mean position,
|
|
281
|
+
rivals, its pages cited), as CSV, JSON, or with `--format daily` in the shape a
|
|
282
|
+
`daily.json` of numbers per product takes (`{product: {day: {"assistants": {...}}}}`).
|
|
283
|
+
`--from`/`--to` bound the days and `--file` writes it out.
|
|
284
|
+
|
|
285
|
+
askround does not schedule itself; the system does. Every Monday at 08:00, on Windows:
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
schtasks /Create /SC WEEKLY /D MON /ST 08:00 /TN askround-modelado ^
|
|
289
|
+
/TR "cmd /c cd /d D:\work && askround run modelado.toml --extract --max-cost 2 && askround export --product Modelado --format daily --file modelado-assistants.json"
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
and with cron:
|
|
293
|
+
|
|
294
|
+
```
|
|
295
|
+
0 8 * * 1 cd ~/work && askround run modelado.toml --extract --max-cost 2 && askround export --product Modelado --format daily --file modelado-assistants.json
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The keys come from the environment the task runs in; `--max-cost` keeps an unattended
|
|
299
|
+
run from spending more than you agreed to.
|
|
300
|
+
|
|
301
|
+
## The report
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
askround report # the latest round, as Markdown
|
|
305
|
+
askround report <round dir> --json # one round, as JSON for a pipeline
|
|
306
|
+
askround report --product Blender # the same round, read for another product
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
`report` reads a stored round back and sums it up: for each product, a table of
|
|
310
|
+
provider by share of runs and positions, what the answers claim about it, which of its
|
|
311
|
+
pages they cite, then the rivals. The JSON holds the same facts and never the answers'
|
|
312
|
+
text, which stays in `round.json`.
|
|
313
|
+
|
|
314
|
+
## Research mode
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
askround research "Which open-source image-to-3D models allow commercial use?"
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
asks every provider once with search, reads each answer for the factual statements it
|
|
321
|
+
makes (each with a quote found in the answer), merges the statements that say the same
|
|
322
|
+
thing, and lists the claims by how many providers support them, each with every
|
|
323
|
+
provider's passage and the sources, those naming the claim's subject first and primary
|
|
324
|
+
ones (repositories, licence texts, papers, official docs) before secondary ones.
|
|
325
|
+
Support is agreement among assistants, not proof: check a claim at its primary source.
|
|
326
|
+
|
|
327
|
+
Where the assistants disagree on a fact (one says a model's licence allows commercial
|
|
328
|
+
use, another that it does not), the report opens with it: both sides, their passages,
|
|
329
|
+
and the primary sources to read to settle it.
|
|
330
|
+
|
|
331
|
+
## For agents: the plugin and the MCP server
|
|
332
|
+
|
|
333
|
+
In Claude Code, the plugin installs the MCP server, four slash commands (`/askround:ask`,
|
|
334
|
+
`/askround:run`, `/askround:report`, `/askround:diff`) and a skill that tells the agent to
|
|
335
|
+
run a question across assistants itself, instead of asking you to paste ChatGPT's
|
|
336
|
+
answer. The plugin carries the package (`plugin/src/askround`) and runs it with the
|
|
337
|
+
Python on the machine, so nothing else is installed and nothing has to be on the PATH:
|
|
338
|
+
|
|
339
|
+
```
|
|
340
|
+
/plugin marketplace add alegauss/askround
|
|
341
|
+
/plugin install askround@askround
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
To wire a project for everyone who clones it, run `askround install` in it (or
|
|
345
|
+
`python plugin/scripts/askround.py install <project>` from a checkout) and commit
|
|
346
|
+
`.claude/settings.json`: it names the marketplace and enables the plugin, keeping every
|
|
347
|
+
other setting, so each clone is offered the plugin on its first session.
|
|
348
|
+
`askround install --check` writes nothing and exits 1 when the entries are missing, for
|
|
349
|
+
CI. Claude Code on the web installs no plugin, so `askround install --vendor` also copies
|
|
350
|
+
askround into `.claude/askround/` with a launcher and names it in the project's
|
|
351
|
+
`.mcp.json`; commit both, and `--vendor --check` says when the copy is behind. The server
|
|
352
|
+
alone: `claude mcp add askround -- askround mcp`. Either serves:
|
|
353
|
+
|
|
354
|
+
| Tool | What it does |
|
|
355
|
+
| --- | --- |
|
|
356
|
+
| `askround_ask` | ask a question across providers; returns the report and lists the answers |
|
|
357
|
+
| `askround_answer` | the full text of one answer, with its sources |
|
|
358
|
+
| `askround_run` | run a prompt set (a product file); returns the set's report |
|
|
359
|
+
| `askround_research` | a research question everywhere, its claims merged by support |
|
|
360
|
+
| `askround_job` | a round started with `background: true`: its progress, then its result |
|
|
361
|
+
| `askround_report` | the report of a stored round or set |
|
|
362
|
+
| `askround_diff` | what changed between two runs of the same questions |
|
|
363
|
+
| `askround_doctor` | which providers can run (key variable names, never values) |
|
|
364
|
+
|
|
365
|
+
The same refusals hold for an agent: personal data, a question naming its product, and
|
|
366
|
+
a run estimated above `max_cost` (or `ASKROUND_MAX_COST`) stop before anything is
|
|
367
|
+
paid for. The tool schemas are written for the project the server runs in: only the
|
|
368
|
+
providers with a key, the product files found at its top or one directory down (with
|
|
369
|
+
their question counts) under `askround_run`'s `file`, and your day and month budget, with
|
|
370
|
+
what is already spent, beside `max_cost`.
|
|
371
|
+
|
|
372
|
+
`askround_ask` returns a brief: a few hundred tokens for a whole round (a real 2x2 round
|
|
373
|
+
with extraction: 614 characters, against 12,253 of answers). Each fact in it carries the
|
|
374
|
+
ids of the answers it came from (`openai#2`, `openai@BR#2`); `askround_answer` with an
|
|
375
|
+
id returns that answer, or with `around: "<quote>"` only the passage behind the fact.
|
|
376
|
+
`detail: true` returns the full report. On the command line: `askround report --brief`.
|
|
377
|
+
`askround_run` returns one brief per question in the set, and `detail: true` the set's
|
|
378
|
+
full report.
|
|
379
|
+
|
|
380
|
+
What each response costs the agent, measured on the recorded rounds in `tests/fixtures`
|
|
381
|
+
(two providers, three runs, extraction on; one token per four characters of JSON, so
|
|
382
|
+
an approximation, the same way for every tool):
|
|
383
|
+
|
|
384
|
+
| Response | Tokens | Budget |
|
|
385
|
+
| --- | ---: | ---: |
|
|
386
|
+
| `askround_ask` (brief) | 304 | 400 |
|
|
387
|
+
| `askround_ask` with `detail: true` | 1,360 | 1,800 |
|
|
388
|
+
| `askround_answer` | 868 | 1,100 |
|
|
389
|
+
| `askround_answer` with `around` | 175 | 250 |
|
|
390
|
+
| `askround_research` | 163 | 250 |
|
|
391
|
+
| `askround_run` (one question per purpose) | 760 | 1,000 |
|
|
392
|
+
| `askround_report` | 1,136 | 1,500 |
|
|
393
|
+
| `askround_diff` | 112 | 200 |
|
|
394
|
+
| `askround_doctor` | 257 | 350 |
|
|
395
|
+
|
|
396
|
+
The budgets live in `budgets/mcp-tokens.json` and `tests/test_tokens.py` fails a response
|
|
397
|
+
over its budget, so a field added to a response is a reviewed change. `pytest -s -k
|
|
398
|
+
figures` prints the table again.
|
|
399
|
+
|
|
400
|
+
A round takes minutes. With `background: true`, `askround_ask` and `askround_run` check
|
|
401
|
+
and estimate it, start it in a process of its own and return a job id at once; the
|
|
402
|
+
agent keeps working and reads it with `askround_job`: running (with how many answers
|
|
403
|
+
are in), done (with its brief), refused, failed, or interrupted. Each answer is written
|
|
404
|
+
the moment it lands, so an interrupted job keeps every answer it finished. The jobs
|
|
405
|
+
live in `jobs/` beside the rounds; `--estimate-only` on the command line runs the same
|
|
406
|
+
checks and prints the estimate without asking anything.
|
|
407
|
+
|
|
408
|
+
## Refused before it is paid for
|
|
409
|
+
|
|
410
|
+
Every check that needs no call runs before the first one, and a refusal lists every
|
|
411
|
+
problem at once, each with a code and the change that passes it:
|
|
412
|
+
|
|
413
|
+
```
|
|
414
|
+
askround: refused before anything was sent: 2 problem(s)
|
|
415
|
+
[question.leading] the question names the product it tests (Modelado): 'Is Modelado good?'
|
|
416
|
+
fix: Reword it as a person who does not know the product would ask; or ...
|
|
417
|
+
[cost.over-cap] the estimate $0.2972 is above the cap $0.0010
|
|
418
|
+
fix: Ask fewer providers or fewer runs, drop --extract, or raise the cap ...
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
`askround explain <code>` says what a code means; `askround explain` lists them. For an
|
|
422
|
+
agent, the MCP schemas carry the same limits (a question's length, the providers that
|
|
423
|
+
have a key), so a call that would be refused fails at the schema.
|
|
424
|
+
|
|
425
|
+
## The output contract
|
|
426
|
+
|
|
427
|
+
Every command takes `--json` (`export` takes `--format json`). Each JSON object names
|
|
428
|
+
its schema, `"schema": "askround/round@1"`, and the schemas are published in
|
|
429
|
+
[`schema/`](schema/) as JSON Schema. In a round, every fact says where it came from: the
|
|
430
|
+
answer's id (`openai#2`, `openai@BR#2`) and the character range of its passage in that
|
|
431
|
+
answer's text (`"range": [412, 437]`, end excluded), so a script or an agent can cite it.
|
|
432
|
+
|
|
433
|
+
| Exit code | Means |
|
|
434
|
+
| --- | --- |
|
|
435
|
+
| 0 | every answer came back |
|
|
436
|
+
| 3 | a partial round: it finished, some answers failed (they are listed) |
|
|
437
|
+
| 1 | no answer came back |
|
|
438
|
+
| 2 | refused before anything was sent (personal data, a leading question, over the cost cap, no key) or a usage error |
|
|
439
|
+
|
|
440
|
+
## Development
|
|
441
|
+
|
|
442
|
+
```
|
|
443
|
+
pip install -e .[dev]
|
|
444
|
+
python -m pytest # no key and no network: a test that calls out fails
|
|
445
|
+
python -m ruff check .
|
|
446
|
+
python -m ruff format --check .
|
|
447
|
+
ASKROUND_LIVE=1 python -m pytest -m live # each adapter with a key, against its real API
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
The adapters are tested against real responses recorded from a live round
|
|
451
|
+
(`tests/fixtures/`, ids scrubbed), so a provider changing its shape shows up as a
|
|
452
|
+
failing recording rather than a silent miss.
|