From 2fede46dcded5bb031047bb987c059e0f57a3b69 Mon Sep 17 00:00:00 2001 From: Andrey Oblivantsev Date: Thu, 13 Aug 2026 21:20:28 +0100 Subject: [PATCH] feat: facts audit/extract/crm shebang wrappers (D14) (#19) Python stays the implementation. Commands are bin/facts/{audit,extract,crm}.go like postgres/query.go. --- AGENTS.md | 4 ++-- PLAN.md | 5 ++--- README.md | 6 +++--- bin/facts/audit.go | 21 +++++++++++++++++++++ bin/facts/crm.go | 20 ++++++++++++++++++++ bin/facts/extract.go | 20 ++++++++++++++++++++ bin/tools/test_bin_layout.py | 7 +++++++ 7 files changed, 75 insertions(+), 8 deletions(-) create mode 100755 bin/facts/audit.go create mode 100755 bin/facts/crm.go create mode 100755 bin/facts/extract.go diff --git a/AGENTS.md b/AGENTS.md index 8bfa9f3..7401bae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -77,8 +77,8 @@ bin/brain/index.go --rebuild # rebuil ## Tools ```bash -bin/facts/audit ["self"|"facts"|"info"|"stale"] # 2-source + staleness gate -bin/facts/crm [--dry-run] # proof person↔company/company↔project (ooCRM × corpus SoT) +bin/facts/audit.go ["self"|"facts"|"info"|"stale"] # 2-source + staleness gate +bin/facts/crm.go [--dry-run] # proof person↔company/company↔project (ooCRM × corpus SoT) bin/kb/search "query" [--repo X] # deprecated wrapper → bin/brain/search.go bin/brain/search.go "query" [--root facts|info] # deduction search → YAML bin/brain/search.go "query" --no-web # local graph only diff --git a/PLAN.md b/PLAN.md index e1c8305..ff9a089 100644 --- a/PLAN.md +++ b/PLAN.md @@ -52,8 +52,7 @@ detective method: **a fact needs ≥2 independent sources or it is docs/ published docs (this conversation → docs/ as md) skills/ in-project skills (web-search, db-yaml, brain, diataxis-docs) bin/ - facts/extract auto-pair 2 sources → lexicon yaml + graph - facts/audit ["self"|"facts"|"info"|"stale"] 2-source + staleness gate + facts/extract.go audit.go crm.go # D14 shebang; Python implementation kb/index Python write path (called by bin/brain/index.go) brain/index.go rebuild FTS + HNSW (incl. --with-mail) brain/get.go stats.go eval.go # Go read (cgo); Python bin/kb/* CI fallback @@ -133,7 +132,7 @@ Common props on every node/edge: `root`, `confidence`, `evidence[]`, `how`, 1. go vet + go test ./... (root module; packages without ladybug cgo) 2. `go test ./internal/brain/rank` (cgo-free ranking + flag parser) 3. python -m unittest discover -s bin/tools (includes published-docs SoT) -4. bin/facts/audit self (lexicon internal consistency) +4. `bin/facts/audit self` (lexicon internal consistency; `bin/facts/audit.go` is the D14 wrapper) 5. `bin/kb/eval` (recall@5 ≥ 0.95). Local SoT is `bin/brain/eval.go`; CI uses the Python twin until the runner has ladybug cgo. Questions live in `internal/brain/rank`. diff --git a/README.md b/README.md index 6515ea3..c473c75 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ graph TB end subgraph dph["2dph tools"] - EX["bin/facts/extract
2-source pairing"] - AU["bin/facts/audit
confidence + staleness"] + EX["bin/facts/extract.go
2-source pairing"] + AU["bin/facts/audit.go
confidence + staleness"] IDX["bin/brain/index.go
chunk + embed"] MD["bin/markdown/import.go
mistune leaves"] SR["bin/brain/search.go
deduction"] @@ -148,7 +148,7 @@ machines. Tests gate every commit. HTTP: `bin/brain/serve.go` calls ```bash uv venv .venv # Python 3.12, uv-managed uv pip install -r requirements.lock.txt # pinned toolchain -bin/facts/audit self # lexicon consistency gate +bin/facts/audit.go self # lexicon consistency gate go test ./... && python -m unittest discover -s bin/tools -t . ``` diff --git a/bin/facts/audit.go b/bin/facts/audit.go new file mode 100755 index 0000000..e36756f --- /dev/null +++ b/bin/facts/audit.go @@ -0,0 +1,21 @@ +//usr/bin/env go run -tags=facts_audit "$0" "$@"; exit +//go:build facts_audit +// +// bin/facts/audit.go - 2-source + lexicon checks. +// +// ./bin/facts/audit.go self +// ./bin/facts/audit.go db +// +// Python bin/facts/audit is the implementation (CI runs it directly). +// NOTE: never run `gofmt -w` on this file — it breaks the shebang. +package main + +import ( + "os" + + "github.com/eSlider/2dph/internal/cmdbin" +) + +func main() { + os.Exit(cmdbin.ExecFile("bin/facts/audit", os.Args[1:])) +} diff --git a/bin/facts/crm.go b/bin/facts/crm.go new file mode 100755 index 0000000..72e0e5a --- /dev/null +++ b/bin/facts/crm.go @@ -0,0 +1,20 @@ +//usr/bin/env go run -tags=facts_crm "$0" "$@"; exit +//go:build facts_crm +// +// bin/facts/crm.go - prove person↔company / company↔project (ooCRM × corpus). +// +// ./bin/facts/crm.go [--dry-run] [--mismatches] +// +// Python bin/facts/crm is the implementation. Graph write stays Python. +// NOTE: never run `gofmt -w` on this file — it breaks the shebang. +package main + +import ( + "os" + + "github.com/eSlider/2dph/internal/cmdbin" +) + +func main() { + os.Exit(cmdbin.ExecFile("bin/facts/crm", os.Args[1:])) +} diff --git a/bin/facts/extract.go b/bin/facts/extract.go new file mode 100755 index 0000000..a9d8635 --- /dev/null +++ b/bin/facts/extract.go @@ -0,0 +1,20 @@ +//usr/bin/env go run -tags=facts_extract "$0" "$@"; exit +//go:build facts_extract +// +// bin/facts/extract.go - acquire confirmed facts (2-source each). +// +// ./bin/facts/extract.go [--json] [--dry-run] +// +// Python bin/facts/extract is the implementation. Graph write stays Python. +// NOTE: never run `gofmt -w` on this file — it breaks the shebang. +package main + +import ( + "os" + + "github.com/eSlider/2dph/internal/cmdbin" +) + +func main() { + os.Exit(cmdbin.ExecFile("bin/facts/extract", os.Args[1:])) +} diff --git a/bin/tools/test_bin_layout.py b/bin/tools/test_bin_layout.py index 81ae569..197b22f 100644 --- a/bin/tools/test_bin_layout.py +++ b/bin/tools/test_bin_layout.py @@ -108,6 +108,13 @@ class BinLayoutTest(unittest.TestCase): self.assertIn(frag, py) self.assertIn("0.95", rank) + def test_facts_methods_are_shebangs(self) -> None: + for method in ("audit.go", "extract.go", "crm.go"): + self._assert_shebang(f"bin/facts/{method}") + text = (ROOT / "bin" / "facts" / method).read_text() + self.assertIn("cmdbin.ExecFile", text) + self.assertIn(f"bin/facts/{method.removesuffix('.go')}", text) + def test_mail_import_is_shebang_not_brain_write(self) -> None: self._assert_shebang("bin/mail/import.go") index_mail = (ROOT / "bin" / "mail" / "index_mail").read_text()