Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ jobs:
with:
go-version: "1.24"
cache: true
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: go vet
run: go vet ./...
- name: staticcheck
run: |
go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck ./...
- name: verify pack checksums
run: python3 scripts/verify_quotes.py
- name: go test
run: go test ./...
85 changes: 85 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# CLAUDE.md

Guidance for Claude (and other coding agents) working in this repo.

## What this project is

`verse-driven` ships a single Go binary (`scripture-mcp`) that serves canonical
scripture passages — KJV Bible, 道德经, 心经, Quran — to coding agents via
local stdio MCP, CLI, and hooks. Read `README.md` and `plan.md` first for the
full picture; `docs/issues-backlog.md` is the work plan.

## Working with scripture text — read this before touching packs

Sacred-text bodies trip Anthropic's output content filter when echoed at
volume in model output. A `400 invalid_request_error: Output blocked by
content filtering policy` will kill the turn mid-tool-call.

**Rule of thumb: scripture text should flow through scripts and files, never
through the model's text output.**

Practical guidance:

- **Don't paste verse text into your responses.** Don't quote passages in
commit messages, PR bodies, code comments, or chat replies. Reference by
citation only (`John 3:16`, `道德经第十一章`).
- **Don't echo verse text via Bash tool output.** Avoid `cat verses.jsonl`,
`head -100 some_pack.txt`, `grep '...' kjv.txt` printed inline. If you need
to inspect data, redirect to a file (`> /tmp/sample.txt`) and then read
only structural metadata (line counts, checksums, first-token, field
names) — not the text itself.
- **Pack builders are write-only.** A build script downloads upstream text
and writes JSONL/JSON directly to `internal/packs/<name>/`. The model never
sees the body. The model only writes the script, runs it, and verifies
byte/verse counts and SHA-256 checksums.
- **Tests assert by checksum, not by content.** `internal/packs/*_test.go`
should look up a known reference (e.g. `bible.kjv.john.3.16`) and compare
its `checksum_sha256` to a hard-coded expected hex digest, not its `Text`.
This keeps test files free of scripture text.
- **Verifier scripts read & hash, never print.** `scripts/verify_quotes.py`
recomputes SHA-256 over each verse's text and compares to the stored
checksum. On mismatch it prints the verse `id` and the two hashes — not
the text.
- **If you must spot-check a verse manually,** do it locally: build the
binary, run `scripture-mcp lookup "<ref>"`, eyeball the terminal. Don't
copy the output back into the conversation.

If you hit the content filter mid-task, stop and refactor: move whatever was
about to be quoted into a file write or a script, then continue.

## Repo layout

```
cmd/scripture-mcp/ main package; CLI entrypoint
internal/schema/ Verse struct + JSON Schema (the contract every pack obeys)
internal/resolver/ free-form reference parser ("John 3:16", "道德经 11", ...)
internal/packs/ embed.FS-backed pack data + registry
bible-kjv/
dao-de-jing/
heart-sutra/
internal/mcp/ stdio MCP server (issue #4)
internal/cli/ CLI subcommands (issue #4)
internal/injector/ inject-once envelope helpers (issues #5/#6)
scripts/ pack builders + verifiers (Python or Go, run at build time)
adapters/ per-agent wiring (claude-code, codex) — issues #5/#6
docs/ issues-backlog.md, benchmarks/
```

## Building & testing

- `make build` → `bin/scripture-mcp`
- `make test` → `go test ./...`
- `make lint` → `go vet` + `staticcheck`
- CI runs all three on push and PR; staticcheck failures block.

## Conventions

- Module path: `github.com/MiaoDX/verse-driven`. Go 1.24.
- Verse IDs are dotted lowercase: `<tradition>.<work>.<book>.<chapter>.<verse>`,
e.g. `bible.kjv.john.3.16`, `dao.daodejing.11.1`, `sutra.heart-sutra.1`.
- Every verse carries a SHA-256 over its `Text` bytes in `checksum_sha256`,
computed at pack-build time. Hashes are the integrity boundary between
upstream sources and the bundled binary.
- Pack metadata (`metadata.json`) lives next to `verses.jsonl` and records
provider, license, attribution, source URL, and build date.
- Branches: feature work goes on `claude-issue-<number>`; one PR per issue.
16 changes: 14 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
.PHONY: build test lint vet staticcheck tidy clean all
.PHONY: build test lint vet staticcheck tidy clean all packs verify-packs

BINARY := scripture-mcp
CMD := ./cmd/scripture-mcp
BUILD_DIR := bin

all: lint test build
all: lint verify-packs test build

# Rebuild the bundled packs from upstream sources (KJV from Project
# Gutenberg, 道德经 from Project Gutenberg). Run after upstream regenerations
# or whenever the JSONL format changes. Requires Python 3.11+ and
# opencc-python-reimplemented for the dao pack.
packs:
python3 scripts/build_packs.py

# Recompute SHA-256 over every bundled verse and compare to the stored
# checksum_sha256. CI runs this as a gate before `go test`.
verify-packs:
python3 scripts/verify_quotes.py

build:
mkdir -p $(BUILD_DIR)
Expand Down
73 changes: 71 additions & 2 deletions cmd/scripture-mcp/main.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,78 @@
// scripture-mcp is the verse-driven binary. The full CLI surface
// (serve / lookup / lookup-from-prompt / recap / init) is implemented
// in issue #4. This entrypoint exposes just enough now to demonstrate
// that the embedded packs from issue #3 are reachable from main.
//
// Usage:
//
// scripture-mcp # prints version and pack summary
// scripture-mcp --packs # prints loaded pack metadata
// scripture-mcp --lookup-id <id> # prints the canonical reference and
// # SHA-256 of one verse (text omitted
// # to keep terminal output filter-safe)
package main

import "fmt"
import (
"flag"
"fmt"
"os"

"github.com/MiaoDX/verse-driven/internal/packs"
)

const Version = "v0.0.0"

func main() {
fmt.Printf("scripture-mcp %s\n", Version)
listPacks := flag.Bool("packs", false, "list loaded packs and exit")
lookupID := flag.String("lookup-id", "", "look up a verse by id and print metadata only")
flag.Parse()

switch {
case *listPacks:
printPacks()
case *lookupID != "":
if err := printLookup(*lookupID); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
default:
fmt.Printf("scripture-mcp %s\n", Version)
fmt.Printf("packs loaded: %d total verses: %d\n",
len(packs.All().Names()), packs.All().TotalVerses())
}
}

func printPacks() {
r := packs.All()
for _, name := range r.Names() {
p := r.Pack(name)
mode := p.Meta.InclusionMode
if mode == "" {
mode = "(unset)"
}
fmt.Printf("%-14s tradition=%-6s work=%-12s lang=%-6s verses=%-6d mode=%s\n",
name, p.Meta.Tradition, p.Meta.Work, p.Meta.Lang, len(p.Verses()), mode)
}
}

func printLookup(id string) error {
v, ok := packs.All().Lookup(id)
if !ok {
return fmt.Errorf("verse not found: %s", id)
}
// Deliberately print only structural fields — never the verse text.
// Callers who need the body should go through the MCP `lookup` tool
// (issue #4), which has explicit user-confirm gating.
fmt.Printf("id: %s\n", v.ID)
fmt.Printf("tradition: %s/%s\n", v.Tradition, v.Work)
fmt.Printf("ref: %s %d:%d", v.CanonicalRef.Book, v.CanonicalRef.Chapter, v.CanonicalRef.VerseStart)
if v.CanonicalRef.VerseEnd != 0 {
fmt.Printf("-%d", v.CanonicalRef.VerseEnd)
}
fmt.Println()
fmt.Printf("lang: %s\n", v.Lang)
fmt.Printf("checksum: %s\n", v.ChecksumSHA256)
fmt.Printf("text_len: %d bytes\n", len(v.Text))
fmt.Printf("source: %s — %s\n", v.Source.Provider, v.Source.License)
return nil
}
82 changes: 82 additions & 0 deletions internal/packs/bible-kjv/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{
"attribution": "King James Version of the Bible, Project Gutenberg eBook #10",
"books": {
"1-chronicles": "1 Chronicles",
"1-corinthians": "1 Corinthians",
"1-john": "1 John",
"1-kings": "1 Kings",
"1-peter": "1 Peter",
"1-samuel": "1 Samuel",
"1-thessalonians": "1 Thessalonians",
"1-timothy": "1 Timothy",
"2-chronicles": "2 Chronicles",
"2-corinthians": "2 Corinthians",
"2-john": "2 John",
"2-kings": "2 Kings",
"2-peter": "2 Peter",
"2-samuel": "2 Samuel",
"2-thessalonians": "2 Thessalonians",
"2-timothy": "2 Timothy",
"3-john": "3 John",
"acts": "Acts",
"amos": "Amos",
"colossians": "Colossians",
"daniel": "Daniel",
"deuteronomy": "Deuteronomy",
"ecclesiastes": "Ecclesiastes",
"ephesians": "Ephesians",
"esther": "Esther",
"exodus": "Exodus",
"ezekiel": "Ezekiel",
"ezra": "Ezra",
"galatians": "Galatians",
"genesis": "Genesis",
"habakkuk": "Habakkuk",
"haggai": "Haggai",
"hebrews": "Hebrews",
"hosea": "Hosea",
"isaiah": "Isaiah",
"james": "James",
"jeremiah": "Jeremiah",
"job": "Job",
"joel": "Joel",
"john": "John",
"jonah": "Jonah",
"joshua": "Joshua",
"jude": "Jude",
"judges": "Judges",
"lamentations": "Lamentations",
"leviticus": "Leviticus",
"luke": "Luke",
"malachi": "Malachi",
"mark": "Mark",
"matthew": "Matthew",
"micah": "Micah",
"nahum": "Nahum",
"nehemiah": "Nehemiah",
"numbers": "Numbers",
"obadiah": "Obadiah",
"philemon": "Philemon",
"philippians": "Philippians",
"proverbs": "Proverbs",
"psalms": "Psalms",
"revelation": "Revelation",
"romans": "Romans",
"ruth": "Ruth",
"song-of-solomon": "Song of Solomon",
"titus": "Titus",
"zechariah": "Zechariah",
"zephaniah": "Zephaniah"
},
"build_date": "2026-05-02",
"edition_id": "pg10-kjv",
"inclusion_mode": "bundled",
"lang": "en",
"license": "Public domain (United States)",
"provider": "Project Gutenberg eBook #10",
"sensitivity": "sacred_exact_quote",
"source_url": "https://www.gutenberg.org/cache/epub/10/pg10.txt",
"tradition": "bible",
"verse_count": 31102,
"work": "KJV"
}
Binary file added internal/packs/bible-kjv/verses.jsonl.gz
Binary file not shown.
15 changes: 15 additions & 0 deletions internal/packs/dao-de-jing/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"attribution": "《道德經》, Project Gutenberg eBook #7337 (produced by Ching-yi Chen). Simplified-Chinese rendering via OpenCC t2s.",
"build_date": "2026-05-02",
"edition_id": "pg7337-laozi-s",
"inclusion_mode": "bundled",
"lang": "zh-Hans",
"license": "Public domain",
"provider": "Project Gutenberg eBook #7337",
"sensitivity": "sacred_exact_quote",
"source_url": "https://www.gutenberg.org/cache/epub/7337/pg7337.txt",
"tradition": "dao",
"transform": "OpenCC t2s (Traditional → Simplified)",
"verse_count": 81,
"work": "daodejing"
}
Binary file added internal/packs/dao-de-jing/verses.jsonl.gz
Binary file not shown.
3 changes: 0 additions & 3 deletions internal/packs/doc.go

This file was deleted.

14 changes: 14 additions & 0 deletions internal/packs/heart-sutra/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"attribution": "《般若波罗蜜多心经》, translated by Xuanzang (玄奘, Tang dynasty, c. 649 CE). Public domain text; CBETA digital edition has its own redistribution terms.",
"build_date": "2026-05-02",
"edition_id": "xuanzang-heart-sutra",
"inclusion_mode": "api_only",
"lang": "zh-Hans",
"license": "See pack release notes",
"note": "Stub pack: text not yet bundled. Issue #3 notes permit api-only fallback while CBETA terms are being reviewed.",
"provider": "CBETA (pending license audit)",
"source_url": "https://cbetaonline.dila.edu.tw/zh/T0251_001",
"tradition": "sutra",
"verse_count": 0,
"work": "heart-sutra"
}
Binary file added internal/packs/heart-sutra/verses.jsonl.gz
Binary file not shown.
Loading
Loading