okf-loremaster 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 (117) hide show
  1. okf_loremaster-0.1.0/.env.example +158 -0
  2. okf_loremaster-0.1.0/.github/workflows/ci.yml +86 -0
  3. okf_loremaster-0.1.0/.github/workflows/release.yml +152 -0
  4. okf_loremaster-0.1.0/.gitignore +53 -0
  5. okf_loremaster-0.1.0/CHANGELOG.md +44 -0
  6. okf_loremaster-0.1.0/LICENSE +201 -0
  7. okf_loremaster-0.1.0/PKG-INFO +799 -0
  8. okf_loremaster-0.1.0/README.md +754 -0
  9. okf_loremaster-0.1.0/assets/okf-loremaster-logo.png +0 -0
  10. okf_loremaster-0.1.0/cspell.json +65 -0
  11. okf_loremaster-0.1.0/pyproject.toml +119 -0
  12. okf_loremaster-0.1.0/scripts/check_dist.py +103 -0
  13. okf_loremaster-0.1.0/scripts/record_fixtures.py +137 -0
  14. okf_loremaster-0.1.0/src/okf_loremaster/__init__.py +9 -0
  15. okf_loremaster-0.1.0/src/okf_loremaster/cli.py +483 -0
  16. okf_loremaster-0.1.0/src/okf_loremaster/clients/__init__.py +162 -0
  17. okf_loremaster-0.1.0/src/okf_loremaster/clients/_http.py +457 -0
  18. okf_loremaster-0.1.0/src/okf_loremaster/clients/bioc.py +141 -0
  19. okf_loremaster-0.1.0/src/okf_loremaster/clients/cassette.py +132 -0
  20. okf_loremaster-0.1.0/src/okf_loremaster/clients/eutils.py +552 -0
  21. okf_loremaster-0.1.0/src/okf_loremaster/clients/icite.py +161 -0
  22. okf_loremaster-0.1.0/src/okf_loremaster/clients/pubtator.py +116 -0
  23. okf_loremaster-0.1.0/src/okf_loremaster/config.py +303 -0
  24. okf_loremaster-0.1.0/src/okf_loremaster/curation.py +270 -0
  25. okf_loremaster-0.1.0/src/okf_loremaster/emitters/__init__.py +35 -0
  26. okf_loremaster-0.1.0/src/okf_loremaster/emitters/okf.py +1243 -0
  27. okf_loremaster-0.1.0/src/okf_loremaster/emitters/vectors.py +700 -0
  28. okf_loremaster-0.1.0/src/okf_loremaster/events.py +149 -0
  29. okf_loremaster-0.1.0/src/okf_loremaster/extraction_cache.py +108 -0
  30. okf_loremaster-0.1.0/src/okf_loremaster/finalize.py +62 -0
  31. okf_loremaster-0.1.0/src/okf_loremaster/graph/__init__.py +7 -0
  32. okf_loremaster-0.1.0/src/okf_loremaster/graph/build.py +446 -0
  33. okf_loremaster-0.1.0/src/okf_loremaster/graph/nodes/__init__.py +39 -0
  34. okf_loremaster-0.1.0/src/okf_loremaster/graph/nodes/charter.py +168 -0
  35. okf_loremaster-0.1.0/src/okf_loremaster/graph/nodes/curate.py +471 -0
  36. okf_loremaster-0.1.0/src/okf_loremaster/graph/nodes/dedupe.py +97 -0
  37. okf_loremaster-0.1.0/src/okf_loremaster/graph/nodes/emit_okf.py +182 -0
  38. okf_loremaster-0.1.0/src/okf_loremaster/graph/nodes/extract.py +270 -0
  39. okf_loremaster-0.1.0/src/okf_loremaster/graph/nodes/fulltext.py +215 -0
  40. okf_loremaster-0.1.0/src/okf_loremaster/graph/nodes/index_vectors.py +82 -0
  41. okf_loremaster-0.1.0/src/okf_loremaster/graph/nodes/rank.py +154 -0
  42. okf_loremaster-0.1.0/src/okf_loremaster/graph/nodes/reconcile.py +272 -0
  43. okf_loremaster-0.1.0/src/okf_loremaster/graph/nodes/review.py +59 -0
  44. okf_loremaster-0.1.0/src/okf_loremaster/graph/nodes/screen.py +222 -0
  45. okf_loremaster-0.1.0/src/okf_loremaster/graph/nodes/search.py +320 -0
  46. okf_loremaster-0.1.0/src/okf_loremaster/graph/nodes/validate.py +65 -0
  47. okf_loremaster-0.1.0/src/okf_loremaster/graph/state.py +255 -0
  48. okf_loremaster-0.1.0/src/okf_loremaster/llm/__init__.py +7 -0
  49. okf_loremaster-0.1.0/src/okf_loremaster/llm/estimate.py +383 -0
  50. okf_loremaster-0.1.0/src/okf_loremaster/llm/fake.py +107 -0
  51. okf_loremaster-0.1.0/src/okf_loremaster/llm/router.py +628 -0
  52. okf_loremaster-0.1.0/src/okf_loremaster/okf/__init__.py +97 -0
  53. okf_loremaster-0.1.0/src/okf_loremaster/okf/frontmatter.py +250 -0
  54. okf_loremaster-0.1.0/src/okf_loremaster/okf/layout.py +177 -0
  55. okf_loremaster-0.1.0/src/okf_loremaster/okf/markdown.py +49 -0
  56. okf_loremaster-0.1.0/src/okf_loremaster/okf/reader.py +347 -0
  57. okf_loremaster-0.1.0/src/okf_loremaster/okf/validate.py +535 -0
  58. okf_loremaster-0.1.0/src/okf_loremaster/prompts.py +456 -0
  59. okf_loremaster-0.1.0/src/okf_loremaster/queries.py +368 -0
  60. okf_loremaster-0.1.0/src/okf_loremaster/ranking.py +419 -0
  61. okf_loremaster-0.1.0/src/okf_loremaster/recurrence.py +401 -0
  62. okf_loremaster-0.1.0/src/okf_loremaster/retention.py +107 -0
  63. okf_loremaster-0.1.0/src/okf_loremaster/review.py +109 -0
  64. okf_loremaster-0.1.0/src/okf_loremaster/run.py +824 -0
  65. okf_loremaster-0.1.0/src/okf_loremaster/schemas/__init__.py +175 -0
  66. okf_loremaster-0.1.0/src/okf_loremaster/schemas/candidates.py +252 -0
  67. okf_loremaster-0.1.0/src/okf_loremaster/schemas/charter.py +246 -0
  68. okf_loremaster-0.1.0/src/okf_loremaster/schemas/common.py +253 -0
  69. okf_loremaster-0.1.0/src/okf_loremaster/schemas/concept.py +462 -0
  70. okf_loremaster-0.1.0/src/okf_loremaster/schemas/evidence.py +126 -0
  71. okf_loremaster-0.1.0/src/okf_loremaster/schemas/limits.py +207 -0
  72. okf_loremaster-0.1.0/src/okf_loremaster/schemas/manifest.py +150 -0
  73. okf_loremaster-0.1.0/src/okf_loremaster/schemas/parse.py +281 -0
  74. okf_loremaster-0.1.0/src/okf_loremaster/schemas/recurrence.py +148 -0
  75. okf_loremaster-0.1.0/src/okf_loremaster/schemas/screening.py +120 -0
  76. okf_loremaster-0.1.0/src/okf_loremaster/schemas/strength.py +52 -0
  77. okf_loremaster-0.1.0/src/okf_loremaster/selftest.py +153 -0
  78. okf_loremaster-0.1.0/src/okf_loremaster/strength.py +323 -0
  79. okf_loremaster-0.1.0/src/okf_loremaster/ui/__init__.py +7 -0
  80. okf_loremaster-0.1.0/src/okf_loremaster/ui/jsonl.py +56 -0
  81. okf_loremaster-0.1.0/src/okf_loremaster/ui/pauses.py +310 -0
  82. okf_loremaster-0.1.0/src/okf_loremaster/ui/plain.py +235 -0
  83. okf_loremaster-0.1.0/src/okf_loremaster/ui/review.py +186 -0
  84. okf_loremaster-0.1.0/src/okf_loremaster/ui/summary.py +186 -0
  85. okf_loremaster-0.1.0/src/okf_loremaster/ui/tui.py +659 -0
  86. okf_loremaster-0.1.0/src/okf_loremaster/verification.py +560 -0
  87. okf_loremaster-0.1.0/tests/conftest.py +134 -0
  88. okf_loremaster-0.1.0/tests/fake_llm.py +268 -0
  89. okf_loremaster-0.1.0/tests/fake_ncbi.py +540 -0
  90. okf_loremaster-0.1.0/tests/fixtures/ncbi.jsonl +8 -0
  91. okf_loremaster-0.1.0/tests/graph_runs.py +249 -0
  92. okf_loremaster-0.1.0/tests/test_afce_contract.py +292 -0
  93. okf_loremaster-0.1.0/tests/test_cassette.py +116 -0
  94. okf_loremaster-0.1.0/tests/test_cli.py +277 -0
  95. okf_loremaster-0.1.0/tests/test_clients.py +448 -0
  96. okf_loremaster-0.1.0/tests/test_config.py +123 -0
  97. okf_loremaster-0.1.0/tests/test_curation.py +325 -0
  98. okf_loremaster-0.1.0/tests/test_domain_agnostic.py +255 -0
  99. okf_loremaster-0.1.0/tests/test_dry_run.py +549 -0
  100. okf_loremaster-0.1.0/tests/test_emit_okf.py +782 -0
  101. okf_loremaster-0.1.0/tests/test_events.py +63 -0
  102. okf_loremaster-0.1.0/tests/test_extraction.py +709 -0
  103. okf_loremaster-0.1.0/tests/test_http.py +396 -0
  104. okf_loremaster-0.1.0/tests/test_okf_validate.py +375 -0
  105. okf_loremaster-0.1.0/tests/test_queries.py +210 -0
  106. okf_loremaster-0.1.0/tests/test_ranking.py +301 -0
  107. okf_loremaster-0.1.0/tests/test_recurrence.py +445 -0
  108. okf_loremaster-0.1.0/tests/test_resume.py +510 -0
  109. okf_loremaster-0.1.0/tests/test_retention.py +140 -0
  110. okf_loremaster-0.1.0/tests/test_router_cost.py +490 -0
  111. okf_loremaster-0.1.0/tests/test_schemas.py +949 -0
  112. okf_loremaster-0.1.0/tests/test_screening.py +495 -0
  113. okf_loremaster-0.1.0/tests/test_strength.py +396 -0
  114. okf_loremaster-0.1.0/tests/test_tui.py +611 -0
  115. okf_loremaster-0.1.0/tests/test_ui_plain.py +162 -0
  116. okf_loremaster-0.1.0/tests/test_vectors.py +512 -0
  117. okf_loremaster-0.1.0/tests/test_verification.py +581 -0
@@ -0,0 +1,158 @@
1
+ # OKF Loremaster — copy to .env and fill in. Never put real values in this file.
2
+ # Every variable is read through okf_loremaster.config; nothing is read from os.environ directly.
3
+
4
+ # ---------------------------------------------------------------------------
5
+ # LLM routing (required)
6
+ # ---------------------------------------------------------------------------
7
+ # Model strings are passed verbatim to LiteLLM, so any LiteLLM-supported
8
+ # provider works. Three roles; bind them to whatever tier you like.
9
+ # FAST cheapest and quickest; screening, one abstract at a time
10
+ # BALANCED mid-priced; query planning, curation, and extraction
11
+ # REASONING most capable and most expensive; the charter, and nothing else
12
+ #
13
+ # Extraction sits on BALANCED because it is the only node that makes a call per
14
+ # paper — two hundred of them against every other node's handful — so whatever it
15
+ # is bound to sets the price of a run. What guards the output there is code, not
16
+ # model tier: every number is checked against the source afterward and every quote
17
+ # is sliced out of it. Bind REASONING to something cheaper and you will barely
18
+ # notice; bind BALANCED to something expensive and you will.
19
+ OKF_LOREMASTER_MODEL_FAST=claude-haiku-4-5
20
+ OKF_LOREMASTER_MODEL_BALANCED=claude-sonnet-5
21
+ OKF_LOREMASTER_MODEL_REASONING=claude-opus-5
22
+
23
+ ANTHROPIC_API_KEY=
24
+ # Optional: gateway or Azure-style base URL, e.g.
25
+ # ANTHROPIC_BASE_URL=https://<resource>.services.ai.azure.com/anthropic/
26
+ ANTHROPIC_BASE_URL=
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Cost accounting
30
+ # ---------------------------------------------------------------------------
31
+ # LiteLLM's completion_cost() returns 0.0 for models it does not know, which is
32
+ # indistinguishable from a free call. Models reached through a gateway or an
33
+ # Azure deployment name are commonly absent from its price map. Set these to get
34
+ # real USD figures; leave them unset and unpriced calls are reported as tokens
35
+ # with "cost unavailable" — never $0.00.
36
+ # Units: USD per 1M tokens.
37
+ # OKF_LOREMASTER_PRICE_FAST_IN=
38
+ # OKF_LOREMASTER_PRICE_FAST_OUT=
39
+ # OKF_LOREMASTER_PRICE_BALANCED_IN=
40
+ # OKF_LOREMASTER_PRICE_BALANCED_OUT=
41
+ # OKF_LOREMASTER_PRICE_REASONING_IN=
42
+ # OKF_LOREMASTER_PRICE_REASONING_OUT=
43
+
44
+ # Soft budget in USD. The run warns and pauses at this threshold; it does not abort.
45
+ # OKF_LOREMASTER_MAX_USD=5.00
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # Model concurrency and retries
49
+ # ---------------------------------------------------------------------------
50
+ # How many calls per tier may be in flight at once. Screening submits every pooled
51
+ # paper at once and these are what meter it, so FAST is the one that matters. The
52
+ # binding limit is usually tokens per minute rather than requests: if a run reports
53
+ # screening calls failing with RateLimitError, lower FAST before anything else.
54
+ # OKF_LOREMASTER_CONCURRENCY_FAST=4
55
+ # BALANCED is what sets a run's wall-clock: extraction is one call per kept paper, so a
56
+ # 200-paper bundle makes 200 of them. At 2 that is hours. Lower it only if extraction
57
+ # starts reporting RateLimitError.
58
+ # OKF_LOREMASTER_CONCURRENCY_BALANCED=3
59
+ # OKF_LOREMASTER_CONCURRENCY_REASONING=3
60
+
61
+ # Attempts per call, not retries on top of the first — warnings count to one less.
62
+ # A rate limit clears on a 60-second window, so this has to be enough to outlast one.
63
+ # OKF_LOREMASTER_MAX_RETRIES=6
64
+
65
+ # Seconds before a single model call is abandoned. An extraction reads 6,000 tokens of
66
+ # source and writes several thousand back, which is minutes rather than seconds; set too
67
+ # low, the call times out on its own success and the paper is lost.
68
+ # OKF_LOREMASTER_REQUEST_TIMEOUT=300
69
+
70
+ # ---------------------------------------------------------------------------
71
+ # NCBI E-utilities
72
+ # ---------------------------------------------------------------------------
73
+ # Optional but strongly recommended: raises the rate limit from 3 to 10 req/s.
74
+ # Free from https://account.ncbi.nlm.nih.gov/settings/
75
+ # We run at 2.5 or 8 req/s respectively — under the ceiling on purpose, because
76
+ # the limit is enforced per IP and a shared address may already be carrying
77
+ # traffic we cannot see. E-utilities, BioC and PubTator share one budget.
78
+ OKF_LOREMASTER_NCBI_API_KEY=
79
+ # Required. NCBI asks for a contact address on every request and throttles
80
+ # traffic that omits it. A build refuses to start without it.
81
+ OKF_LOREMASTER_NCBI_EMAIL=
82
+ OKF_LOREMASTER_NCBI_TOOL=okf-loremaster
83
+
84
+ # ---------------------------------------------------------------------------
85
+ # HTTP
86
+ # ---------------------------------------------------------------------------
87
+ # OKF_LOREMASTER_HTTP_TIMEOUT=30
88
+ # OKF_LOREMASTER_HTTP_MAX_RETRIES=4
89
+ # Responses are cached on disk, keyed by the request with credentials stripped,
90
+ # so rotating an API key does not orphan the cache. Bibliographic records are
91
+ # effectively immutable, hence the long default.
92
+ # OKF_LOREMASTER_HTTP_CACHE_ENABLED=true
93
+ # OKF_LOREMASTER_HTTP_CACHE_TTL_DAYS=30
94
+ #
95
+ # A CA bundle to verify TLS against, instead of the default trust store. Needed on
96
+ # a network whose proxy terminates TLS and presents its own certificate: the run
97
+ # then reports a certificate failure against a host that is perfectly healthy, and
98
+ # it can hit some hosts and not others depending on what the proxy allowlists.
99
+ # Ask your IT group for the organization's root CA in PEM form. There is
100
+ # deliberately no option to skip verification — the provenance a bundle claims
101
+ # rests on the bytes having come from who they say they came from.
102
+ # OKF_LOREMASTER_CA_BUNDLE=/path/to/corporate-ca.pem
103
+
104
+ # ---------------------------------------------------------------------------
105
+ # Embeddings (only used by the optional vector index)
106
+ # ---------------------------------------------------------------------------
107
+ # Must be a locally-runnable model: downstream consumers reject remote embedders
108
+ # on attach. Pinned by revision so a rebuild reproduces the same vectors.
109
+ # OKF_LOREMASTER_EMBED_MODEL=pritamdeka/S-PubMedBert-MS-MARCO
110
+ # OKF_LOREMASTER_EMBED_REVISION=
111
+
112
+ # Shared Hugging Face cache, so the embedding model downloads once per machine
113
+ # rather than once per environment. Use an absolute path — a literal ~ is not
114
+ # expanded here. Keep it OUTSIDE OneDrive: the hub cache symlinks snapshots/
115
+ # into blobs/, which sync clients mangle.
116
+ HF_HOME=/Users/<you>/.cache/huggingface
117
+
118
+ # ---------------------------------------------------------------------------
119
+ # Review
120
+ # ---------------------------------------------------------------------------
121
+ # Who a `--review` sign-off is attributed to. Recorded in every document as
122
+ # verified: [{by: "human:<id>", ...}], which is what lifts the bundle above the
123
+ # unverified trust tier — so it has to name someone who can be asked about it.
124
+ # Unset falls back to the OS login name; set it when that is a service account
125
+ # or a shared box.
126
+ # OKF_LOREMASTER_REVIEWER_ID=
127
+
128
+ # ---------------------------------------------------------------------------
129
+ # Paths
130
+ # ---------------------------------------------------------------------------
131
+ # HTTP response cache and run checkpoints. Defaults to a platform cache dir.
132
+ # OKF_LOREMASTER_CACHE_DIR=
133
+ # Where runs are written. Defaults to ./bundles relative to the working dir.
134
+ # Every run lands here: `-o <name>` takes a name, not a path, and it is resolved
135
+ # against this. An absolute -o overrides it.
136
+ # OKF_LOREMASTER_OUTPUT_DIR=
137
+ #
138
+ # How many past runs keep their checkpoints. A build writes 100 to 350 MB of them
139
+ # — the whole state is saved once per node, and by the later ones it holds full
140
+ # texts and extractions — so without this the cache directory grows forever. Each
141
+ # build drops all but this many, newest first; 0 keeps everything. Only affects
142
+ # what `--resume` can still pick up. Bundles are never touched.
143
+ # OKF_LOREMASTER_CHECKPOINT_KEEP_RUNS=5
144
+ #
145
+ # Ceilings in MB on the three things that accumulate. Applied at both ends of a
146
+ # build, oldest entries first, so between builds each store is under its ceiling and
147
+ # only a run in flight is over. Nothing is reclaimed unless you build. 0 turns one
148
+ # off. `okf-loremaster runs` prints each size against its cap.
149
+ #
150
+ # Checkpoints are scratch, and the count above is meant to be what binds: five runs
151
+ # measure around 900 MB, so this is set clear of that rather than quietly cutting how
152
+ # many are resumable. The other two are not scratch — they are keyed by the request
153
+ # and shared by every bundle, which is what makes a rebuild fast and re-reading a
154
+ # paper free — so they sit where an ordinary year of use will not reach them. Raise
155
+ # the last one before the others if you rerun often.
156
+ # OKF_LOREMASTER_CHECKPOINT_MAX_MB=2048
157
+ # OKF_LOREMASTER_HTTP_CACHE_MAX_MB=1024
158
+ # OKF_LOREMASTER_EXTRACTION_CACHE_MAX_MB=512
@@ -0,0 +1,86 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ concurrency:
10
+ group: ci-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ python-version: ["3.11", "3.12"]
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+
26
+ - uses: actions/setup-python@v5
27
+ with:
28
+ python-version: ${{ matrix.python-version }}
29
+ cache: pip
30
+ cache-dependency-path: pyproject.toml
31
+
32
+ # `[all]` rather than the base install: the Chroma and Textual tests
33
+ # `importorskip` their dependency, so a lean install would report them as passing
34
+ # when nothing ran. The torch download is the price of actually covering them.
35
+ - name: Install
36
+ run: pip install -e ".[all,dev]"
37
+
38
+ # `ruff check` only. The project does not use `ruff format` and is not formatted to
39
+ # it — adding the check here would fail on 48 files that are deliberately hand-set.
40
+ - name: Lint
41
+ run: ruff check src/ tests/
42
+
43
+ # Only on the version mypy is configured for. `python_version = "3.11"` in
44
+ # `pyproject.toml` is the point of the check — the floor is what has to typecheck —
45
+ # but under 3.12 mypy reads that interpreter's numpy stubs, which use `type`
46
+ # statements, and rejects 3.12 syntax it was told to treat as 3.11.
47
+ - name: Types
48
+ if: matrix.python-version == '3.11'
49
+ run: mypy src/
50
+
51
+ # No network reachable from the suite by design — conftest blocks the httpx
52
+ # transports outright, so a test that starts calling out fails rather than
53
+ # depending on NCBI being up.
54
+ - name: Tests
55
+ run: pytest -q
56
+
57
+ package:
58
+ name: Build and inspect the distributions
59
+ runs-on: ubuntu-latest
60
+ steps:
61
+ # The whole history and all tags: `check_dist.py` compares the sdist against
62
+ # `git ls-files`, which needs a real repository rather than an export.
63
+ - uses: actions/checkout@v4
64
+
65
+ - uses: actions/setup-python@v5
66
+ with:
67
+ python-version: "3.11"
68
+
69
+ - name: Install build tooling
70
+ run: pip install build twine
71
+
72
+ - name: Build
73
+ run: python -m build
74
+
75
+ # Fails if the sdist contains a path git does not track, if any `.env` appears,
76
+ # or if the packaged `.env` template is missing from the wheel.
77
+ - name: Nothing untracked ships
78
+ run: python scripts/check_dist.py dist/
79
+
80
+ - name: Metadata renders on PyPI
81
+ run: twine check --strict dist/*
82
+
83
+ - uses: actions/upload-artifact@v4
84
+ with:
85
+ name: distributions
86
+ path: dist/
@@ -0,0 +1,152 @@
1
+ # Publishing is driven by the tag, and the tag alone decides where it lands:
2
+ #
3
+ # v0.1.0rc1, v0.1.0a2, v0.1.0b1 → TestPyPI (rehearsal; the page can be looked at)
4
+ # v0.1.0 → PyPI (permanent; a version is never reusable)
5
+ #
6
+ # Uploads use PyPI Trusted Publishing (OpenID Connect), so there is no API token in the
7
+ # repository to leak or rotate. Each index needs a publisher registered once, against this
8
+ # repository, this workflow filename, and the environment named below.
9
+ name: Release
10
+
11
+ on:
12
+ push:
13
+ tags: ["v*"]
14
+ workflow_dispatch:
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ build:
21
+ runs-on: ubuntu-latest
22
+ outputs:
23
+ prerelease: ${{ steps.classify.outputs.prerelease }}
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: "3.11"
30
+
31
+ - name: Install build tooling
32
+ run: pip install build twine
33
+
34
+ # The version is read from `src/okf_loremaster/__init__.py`, so a tag that disagrees
35
+ # with it would publish something named differently from what the CLI reports.
36
+ - name: Tag matches the package version
37
+ if: startsWith(github.ref, 'refs/tags/')
38
+ run: |
39
+ tag="${GITHUB_REF_NAME#v}"
40
+ pkg="$(python -c 'import re,pathlib; print(re.search(r"__version__ = \"([^\"]+)\"", pathlib.Path("src/okf_loremaster/__init__.py").read_text()).group(1))')"
41
+ if [ "$tag" != "$pkg" ]; then
42
+ echo "tag $GITHUB_REF_NAME says $tag, __version__ says $pkg — retag or bump" >&2
43
+ exit 1
44
+ fi
45
+ echo "both say $pkg"
46
+
47
+ - name: Classify the tag
48
+ id: classify
49
+ run: |
50
+ if printf '%s' "${GITHUB_REF_NAME}" | grep -qE '(a|b|rc)[0-9]+$'; then
51
+ echo "prerelease=true" >> "$GITHUB_OUTPUT"
52
+ echo "${GITHUB_REF_NAME} is a pre-release — TestPyPI"
53
+ else
54
+ echo "prerelease=false" >> "$GITHUB_OUTPUT"
55
+ echo "${GITHUB_REF_NAME} is a final release — PyPI"
56
+ fi
57
+
58
+ - name: Build
59
+ run: python -m build
60
+
61
+ - name: Nothing untracked ships
62
+ run: python scripts/check_dist.py dist/
63
+
64
+ - name: Metadata renders on PyPI
65
+ run: twine check --strict dist/*
66
+
67
+ - uses: actions/upload-artifact@v4
68
+ with:
69
+ name: distributions
70
+ path: dist/
71
+
72
+ # Installs the built wheel in a clean environment with no checkout on the path, which is
73
+ # the only way to catch a file that the repository has and the wheel does not. `init` is
74
+ # the command that fails that way: it needs the packaged .env template.
75
+ smoke-test:
76
+ needs: build
77
+ runs-on: ubuntu-latest
78
+ strategy:
79
+ matrix:
80
+ python-version: ["3.11", "3.12"]
81
+ steps:
82
+ - uses: actions/setup-python@v5
83
+ with:
84
+ python-version: ${{ matrix.python-version }}
85
+
86
+ - uses: actions/download-artifact@v4
87
+ with:
88
+ name: distributions
89
+ path: dist/
90
+
91
+ - name: Install the wheel alone
92
+ run: pip install dist/*.whl
93
+
94
+ - name: It runs, and writes a template from somewhere other than a checkout
95
+ run: |
96
+ mkdir -p /tmp/elsewhere && cd /tmp/elsewhere
97
+ okf-loremaster --version
98
+ okf-loremaster --help > /dev/null
99
+ okf-loremaster init || true
100
+ test -s .env || { echo "init wrote no .env — the packaged template is missing" >&2; exit 1; }
101
+ grep -q '^ANTHROPIC_API_KEY=$' .env || { echo ".env template is not blank" >&2; exit 1; }
102
+ echo "init wrote a blank .env from the packaged template"
103
+
104
+ testpypi:
105
+ needs: [build, smoke-test]
106
+ if: needs.build.outputs.prerelease == 'true'
107
+ runs-on: ubuntu-latest
108
+ environment:
109
+ name: testpypi
110
+ url: https://test.pypi.org/p/okf-loremaster
111
+ permissions:
112
+ id-token: write
113
+ steps:
114
+ - uses: actions/download-artifact@v4
115
+ with:
116
+ name: distributions
117
+ path: dist/
118
+ - uses: pypa/gh-action-pypi-publish@release/v1
119
+ with:
120
+ repository-url: https://test.pypi.org/legacy/
121
+
122
+ pypi:
123
+ needs: [build, smoke-test]
124
+ if: needs.build.outputs.prerelease == 'false' && startsWith(github.ref, 'refs/tags/')
125
+ runs-on: ubuntu-latest
126
+ environment:
127
+ name: pypi
128
+ url: https://pypi.org/p/okf-loremaster
129
+ permissions:
130
+ id-token: write
131
+ steps:
132
+ - uses: actions/download-artifact@v4
133
+ with:
134
+ name: distributions
135
+ path: dist/
136
+ - uses: pypa/gh-action-pypi-publish@release/v1
137
+
138
+ # Last, so a GitHub release only ever describes something that actually published.
139
+ github-release:
140
+ needs: pypi
141
+ runs-on: ubuntu-latest
142
+ permissions:
143
+ contents: write
144
+ steps:
145
+ - uses: actions/download-artifact@v4
146
+ with:
147
+ name: distributions
148
+ path: dist/
149
+ - uses: softprops/action-gh-release@v2
150
+ with:
151
+ files: dist/*
152
+ generate_release_notes: true
@@ -0,0 +1,53 @@
1
+ # Secrets. `.env.local` and `.env.production` matched neither of the first two
2
+ # patterns — `.env` is exact and `*.env` needs something before the dot — so the third
3
+ # is the one that closes it. The exception has to come last to survive them all.
4
+ .env
5
+ *.env
6
+ .env.*
7
+ !.env.example
8
+
9
+ __pycache__/
10
+ *.py[cod]
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ .pytest_cache/
14
+ *.egg-info/
15
+ build/
16
+ dist/
17
+ .coverage
18
+ htmlcov/
19
+
20
+ # Build outputs: bundles are regenerable and large.
21
+ bundles/
22
+ *.chroma/
23
+ .okf-loremaster-cache/
24
+
25
+ # Run artifacts. Checkpoints normally live under the cache directory, outside the
26
+ # repo — these catch a cache dir pointed back inside it.
27
+ *.sqlite
28
+ *.sqlite-journal
29
+
30
+ # A charter is one run's input, not the package's. Anchored to the root so a
31
+ # fixture or example charter under tests/ still ships.
32
+ /charter.yaml
33
+ /*.charter.yaml
34
+
35
+ # Internal working notes: the agent operating manual and the datestamped build log.
36
+ # Kept local — they are about how this was built, not about how it is used.
37
+ /CLAUDE.md
38
+ /Build_Progress.md
39
+
40
+ # Agent tooling state: local permissions, session files, transcripts. Machine- and
41
+ # operator-specific, and none of it describes the package.
42
+ .claude/
43
+
44
+ # A virtualenv inside the repo is against policy — the conda env is kept outside so it
45
+ # does not bloat cloud sync — but a stray one committed is thousands of files, so it is
46
+ # ignored rather than trusted not to appear.
47
+ .venv/
48
+ venv/
49
+
50
+ .DS_Store
51
+ .vscode/
52
+ .idea/
53
+ .ipynb_checkpoints/
@@ -0,0 +1,44 @@
1
+ # Changelog
2
+
3
+ Notable changes to OKF Loremaster. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
4
+ versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+
6
+ Version 0.x means the OKF bundle layout and the CLI may still change between minor releases.
7
+ The bundle contract downstream reads is the part to watch — it is called out here when it moves.
8
+
9
+ ## [Unreleased]
10
+
11
+ ## [0.1.0] — 2026-08-14
12
+
13
+ First public release.
14
+
15
+ ### Added
16
+
17
+ - Build a task-scoped biomedical literature bundle from PubMed/PMC in Open Knowledge Format
18
+ v0.2, from a plain-language research question: `okf-loremaster build "..."`.
19
+ - A thirteen-node graph — `charter → search → dedupe → rank → screen → curate → fulltext →
20
+ extract → reconcile → review → emit_okf → validate → index_vectors`. Five agents make the
21
+ judgment calls; HTTP, dedup, ranking, MMR, license logic, validation and indexing are code.
22
+ - Per-paper markdown carrying predictor rows with effect sizes, operationalization, timing,
23
+ evidence type, null findings and vocabulary hints, plus a `predictors.md` index that points
24
+ into them and a `search.md` that reproduces the search.
25
+ - Deterministic numeric verification: an effect the source text does not contain becomes
26
+ `effect=None` with a downgraded confidence and a logged warning, and the run continues.
27
+ - A computed `strength` per row from study design, adjustment and sample-size scale — never
28
+ asked of the model.
29
+ - Optional Chroma vector index over the finished bundle (`[vectors]`), built by walking what
30
+ was emitted rather than by a second extraction pass.
31
+ - Resume from a checkpoint (`--resume`), a `runs` listing, a Textual TUI (`[tui]`), JSONL
32
+ event output, and cost reporting that says "cost unavailable" rather than `$0.00` for a
33
+ model LiteLLM cannot price.
34
+ - `okf-loremaster init` writes an annotated `.env` and reports what is still unset.
35
+
36
+ ### Notes
37
+
38
+ - Python 3.11 and 3.12.
39
+ - NCBI access is E-utilities, BioC, PubTator and iCite only; no page is ever scraped.
40
+ - Apache-2.0 covers this code, not the bundles it builds. Each emitted document records the
41
+ `license` its publisher reported, and `export_safe` says whether it may leave.
42
+
43
+ [Unreleased]: https://github.com/PFreda-Lab/okf-loremaster/compare/v0.1.0...HEAD
44
+ [0.1.0]: https://github.com/PFreda-Lab/okf-loremaster/releases/tag/v0.1.0