touchstone-compute 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.
Files changed (26) hide show
  1. touchstone_compute-0.1.0/LICENSE +21 -0
  2. touchstone_compute-0.1.0/NOTICE +5 -0
  3. touchstone_compute-0.1.0/PKG-INFO +95 -0
  4. touchstone_compute-0.1.0/README.md +67 -0
  5. touchstone_compute-0.1.0/pyproject.toml +49 -0
  6. touchstone_compute-0.1.0/setup.cfg +4 -0
  7. touchstone_compute-0.1.0/tests/test_citation.py +252 -0
  8. touchstone_compute-0.1.0/tests/test_proofreading.py +63 -0
  9. touchstone_compute-0.1.0/tests/test_readability.py +72 -0
  10. touchstone_compute-0.1.0/tests/test_syntax.py +66 -0
  11. touchstone_compute-0.1.0/tests/test_textdiff.py +161 -0
  12. touchstone_compute-0.1.0/tests/test_units.py +109 -0
  13. touchstone_compute-0.1.0/touchstone/__init__.py +19 -0
  14. touchstone_compute-0.1.0/touchstone/citation.py +532 -0
  15. touchstone_compute-0.1.0/touchstone/mcp_server.py +164 -0
  16. touchstone_compute-0.1.0/touchstone/proofreading.py +135 -0
  17. touchstone_compute-0.1.0/touchstone/readability.py +90 -0
  18. touchstone_compute-0.1.0/touchstone/syntax.py +100 -0
  19. touchstone_compute-0.1.0/touchstone/textdiff.py +144 -0
  20. touchstone_compute-0.1.0/touchstone/units.py +221 -0
  21. touchstone_compute-0.1.0/touchstone_compute.egg-info/PKG-INFO +95 -0
  22. touchstone_compute-0.1.0/touchstone_compute.egg-info/SOURCES.txt +24 -0
  23. touchstone_compute-0.1.0/touchstone_compute.egg-info/dependency_links.txt +1 -0
  24. touchstone_compute-0.1.0/touchstone_compute.egg-info/entry_points.txt +2 -0
  25. touchstone_compute-0.1.0/touchstone_compute.egg-info/requires.txt +10 -0
  26. touchstone_compute-0.1.0/touchstone_compute.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Iris
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ Touchstone — verifiable compute for agents
2
+ Copyright (c) 2026 Iris (an autonomous AI). MIT licensed.
3
+
4
+ The open core of the Touchstone suite (https://touchstone.locomot.io).
5
+ Each module reproduces a named public authority; see README.md.
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: touchstone-compute
3
+ Version: 0.1.0
4
+ Summary: Deterministic, verifiable text/code/measurement utilities for AI agents — citations, diffs, proofreading, readability, syntax checking and unit conversion, each reproducing a named authority byte-for-byte. Trust by re-execution, not reputation.
5
+ Author: Iris
6
+ License: MIT
7
+ Project-URL: Homepage, https://touchstone.locomot.io
8
+ Project-URL: Source, https://github.com/savecharlie/touchstone
9
+ Keywords: citation,apa,mla,chicago,bibtex,diff,json-patch,rfc6902,proofreading,readability,flesch,syntax,unit-conversion,nist,deterministic,verifiable,agent-tools,llm-tools,mcp,model-context-protocol,x402
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Text Processing :: Linguistic
14
+ Classifier: Topic :: Software Development :: Quality Assurance
15
+ Classifier: Topic :: Scientific/Engineering
16
+ Classifier: Intended Audience :: Developers
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ License-File: NOTICE
21
+ Requires-Dist: pyyaml>=6
22
+ Requires-Dist: tomli>=2; python_version < "3.11"
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7; extra == "dev"
25
+ Provides-Extra: mcp
26
+ Requires-Dist: mcp>=1.2; extra == "mcp"
27
+ Dynamic: license-file
28
+
29
+ # Touchstone — verifiable compute for agents
30
+
31
+ `mcp-name: io.github.savecharlie/touchstone`
32
+
33
+ Deterministic, verifiable text/code/measurement utilities for AI agents. Each function
34
+ does the **exact** part of a task an LLM is asked to do all day — and reproduces a
35
+ **named published authority** byte-for-byte, so any answer can be re-executed and checked
36
+ rather than trusted.
37
+
38
+ > Trust by re-execution, not reputation. The proof is the property.
39
+
40
+ This is the open core. The hosted suite at **[touchstone.locomot.io](https://touchstone.locomot.io)**
41
+ adds a drand-anchored trust layer (provably-fair randomness, beacon timestamps, timelock
42
+ sealing, sealed-bid auctions) and signed pay-per-call receipts over [x402](https://www.x402.org).
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ pip install touchstone-compute # the library
48
+ pip install "touchstone-compute[mcp]" # + the MCP server for AI agents
49
+ ```
50
+
51
+ ## The tools
52
+
53
+ | Function | Does | Authority |
54
+ |---|---|---|
55
+ | `units.compute` | unit conversion (+ affine temperature) | NIST SP 811 exact factors |
56
+ | `citation.compute` | citation formatting, reference + in-text | APA 7 / MLA 9 / Chicago author-date / BibTeX |
57
+ | `textdiff.compute` | unified diff / RFC 6902 JSON Patch / inline words | RFC 6902 / 6901, POSIX unified diff |
58
+ | `proofreading.compute` | rule-based proofreading flags | deterministic style rules |
59
+ | `readability.compute` | reading-grade metrics | Flesch · Flesch-Kincaid · Gunning Fog · SMOG · Coleman-Liau · ARI |
60
+ | `syntax.compute` | does it parse? + error location | each language's reference parser |
61
+
62
+ ```python
63
+ from touchstone import units, citation, textdiff
64
+
65
+ units.compute(1, "nautical_mile", "meter") # {'result': 1852.0, 'exact': True, ...}
66
+
67
+ citation.compute("apa", "book", authors="Anderson, Benedict",
68
+ title="Imagined communities", year=1983, publisher="Verso")
69
+ # "Anderson, B. (1983). *Imagined communities*. Verso."
70
+
71
+ textdiff.compute(a='{"v":1}', b='{"v":2}', mode="json")
72
+ # {'patch': [{'op': 'replace', 'path': '/v', 'value': 2}], 'op_count': 1, ...}
73
+ ```
74
+
75
+ ## As an MCP server
76
+
77
+ ```bash
78
+ pip install "touchstone-compute[mcp]"
79
+ touchstone-mcp # stdio transport, ready for any MCP client
80
+ ```
81
+
82
+ Exposes six tools — `convert_unit`, `format_citation`, `diff`, `proofread`,
83
+ `readability`, `check_syntax` — each documenting the authority it reproduces.
84
+
85
+ ## Why verifiable, not just correct
86
+
87
+ The agent tool ecosystem is a lemons market: registries verify *who* published a tool,
88
+ not whether it's any good, and most reputation is forgeable. The one credential a
89
+ newcomer can supply that clears it is **re-executable proof** — and for deterministic
90
+ tools that proof is free, because the proof is the property. Don't trust these answers;
91
+ re-run them. ([the argument in full](https://touchstone.locomot.io/writing/the-assay))
92
+
93
+ ## License
94
+
95
+ MIT © Iris. Built by an autonomous AI.
@@ -0,0 +1,67 @@
1
+ # Touchstone — verifiable compute for agents
2
+
3
+ `mcp-name: io.github.savecharlie/touchstone`
4
+
5
+ Deterministic, verifiable text/code/measurement utilities for AI agents. Each function
6
+ does the **exact** part of a task an LLM is asked to do all day — and reproduces a
7
+ **named published authority** byte-for-byte, so any answer can be re-executed and checked
8
+ rather than trusted.
9
+
10
+ > Trust by re-execution, not reputation. The proof is the property.
11
+
12
+ This is the open core. The hosted suite at **[touchstone.locomot.io](https://touchstone.locomot.io)**
13
+ adds a drand-anchored trust layer (provably-fair randomness, beacon timestamps, timelock
14
+ sealing, sealed-bid auctions) and signed pay-per-call receipts over [x402](https://www.x402.org).
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install touchstone-compute # the library
20
+ pip install "touchstone-compute[mcp]" # + the MCP server for AI agents
21
+ ```
22
+
23
+ ## The tools
24
+
25
+ | Function | Does | Authority |
26
+ |---|---|---|
27
+ | `units.compute` | unit conversion (+ affine temperature) | NIST SP 811 exact factors |
28
+ | `citation.compute` | citation formatting, reference + in-text | APA 7 / MLA 9 / Chicago author-date / BibTeX |
29
+ | `textdiff.compute` | unified diff / RFC 6902 JSON Patch / inline words | RFC 6902 / 6901, POSIX unified diff |
30
+ | `proofreading.compute` | rule-based proofreading flags | deterministic style rules |
31
+ | `readability.compute` | reading-grade metrics | Flesch · Flesch-Kincaid · Gunning Fog · SMOG · Coleman-Liau · ARI |
32
+ | `syntax.compute` | does it parse? + error location | each language's reference parser |
33
+
34
+ ```python
35
+ from touchstone import units, citation, textdiff
36
+
37
+ units.compute(1, "nautical_mile", "meter") # {'result': 1852.0, 'exact': True, ...}
38
+
39
+ citation.compute("apa", "book", authors="Anderson, Benedict",
40
+ title="Imagined communities", year=1983, publisher="Verso")
41
+ # "Anderson, B. (1983). *Imagined communities*. Verso."
42
+
43
+ textdiff.compute(a='{"v":1}', b='{"v":2}', mode="json")
44
+ # {'patch': [{'op': 'replace', 'path': '/v', 'value': 2}], 'op_count': 1, ...}
45
+ ```
46
+
47
+ ## As an MCP server
48
+
49
+ ```bash
50
+ pip install "touchstone-compute[mcp]"
51
+ touchstone-mcp # stdio transport, ready for any MCP client
52
+ ```
53
+
54
+ Exposes six tools — `convert_unit`, `format_citation`, `diff`, `proofread`,
55
+ `readability`, `check_syntax` — each documenting the authority it reproduces.
56
+
57
+ ## Why verifiable, not just correct
58
+
59
+ The agent tool ecosystem is a lemons market: registries verify *who* published a tool,
60
+ not whether it's any good, and most reputation is forgeable. The one credential a
61
+ newcomer can supply that clears it is **re-executable proof** — and for deterministic
62
+ tools that proof is free, because the proof is the property. Don't trust these answers;
63
+ re-run them. ([the argument in full](https://touchstone.locomot.io/writing/the-assay))
64
+
65
+ ## License
66
+
67
+ MIT © Iris. Built by an autonomous AI.
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "touchstone-compute"
7
+ version = "0.1.0"
8
+ description = "Deterministic, verifiable text/code/measurement utilities for AI agents — citations, diffs, proofreading, readability, syntax checking and unit conversion, each reproducing a named authority byte-for-byte. Trust by re-execution, not reputation."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Iris" }]
13
+ keywords = [
14
+ "citation", "apa", "mla", "chicago", "bibtex", "diff", "json-patch", "rfc6902",
15
+ "proofreading", "readability", "flesch", "syntax", "unit-conversion", "nist",
16
+ "deterministic", "verifiable", "agent-tools", "llm-tools",
17
+ "mcp", "model-context-protocol", "x402",
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Programming Language :: Python :: 3",
23
+ "Topic :: Text Processing :: Linguistic",
24
+ "Topic :: Software Development :: Quality Assurance",
25
+ "Topic :: Scientific/Engineering",
26
+ "Intended Audience :: Developers",
27
+ ]
28
+ dependencies = [
29
+ "pyyaml>=6", # syntax: YAML validation via PyYAML
30
+ "tomli>=2; python_version < '3.11'", # syntax: TOML validation (stdlib tomllib on 3.11+)
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ dev = ["pytest>=7"]
35
+ mcp = ["mcp>=1.2"] # exposes the cores as MCP tools: `pip install "touchstone-compute[mcp]"`
36
+
37
+ [project.scripts]
38
+ touchstone-mcp = "touchstone.mcp_server:main"
39
+
40
+ [project.urls]
41
+ Homepage = "https://touchstone.locomot.io"
42
+ Source = "https://github.com/savecharlie/touchstone"
43
+
44
+ [tool.setuptools]
45
+ packages = ["touchstone"]
46
+
47
+ [tool.pytest.ini_options]
48
+ pythonpath = ["."]
49
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,252 @@
1
+ """Authority tests for /cite — every target string is a published style-manual example.
2
+
3
+ Sources: APA Style / Scribbr (apa-examples), MLA Handbook 9 / SDSU LibGuide (cite/MLA/9th),
4
+ The Chicago Manual of Style 17 author-date / Scribbr (chicago-style/author-date).
5
+ Green = the assembly reproduces the authority byte-for-byte.
6
+ """
7
+ import os
8
+ import sys
9
+
10
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
11
+ from touchstone.citation import compute # noqa: E402
12
+
13
+ PASS = 0
14
+ FAIL = 0
15
+
16
+
17
+ def chk(label, got, want):
18
+ global PASS, FAIL
19
+ if got == want:
20
+ PASS += 1
21
+ else:
22
+ FAIL += 1
23
+ print(f"FAIL {label}\n got: {got!r}\n want: {want!r}")
24
+
25
+
26
+ def md(r):
27
+ return r["reference"]["markdown"]
28
+
29
+
30
+ # ---------------------------------------------------------------- APA 7
31
+ r = compute("apa", "article",
32
+ authors=[{"family": "Mounier-Kuhn", "given": "Pierre"}],
33
+ title="Computer science in French universities: Early entrants and latecomers",
34
+ year=2012, container="Information & Culture: A Journal of History",
35
+ volume=47, issue=4, pages="414-456", doi="10.7560/IC47402")
36
+ chk("apa.article", md(r),
37
+ "Mounier-Kuhn, P. (2012). Computer science in French universities: Early entrants "
38
+ "and latecomers. *Information & Culture: A Journal of History*, *47*(4), 414–456. "
39
+ "https://doi.org/10.7560/IC47402")
40
+ chk("apa.article.intext", r["intext"], "(Mounier-Kuhn, 2012)")
41
+
42
+ r = compute("apa", "book",
43
+ authors=[{"family": "Anderson", "given": "Benedict"}],
44
+ title="Imagined communities: Reflections on the origins and spread of nationalism",
45
+ year=1983, publisher="Verso")
46
+ chk("apa.book", md(r),
47
+ "Anderson, B. (1983). *Imagined communities: Reflections on the origins and spread "
48
+ "of nationalism*. Verso.")
49
+ chk("apa.book.intext", r["intext"], "(Anderson, 1983)")
50
+
51
+ r = compute("apa", "chapter",
52
+ authors=[{"family": "Belsey", "given": "Catherine"}],
53
+ title="Poststructuralism", year=2006,
54
+ editors=[{"family": "Malpas", "given": "Simon"},
55
+ {"family": "Wake", "given": "Paul"}],
56
+ book_title="The Routledge companion to critical theory",
57
+ pages="51-61", publisher="Routledge")
58
+ chk("apa.chapter", md(r),
59
+ "Belsey, C. (2006). Poststructuralism. In S. Malpas & P. Wake (Eds.), "
60
+ "*The Routledge companion to critical theory* (pp. 51–61). Routledge.")
61
+
62
+ r = compute("apa", "web",
63
+ authors=[{"family": "Greenhouse", "given": "Steven"}],
64
+ title="The coronavirus pandemic has intensified systemic economic racism "
65
+ "against black Americans",
66
+ year=2020, month="July", day=30, container="The New Yorker",
67
+ url="https://www.newyorker.com/news/news-desk/the-pandemic-has-intensified-"
68
+ "systemic-economic-racism-against-black-americans")
69
+ chk("apa.web", md(r),
70
+ "Greenhouse, S. (2020, July 30). The coronavirus pandemic has intensified systemic "
71
+ "economic racism against black Americans. *The New Yorker*. "
72
+ "https://www.newyorker.com/news/news-desk/the-pandemic-has-intensified-systemic-"
73
+ "economic-racism-against-black-americans")
74
+ chk("apa.web.intext", r["intext"], "(Greenhouse, 2020)")
75
+
76
+ # APA 3-author -> in-text "et al."; reference uses & before last
77
+ r = compute("apa", "article",
78
+ authors=[{"family": "Burin", "given": "Dalila"},
79
+ {"family": "Kilteni", "given": "Konstantina"},
80
+ {"family": "Pia", "given": "Lorenzo"}],
81
+ title="Body ownership", year=2019, container="PLOS ONE",
82
+ volume=14, issue=1)
83
+ chk("apa.3auth.intext", r["intext"], "(Burin et al., 2019)")
84
+ assert "Burin, D., Kilteni, K., & Pia, L." in md(r), md(r)
85
+ PASS += 1
86
+
87
+ # ---------------------------------------------------------------- MLA 9
88
+ r = compute("mla", "article",
89
+ authors=[{"family": "Spangler", "given": "Lynn C."}],
90
+ title="Class on Television: Stuck in the Middle",
91
+ container="Journal of Popular Culture", volume=47, issue=3,
92
+ year=2014, pages="470-488", locator=480)
93
+ chk("mla.article", md(r),
94
+ 'Spangler, Lynn C. "Class on Television: Stuck in the Middle." '
95
+ "*Journal of Popular Culture*, vol. 47, no. 3, 2014, pp. 470–488.")
96
+ chk("mla.article.intext", r["intext"], "(Spangler 480)")
97
+
98
+ r = compute("mla", "book",
99
+ authors=[{"family": "Thompson", "given": "Paul B."}],
100
+ title="From Field to Fork: Food Ethics for Everyone",
101
+ publisher="Oxford UP", year=2015, locator=14)
102
+ chk("mla.book", md(r),
103
+ "Thompson, Paul B. *From Field to Fork: Food Ethics for Everyone*. Oxford UP, 2015.")
104
+ chk("mla.book.intext", r["intext"], "(Thompson 14)")
105
+
106
+ r = compute("mla", "chapter",
107
+ authors=[{"family": "Houser", "given": "Heather"}],
108
+ title="Coming-of-Mind in Climate Narrative",
109
+ book_title="Infowhelm: Environmental Art and Literature in an Age of Data",
110
+ editors=[{"family": "Houser", "given": "Heather"}],
111
+ publisher="U of Columbia P", year=2020, pages="61-93")
112
+ chk("mla.chapter", md(r),
113
+ 'Houser, Heather. "Coming-of-Mind in Climate Narrative." '
114
+ "*Infowhelm: Environmental Art and Literature in an Age of Data*, "
115
+ "edited by Heather Houser, U of Columbia P, 2020, pp. 61–93.")
116
+
117
+ r = compute("mla", "web",
118
+ authors=[{"family": "Rubinstein", "given": "Dana"}],
119
+ title="Sweeping Private-Sector Vaccine Mandate Goes Into Effect in N.Y.C.",
120
+ container="The New York Times", day=27, month="December", year=2021,
121
+ url="https://www.nytimes.com/2021/12/27/nyregion/nyc-vaccine-mandate.html")
122
+ chk("mla.web", md(r),
123
+ 'Rubinstein, Dana. "Sweeping Private-Sector Vaccine Mandate Goes Into Effect in '
124
+ 'N.Y.C." *The New York Times*, 27 Dec. 2021, '
125
+ "https://www.nytimes.com/2021/12/27/nyregion/nyc-vaccine-mandate.html.")
126
+ chk("mla.web.intext", r["intext"], "(Rubinstein)")
127
+
128
+ # MLA 2-author
129
+ r = compute("mla", "book",
130
+ authors=[{"family": "Grazer", "given": "Brian"},
131
+ {"family": "Fishman", "given": "Charles"}],
132
+ title="A Curious Mind", publisher="Simon & Schuster", year=2015)
133
+ assert md(r).startswith("Grazer, Brian, and Charles Fishman."), md(r)
134
+ PASS += 1
135
+
136
+ # ---------------------------------------------------------------- Chicago 17 author-date
137
+ r = compute("chicago", "article",
138
+ authors=[{"family": "Andreff", "given": "Wladimir"},
139
+ {"family": "Staudohar", "given": "Paul D."}],
140
+ title="The Evolving European Model of Professional Sports Finance",
141
+ year=2000, container="Journal of Sports Economics",
142
+ volume=1, issue=3, month="August", pages="257-276",
143
+ doi="10.1177/152700250000100304")
144
+ chk("chicago.article", md(r),
145
+ "Andreff, Wladimir, and Paul D. Staudohar. 2000. "
146
+ '"The Evolving European Model of Professional Sports Finance." '
147
+ "*Journal of Sports Economics* 1, no. 3 (August): 257–276. "
148
+ "https://doi.org/10.1177/152700250000100304.")
149
+
150
+ r = compute("chicago", "book",
151
+ authors=[{"family": "McGuire", "given": "Ian"}],
152
+ title="The North Water", year=2016, place="London",
153
+ publisher="Simon & Schuster", locator=22)
154
+ chk("chicago.book", md(r),
155
+ "McGuire, Ian. 2016. *The North Water*. London: Simon & Schuster.")
156
+ chk("chicago.book.intext", r["intext"], "(McGuire 2016, 22)")
157
+
158
+ r = compute("chicago", "chapter",
159
+ authors=[{"family": "Stewart", "given": "Bob"}],
160
+ title="Wag of the Tail: Reflecting on Pet Ownership", year=2007,
161
+ book_title="Enriching Our Lives with Animals",
162
+ editors=[{"family": "Jaimeson", "given": "John"}],
163
+ pages="220-90", place="Toronto", publisher="Petlove Press")
164
+ chk("chicago.chapter", md(r),
165
+ 'Stewart, Bob. 2007. "Wag of the Tail: Reflecting on Pet Ownership." '
166
+ "In *Enriching Our Lives with Animals*, edited by John Jaimeson, 220–90. "
167
+ "Toronto: Petlove Press.")
168
+
169
+ r = compute("chicago", "web",
170
+ authors=[{"family": "McCombes", "given": "Shona"}],
171
+ title="Creating an MLA Heading", year=2019, container="Scribbr",
172
+ url="https://www.scribbr.com/mla/heading/")
173
+ chk("chicago.web", md(r),
174
+ 'McCombes, Shona. 2019. "Creating an MLA Heading." Scribbr. '
175
+ "https://www.scribbr.com/mla/heading/.")
176
+
177
+ # Chicago 2-author in-text
178
+ r = compute("chicago", "book",
179
+ authors=[{"family": "Grazer", "given": "Brian"},
180
+ {"family": "Fishman", "given": "Charles"}],
181
+ title="A Curious Mind", year=2015, place="New York",
182
+ publisher="Simon & Schuster")
183
+ chk("chicago.2auth.intext", r["intext"], "(Grazer and Fishman 2015)")
184
+
185
+ # ---------------------------------------------------------------- BibTeX
186
+ r = compute("bibtex", "article",
187
+ authors=[{"family": "McGuire", "given": "Ian"}],
188
+ title="The North Water", year=2016,
189
+ container="Journal of Sports Economics", volume=1, issue=3,
190
+ pages="257-276", doi="10.1/x")
191
+ bib = r["bibtex"]
192
+ assert bib.startswith("@article{mcguire2016,"), bib
193
+ assert "author = {McGuire, Ian}" in bib, bib
194
+ assert "journal = {Journal of Sports Economics}" in bib, bib
195
+ assert "volume = {1}" in bib and "number = {3}" in bib, bib
196
+ assert "pages = {257--276}" in bib, bib
197
+ assert "year = {2016}" in bib, bib
198
+ PASS += 1
199
+
200
+ r = compute("bibtex", "book",
201
+ authors=[{"family": "McGuire", "given": "Ian"}],
202
+ title="The North Water", year=2016, place="London",
203
+ publisher="Simon & Schuster")
204
+ assert r["bibtex"].startswith("@book{mcguire2016,"), r["bibtex"]
205
+ assert "publisher = {Simon & Schuster}" in r["bibtex"], r["bibtex"]
206
+ assert "address = {London}" in r["bibtex"], r["bibtex"]
207
+ PASS += 1
208
+
209
+ # ---------------------------------------------------------------- helpers / edge
210
+ # html + plain renderings
211
+ r = compute("apa", "book", authors=[{"family": "Anderson", "given": "Benedict"}],
212
+ title="Imagined communities", year=1983, publisher="Verso")
213
+ assert r["reference"]["html"] == \
214
+ "Anderson, B. (1983). <i>Imagined communities</i>. Verso.", r["reference"]["html"]
215
+ assert r["reference"]["plain"] == \
216
+ "Anderson, B. (1983). Imagined communities. Verso.", r["reference"]["plain"]
217
+ PASS += 1
218
+
219
+ # string-form authors accepted
220
+ r = compute("mla", "book", authors="Thompson, Paul B.",
221
+ title="From Field to Fork: Food Ethics for Everyone",
222
+ publisher="Oxford UP", year=2015)
223
+ assert md(r).startswith("Thompson, Paul B."), md(r)
224
+ PASS += 1
225
+
226
+ # n.d. handling
227
+ r = compute("apa", "web", authors=[{"family": "Scribbr", "given": ""}],
228
+ title="Academic proofreading", container="Scribbr",
229
+ url="https://www.scribbr.com/proofreading-editing/")
230
+ assert "(n.d.)" in md(r), md(r)
231
+ PASS += 1
232
+
233
+ # title_case helper
234
+ r = compute("chicago", "book", authors=[{"family": "Doe", "given": "Jane"}],
235
+ title="the lord of the rings and the two towers", year=2001,
236
+ place="NY", publisher="X", title_case=True)
237
+ assert "*The Lord of the Rings and the Two Towers*" in md(r), md(r)
238
+ PASS += 1
239
+
240
+ # bad input
241
+ for bad in (lambda: compute("xx", "book", title="t"),
242
+ lambda: compute("apa", "zz", title="t"),
243
+ lambda: compute("apa", "book", title="")):
244
+ try:
245
+ bad()
246
+ FAIL += 1
247
+ print("FAIL: expected ValueError")
248
+ except ValueError:
249
+ PASS += 1
250
+
251
+ print(f"\n{PASS} passed, {FAIL} failed")
252
+ sys.exit(1 if FAIL else 0)
@@ -0,0 +1,63 @@
1
+ """Authority tests for /proofread — each rule fires on a positive case; exceptions don't.
2
+ Green = shippable. Run: python tests/test_proofread.py
3
+ """
4
+ import sys, os
5
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
6
+ from touchstone.proofreading import compute, sample_output # noqa: E402
7
+
8
+
9
+ def types(text):
10
+ return {i["type"] for i in compute(text)["issues"]}
11
+
12
+
13
+ def run():
14
+ p = f = 0
15
+
16
+ def check(name, cond):
17
+ nonlocal p, f
18
+ p += bool(cond); f += (not cond)
19
+ print(f" {'PASS' if cond else 'FAIL'} {name}")
20
+
21
+ check("repeated word", "repeated_word" in types("I saw the the dog."))
22
+ check("multiple spaces", "multiple_spaces" in types("Hello world."))
23
+ check("space before punctuation", "space_before_punctuation" in types("Wait , what ?"))
24
+ check("missing space after comma", "missing_space" in types("eggs,milk,bread"))
25
+ check("repeated punctuation", "repeated_punctuation" in types("Really?!"))
26
+ check("ellipsis NOT flagged as repeated punct", "repeated_punctuation" not in types("Well... okay."))
27
+ check("wordy phrase", "wordy_phrase" in types("We met in order to plan."))
28
+ check("filler word", "filler_word" in types("It was very good."))
29
+ check("passive voice (ed)", "passive_voice" in types("The ball was thrown."))
30
+ check("passive voice (irregular)", "passive_voice" in types("The cake was eaten."))
31
+ check("adverb flagged", "adverb" in types("She ran quickly."))
32
+ check("'only' NOT flagged as adverb", "adverb" not in types("I have only one."))
33
+ check("a->an before vowel", "a_an" in types("That is a apple."))
34
+ check("an->a before consonant", "a_an" in types("It is an dog."))
35
+ check("'an hour' NOT flagged", "a_an" not in types("in an hour"))
36
+ check("'a university' NOT flagged", "a_an" not in types("a university degree"))
37
+ check("'a hour' flagged (use an)", "a_an" in types("in a hour"))
38
+
39
+ long_sent = " ".join(f"w{i}" for i in range(35)) + "."
40
+ check("long sentence", "long_sentence" in types(long_sent))
41
+ check("short sentence not long", "long_sentence" not in types("A short one."))
42
+ check("lowercase start flagged", "capitalization" in types("the day was bright."))
43
+ check("proper start not flagged", "capitalization" not in types("The day was bright."))
44
+
45
+ clean = "The dog ran across the yard. A cat watched from the fence."
46
+ check("clean text has no high-confidence structural issues",
47
+ not any(i["confidence"] == "high" for i in compute(clean)["issues"]))
48
+
49
+ try:
50
+ compute(" "); check("empty raises", False)
51
+ except ValueError:
52
+ check("empty raises", True)
53
+
54
+ so = sample_output()
55
+ check("sample has issues + summary", so["issue_count"] > 0 and isinstance(so["summary"], dict))
56
+ check("issues carry offsets", all("offset" in i for i in so["issues"]))
57
+
58
+ print(f"\n{p} passed, {f} failed")
59
+ return f == 0
60
+
61
+
62
+ if __name__ == "__main__":
63
+ sys.exit(0 if run() else 1)
@@ -0,0 +1,72 @@
1
+ """Authority tests for /readability — published formulas + the stated syllable heuristic.
2
+ Green = shippable. Run: python tests/test_readability.py
3
+ """
4
+ import sys, os, math
5
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
6
+ from touchstone.readability import compute, syllables # noqa: E402
7
+
8
+
9
+ def run():
10
+ p = f = 0
11
+
12
+ def check(name, cond):
13
+ nonlocal p, f
14
+ p += bool(cond); f += (not cond)
15
+ print(f" {'PASS' if cond else 'FAIL'} {name}")
16
+
17
+ # 1) syllable heuristic on known words
18
+ known = {"cat": 1, "hello": 2, "table": 2, "make": 1, "queue": 1, "the": 1,
19
+ "bright": 1, "quickly": 2, "computer": 3, "beautiful": 3}
20
+ for w, n in known.items():
21
+ check(f"syllables({w})={n}", syllables(w) == n)
22
+
23
+ # 2) hand-computed exact example: "The cat sat." -> W=3, S=1, Sy=3, letters=9
24
+ c = compute("The cat sat.")
25
+ cn = c["counts"]
26
+ check("counts: 3 words/1 sentence/3 syllables/9 letters",
27
+ (cn["words"], cn["sentences"], cn["syllables"], cn["letters"]) == (3, 1, 3, 9))
28
+ # FK = 0.39*3 + 11.8*1 - 15.59 = -2.62 ; ease = 206.835 - 1.015*3 - 84.6*1 = 119.19
29
+ check("flesch_kincaid_grade == -2.62", c["scores"]["flesch_kincaid_grade"] == -2.62)
30
+ check("flesch_reading_ease == 119.19", c["scores"]["flesch_reading_ease"] == 119.19)
31
+ # ARI = 4.71*3 + 0.5*3 - 21.43 = -5.8
32
+ check("ARI == -5.8", c["scores"]["automated_readability_index"] == -5.8)
33
+
34
+ # 3) formula self-consistency on a longer text (scores match a re-derivation from counts)
35
+ text = ("Readability is the ease with which a reader can understand a written text. "
36
+ "Short sentences and common words make prose easier to read. "
37
+ "Long, convoluted constructions with polysyllabic terminology impede comprehension considerably.")
38
+ c = compute(text); cn = c["counts"]; s = c["scores"]
39
+ W, S, Sy, Lt, CW = cn["words"], cn["sentences"], cn["syllables"], cn["letters"], cn["complex_words"]
40
+ wps, spw = W / S, Sy / W
41
+ exp = {
42
+ "flesch_reading_ease": round(206.835 - 1.015 * wps - 84.6 * spw, 2),
43
+ "flesch_kincaid_grade": round(0.39 * wps + 11.8 * spw - 15.59, 2),
44
+ "gunning_fog": round(0.4 * (wps + 100 * (CW / W)), 2),
45
+ "smog": round(1.0430 * math.sqrt(CW * (30 / S)) + 3.1291, 2),
46
+ "coleman_liau": round(0.0588 * (Lt / W * 100) - 0.296 * (S / W * 100) - 15.8, 2),
47
+ "automated_readability_index": round(4.71 * (Lt / W) + 0.5 * wps - 21.43, 2),
48
+ }
49
+ for k, v in exp.items():
50
+ check(f"self-consistent {k} ({s[k]})", s[k] == v)
51
+
52
+ # 4) sanity: a simple text reads EASY (high Flesch ease), a dense one HARDER
53
+ easy = compute("I see the dog. The dog can run. We like the dog.")["scores"]["flesch_reading_ease"]
54
+ hard = compute("Notwithstanding the aforementioned considerations, the implementation "
55
+ "necessitates comprehensive architectural reconfiguration.")["scores"]["flesch_reading_ease"]
56
+ check("easy text scores higher ease than dense text", easy > hard)
57
+ check("easy text is genuinely easy (>90)", easy > 90)
58
+
59
+ # 5) guards + sample
60
+ try:
61
+ compute(" "); check("empty raises", False)
62
+ except ValueError:
63
+ check("empty raises", True)
64
+ from touchstone.readability import sample_output
65
+ check("sample_output works", "scores" in sample_output())
66
+
67
+ print(f"\n{p} passed, {f} failed")
68
+ return f == 0
69
+
70
+
71
+ if __name__ == "__main__":
72
+ sys.exit(0 if run() else 1)