Files
ely-eye/ely-eye-mcp/internal/store/proof_evidence.go
T
ZacharyZhang-NY 4cd29f91e5 Add Ely-Eye MCP server
Local read-only Model Context Protocol server (Go) exposing the .ely_eye memory
store to coding agents such as Codex and Claude Code: nine tools over status,
cartridges, Evidence Atoms, compiled evidence packs, proof suites, and HashHop
proofs. One-line cross-platform install (no Docker), with a prebuilt-binary
release workflow.
2026-05-30 23:51:45 -04:00

168 lines
4.9 KiB
Go

package store
import (
"context"
"encoding/json"
"errors"
"io/fs"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/ZacharyZhang-NY/ely-eye/ely-eye-mcp/internal/domain"
)
func (s *Store) evalProofsRoot() string {
return filepath.Join(s.cfg.ElyEyeHome, "data", "eval_proofs")
}
// ListHashHopProofs aggregates every HashHop and Visual HashHop proof under the
// eval set into pass-rate buckets keyed by kind, hop count, and token budget,
// plus a capped list of recent example proofs.
func (s *Store) ListHashHopProofs(ctx context.Context, kind string, limit int) (domain.HashHopReport, error) {
if kind != "" && kind != "text_hashhop" && kind != "visual_hashhop" {
return domain.HashHopReport{}, errors.New("kind must be text_hashhop or visual_hashhop")
}
if limit <= 0 || limit > 200 {
limit = 25
}
return aggregateHashHop(ctx, s.evalProofsRoot(), kind, limit)
}
// GetProofSuite reads one PRD proof suite by id, including all checks, and
// summarizes the HashHop proofs stored alongside it.
func (s *Store) GetProofSuite(ctx context.Context, proofID string) (domain.ProofSuiteDetail, error) {
if err := validateProofID(proofID); err != nil {
return domain.ProofSuiteDetail{}, err
}
dir := filepath.Join(s.evalProofsRoot(), proofID)
path := filepath.Join(dir, "proof_suite.json")
data, err := os.ReadFile(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return domain.ProofSuiteDetail{}, errors.New("proof suite not found")
}
return domain.ProofSuiteDetail{}, err
}
var detail domain.ProofSuiteDetail
if err := json.Unmarshal(data, &detail); err != nil {
return domain.ProofSuiteDetail{}, err
}
detail.Path = path
report, err := aggregateHashHop(ctx, filepath.Join(dir, "hashhop"), "", 0)
if err != nil {
return domain.ProofSuiteDetail{}, err
}
detail.HashHop = report
return detail, nil
}
// aggregateHashHop walks a directory tree for proof.json files and builds a
// HashHopReport. proofCap <= 0 returns buckets only; a positive cap also
// includes that many most-recent example proofs.
func aggregateHashHop(ctx context.Context, root string, kind string, proofCap int) (domain.HashHopReport, error) {
buckets := map[string]*domain.HashHopBucket{}
seen := map[string]bool{}
var proofs []domain.HashHopProof
report := domain.HashHopReport{}
err := filepath.WalkDir(root, func(path string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if err := ctx.Err(); err != nil {
return err
}
if entry.IsDir() || entry.Name() != "proof.json" {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return err
}
var proof domain.HashHopProof
if err := json.Unmarshal(data, &proof); err != nil {
return err
}
if kind != "" && proof.Kind != kind {
return nil
}
// A suite stores each HashHop proof under its own hashhop/ directory and
// also promotes a byte-identical copy to a top-level proof directory.
// Count each proof once by its content-derived id.
if seen[proof.ProofID] {
return nil
}
seen[proof.ProofID] = true
proof.Path = path
report.Total++
passed := proof.Passed != nil && *proof.Passed
if passed {
report.Passed++
}
key := proof.Kind + "|" + strconv.Itoa(proof.Hops) + "|" + strconv.FormatInt(proof.TokenEquivalent, 10)
bucket, ok := buckets[key]
if !ok {
bucket = &domain.HashHopBucket{Kind: proof.Kind, Hops: proof.Hops, TokenEquivalent: proof.TokenEquivalent}
buckets[key] = bucket
}
bucket.Total++
if passed {
bucket.Passed++
}
if proofCap > 0 {
proofs = append(proofs, proof)
}
return nil
})
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return domain.HashHopReport{Buckets: []domain.HashHopBucket{}}, nil
}
return domain.HashHopReport{}, err
}
report.Buckets = sortedBuckets(buckets)
if proofCap > 0 {
report.Proofs = capRecentProofs(proofs, proofCap)
}
return report, nil
}
func sortedBuckets(buckets map[string]*domain.HashHopBucket) []domain.HashHopBucket {
out := make([]domain.HashHopBucket, 0, len(buckets))
for _, bucket := range buckets {
out = append(out, *bucket)
}
sort.Slice(out, func(i int, j int) bool {
if out[i].Kind != out[j].Kind {
return out[i].Kind < out[j].Kind
}
if out[i].TokenEquivalent != out[j].TokenEquivalent {
return out[i].TokenEquivalent < out[j].TokenEquivalent
}
return out[i].Hops < out[j].Hops
})
return out
}
func capRecentProofs(proofs []domain.HashHopProof, limit int) []domain.HashHopProof {
sort.Slice(proofs, func(i int, j int) bool {
return proofs[i].CreatedAt > proofs[j].CreatedAt
})
if len(proofs) > limit {
proofs = proofs[:limit]
}
return proofs
}
func validateProofID(id string) error {
if id == "" {
return errors.New("proof_id is required")
}
if id == "." || id == ".." || strings.ContainsAny(id, `/\`) || filepath.VolumeName(id) != "" || filepath.Base(id) != id {
return errors.New("proof_id must be a single path element")
}
return nil
}