/new-source skill — contract¶
Project-local Claude Code skill at .claude/skills/new-source/SKILL.md. Invoked with /new-source <name> [landscape-id] to onboard a new data source.
Why this exists¶
Adding a data source is OGUR's most-repeated workstream — roughly one in three recent merged PRs (wires #130, cninfo #124/#132, HKEX #120/#131, CDE #138, SEC variants #84/#118/#128, patent backends #119/#154). The mechanical recipe already lives in CONTRIBUTING — Adding a new data source. What a checklist can't capture is the part that actually burns time:
- Feasibility is unknown until you probe. The same four
sirna_plan_*.mddocs were hand-written, each re-deriving "does this source have an API, what's the auth, does it even recover anything new." CDE (#138) is the canonical example — its direct API is Ruishu-WAF-blocked, and only a Phase-0 probe surfaced that before integration code existed. A naive build would have shipped a source that 403s. - The same gotchas recur every time.
compute_hashas the dedup key,event_datepopulation (#90/#98/#103), thesource_filtersopt-in with the non-dict guard (#138), hermetic tests (#121), and the "feed the corpus, never fork the matcher" boundary (the CDE/CJK split). Each new source has re-learned at least one of these the hard way.
/new-source converts the recipe-you-must-remember into a procedure with a hard feasibility gate and the gotchas baked in.
The contract — two phases, one gate¶
| Phase | Output | Gate |
|---|---|---|
| 0 — Probe | Throwaway probe_<name>.py + a docs/development/<name>_plan.md in the workstream template. Answers: programmatic access (REST/GraphQL/OAuth/RSS/WAF), auth, rate limit + paging, relevance (does it recover corpus-missing assets?), signal shape. |
Ends in an explicit GO or NO-GO/REFRAME verdict, surfaced to the user before any ogur/sources/ code. |
| 1 — Scaffold | ogur/sources/<name>.py + hermetic tests/unit/sources/test_<name>.py + seed registration + source_filters opt-in + docs/data-sources.md entry. |
Pass criterion is recovery of the named assets (via inspect_sirna_seed.py), not a green suite. make lint && make test-fast must also pass. |
Baked-in invariants¶
| Invariant | Why | Enforced where |
|---|---|---|
Source.compute_hash(name, source_id, signal_type) is the only hashing scheme |
DB-level dedup unique constraint depends on it | ogur/sources/base.py:28 |
event_date populated from the real event timestamp |
Temporal layer (ordering, recency rendering) reads it | detector + frontend (#90/#98/#103) |
source_filters.<name> opt-in, default off, with a non-dict guard |
Existing indication landscapes must stay unchanged; a "yes" value must not crash |
ADR-0005; #138 Codex fix |
| Never fork the matcher — emit raw provenance (incl. raw CJK), normalize via config not code | Keeps sources additive; matcher changes are a separate reviewed concern | CDE/CJK boundary, PR #138/#147 |
Hermetic tests (httpx_mock + make_landscape, no live ogur.db) |
Source suite must be deterministic in CI | #121 |
Scope¶
- In scope:
ogur/sources/<name>.py, its test, seed registration, thesource_filtersblock, the data-sources.md entry, and (only if a genuinely new domain) aDomainAgent. - Out of scope: editing matchers/aliases for recall, fixing an existing source, building the landscape to point at, anything the source can't actually deliver (if Phase 0 is no-go, the no-go is the deliverable).
How it differs from the CONTRIBUTING recipe¶
| CONTRIBUTING recipe | /new-source skill |
|
|---|---|---|
| Feasibility | Assumed — you've already decided to build | A Phase-0 probe that can return no-go before integration |
| Relevance | Not addressed | A coverage test against the landscape benchmark is the gate |
| Gotchas | Listed as steps to remember | Baked into the scaffold with the failing-PR references |
| Verification | "Register in the seed script" | "The named assets now carry a <name> signal" — recovery, not just green |
The recipe is the reference; the skill is the executable procedure that won't skip the gate.
When to invoke¶
- At the start of any "we should pull in source X" idea — invoke for Phase 0 to get a go/no-go cheaply, before committing a branch.
- When a closed-world benchmark (e.g. the siRNA deck) has named gaps and you're hunting for a source to close them.
Future versions¶
- v2 — fold in a
DomainAgentdecision helper (most sources reusecompany; the skill currently flags this but doesn't decide it). - v2 — emit the coverage before/after diff as a committed artifact under
archived_data/, the way the eval harnesses do, so each onboarding leaves an audit trail. - Pairs naturally with a future
coverage-evalskill (the second-ranked candidate from the skills audit):/new-sourceproves recovery once;coverage-evalre-measures it on a cadence.
Implementing rule of thumb¶
When unsure whether to start writing the Source subclass: write the probe first. If the probe can't pull a single relevant record the corpus is missing, the source isn't worth building yet — and you've spent 30 minutes, not a PR.