From 586b8c3977ee5178a5850c58f4b0efac8737c957 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 15:07:36 +0000 Subject: [PATCH 1/2] chore: fix FRAGMENTS.md layer paths, drop unused mypy overrides, ignore tool caches - FRAGMENTS.md pointed at layers/.py, which does not exist from the repo root; the modules live under convergence/layers/. README got the same fix earlier. - mypy reported the pexpect.*/wexpect.* override entries as unused (nothing imports either); remove them so the note goes away. - Ignore .mypy_cache/ and .ruff_cache/, both created by the CI checks. Verified: ruff check ., mypy convergence web, pytest (272 passed, 84.92% coverage), and the web/site data drift check all pass. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01U142aAXSzf4pUt89HSvXTH --- .gitignore | 2 ++ FRAGMENTS.md | 12 ++++++------ pyproject.toml | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index e4f30e1..a4237a8 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ dist/ venv/ env/ .pytest_cache/ +.mypy_cache/ +.ruff_cache/ .coverage htmlcov/ diff --git a/FRAGMENTS.md b/FRAGMENTS.md index 492cd65..0733e45 100644 --- a/FRAGMENTS.md +++ b/FRAGMENTS.md @@ -30,7 +30,7 @@ and where to extend it. --- ## L1 · borrow-authority / displace-accountability -`layers/pattern_detector.py` +`convergence/layers/pattern_detector.py` Presenting a decision as the dictate of an outside authority ("the platform's policy says…", "my accountant says…") so the speaker never owns it. A fragment @@ -56,7 +56,7 @@ corpus seq-6 negative case.) --- ## L3 · claim contradicted (denial of agreement) -`layers/third_party.py` +`convergence/layers/third_party.py` A denial fragment whose factual claim is tested against an external records set. The denial is only the *trigger*; the contradiction is confirmed by a record, not @@ -73,7 +73,7 @@ No matching record → nothing emitted. The engine never invents a contradiction --- ## L5 · register anomaly (formal-register fragments) -`layers/phrase_fragmentation.py` +`convergence/layers/phrase_fragmentation.py` Not a tactic by itself — a *contextual* signal. These legalistic tokens raise a message's "formal share"; a spike above the sender's own baseline flags a register @@ -92,7 +92,7 @@ elevates a finding alone. --- ## L6 · cross-channel divergence (claim vs. admission) -`layers/cross_channel.py` +`convergence/layers/cross_channel.py` Paired fragments: a favorable **claim** in one channel vs. the same sender's contradicting **admission** in another. Aligned by sender *and* predicate. @@ -139,11 +139,11 @@ same "the lawyer says" fragment, now read as the *justify* role. Two layers carry no phrase fragments — their signal is **structure**, which is why they are often the most decisive: -- **L2 · within-thread omission** (`layers/gap_detector.py`) — the building block is +- **L2 · within-thread omission** (`convergence/layers/gap_detector.py`) — the building block is a *gap*: a message cut from inside one continuous thread (its shown neighbors on both sides share its thread). You cannot see a splice by reading the splice; you reconstruct the record and watch which interior pieces are missing. -- **L4 · domain convergence** (`layers/domain_convergence.py`) — the building block +- **L4 · domain convergence** (`convergence/layers/domain_convergence.py`) — the building block is a shared **anchor** (a content word/bigram, ≥4 chars, non-stopword) recurring across ≥2 independent domains. It also maintains an *anti-fragment* `STOPWORDS` list — the connective tissue and generic verbs that must never count as anchors. diff --git a/pyproject.toml b/pyproject.toml index 45891af..f713cb1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ warn_unused_configs = true # warn_return_any, strict_equality. [[tool.mypy.overrides]] -module = ["openai.*", "anthropic.*", "google.*", "pexpect.*", "wexpect.*", "winpty.*"] +module = ["openai.*", "anthropic.*", "google.*", "winpty.*"] ignore_missing_imports = true [tool.coverage.run] From 9da815c35860f202551ce293b2c658b3f3c4ed96 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 15:21:11 +0000 Subject: [PATCH 2/2] docs: document data sets, extra CLI flags, and server limit env vars - README counted "fourteen data files"; data/ also carries the adversarial, holdout, and regression sets the tests use. Say so in the intro and the layout block. - Add the --db-table, --db-limit, --summary, --sender, and --seq flags from demo.py to the Run examples (each example was run to confirm). - .env.example: add CONVERGENCE_MAX_BODY_BYTES and CONVERGENCE_LLM_TIMEOUT_S, both read by web/server.py, with their defaults. - ENGINEERING.md said the SDK is touched "in exactly one file (adapters/)"; adapters/ is a directory with one module per backend. ruff, mypy, pytest (272 passed), and the web data drift check pass. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01U142aAXSzf4pUt89HSvXTH --- .env.example | 4 ++++ ENGINEERING.md | 4 ++-- README.md | 16 +++++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index 96123fd..612d5f4 100644 --- a/.env.example +++ b/.env.example @@ -13,6 +13,10 @@ GEMINI_API_KEY= # --model gemini (API-key path; the gemini CLI path ne CONVERGENCE_HOST=127.0.0.1 # set to 0.0.0.0 only to expose beyond localhost (e.g. in Docker) CONVERGENCE_PORT=8765 +# --- web server limits (web/server.py) --- +CONVERGENCE_MAX_BODY_BYTES=16384 # largest accepted /api/chat request body +CONVERGENCE_LLM_TIMEOUT_S=30 # seconds before a chat backend call is abandoned + # --- web server auth (optional) --- # If set, /api/chat requires a matching `X-API-Key` request header. Leave empty # for localhost dev; set it for any exposed deployment, since /api/chat proxies diff --git a/ENGINEERING.md b/ENGINEERING.md index 4d92558..4041eba 100644 --- a/ENGINEERING.md +++ b/ENGINEERING.md @@ -16,8 +16,8 @@ load-bearing nowhere a decision is made. file — which a probabilistic classifier cannot give. 2. **The model sits at a narrow, grounded seam.** `conversation.py` receives only the *structured findings*, never the raw corpus or the detection logic, via an injected - `complete()`. The model can rephrase or explain a verdict; it cannot move one. The - SDK is touched in exactly one file (`adapters/`). + `complete()`. The model can rephrase or explain a verdict; it cannot move one. Vendor + SDKs are imported only inside `convergence/adapters/`, one module per backend. 3. **Evals are first-class.** 100+ deterministic tests, a five-corpus discrimination eval, doc-sync guards that fail on drift, and a scored report (`demo.py --eval`). "It works" is replaced by numbers. diff --git a/README.md b/README.md index 1102be4..1919e78 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ A six-layer engine for analyzing a corpus of written communications. It does **not** know or care whose messages it is fed. It was *built from* the structure of message records — not *tailored to* any one conversation — so the **same -engine** runs on unrelated corpora with no code changes. (Fourteen data files -across the bundled corpora ship here — contractor, coparenting, channels, -grammar, and five dynamics variants; the only difference between runs is the -data in `data/`.) +engine** runs on unrelated corpora with no code changes. (Fourteen corpus files +ship here — contractor, coparenting, channels, grammar, and five dynamics +variants — alongside adversarial, holdout, and regression sets used by the +tests; the only difference between runs is the data in `data/`.) **Who it's for:** anyone who needs to show *why* a finding holds with an **auditable, deterministic** method instead of a black-box classifier — @@ -194,6 +194,10 @@ convergence --corpus contractor --voice blanc # the Voice of Convergence convergence --corpus grammar # coercion-grammar structural analysis convergence --corpus dynamics # 5-type discrimination table convergence --corpus db --db /path/to/your.db # run on your own SQLite export +convergence --corpus db --db x.db --db-table msgs --db-limit 500 # pick table, cap rows +convergence --summary # compact findings summary +convergence --summary --sender Morgan # ... only findings involving one sender +convergence --seq 12 # one message and the findings that touch it convergence --eval # scored discriminator report convergence --investigate # propose + verify new detectors convergence --chat --voice blanc # conversational Blanc with Claude @@ -271,7 +275,9 @@ convergence/ engine (corpus-agnostic; stdlib only in core) conversation.py grounded Q&A seam (model injected) layers/ one module per analytical layer (L1–L6) adapters/ optional LLM adapters (Claude, OpenAI, Grok, Gemini, agy) -data/ 14 data files: contractor (sample_*), coparenting, channels (formal+casual), grammar, dynamics (dyn_*) +data/ 14 corpus files: contractor (sample_*), coparenting, channels (formal+casual), grammar, dynamics (dyn_*) + adv_*.json adversarial record-assembly cases (tests) + holdout/, regression/ held-out and regression dynamics corpora (tests) tools/ generate_dynamics.py — seeded dynamics-corpus generator tests/ 272 deterministic tests web/ static frontend + local stdlib server