timbro 0.6.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.
- timbro-0.6.0/.claude-plugin/marketplace.json +17 -0
- timbro-0.6.0/.claude-plugin/plugin.json +19 -0
- timbro-0.6.0/.github/workflows/ci.yml +36 -0
- timbro-0.6.0/.github/workflows/publish.yml +66 -0
- timbro-0.6.0/.gitignore +20 -0
- timbro-0.6.0/.python-version +1 -0
- timbro-0.6.0/CLAUDE.md +37 -0
- timbro-0.6.0/CONTRIBUTING.md +26 -0
- timbro-0.6.0/LICENSE +21 -0
- timbro-0.6.0/PKG-INFO +309 -0
- timbro-0.6.0/README.md +275 -0
- timbro-0.6.0/assets/distance.svg +10 -0
- timbro-0.6.0/assets/logo-dark.svg +20 -0
- timbro-0.6.0/assets/logo.svg +20 -0
- timbro-0.6.0/eval/harness.py +94 -0
- timbro-0.6.0/eval/rubric_dashboard.py +154 -0
- timbro-0.6.0/eval/slop_bench/hc3/SOURCE.md +5 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/finance-00.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/finance-01.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/finance-02.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/finance-03.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/finance-04.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/finance-05.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/finance-06.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/finance-07.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/medicine-00.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/medicine-01.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/medicine-02.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/medicine-03.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/medicine-04.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/medicine-05.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/medicine-06.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/medicine-07.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/open_qa-00.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/open_qa-01.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/open_qa-02.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/open_qa-03.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/open_qa-04.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/open_qa-05.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/open_qa-06.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/open_qa-07.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/reddit_eli5-00.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/reddit_eli5-01.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/reddit_eli5-02.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/reddit_eli5-03.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/reddit_eli5-04.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/reddit_eli5-05.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/reddit_eli5-06.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/reddit_eli5-07.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/wiki_csai-00.md +4 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/wiki_csai-01.md +5 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/wiki_csai-02.md +2 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/wiki_csai-03.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/wiki_csai-04.md +3 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/wiki_csai-05.md +2 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/wiki_csai-06.md +2 -0
- timbro-0.6.0/eval/slop_bench/hc3/human/wiki_csai-07.md +6 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/finance-00.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/finance-01.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/finance-02.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/finance-03.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/finance-04.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/finance-05.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/finance-06.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/finance-07.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/medicine-00.md +3 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/medicine-01.md +11 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/medicine-02.md +5 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/medicine-03.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/medicine-04.md +9 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/medicine-05.md +5 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/medicine-06.md +11 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/medicine-07.md +5 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/open_qa-00.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/open_qa-01.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/open_qa-02.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/open_qa-03.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/open_qa-04.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/open_qa-05.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/open_qa-06.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/open_qa-07.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/reddit_eli5-00.md +4 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/reddit_eli5-01.md +6 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/reddit_eli5-02.md +4 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/reddit_eli5-03.md +4 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/reddit_eli5-04.md +5 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/reddit_eli5-05.md +3 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/reddit_eli5-06.md +4 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/reddit_eli5-07.md +3 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/wiki_csai-00.md +13 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/wiki_csai-01.md +13 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/wiki_csai-02.md +17 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/wiki_csai-03.md +13 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/wiki_csai-04.md +9 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/wiki_csai-05.md +1 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/wiki_csai-06.md +13 -0
- timbro-0.6.0/eval/slop_bench/hc3/llm/wiki_csai-07.md +9 -0
- timbro-0.6.0/eval/slop_benchmark.py +90 -0
- timbro-0.6.0/pyproject.toml +67 -0
- timbro-0.6.0/scripts/derive_concreteness_prior.py +169 -0
- timbro-0.6.0/scripts/derive_fw_reference.py +103 -0
- timbro-0.6.0/scripts/hc3_subsample.py +99 -0
- timbro-0.6.0/scripts/release.sh +64 -0
- timbro-0.6.0/skills/timbro/SKILL.md +94 -0
- timbro-0.6.0/src/timbro/__init__.py +11 -0
- timbro-0.6.0/src/timbro/analyze.py +449 -0
- timbro-0.6.0/src/timbro/cleanup/__init__.py +15 -0
- timbro-0.6.0/src/timbro/cleanup/latex.py +118 -0
- timbro-0.6.0/src/timbro/cleanup/papers.py +261 -0
- timbro-0.6.0/src/timbro/cli.py +251 -0
- timbro-0.6.0/src/timbro/concreteness.py +94 -0
- timbro-0.6.0/src/timbro/config.py +142 -0
- timbro-0.6.0/src/timbro/flow.py +128 -0
- timbro-0.6.0/src/timbro/fw.py +88 -0
- timbro-0.6.0/src/timbro/hedge.py +104 -0
- timbro-0.6.0/src/timbro/lexicons/boosters.txt +66 -0
- timbro-0.6.0/src/timbro/lexicons/connectives_conditional.txt +17 -0
- timbro-0.6.0/src/timbro/lexicons/hedges.txt +90 -0
- timbro-0.6.0/src/timbro/lexicons/hype.txt +61 -0
- timbro-0.6.0/src/timbro/lexicons/negations.txt +22 -0
- timbro-0.6.0/src/timbro/lexicons/plain_wording.txt +223 -0
- timbro-0.6.0/src/timbro/mcp_server.py +80 -0
- timbro-0.6.0/src/timbro/metric.py +120 -0
- timbro-0.6.0/src/timbro/model.py +510 -0
- timbro-0.6.0/src/timbro/norms/NOTICE.md +9 -0
- timbro-0.6.0/src/timbro/norms/concreteness_brysbaert2014.csv.gz +0 -0
- timbro-0.6.0/src/timbro/profiles.py +253 -0
- timbro-0.6.0/src/timbro/report.py +224 -0
- timbro-0.6.0/src/timbro/rewrite.py +63 -0
- timbro-0.6.0/src/timbro/rubrics/__init__.py +23 -0
- timbro-0.6.0/src/timbro/rubrics/base.py +50 -0
- timbro-0.6.0/src/timbro/rubrics/density/__init__.py +5 -0
- timbro-0.6.0/src/timbro/rubrics/density/checks.py +127 -0
- timbro-0.6.0/src/timbro/rubrics/density/rubric.py +24 -0
- timbro-0.6.0/src/timbro/rubrics/features.py +804 -0
- timbro-0.6.0/src/timbro/rubrics/registry.py +15 -0
- timbro-0.6.0/src/timbro/rubrics/report.py +51 -0
- timbro-0.6.0/src/timbro/rubrics/rules.py +496 -0
- timbro-0.6.0/src/timbro/rubrics/schimel/__init__.py +5 -0
- timbro-0.6.0/src/timbro/rubrics/schimel/rubric.py +21 -0
- timbro-0.6.0/src/timbro/rubrics/sections.py +32 -0
- timbro-0.6.0/src/timbro/rubrics/slop/__init__.py +5 -0
- timbro-0.6.0/src/timbro/rubrics/slop/checks.py +91 -0
- timbro-0.6.0/src/timbro/rubrics/slop/rubric.py +26 -0
- timbro-0.6.0/src/timbro/sample/contrast/01-synergy.md +9 -0
- timbro-0.6.0/src/timbro/sample/contrast/02-revolutionize.md +11 -0
- timbro-0.6.0/src/timbro/sample/contrast/03-paradigm.md +9 -0
- timbro-0.6.0/src/timbro/sample/exemplars/01-shipping.md +9 -0
- timbro-0.6.0/src/timbro/sample/exemplars/02-debugging.md +9 -0
- timbro-0.6.0/src/timbro/sample/exemplars/03-reviews.md +9 -0
- timbro-0.6.0/src/timbro/sample/exemplars/04-estimates.md +9 -0
- timbro-0.6.0/src/timbro/spacy_model.py +30 -0
- timbro-0.6.0/src/timbro/tells.py +324 -0
- timbro-0.6.0/src/timbro/text.py +169 -0
- timbro-0.6.0/tests/test_analyze.py +273 -0
- timbro-0.6.0/tests/test_analyze_folk_advice.py +166 -0
- timbro-0.6.0/tests/test_concreteness.py +113 -0
- timbro-0.6.0/tests/test_derive_concreteness_prior.py +130 -0
- timbro-0.6.0/tests/test_derive_fw_reference.py +59 -0
- timbro-0.6.0/tests/test_fw.py +123 -0
- timbro-0.6.0/tests/test_hedge.py +120 -0
- timbro-0.6.0/tests/test_leading_words.py +96 -0
- timbro-0.6.0/tests/test_markdown_scoring.py +75 -0
- timbro-0.6.0/tests/test_preprocess.py +217 -0
- timbro-0.6.0/tests/test_profiles.py +100 -0
- timbro-0.6.0/tests/test_report_attribution.py +66 -0
- timbro-0.6.0/tests/test_report_severity.py +46 -0
- timbro-0.6.0/tests/test_rubric_density.py +131 -0
- timbro-0.6.0/tests/test_rubric_schimel.py +585 -0
- timbro-0.6.0/tests/test_rubric_slop.py +84 -0
- timbro-0.6.0/tests/test_runtime_latex.py +121 -0
- timbro-0.6.0/tests/test_scoring_policy.py +52 -0
- timbro-0.6.0/tests/test_slop_benchmark.py +77 -0
- timbro-0.6.0/tests/test_tells.py +107 -0
- timbro-0.6.0/uv.lock +2739 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
3
|
+
"name": "timbro",
|
|
4
|
+
"description": "Keep your writing sounding like you — even when an LLM is writing.",
|
|
5
|
+
"owner": {
|
|
6
|
+
"name": "Nicolo' Brandizzi",
|
|
7
|
+
"url": "https://github.com/nicofirst1"
|
|
8
|
+
},
|
|
9
|
+
"plugins": [
|
|
10
|
+
{
|
|
11
|
+
"name": "timbro",
|
|
12
|
+
"description": "Score a draft against your voice and get named, content-preserving edits to align it. Bundles the skill + the score_voice / accept_rewrite / check_voice MCP tools.",
|
|
13
|
+
"source": "./",
|
|
14
|
+
"category": "productivity"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "timbro",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Keep your writing sounding like you — even when an LLM is writing. Scores a draft's distance from your voice (score_voice) and runs a deterministic Schimel writing rubric (check_voice). Returns named, content-preserving edits. Local, white-box, MCP-ready.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Nicolo' Brandizzi",
|
|
7
|
+
"url": "https://nicolobrandizzi.com"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/nicofirst1/timbro",
|
|
10
|
+
"keywords": ["writing", "style", "voice", "consistency", "mcp", "ai-agents"],
|
|
11
|
+
"mcpServers": {
|
|
12
|
+
"timbro": {
|
|
13
|
+
"command": "uv",
|
|
14
|
+
"args": ["run", "--directory", "${CLAUDE_PLUGIN_ROOT}", "timbro-mcp"],
|
|
15
|
+
"comment": "Runs on the packaged sample voice out of the box. To use your real voice, add TIMBRO_EXEMPLARS / TIMBRO_CONTRAST (absolute paths to your own corpora) to the env below.",
|
|
16
|
+
"env": {}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, dev]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
os: [ubuntu-latest, macos-latest]
|
|
13
|
+
runs-on: ${{ matrix.os }}
|
|
14
|
+
timeout-minutes: 20
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Install uv
|
|
19
|
+
uses: astral-sh/setup-uv@v5
|
|
20
|
+
with:
|
|
21
|
+
enable-cache: true
|
|
22
|
+
|
|
23
|
+
- name: Cache HF models
|
|
24
|
+
uses: actions/cache@v4
|
|
25
|
+
with:
|
|
26
|
+
path: ~/.cache/huggingface
|
|
27
|
+
key: ${{ runner.os }}-hf-${{ hashFiles('uv.lock') }}
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: uv sync
|
|
31
|
+
|
|
32
|
+
- name: Lint
|
|
33
|
+
run: uvx ruff@0.15.20 check src/
|
|
34
|
+
|
|
35
|
+
- name: Test
|
|
36
|
+
run: uv run pytest
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Runs on version tags (vX.Y.Z). Publishing uses PyPI's trusted-publisher OIDC flow
|
|
4
|
+
# (pypa/gh-action-pypi-publish) -- no API token secret to manage or leak. The human
|
|
5
|
+
# step this workflow depends on: register this repo/workflow as a trusted publisher
|
|
6
|
+
# for the `timbro` project at https://pypi.org/manage/project/timbro/settings/publishing/
|
|
7
|
+
# (one-time setup, done after the first manual `uv publish`).
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
tags:
|
|
12
|
+
- "v*.*.*"
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
verify-and-build:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.11"
|
|
24
|
+
|
|
25
|
+
- name: Assert tag == pyproject.toml == plugin.json version
|
|
26
|
+
run: |
|
|
27
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
28
|
+
PYPROJECT_VERSION=$(grep -m1 '^version = ' pyproject.toml | sed -E 's/version = "(.*)"/\1/')
|
|
29
|
+
PLUGIN_VERSION=$(python3 -c "import json; print(json.load(open('.claude-plugin/plugin.json'))['version'])")
|
|
30
|
+
echo "tag=$TAG_VERSION pyproject=$PYPROJECT_VERSION plugin=$PLUGIN_VERSION"
|
|
31
|
+
if [ "$TAG_VERSION" != "$PYPROJECT_VERSION" ] || [ "$TAG_VERSION" != "$PLUGIN_VERSION" ]; then
|
|
32
|
+
echo "::error::version mismatch: tag=$TAG_VERSION pyproject=$PYPROJECT_VERSION plugin=$PLUGIN_VERSION"
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
- name: Build
|
|
37
|
+
run: uv build
|
|
38
|
+
|
|
39
|
+
- name: twine check
|
|
40
|
+
run: uvx twine check dist/*
|
|
41
|
+
|
|
42
|
+
- name: Cold-start smoke test (clean venv, no repo deps)
|
|
43
|
+
run: |
|
|
44
|
+
python3 -m venv /tmp/cold-venv
|
|
45
|
+
/tmp/cold-venv/bin/pip install --quiet dist/*.whl
|
|
46
|
+
/tmp/cold-venv/bin/timbro check README.md
|
|
47
|
+
|
|
48
|
+
- uses: actions/upload-artifact@v4
|
|
49
|
+
with:
|
|
50
|
+
name: dist
|
|
51
|
+
path: dist/
|
|
52
|
+
|
|
53
|
+
publish:
|
|
54
|
+
needs: verify-and-build
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
environment: pypi
|
|
57
|
+
permissions:
|
|
58
|
+
id-token: write # OIDC trusted publishing, no token secret
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/download-artifact@v4
|
|
61
|
+
with:
|
|
62
|
+
name: dist
|
|
63
|
+
path: dist/
|
|
64
|
+
|
|
65
|
+
- name: Publish to PyPI
|
|
66
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
timbro-0.6.0/.gitignore
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
.worktrees/
|
|
12
|
+
|
|
13
|
+
# Local corpora (private / third-party content) -- bring your own
|
|
14
|
+
data/
|
|
15
|
+
|
|
16
|
+
# Local agent config
|
|
17
|
+
.claude/
|
|
18
|
+
|
|
19
|
+
# Internal build spec / thinking record (kept local, not published)
|
|
20
|
+
PLAN.md
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
timbro-0.6.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Timbro — agent notes
|
|
2
|
+
|
|
3
|
+
Measures a draft's distance from a target voice and returns a named revision direction. Does not rewrite — the agent does. Local, CPU-only.
|
|
4
|
+
|
|
5
|
+
## Active plan — follow the GitHub milestones
|
|
6
|
+
|
|
7
|
+
All planned work lives in GitHub issues (`gh issue list`), grouped into milestones. **Before starting anything substantive, read the relevant issue and work within it** — don't invent parallel work; if something is missing, add an issue to the right milestone instead.
|
|
8
|
+
|
|
9
|
+
### Implementer guardrails
|
|
10
|
+
|
|
11
|
+
Issues are labeled by required capability: `agent:mechanical` = fully specified, follow the "Implementer spec" section literally; `agent:judgment` = has open taste/decision surface — ask the user before deviating or deciding, don't guess.
|
|
12
|
+
|
|
13
|
+
- One issue per branch/PR. Don't fold in drive-by refactors.
|
|
14
|
+
- The "Implementer spec" sections are decisions, not suggestions. If a number or approach in one looks wrong, comment on the issue and stop — do not silently substitute your own.
|
|
15
|
+
- Before declaring done, run `uv run pytest` and `uv run ruff check src/` and quote the output in the PR.
|
|
16
|
+
- Never touch `_PENALTY` / `_WEIGHTS` values, the verdict thresholds in `report.py`, or add a dependency, unless the issue explicitly says so.
|
|
17
|
+
- Respect issue dependencies. If your issue is blocked, say so instead of working around it.
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
- `uv run timbro score draft.md` — score a file (runs on the packaged sample voice if no corpus env vars set)
|
|
22
|
+
- `uv run timbro-mcp` — MCP server (stdio)
|
|
23
|
+
- `uv run python -m timbro.model` — core smoke test
|
|
24
|
+
- `uv run ruff check src/` — lint
|
|
25
|
+
- Corpus: `TIMBRO_EXEMPLARS` (toward) / `TIMBRO_CONTRAST` (away). Named profiles live under `$XDG_DATA_HOME/timbro/profiles/<name>/{exemplars,contrast}/` by default (`$XDG_DATA_HOME` falls back to `~/.local/share`); an existing `~/.timbro/profiles/` is used instead if present (legacy installs); `TIMBRO_PROFILE_ROOT` overrides both.
|
|
26
|
+
|
|
27
|
+
## Releasing an update
|
|
28
|
+
|
|
29
|
+
The plugin updater compares by **version string**, so without a bump it will not pick up code changes (`already at latest version`).
|
|
30
|
+
|
|
31
|
+
Run `scripts/release.sh <new-version>` — it bumps both `.claude-plugin/plugin.json` and `pyproject.toml` (and fails loud if they end up mismatched), `uv lock`s, commits, confirms before pushing to `main`, then refreshes the marketplace clone, updates the plugin, and syncs the new cache venv.
|
|
32
|
+
|
|
33
|
+
## Gotchas
|
|
34
|
+
|
|
35
|
+
- `en_core_web_sm` is pinned as a direct-URL wheel dep (needs `tool.hatch.metadata.allow-direct-references`). No manual `spacy download`.
|
|
36
|
+
- Defaults resolve relative to the package dir (`src/timbro/sample/`), not CWD — so the plugin works inside its cache sandbox.
|
|
37
|
+
- `data/` is gitignored (private corpora); the shipped `src/timbro/sample/` is the only corpus that publishes.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
uv sync
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
That's it — `uv` installs everything, including the pinned spaCy model wheel (no manual `spacy download`).
|
|
10
|
+
|
|
11
|
+
## Before you start
|
|
12
|
+
|
|
13
|
+
Read `CLAUDE.md`'s "Active plan" section and the [open issues](https://github.com/nicofirst1/timbro/issues) first. All planned work lives in GitHub issues grouped into milestones — work within an existing issue rather than inventing parallel work. One issue per branch/PR; don't fold in drive-by refactors.
|
|
14
|
+
|
|
15
|
+
## Test and lint
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
uv run pytest
|
|
19
|
+
uv run ruff check src/
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Both must pass before you open a PR — CI runs the same two commands on push/PR (ubuntu + macos).
|
|
23
|
+
|
|
24
|
+
## Releasing
|
|
25
|
+
|
|
26
|
+
See the "Releasing an update" section in `CLAUDE.md` (`scripts/release.sh <new-version>`). Only maintainers cut releases.
|
timbro-0.6.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nicolo' Brandizzi
|
|
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.
|
timbro-0.6.0/PKG-INFO
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: timbro
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Measure the timbre of your writing as a metric space and get an interpretable, content-preserving direction to revise a draft toward your voice.
|
|
5
|
+
Project-URL: Homepage, https://github.com/nicofirst1/timbro
|
|
6
|
+
Project-URL: Repository, https://github.com/nicofirst1/timbro
|
|
7
|
+
Project-URL: Issues, https://github.com/nicofirst1/timbro/issues
|
|
8
|
+
Author: Nicolo' Brandizzi
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: editing,linting,nlp,spacy,style,voice,writing
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
22
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: lexical-diversity>=0.1.1
|
|
25
|
+
Requires-Dist: mcp>=1.27.2
|
|
26
|
+
Requires-Dist: numpy>=2.4.6
|
|
27
|
+
Requires-Dist: scikit-learn>=1.9.0
|
|
28
|
+
Requires-Dist: sentence-transformers>=5.5.1
|
|
29
|
+
Requires-Dist: setuptools<81
|
|
30
|
+
Requires-Dist: spacy>=3.8.14
|
|
31
|
+
Requires-Dist: textdescriptives>=2.8.2
|
|
32
|
+
Requires-Dist: wordfreq>=3.1.1
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
<p align="center">
|
|
36
|
+
<picture>
|
|
37
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/logo-dark.svg">
|
|
38
|
+
<img src="assets/logo.svg" width="180" alt="Timbro">
|
|
39
|
+
</picture>
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
<h1 align="center">Timbro</h1>
|
|
43
|
+
|
|
44
|
+
<p align="center">
|
|
45
|
+
<em>Catch AI slop with deterministic, offline checks that never call an LLM. Then keep what's left sounding like you.</em>
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
<p align="center">
|
|
49
|
+
<a href="https://github.com/nicofirst1/timbro/actions/workflows/ci.yml"><img src="https://github.com/nicofirst1/timbro/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
50
|
+
<img src="https://img.shields.io/badge/python-3.11%2B-111111?style=flat-square" alt="Python 3.11+">
|
|
51
|
+
<img src="https://img.shields.io/badge/inference-local%20·%20CPU--only-111111?style=flat-square" alt="Local CPU-only inference">
|
|
52
|
+
<img src="https://img.shields.io/badge/MCP-ready-111111?style=flat-square" alt="MCP ready">
|
|
53
|
+
<img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT license">
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
**LLM prose has a tell.** Em/en dashes everywhere, "it's not X, it's Y", the _delve / tapestry / seamless_ vocabulary, a tidy wrap-up about the future. A reader feels it, but "sounds AI-written" is not something you can put in CI.
|
|
59
|
+
|
|
60
|
+
Timbro makes it one. `timbro slop` runs ~19 deterministic detectors (regex + part-of-speech, no model, no network) and returns a verdict, four dimension scores, and the exact markers it found:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
$ timbro slop draft.md
|
|
64
|
+
slop: WARN (0.69)
|
|
65
|
+
|
|
66
|
+
diction 0.70
|
|
67
|
+
construction 0.70
|
|
68
|
+
rhythm 0.80
|
|
69
|
+
formatting 0.55
|
|
70
|
+
|
|
71
|
+
Top findings
|
|
72
|
+
- formatting: 2× em/en dashes
|
|
73
|
+
- diction: 12× AI-tell diction (delve, tapestry, seamless, robust, …)
|
|
74
|
+
- construction: signposting phrases, wrap-up phrases
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Delete the flagged markers, re-run, and it reads `slop: PASS (1.00)`. Same meaning, no tells.
|
|
78
|
+
|
|
79
|
+
**Sanity check, not a headline number:** `eval/slop_benchmark.py` scores a small set of genuinely LLM-generated paragraphs against the packaged known-good human prose and reports how often `slop` fires on each side. It's a repo-local smoke test, not independent validation — the human side is the same corpus the tell rules were tuned against (see the script's own caveat), so its false-positive rate isn't a claim about unseen writing. Run it yourself:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
uv run python eval/slop_benchmark.py
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Why not just ask an LLM "does this read AI-generated?"** Because that is an LLM grading an LLM: nondeterministic, an API call every time, and it can't show you _which_ words tripped it. Timbro is white-box. Every flag is a named marker you can see, cite, and remove; it runs local and CPU-only, gives the same answer every time, and is fast enough for a git hook.
|
|
86
|
+
|
|
87
|
+
## And a positive target, not just a blocklist
|
|
88
|
+
|
|
89
|
+
Any regex list can tell you what to strip. Timbro's second act tells you what your writing should sound _like_. Seed it with posts you've accepted as your voice, and it scores any draft for **how far** it sits from that voice and **which way** to revise it, in named features, without changing what it says. That positive target is what separates it from every slop-lister.
|
|
90
|
+
|
|
91
|
+
- **You, consistently.** A personal blog or newsletter should sound like one person across years of posts — not like whichever model wrote each one.
|
|
92
|
+
- **A company on-brand.** Marketing, docs, and posts drift across authors and tools. Seed Timbro with your on-brand corpus and every draft gets measured against it.
|
|
93
|
+
- **An agent that self-corrects.** LLMs are fluent but stylistically inconsistent. Timbro gives an agent a _measurable target_ and a _named direction_, so it can revise toward a voice instead of guessing.
|
|
94
|
+
|
|
95
|
+
## Numbers
|
|
96
|
+
|
|
97
|
+
This README was written by Claude (Opus 4.8). With Timbro you can see exactly how it scores against [my actual blog voice](https://nicolobrandizzi.com/blog/) — the same number your agent watches as it revises:
|
|
98
|
+
|
|
99
|
+
<p align="center">
|
|
100
|
+
<img src="assets/distance.svg" width="780" alt="A 0-to-far axis: my blog voice sits in a 9–35 band; this README lands just outside it at 47; marketing hype is far out at 86">
|
|
101
|
+
</p>
|
|
102
|
+
|
|
103
|
+
It lands at 47 — outside my blog range (9–35): recognizably _not_ my essay voice (it's code-heavy docs), but a world away from sales-speak at 86. And Timbro hands back the _direction_ to close the gap: **more conjunctions, fewer abstract nouns, less code-block punctuation**. Scored against: [Horizon AI Fragmentation](https://nicolobrandizzi.com/blog/horizon-analysis/), [Teaching Machines to Think](https://nicolobrandizzi.com/blog/rl-reasoning-llm/), [The Digital Poisoners](https://nicolobrandizzi.com/blog/pravda-grooming/), [The SOTA Trap](https://nicolobrandizzi.com/blog/sota-trap/), [AI Gigafactories](https://nicolobrandizzi.com/blog/ai-gigafactories-tool/).
|
|
104
|
+
|
|
105
|
+
## How it works
|
|
106
|
+
|
|
107
|
+
Your agent runs one loop, and Timbro scores every turn of it:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
score → how far from your voice, and which way to move
|
|
111
|
+
edit → revise toward the named direction
|
|
112
|
+
re-score → distance dropped AND meaning held?
|
|
113
|
+
repeat → until the distance stops falling
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Each score is three legible layers plus a guard:
|
|
117
|
+
|
|
118
|
+
- **Scalar — "how far"** — a pre-trained [StyleDistance](https://huggingface.co/StyleDistance/styledistance) embedding, scored by multi-modal **kNN**.
|
|
119
|
+
- **Direction — "which way"** — **POS-unigram** rates, z-scored against your corpus and weighted by each feature's R². Every move is a named habit.
|
|
120
|
+
- **Flow** — paragraph-embedding trajectory (speed, volume, circuitousness) + the Schimel "circle-back" (`cos(first, last)`).
|
|
121
|
+
- **Content guard** — semantic cosine via a _general_ model (all-MiniLM): changes _how_ it reads, never _what_ it says.
|
|
122
|
+
|
|
123
|
+
## The writing rubric (`check`)
|
|
124
|
+
|
|
125
|
+
Voice alignment answers _"does this sound like me?"_. The rubric answers a separate question — _"is this good prose?"_ — and needs **no voice corpus**. `timbro check` (and the `check_voice` MCP tool) runs ~30 deterministic checks distilled from Joshua Schimel's _Writing Science_, all linguistic/structural (spaCy dependency parse + POS + counting), **no LLM-as-judge**: buried subject–verb core, passive voice, comma splices, expletive openings, preposition chains, nominalizations, long Latinate words, word-echo repetition, inconsistent terminology, metadiscourse and citation-as-subject frames, caveat/defensive closings, unearned claim words, significance-without-magnitude, and more. It returns a per-dimension score and a ranked findings list — recall-first, so a model consumer filters the occasional false positive. Rubrics are pluggable via a registry (`--rubric <name>`); `schimel` ships today. `uv run python eval/rubric_dashboard.py` prints each rule's findings-per-1000-words on known-good prose, so noisy rules can be spotted and demoted rather than deleted.
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
uv run timbro check draft.md # human-readable
|
|
129
|
+
uv run timbro check draft.md --json # {verdict, overall, dimensions, findings}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Install
|
|
133
|
+
|
|
134
|
+
### As a one-shot CLI, no clone (fastest)
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
uvx timbro check draft.md # first run downloads the spaCy POS model, then scores
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### As a Claude Code plugin (one command)
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
/plugin marketplace add nicofirst1/timbro
|
|
144
|
+
/plugin install timbro@timbro
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
This installs the **skill** _and_ wires up the **MCP tools** (`score_voice`, `accept_rewrite`, `check_voice`) in one shot. It works immediately on a small **packaged sample voice** — ask Claude _"score this against the Timbro sample voice"_ to see it run.
|
|
148
|
+
|
|
149
|
+
To use **your** voice, point the MCP server at your own corpus. Edit the `timbro` entry in your MCP config (or the plugin's `plugin.json`) to set absolute paths:
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
"env": {
|
|
153
|
+
"TIMBRO_EXEMPLARS": "/abs/path/to/your/exemplars",
|
|
154
|
+
"TIMBRO_CONTRAST": "/abs/path/to/your/contrast"
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The POS model and the sample corpus both ship with the plugin — no manual download step.
|
|
159
|
+
|
|
160
|
+
### As a skill
|
|
161
|
+
|
|
162
|
+
Copy just the skill so the agent knows when and how to use Timbro:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
cp -r skills/timbro ~/.claude/skills/ # personal, or .claude/skills/ per-project
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Now ask Claude the same way — it runs Timbro, reads the direction, and proposes content-preserving edits.
|
|
169
|
+
|
|
170
|
+
### As an MCP server (Claude Code, Cursor, Windsurf, Claude Desktop, …)
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# Claude Code
|
|
174
|
+
claude mcp add timbro \
|
|
175
|
+
-e TIMBRO_EXEMPLARS=$PWD/data/exemplars \
|
|
176
|
+
-e TIMBRO_CONTRAST=$PWD/data/contrast \
|
|
177
|
+
-- uv run --directory $PWD timbro-mcp
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Or drop this into any agent's `.mcp.json` / MCP settings:
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"mcpServers": {
|
|
185
|
+
"timbro": {
|
|
186
|
+
"command": "uv",
|
|
187
|
+
"args": ["run", "--directory", "/abs/path/to/timbro", "timbro-mcp"],
|
|
188
|
+
"env": {
|
|
189
|
+
"TIMBRO_EXEMPLARS": "/abs/path/to/timbro/data/exemplars",
|
|
190
|
+
"TIMBRO_CONTRAST": "/abs/path/to/timbro/data/contrast"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The agent gets three tools:
|
|
198
|
+
|
|
199
|
+
| Tool | Returns |
|
|
200
|
+
| ----------------------------------- | ------------------------------------------------------------------------------------- |
|
|
201
|
+
| `score_voice(text)` | `{distance, direction, flow}` |
|
|
202
|
+
| `accept_rewrite(original, revised)` | `{accepted, content_ok, similarity, distance_before, distance_after, improved}` |
|
|
203
|
+
| `check_voice(text)` | `{verdict, overall, dimensions, findings}` — the deterministic writing rubric (below) |
|
|
204
|
+
|
|
205
|
+
### As a one-shot CLI
|
|
206
|
+
|
|
207
|
+
No server, no agent — just score a file:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
uv run timbro slop draft.md # deterministic AI-slop / tells report
|
|
211
|
+
uv run timbro score draft.md # distance from your voice + revision direction
|
|
212
|
+
cat draft.md | uv run timbro score - # stdin
|
|
213
|
+
uv run timbro score draft.md --json # raw payload
|
|
214
|
+
uv run timbro check draft.md # Schimel prose-quality rubric (below)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### From source (required for the MCP and CLI options above)
|
|
218
|
+
|
|
219
|
+
Requires Python ≥ 3.11 and [`uv`](https://docs.astral.sh/uv/).
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
git clone git@github.com:nicofirst1/timbro.git && cd timbro
|
|
223
|
+
uv sync # pulls deps + the en_core_web_sm POS model (no manual spacy download)
|
|
224
|
+
|
|
225
|
+
uv run timbro score draft.md # runs immediately on the packaged sample voice
|
|
226
|
+
|
|
227
|
+
# to use your own voice, bring a corpus (both dirs are gitignored — your writing stays private)
|
|
228
|
+
mkdir -p data/exemplars data/contrast
|
|
229
|
+
# data/exemplars/ → posts that define your (or your company's) voice — 6+ pieces
|
|
230
|
+
# data/contrast/ → other authors' posts (the "not-our-voice" set), optional but sharpens it
|
|
231
|
+
|
|
232
|
+
TIMBRO_EXEMPLARS=data/exemplars TIMBRO_CONTRAST=data/contrast uv run timbro score draft.md
|
|
233
|
+
uv run python eval/harness.py data/exemplars data/contrast # confirm it separates your voice
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The two sentence-transformer models download from Hugging Face on first use. Everything runs **local and CPU-only** at inference — no API calls.
|
|
237
|
+
|
|
238
|
+
## FAQ
|
|
239
|
+
|
|
240
|
+
**My voice legitimately uses em-dashes — won't `slop` nag me?** By default it flags against zero, so yes. Add `timbro slop draft.md --profile <name>` to baseline the tells against your own corpus instead: a tell is flagged only where the draft _overuses_ it relative to how you normally write. Absolute mode answers "is this AI-generated?"; `--profile` answers "is this driftier than my own writing?".
|
|
241
|
+
|
|
242
|
+
**Do I need the contrast set?** No, but it sharpens the direction — without it, every feature looks equally informative.
|
|
243
|
+
|
|
244
|
+
**Will it work on one author / a whole company?** Both. The "voice" is whatever you put in `data/exemplars/`. Mixed registers (blogs + papers) are fine — the scorer is multi-modal.
|
|
245
|
+
|
|
246
|
+
**Can I keep several directions (academic vs. slop, clear vs. jargon)?** Yes — one folder pair per dimension, selected by env var. Profiles live under `~/.timbro/profiles/<name>/{exemplars,contrast}/` by default (override with `TIMBRO_PROFILE_ROOT`). Point the env vars at the one you want for a given task:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
P=~/.timbro/profiles/academic
|
|
250
|
+
TIMBRO_EXEMPLARS=$P/exemplars TIMBRO_CONTRAST=$P/contrast uv run timbro score draft.md
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
No code, no flags — collect good/bad examples per dimension and swap the two paths. If you want Timbro to scaffold and manage the local profile layout for you, use the `timbro profiles ...` commands below.
|
|
254
|
+
|
|
255
|
+
**Can Timbro create and manage profiles for me?** Yes. Use the built-in profile helpers to scaffold a profile, describe it, add files, and print the right env vars:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
uv run timbro profiles init science-clarity --about "Plain-language scientific explanation."
|
|
259
|
+
uv run timbro profiles add-file science-clarity notes/pvalue.md --to exemplars
|
|
260
|
+
uv run timbro profiles add-file science-clarity sloppy-example.md --to contrast
|
|
261
|
+
uv run timbro profiles add-file science-clarity paper.tex --to exemplars
|
|
262
|
+
uv run timbro profiles env science-clarity
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Programmatically:
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
from timbro.profiles import init_profile, add_file
|
|
269
|
+
|
|
270
|
+
profile = init_profile("science-clarity", about="Plain-language scientific explanation.")
|
|
271
|
+
add_file("science-clarity", "notes/pvalue.md", bucket="exemplars")
|
|
272
|
+
add_file("science-clarity", "paper.tex", bucket="exemplars")
|
|
273
|
+
print(profile.env)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
If `detex` is installed, `.tex` files are converted on ingest and raw LaTeX is normalized automatically during scoring.
|
|
277
|
+
|
|
278
|
+
For scoring, prefer profile-native selection over manual env vars:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
uv run timbro score draft.md --profile science-clarity
|
|
282
|
+
uv run timbro score draft.md --profile science-clarity,academic
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Does it rewrite for me?** No, and that's deliberate. Timbro _measures_; your agent rewrites and Timbro judges the result (closer to voice **and** same meaning). Keeps the scoring honest and local.
|
|
286
|
+
|
|
287
|
+
## Layout
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
src/timbro/
|
|
291
|
+
├── model.py # corpus → POS features + StyleDistance embedding → VoiceModel
|
|
292
|
+
├── text.py # shared substrate: split_paragraphs/_sentences, strip_markup, MiniLM embedder
|
|
293
|
+
├── flow.py # paragraph trajectory, circle-back, order gates
|
|
294
|
+
├── rewrite.py # content-preservation guard + accept-rewrite loop
|
|
295
|
+
├── report.py # the shared {distance, direction, flow} payload
|
|
296
|
+
├── tells.py # AI-tell detectors (regex + POS); feed the `slop` rubric and the score direction
|
|
297
|
+
├── rubrics/ # `check` (schimel/density) + `slop` (tells) rubrics: features + rules + registry
|
|
298
|
+
├── cleanup/ # ingest-time corpus prep (LaTeX/paper extraction — not markdown)
|
|
299
|
+
├── cli.py # `timbro score` + `timbro check` + `timbro slop`
|
|
300
|
+
└── mcp_server.py # MCP wrapper: score_voice, accept_rewrite, check_voice
|
|
301
|
+
skills/timbro/ # Claude Code skill
|
|
302
|
+
eval/harness.py # LOO-AUC, permutation baseline, direction sign test
|
|
303
|
+
eval/rubric_dashboard.py # per-rule findings-per-1000-words on known-good prose
|
|
304
|
+
eval/slop_benchmark.py # slop hit rate / false-positive rate on a small LLM/human corpus
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Contributing
|
|
308
|
+
|
|
309
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|