feat(auto_context): port to FULL Go — dichotomy wave

Full name-preserving port of src/rune/auto_context.py to src/go/auto_context
(GO verdict, #2479 dichotomy tranche — 8/8 fns, dichotomy audit match clean):
- run_warm_router / run_memory_recall / run_always_digest: subprocess seam
  (RunScript) with the oracle's exact degrade paths (nonzero exit, timeout,
  bad JSON, empty stdout → None; non-list recall payload → []), budget
  truncation data[:budget]
- load_active_tasks: persistence-ledger filter (open/in_progress) with every
  best-effort degrade branch (missing file, malformed JSON, non-dict ledger,
  non-dict task → [])
- build_auto_context: stopword table 1:1, len>4 + isalpha + first-5 query
  extraction, 200-rune preview truncation, Python truthiness on every
  optional payload, frozen-clock isoformat
- save_context: atomic tmp+rename, json.dumps(indent=2, ensure_ascii)
  byte-parity via pyjson.go
- handle_hook: message/text/str(data) fallback chain (Python dict repr
  included), both best-effort error dicts verbatim ('list' object has no
  attribute 'get' / object of type 'int' has no len())
- CLI main(): argparse-compatible build/show/hook (--event/--data/--json),
  byte-parity stdout, exit codes (invalid choice/missing cmd → 2)
- PLUGIN manifest pinned (pluginManifest)
- Battery: 52 frozen truth rows vs the LIVE oracle (clock frozen, env-driven
  stub scripts echo the exact message/query the port hands the seam),
  deterministic across two generator runs; replay gate + TestTruthParity
This commit is contained in:
Nick committed 2026-10-12 00:40:15 +01:00
1 parent 72f70aa47a
commit f129a6a469
6 files changed
+3194

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
module rune/auto_context
go 1.22
+775
View File
@@ -0,0 +1,775 @@
// Package main — the FULL GO port of src/rune/auto_context.py (#2479
// dichotomy tranche, GO side verdict). Every public Python function has a
// name-preserving counterpart: run_warm_router → runWarmRouter,
// run_memory_recall → runMemoryRecall, run_always_digest → runAlwaysDigest,
// load_active_tasks → loadActiveTasks, build_auto_context →
// buildAutoContext, save_context → saveContext, handle_hook → handleHook,
// and the module CLI main() (argparse-compatible: build/show/hook, --event,
// --data, --json; invalid choices exit 2). The PLUGIN manifest rides along
// as pluginManifest().
//
// IO (subprocess runs, memory paths, wall clock) stays behind injectable
// seams so the frozen truth battery replays deterministically: RunScript,
// MemoryDir, ScriptsDir, PythonBin and ClockNow are swappable vars, swapped
// per truth row by replay.go and restored after each row. The oracle's
// subprocess payloads (warm_router.py route/always, memory_store.py recall)
// arrive through the RunScript seam; the frozen truth pins the exact
// message/query strings the port hands to that seam.
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
"unicode"
"unicode/utf8"
)
// ── configuration ────────────────────────────────────────────────────────
// stopwordSet — the oracle's recall stopword table, 1:1.
var stopwordSet = map[string]struct{}{
"what": {}, "when": {}, "where": {}, "which": {}, "that": {}, "this": {},
"with": {}, "from": {}, "have": {}, "been": {}, "were": {}, "they": {},
"their": {}, "about": {}, "would": {}, "could": {}, "should": {},
"will": {}, "into": {}, "your": {}, "just": {}, "like": {}, "know": {},
"think": {}, "also": {}, "back": {}, "then": {}, "than": {}, "more": {},
"some": {}, "only": {}, "very": {}, "does": {}, "done": {}, "make": {},
"made": {}, "want": {}, "need": {}, "give": {}, "tell": {}, "there": {},
"these": {}, "those": {}, "because": {}, "between": {}, "after": {},
"before": {}, "under": {}, "over": {}, "again": {}, "once": {},
"here": {}, "each": {}, "every": {}, "both": {}, "few": {}, "most": {},
"other": {}, "such": {}, "same": {}, "being": {}, "having": {},
}
// ── seams (swapped per truth row by replay.go) ───────────────────────────
// ExecResult — the slice of subprocess.run(capture_output=True, text=True)
// the oracle consumes: stdout text, return code, and a non-nil Err when the
// run failed to start, was killed, or hit its timeout (subprocess raised).
type ExecResult struct {
Stdout string
ReturnCode int
Err error
}
var (
// defaultPythonBin — memory_env.python_bin(): sys.executable of the
// parent process (same venv, portable by construction).
defaultPythonBin = func() string {
if p := os.Getenv("RUNE_PYTHON"); p != "" {
return p
}
return "python3"
}
// defaultScriptsDir — memory_env.scripts_dir() (RUNE_SCRIPTS_DIR).
defaultScriptsDir = func() string {
if d := os.Getenv("RUNE_SCRIPTS_DIR"); d != "" {
return d
}
return "src/rune"
}
// defaultMemoryDir — memory_env.memory_dir() (RUNE_MEMORY_DIR else
// RUNE_HOME/memory else the default rune home).
defaultMemoryDir = func() string {
if d := os.Getenv("RUNE_MEMORY_DIR"); d != "" {
return d
}
home := os.Getenv("RUNE_HOME")
if home == "" {
home = "/mnt/workdrive/rune"
}
return filepath.Join(home, "memory")
}
// defaultClockNow — datetime.now(timezone.utc); build_auto_context
// calls it once per build.
defaultClockNow = func() time.Time { return time.Now().UTC() }
// defaultRunScript — subprocess.run([python, script, ...], timeout=N):
// timeout expiry surfaces as Err (subprocess.TimeoutExpired), a
// nonzero exit keeps Stdout and sets ReturnCode.
defaultRunScript = func(exe string, args []string, timeout time.Duration) ExecResult {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, exe, args...)
out, err := cmd.Output()
res := ExecResult{Stdout: string(out)}
if err != nil {
res.Err = err
if ee, ok := err.(*exec.ExitError); ok {
res.ReturnCode = ee.ExitCode()
} else {
res.ReturnCode = -1
}
}
return res
}
)
var (
PythonBin = defaultPythonBin
ScriptsDir = defaultScriptsDir
MemoryDir = defaultMemoryDir
ClockNow = defaultClockNow
RunScript = defaultRunScript
)
func warmRouterPath() string { return filepath.Join(ScriptsDir(), "warm_router.py") }
func memoryStorePath() string {
return filepath.Join(ScriptsDir(), "memory_store.py")
}
func contextFilePath() string {
return filepath.Join(MemoryDir(), "index", "auto_context.json")
}
func tasksFilePath() string {
return filepath.Join(MemoryDir(), "tasks", "persisted.json")
}
// isoformat — datetime.isoformat() on an aware UTC datetime: fractional
// part only when microseconds are nonzero, offset always "+00:00" shape.
func isoformat(t time.Time) string {
base := t.Format("2006-01-02T15:04:05")
if us := t.Nanosecond() / 1000; us != 0 {
base += fmt.Sprintf(".%06d", us)
}
return base + t.Format("-07:00")
}
// ── Python value semantics helpers ───────────────────────────────────────
// pyTruthy — Python truthiness for JSON-decoded values: None, False, 0,
// "" and every empty container are falsy.
func pyTruthy(v any) bool {
switch t := v.(type) {
case nil:
return false
case bool:
return t
case string:
return t != ""
case float64:
return t != 0
case json.Number:
f, _ := t.Float64()
return f != 0
case []any:
return len(t) > 0
case []*OrderedMap:
return len(t) > 0
case *OrderedMap:
return t != nil && t.Len() > 0
default:
return true
}
}
// pyTypeName — Python type() name for JSON-decoded values (integral
// floats decode as 'int', mirroring json.loads producing ints).
func pyTypeName(v any) string {
switch t := v.(type) {
case nil:
return "NoneType"
case bool:
return "bool"
case string:
return "str"
case float64:
if t == float64(int64(t)) {
return "int"
}
return "float"
case json.Number:
f, _ := t.Float64()
if f == float64(int64(f)) && !strings.ContainsAny(t.String(), ".eE") {
return "int"
}
return "float"
case []any, []*OrderedMap:
return "list"
case *OrderedMap:
return "dict"
default:
return "object"
}
}
// pyIsAlpha — str.isalpha(): non-empty and every rune a Unicode letter.
func pyIsAlpha(s string) bool {
if s == "" {
return false
}
for _, r := range s {
if !unicode.IsLetter(r) {
return false
}
}
return true
}
// runeSlice — Python's s[:n] (codepoint slice).
func runeSlice(s string, n int) string {
if utf8.RuneCountInString(s) <= n {
return s
}
return string([]rune(s)[:n])
}
func truncateRunes(s string, n int) string { return runeSlice(s, n) }
// pyParse — json.loads with order-preserving objects and json.Number
// numerals (so ints survive byte-parity re-encoding).
func pyParse(s string) (any, error) {
dec := json.NewDecoder(strings.NewReader(s))
dec.UseNumber()
return decodeJSONValue(dec)
}
// pythonRepr — repr() of a JSON-decoded value (the handle_hook
// str(data) fallback), Python quoting rules included.
func pythonRepr(v any) string {
switch t := v.(type) {
case nil:
return "None"
case bool:
if t {
return "True"
}
return "False"
case string:
return pythonReprString(t)
case json.Number:
if !strings.ContainsAny(t.String(), ".eE") {
return t.String()
}
f, _ := t.Float64()
return strconv.FormatFloat(f, 'g', -1, 64)
case float64:
return strconv.FormatFloat(t, 'g', -1, 64)
case []any:
parts := make([]string, len(t))
for i, item := range t {
parts[i] = pythonRepr(item)
}
return "[" + strings.Join(parts, ", ") + "]"
case *OrderedMap:
entries := t.Entries()
parts := make([]string, 0, len(entries))
for _, kv := range entries {
parts = append(parts, pythonReprString(kv.Key)+": "+pythonRepr(kv.Value))
}
return "{" + strings.Join(parts, ", ") + "}"
default:
return fmt.Sprintf("%v", t)
}
}
// pythonReprString — repr() of a str: single quotes preferred, double
// quotes when the string contains ' but not ", backslash and quote
// escapes, \n \r \t shortcuts.
func pythonReprString(s string) string {
escape := func(quote byte) string {
var sb strings.Builder
sb.WriteByte(quote)
for _, r := range s {
switch r {
case '\\':
sb.WriteString(`\\`)
case '\n':
sb.WriteString(`\n`)
case '\r':
sb.WriteString(`\r`)
case '\t':
sb.WriteString(`\t`)
case '\'':
if quote == '\'' {
sb.WriteString(`\'`)
} else {
sb.WriteByte('\'')
}
case '"':
if quote == '"' {
sb.WriteString(`\"`)
} else {
sb.WriteByte('"')
}
default:
sb.WriteRune(r)
}
}
sb.WriteByte(quote)
return sb.String()
}
if strings.Contains(s, "'") && !strings.Contains(s, "\"") {
return escape('"')
}
return escape('\'')
}
// ── the port (1:1 with the oracle) ───────────────────────────────────────
// runWarmRouter — run_warm_router: route the message through the warm
// router subprocess and return the fired-neurons payload; ANY failure
// (nonzero exit, timeout, bad JSON, empty stdout) degrades to None.
func runWarmRouter(message string) any {
res := RunScript(PythonBin(), []string{warmRouterPath(), "route", message, "--json"}, 10*time.Second)
if res.Err == nil && res.ReturnCode == 0 && strings.TrimSpace(res.Stdout) != "" {
if v, err := pyParse(res.Stdout); err == nil {
return v
}
}
return nil
}
// runMemoryRecall — run_memory_recall: recall relevant entries; a
// non-list payload degrades to [], a list is capped at budget entries.
func runMemoryRecall(query string, budget int) []any {
res := RunScript(PythonBin(), []string{memoryStorePath(), "recall", query, "--json"}, 15*time.Second)
if res.Err == nil && res.ReturnCode == 0 && strings.TrimSpace(res.Stdout) != "" {
if v, err := pyParse(res.Stdout); err == nil {
if list, ok := v.([]any); ok {
if budget >= 0 && budget < len(list) {
return list[:budget]
}
return list
}
}
}
return []any{}
}
// runAlwaysDigest — run_always_digest: the always-on digest payload or
// None on any failure.
func runAlwaysDigest() any {
res := RunScript(PythonBin(), []string{warmRouterPath(), "always", "--json"}, 10*time.Second)
if res.Err == nil && res.ReturnCode == 0 && strings.TrimSpace(res.Stdout) != "" {
if v, err := pyParse(res.Stdout); err == nil {
return v
}
}
return nil
}
// loadActiveTasks — load_active_tasks: read the persistence ledger and
// keep open/in_progress tasks; missing file, malformed JSON, a non-dict
// ledger, a non-dict task entry — every failure degrades to [].
func loadActiveTasks() []any {
raw, err := os.ReadFile(tasksFilePath())
if err != nil {
return []any{}
}
v, err := pyParse(string(raw))
if err != nil {
return []any{}
}
ledger, ok := v.(*OrderedMap)
if !ok {
return []any{}
}
tasks, ok := ledger.Get("tasks")
if !ok {
return []any{}
}
list, ok := tasks.([]any)
if !ok {
return []any{}
}
active := []any{}
for _, t := range list {
tom, ok := t.(*OrderedMap)
if !ok {
return []any{} // t.get on a non-dict raises → the oracle's except
}
status, _ := tom.Get2("status").(string)
if status == "open" || status == "in_progress" {
active = append(active, tom)
}
}
return active
}
// recallQueryWords — the oracle's key-term extraction: lowercase, split on
// whitespace, keep words longer than 4 chars that are pure alphabetic and
// not stopwords.
func recallQueryWords(message string) []string {
words := []string{}
for _, w := range strings.Fields(strings.ToLower(message)) {
if utf8.RuneCountInString(w) > 4 && pyIsAlpha(w) {
if _, stop := stopwordSet[w]; !stop {
words = append(words, w)
}
}
}
return words
}
// buildAutoContext — build_auto_context: warm neurons, active tasks,
// memory recall (substantive messages only), always-on digest.
func buildAutoContext(message string) *OrderedMap {
context := NewOrderedMap(
KV{"timestamp", isoformat(ClockNow().UTC())},
KV{"message_preview", runeSlice(message, 200)},
KV{"warm_neurons", nil},
KV{"memory_recall", nil},
KV{"always_digest", nil},
KV{"active_tasks", nil},
)
warm := runWarmRouter(message)
if pyTruthy(warm) {
context.Set("warm_neurons", warm)
}
tasks := loadActiveTasks()
if len(tasks) > 0 {
context.Set("active_tasks", tasks)
}
if message != "" && utf8.RuneCountInString(strings.TrimSpace(message)) > 10 {
words := recallQueryWords(message)
if len(words) > 0 {
n := 5
if len(words) < n {
n = len(words)
}
recall := runMemoryRecall(strings.Join(words[:n], " "), 5)
if len(recall) > 0 {
context.Set("memory_recall", recall)
}
}
}
digest := runAlwaysDigest()
if pyTruthy(digest) {
context.Set("always_digest", digest)
}
return context
}
// saveContext — save_context: atomic write (tmp file + rename) of the
// context JSON, indent=2, ensure_ascii.
func saveContext(context *OrderedMap) error {
if err := os.MkdirAll(filepath.Dir(contextFilePath()), 0o777); err != nil {
return err
}
tmp := contextFilePath() + ".tmp"
if err := os.WriteFile(tmp, []byte(encodePythonJSON(context, 0)), 0o644); err != nil {
return err
}
return os.Rename(tmp, contextFilePath())
}
// handleHook — handle_hook: the on_message plugin hook. The truthiness
// guard, the message/text/str(data) fallback chain and the two nested
// count expressions reproduce the oracle exactly, including its
// best-effort error dicts (truncated to 120 runes).
func handleHook(event string, data *OrderedMap) *OrderedMap {
errorDict := func(msg string) *OrderedMap {
return NewOrderedMap(
KV{"status", "error"},
KV{"event", event},
KV{"error", truncateRunes(msg, 120)},
)
}
if event == "on_message" && pyTruthy(data) {
message := any("")
for _, key := range []string{"message", "text"} {
v, ok := data.Get(key)
if !ok {
v = ""
}
if pyTruthy(v) {
message = v
break
}
}
if !pyTruthy(message) {
message = pythonRepr(data)
}
msgStr, isStr := message.(string)
if !isStr {
return errorDict(fmt.Sprintf("'%s' object has no attribute 'strip'", pyTypeName(message)))
}
if msgStr != "" && utf8.RuneCountInString(strings.TrimSpace(msgStr)) > 2 {
context := buildAutoContext(msgStr)
if err := saveContext(context); err != nil {
return errorDict(err.Error())
}
neurons := context.Get2("warm_neurons")
recall := context.Get2("memory_recall")
tasks := context.Get2("active_tasks")
neuronsFired := 0
if pyTruthy(neurons) {
om, ok := neurons.(*OrderedMap)
if !ok {
return errorDict(fmt.Sprintf("'%s' object has no attribute 'get'", pyTypeName(neurons)))
}
fired, _ := om.Get("fired")
// .get("fired", []) — a MISSING key defaults to [], a
// present-but-None key does not.
if fired == nil {
if _, present := om.Get("fired"); !present {
fired = []any{}
}
}
n, err := pyLen(fired)
if err != nil {
return errorDict(err.Error())
}
neuronsFired = n
}
memoriesRecalled := 0
if pyTruthy(recall) {
if n, err := pyLen(recall); err != nil {
return errorDict(err.Error())
} else {
memoriesRecalled = n
}
}
activeTasks := 0
if pyTruthy(tasks) {
if n, err := pyLen(tasks); err != nil {
return errorDict(err.Error())
} else {
activeTasks = n
}
}
return NewOrderedMap(
KV{"status", "ok"},
KV{"event", event},
KV{"handler", "auto_context"},
KV{"neurons_fired", neuronsFired},
KV{"memories_recalled", memoriesRecalled},
KV{"active_tasks", activeTasks},
KV{"context_file", contextFilePath()},
)
}
}
return NewOrderedMap(
KV{"status", "ok"},
KV{"event", event},
KV{"handler", "auto_context"},
)
}
// pyLen — len() for JSON-decoded values; non-sized types raise TypeError
// in Python ("object of type 'int' has no len()").
func pyLen(v any) (int, error) {
switch t := v.(type) {
case nil:
return 0, fmt.Errorf("object of type 'NoneType' has no len()")
case string:
return utf8.RuneCountInString(t), nil
case []any:
return len(t), nil
case []*OrderedMap:
return len(t), nil
case *OrderedMap:
return t.Len(), nil
default:
return 0, fmt.Errorf("object of type '%s' has no len()", pyTypeName(v))
}
}
// pluginManifest — the module-level PLUGIN dict (microkernel discovery
// metadata), key order preserved.
func pluginManifest() *OrderedMap {
return NewOrderedMap(
KV{"name", "auto_context"},
KV{"version", "1.0.0"},
KV{"description", "Auto-context — automatic memory and warm neuron activation on every message."},
KV{"capabilities", []any{"context", "memory", "activation"}},
KV{"hooks", NewOrderedMap(KV{"on_message", "handle_hook"})},
KV{"dependencies", []any{"memory_store", "warm_router"}},
KV{"provides", []any{"auto_context"}},
KV{"priority", 5},
KV{"auto_activate", true},
)
}
// ── CLI (auto_context.py main(), byte-parity stdout) ─────────────────────
func argparseUsage(w io.Writer) {
fmt.Fprintln(w, "usage: auto_context.py [-h] [--event EVENT] [--data DATA] [--json] {build,show,hook} [arg]")
}
// RunCLI executes auto_context.py main() against argv (program name at
// index 0) and returns the process exit code. argparse failures print
// usage to stderr and exit 2.
func RunCLI(argv []string, stdout io.Writer) int {
stderr := os.Stderr
var positionals []string
event, hasEvent := "", false
dataStr, hasData := "", false
jsonFlag := false
i := 1
for i < len(argv) {
a := argv[i]
switch {
case a == "--json":
jsonFlag = true
case a == "--event":
i++
if i >= len(argv) {
fmt.Fprintf(stderr, "auto_context.py: error: argument --event: expected one argument\n")
argparseUsage(stderr)
return 2
}
event, hasEvent = argv[i], true
case strings.HasPrefix(a, "--event="):
event, hasEvent = strings.TrimPrefix(a, "--event="), true
case a == "--data":
i++
if i >= len(argv) {
fmt.Fprintf(stderr, "auto_context.py: error: argument --data: expected one argument\n")
argparseUsage(stderr)
return 2
}
dataStr, hasData = argv[i], true
case strings.HasPrefix(a, "--data="):
dataStr, hasData = strings.TrimPrefix(a, "--data="), true
case a == "-h" || a == "--help":
argparseUsage(stdout)
return 0
case strings.HasPrefix(a, "-") && a != "-":
fmt.Fprintf(stderr, "auto_context.py: error: unrecognized arguments: %s\n", a)
argparseUsage(stderr)
return 2
default:
positionals = append(positionals, a)
}
i++
}
if len(positionals) == 0 {
fmt.Fprintf(stderr, "auto_context.py: error: the following arguments are required: cmd\n")
argparseUsage(stderr)
return 2
}
if len(positionals) > 2 {
fmt.Fprintf(stderr, "auto_context.py: error: unrecognized arguments: %s\n", strings.Join(positionals[2:], " "))
argparseUsage(stderr)
return 2
}
cmd, arg := positionals[0], ""
if len(positionals) == 2 {
arg = positionals[1]
}
switch cmd {
case "build":
message := arg
if message == "" {
message = "general context"
}
context := buildAutoContext(message)
if err := saveContext(context); err != nil {
fmt.Fprintf(stderr, "Traceback (most recent call last): %v\n", err)
return 1
}
if jsonFlag {
fmt.Fprintln(stdout, encodePythonJSON(context, 0))
} else {
n := 0
if warm := context.Get2("warm_neurons"); pyTruthy(warm) {
if om, ok := warm.(*OrderedMap); ok {
fired, _ := om.Get("fired")
if fired == nil {
if _, present := om.Get("fired"); !present {
fired = []any{}
}
}
if c, err := pyLen(fired); err == nil {
n = c
}
}
}
m := 0
if recall, ok := context.Get2("memory_recall").([]any); ok {
m = len(recall)
}
fmt.Fprintf(stdout, "Auto-context built: %d neurons, %d memories\n", n, m)
}
case "show":
raw, err := os.ReadFile(contextFilePath())
if err != nil {
fmt.Fprintln(stdout, "No auto-context file found")
return 0
}
v, err := pyParse(string(raw))
if err != nil {
fmt.Fprintf(stderr, "Traceback (most recent call last): %v\n", err)
return 1
}
fmt.Fprintln(stdout, encodePythonJSON(v, 0))
case "hook":
event2 := "unknown"
if hasEvent && event != "" {
event2 = event
}
var data *OrderedMap
if hasData && dataStr != "" {
v, err := pyParse(dataStr)
if err != nil {
fmt.Fprintf(stderr, "Traceback (most recent call last): %v\n", err)
return 1
}
if om, ok := v.(*OrderedMap); ok {
data = om
} else {
// json.loads gave a non-object; data.get would raise
// AttributeError outside handle_hook's try
fmt.Fprintf(stderr, "Traceback (most recent call last): '%s' object has no attribute 'get'\n", pyTypeName(v))
return 1
}
}
result := handleHook(event2, data)
fmt.Fprintln(stdout, encodePythonJSONCompact(result))
default:
fmt.Fprintf(stderr, "auto_context.py: error: argument cmd: invalid choice: '%s' (choose from 'build', 'show', 'hook')\n", cmd)
argparseUsage(stderr)
return 2
}
return 0
}
// ── entry point: CLI + truth replay subcommand for the HARD CI gate ─────
func truthRowCount(path string) int {
raw, err := os.ReadFile(path)
if err != nil {
return -1
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
return -1
}
return len(doc.Rows)
}
func main() {
if len(os.Args) >= 2 && os.Args[1] == "replay" {
path := "truth.json"
if len(os.Args) >= 3 {
path = os.Args[2]
}
fails := RunTruth(path)
if len(fails) > 0 {
for _, f := range fails {
fmt.Fprintln(os.Stderr, f)
}
fmt.Fprintf(os.Stderr, "PARITY FAILED: %d divergence(s)\n", len(fails))
os.Exit(1)
}
fmt.Printf("PARITY OK: %d truth rows exact-equal vs the Python oracle\n", truthRowCount(path))
return
}
os.Exit(RunCLI(os.Args, os.Stdout))
}
+363
View File
@@ -0,0 +1,363 @@
// Python json interop: insertion-ordered maps and json.dumps-compatible
// encoding (indent=2 and the default compact (", ", ": ") separators,
// ensure_ascii=True) so save_context, show and hook CLI output are
// byte-parity with the oracle (adapted from the message_queue port's
// pyjson.go; the float renderer follows Python repr, which keeps ".0" on
// integral floats).
package main
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"unicode/utf16"
)
// KV — one ordered key/value pair.
type KV struct {
Key string
Value any
}
// OrderedMap — a dict with preserved insertion order.
type OrderedMap struct {
Keys []string
Vals map[string]any
}
// NewOrderedMap builds an ordered map from pairs.
func NewOrderedMap(pairs ...KV) *OrderedMap {
om := &OrderedMap{Vals: map[string]any{}}
for _, kv := range pairs {
om.Set(kv.Key, kv.Value)
}
return om
}
// OrderedMapFromMap wraps a plain map (order unspecified — canonical
// comparisons sort keys anyway).
func OrderedMapFromMap(m map[string]any) *OrderedMap {
om := &OrderedMap{Vals: map[string]any{}}
for k, v := range m {
om.Set(k, v)
}
return om
}
// Set inserts or replaces, preserving first-insertion order.
func (o *OrderedMap) Set(key string, value any) {
if _, ok := o.Vals[key]; !ok {
o.Keys = append(o.Keys, key)
}
o.Vals[key] = value
}
// Get retrieves a value.
func (o *OrderedMap) Get(key string) (any, bool) {
v, ok := o.Vals[key]
return v, ok
}
// Get2 returns the value at key (nil when absent) for direct type asserts.
func (o *OrderedMap) Get2(key string) any {
return o.Vals[key]
}
// Entries returns key/value pairs in insertion order.
func (o *OrderedMap) Entries() []KV {
out := make([]KV, 0, len(o.Keys))
for _, k := range o.Keys {
out = append(out, KV{k, o.Vals[k]})
}
return out
}
// Len returns the number of entries.
func (o *OrderedMap) Len() int { return len(o.Keys) }
// decodeJSONValue decodes one JSON value from the decoder, decoding objects
// as *OrderedMap (order-preserving) and numbers as float64.
func decodeJSONValue(dec *json.Decoder) (any, error) {
tok, err := dec.Token()
if err != nil {
return nil, err
}
return decodeFromToken(dec, tok)
}
func decodeFromToken(dec *json.Decoder, tok json.Token) (any, error) {
switch t := tok.(type) {
case json.Delim:
switch t {
case '{':
om := &OrderedMap{Vals: map[string]any{}}
for dec.More() {
keyTok, err := dec.Token()
if err != nil {
return nil, err
}
key, _ := keyTok.(string)
val, err := decodeJSONValue(dec)
if err != nil {
return nil, err
}
om.Set(key, val)
}
if _, err := dec.Token(); err != nil { // consume '}'
return nil, err
}
return om, nil
case '[':
arr := []any{}
for dec.More() {
val, err := decodeJSONValue(dec)
if err != nil {
return nil, err
}
arr = append(arr, val)
}
if _, err := dec.Token(); err != nil { // consume ']'
return nil, err
}
return arr, nil
}
return nil, fmt.Errorf("unexpected delim %v", t)
default:
return tok, nil
}
}
// encodePythonJSON renders v exactly like Python json.dumps(v, indent=N)
// with default ensure_ascii=True and (',', ': ') separators.
func encodePythonJSON(v any, indent int) string {
var sb strings.Builder
encodeValue(&sb, v, indent)
return sb.String()
}
func encodeValue(sb *strings.Builder, v any, indent int) {
switch t := v.(type) {
case nil:
sb.WriteString("null")
case bool:
if t {
sb.WriteString("true")
} else {
sb.WriteString("false")
}
case string:
sb.WriteString(pythonEscapeString(t))
case int:
sb.WriteString(strconv.Itoa(t))
case int64:
sb.WriteString(strconv.FormatInt(t, 10))
case float64:
sb.WriteString(pythonFloat(t))
case json.Number:
sb.WriteString(t.String())
case []any:
if len(t) == 0 {
sb.WriteString("[]")
return
}
sb.WriteString("[\n")
for i, item := range t {
writeIndent(sb, indent+1)
encodeValue(sb, item, indent+1)
if i < len(t)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("]")
case []*OrderedMap:
if len(t) == 0 {
sb.WriteString("[]")
return
}
sb.WriteString("[\n")
for i, item := range t {
writeIndent(sb, indent+1)
encodeValue(sb, item, indent+1)
if i < len(t)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("]")
case []string:
if len(t) == 0 {
sb.WriteString("[]")
return
}
sb.WriteString("[\n")
for i, item := range t {
writeIndent(sb, indent+1)
encodeValue(sb, item, indent+1)
if i < len(t)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("]")
case *OrderedMap:
if t.Len() == 0 {
sb.WriteString("{}")
return
}
sb.WriteString("{\n")
entries := t.Entries()
for i, kv := range entries {
writeIndent(sb, indent+1)
sb.WriteString(pythonEscapeString(kv.Key))
sb.WriteString(": ")
encodeValue(sb, kv.Value, indent+1)
if i < len(entries)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("}")
case map[string]any:
encodeValue(sb, OrderedMapFromMap(t), indent)
default:
sb.WriteString(pythonEscapeString(fmt.Sprintf("%v", t)))
}
}
func writeIndent(sb *strings.Builder, level int) {
for i := 0; i < level; i++ {
sb.WriteString(" ")
}
}
// encodePythonJSONCompact renders v exactly like Python json.dumps(v) with
// default separators (", ", ": ") and ensure_ascii=True — the compact form
// the CLI hook path prints.
func encodePythonJSONCompact(v any) string {
var sb strings.Builder
encodeCompactValue(&sb, v)
return sb.String()
}
func encodeCompactValue(sb *strings.Builder, v any) {
switch t := v.(type) {
case nil:
sb.WriteString("null")
case bool:
if t {
sb.WriteString("true")
} else {
sb.WriteString("false")
}
case string:
sb.WriteString(pythonEscapeString(t))
case int:
sb.WriteString(strconv.Itoa(t))
case int64:
sb.WriteString(strconv.FormatInt(t, 10))
case float64:
sb.WriteString(pythonFloat(t))
case json.Number:
sb.WriteString(t.String())
case []any:
sb.WriteString("[")
for i, item := range t {
if i > 0 {
sb.WriteString(", ")
}
encodeCompactValue(sb, item)
}
sb.WriteString("]")
case []*OrderedMap:
sb.WriteString("[")
for i, item := range t {
if i > 0 {
sb.WriteString(", ")
}
encodeCompactValue(sb, item)
}
sb.WriteString("]")
case []string:
sb.WriteString("[")
for i, item := range t {
if i > 0 {
sb.WriteString(", ")
}
encodeCompactValue(sb, item)
}
sb.WriteString("]")
case *OrderedMap:
sb.WriteString("{")
entries := t.Entries()
for i, kv := range entries {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(pythonEscapeString(kv.Key))
sb.WriteString(": ")
encodeCompactValue(sb, kv.Value)
}
sb.WriteString("}")
case map[string]any:
encodeCompactValue(sb, OrderedMapFromMap(t))
default:
sb.WriteString(pythonEscapeString(fmt.Sprintf("%v", t)))
}
}
// pythonFloat renders a float like Python repr (json.dumps uses
// float.__repr__): shortest round-trip digits, and integral floats keep
// one decimal place (1.0 stays "1.0", not "1").
func pythonFloat(f float64) string {
s := strconv.FormatFloat(f, 'g', -1, 64)
if !strings.ContainsAny(s, ".eE") &&
!strings.Contains(s, "Inf") && !strings.Contains(s, "NaN") {
s += ".0"
}
return s
}
// pythonEscapeString — Python json.dumps string escaping with
// ensure_ascii=True: control chars via \uXXXX (with \b \f \n \r \t
// shortcuts), non-ASCII via \uXXXX / surrogate pairs, lowercase hex.
func pythonEscapeString(s string) string {
var sb strings.Builder
sb.WriteByte('"')
for _, r := range s {
switch r {
case '"':
sb.WriteString(`\"`)
case '\\':
sb.WriteString(`\\`)
case '\n':
sb.WriteString(`\n`)
case '\r':
sb.WriteString(`\r`)
case '\t':
sb.WriteString(`\t`)
case '\b':
sb.WriteString(`\b`)
case '\f':
sb.WriteString(`\f`)
default:
if r < 0x20 || r > 0x7e {
if r > 0xffff {
r1, r2 := utf16.EncodeRune(r)
fmt.Fprintf(&sb, `\u%04x\u%04x`, r1, r2)
} else {
fmt.Fprintf(&sb, `\u%04x`, r)
}
} else {
sb.WriteRune(r)
}
}
}
sb.WriteByte('"')
return sb.String()
}
+469
View File
@@ -0,0 +1,469 @@
// Replay — the frozen-truth parity engine. Reads truth.json (generated by
// /tmp/gen_auto_context_truth.py against the live Python oracle with the
// clock frozen and env-driven stub scripts pinned) and re-executes every
// row through this Go port, comparing results. Exact rows (context-file
// bytes, CLI stdout) compare byte-for-byte; everything else compares
// canonically (sorted-key JSON). Absolute memory-dir paths are normalized
// to @MEMDIR@ on both sides.
package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
// TruthDoc — the frozen truth file structure.
type TruthDoc struct {
FrozenTS string `json:"frozen_ts"`
Oracle string `json:"oracle"`
Rows []TruthRow `json:"rows"`
}
// TruthRow — one frozen case.
type TruthRow struct {
Name string `json:"name"`
Op string `json:"op"`
Setup json.RawMessage `json:"setup"`
Want json.RawMessage `json:"want"`
}
// RunTruth replays every row and returns a divergence report (empty on 100%).
func RunTruth(path string) []string {
raw, err := os.ReadFile(path)
if err != nil {
return []string{fmt.Sprintf("read truth: %v", err)}
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
return []string{fmt.Sprintf("parse truth: %v", err)}
}
var fails []string
for _, row := range doc.Rows {
if msg := compareOne(&doc, &row); msg != "" {
fails = append(fails, row.Name+": "+msg)
}
}
return fails
}
// compareOne replays one row and returns "" on parity, else a divergence
// description. Shared by RunTruth and the per-function battery tests.
func compareOne(doc *TruthDoc, row *TruthRow) string {
got, exact, err := execRow(doc, row)
if err != nil {
return fmt.Sprintf("error: %v", err)
}
var wantStr, gotStr string
if exact {
var w string
if err := json.Unmarshal(row.Want, &w); err != nil {
return fmt.Sprintf("want not a string: %v", err)
}
wantStr, gotStr = w, got.(string)
} else {
wantStr, err = canonicalFromRaw(row.Want)
if err != nil {
return fmt.Sprintf("canonicalize want: %v", err)
}
gotStr, err = canonicalFromRaw([]byte(mustJSON(got)))
if err != nil {
return fmt.Sprintf("canonicalize got: %v", err)
}
}
if wantStr != gotStr {
return fmt.Sprintf("\n want: %s\n got: %s", wantStr, gotStr)
}
return ""
}
// execRow executes one truth row: returns got (any) and, for exact rows,
// the exact string in got.
func execRow(doc *TruthDoc, row *TruthRow) (got any, exact bool, err error) {
setup := NewOrderedMap()
if len(row.Setup) > 0 {
dec := json.NewDecoder(bytes.NewReader(row.Setup))
dec.UseNumber()
tok, err := dec.Token()
if err != nil {
return nil, false, err
}
if d, ok := tok.(json.Delim); !ok || d != '{' {
return nil, false, fmt.Errorf("setup not an object")
}
v, err := decodeFromToken(dec, tok)
if err != nil {
return nil, false, err
}
setup = v.(*OrderedMap)
}
// freeze the seams per row: fresh temp memory dir, frozen clock,
// env-driven fake runner mirroring /tmp/actx_stubs payloads
home, err := os.MkdirTemp("", "actx_truth_row_")
if err != nil {
return nil, false, err
}
defer os.RemoveAll(home)
defer func() {
MemoryDir = defaultMemoryDir
ClockNow = defaultClockNow
RunScript = defaultRunScript
PythonBin = defaultPythonBin
ScriptsDir = defaultScriptsDir
}()
memdir := filepath.Join(home, "memory")
if err := os.MkdirAll(memdir, 0o777); err != nil {
return nil, false, err
}
MemoryDir = func() string { return memdir }
ScriptsDir = func() string { return "/tmp/actx_stubs" }
PythonBin = func() string { return "python3" }
base, err := time.Parse(time.RFC3339, doc.FrozenTS)
if err != nil {
return nil, false, err
}
ClockNow = func() time.Time { return base }
// file seeds from setup
if f, ok := setup.Get("tasks_file"); ok {
s, _ := f.(string)
if err := os.MkdirAll(filepath.Join(memdir, "tasks"), 0o777); err != nil {
return nil, false, err
}
if err := os.WriteFile(filepath.Join(memdir, "tasks", "persisted.json"), []byte(s), 0o644); err != nil {
return nil, false, err
}
}
if f, ok := setup.Get("seed_context"); ok {
s, _ := f.(string)
if err := os.MkdirAll(filepath.Join(memdir, "index"), 0o777); err != nil {
return nil, false, err
}
if err := os.WriteFile(filepath.Join(memdir, "index", "auto_context.json"), []byte(s), 0o644); err != nil {
return nil, false, err
}
}
// stub modes (default "ok", like the stub scripts' env defaults)
mode := func(key string) string {
if v, ok := setup.Get(key); ok {
if s, ok2 := v.(string); ok2 {
return s
}
}
return "ok"
}
routeMode, alwaysMode, recallMode := mode("stub_route"), mode("stub_always"), mode("stub_recall")
RunScript = func(exe string, args []string, timeout time.Duration) ExecResult {
var out string
code := 0
base := filepath.Base(args[0])
if base == "warm_router.py" {
if len(args) > 1 && args[1] == "always" {
switch alwaysMode {
case "ok":
out = encodePythonJSONCompact(NewOrderedMap(
KV{"digest", "daily-summary"}, KV{"items", 7},
KV{"topics", []any{"memory", "tasks", "recall"}}))
case "fail":
code = 1
case "badjson":
out = "not-json{{{"
case "empty":
// print nothing
}
} else {
msg := ""
if len(args) > 2 {
msg = args[2]
}
switch routeMode {
case "ok":
out = encodePythonJSONCompact(NewOrderedMap(
KV{"echo_message", msg},
KV{"fired", []any{"warm-alpha", "warm-beta", "warm-gamma"}},
KV{"matched", 3}, KV{"route", "focus"}))
case "list":
out = "[1, 2]"
case "emptylist":
out = "[]"
case "firedint":
out = encodePythonJSONCompact(NewOrderedMap(
KV{"fired", 3}, KV{"echo_message", msg}))
case "fail":
code = 1
case "badjson":
out = "not-json{{{"
case "empty":
// print nothing
}
}
} else {
query := ""
if len(args) > 2 {
query = args[2]
}
switch recallMode {
case "ok":
items := []any{NewOrderedMap(KV{"echo_query", query})}
for _, e := range recallEntries {
items = append(items, e)
}
out = encodePythonJSONCompact(items)
case "nonlist":
out = encodePythonJSONCompact(NewOrderedMap(
KV{"query", query}, KV{"note", "not a list"}))
case "fail":
code = 1
case "badjson":
out = "<xml?>"
case "emptyout":
// print nothing
}
}
return ExecResult{Stdout: out, ReturnCode: code}
}
scrub := func(v any) any {
return scrubPaths(v, memdir)
}
strSetup := func(key string) string {
s, _ := setup.Get2(key).(string)
return s
}
switch row.Op {
case "warm_router":
return scrub(runWarmRouter(strSetup("message"))), false, nil
case "recall":
budget := 5
if b, ok := setup.Get("budget"); ok {
switch n := b.(type) {
case float64:
budget = int(n)
case json.Number:
f, _ := n.Float64()
budget = int(f)
}
}
return scrub(runMemoryRecall(strSetup("query"), budget)), false, nil
case "always":
return scrub(runAlwaysDigest()), false, nil
case "load_tasks":
return scrub(loadActiveTasks()), false, nil
case "build":
return scrub(buildAutoContext(strSetup("message"))), false, nil
case "save":
om, ok := setup.Get2("context").(*OrderedMap)
if !ok {
return nil, false, fmt.Errorf("save: no context")
}
if err := saveContext(om); err != nil {
return nil, false, err
}
raw, err := os.ReadFile(contextFilePath())
if err != nil {
return nil, false, err
}
reads := []any{scrubPaths(string(raw), memdir)}
if om2, ok2 := setup.Get2("context2").(*OrderedMap); ok2 {
// overwrite semantics: second save replaces the first atomically
if err := saveContext(om2); err != nil {
return nil, false, err
}
raw2, err := os.ReadFile(contextFilePath())
if err != nil {
return nil, false, err
}
reads = append(reads, scrubPaths(string(raw2), memdir))
}
_, statErr := os.Stat(contextFilePath() + ".tmp")
return NewOrderedMap(
KV{"reads", reads},
KV{"tmp_gone", os.IsNotExist(statErr)},
), false, nil
case "hook":
event := strSetup("event")
var data *OrderedMap
if v, ok := setup.Get("data"); ok && v != nil {
om, ok2 := v.(*OrderedMap)
if !ok2 {
return nil, false, fmt.Errorf("hook: data not an object")
}
data = om
}
return scrub(handleHook(event, data)), false, nil
case "cli":
argvAny, _ := setup.Get2("argv").([]any)
argv := []string{}
for _, a := range argvAny {
s, _ := a.(string)
argv = append(argv, s)
}
var sb strings.Builder
code := RunCLI(argv, &sb)
out := strings.ReplaceAll(sb.String(), memdir, "@MEMDIR@")
return NewOrderedMap(KV{"stdout", out}, KV{"exit", code}), false, nil
case "plugin":
return pluginManifest(), false, nil
}
return nil, false, fmt.Errorf("unknown row op %s (%s)", row.Op, row.Name)
}
// scrubPaths — normalize the row's memory-dir prefix to @MEMDIR@ so the
// frozen want (recorded with the generator's temp dirs) compares equal.
func scrubPaths(v any, memdir string) any {
switch t := v.(type) {
case string:
return strings.ReplaceAll(t, memdir, "@MEMDIR@")
case []any:
out := make([]any, len(t))
for i, item := range t {
out[i] = scrubPaths(item, memdir)
}
return out
case *OrderedMap:
out := NewOrderedMap()
for _, kv := range t.Entries() {
out.Set(kv.Key, scrubPaths(kv.Value, memdir))
}
return out
default:
return v
}
}
// recallEntries — the memory_store.py stub's fixed entry list (m2's title
// pins ensure_ascii round-tripping).
var recallEntries = []any{
NewOrderedMap(KV{"id", "m1"}, KV{"score", 0.91},
KV{"tags", []any{"alpha", "beta"}}, KV{"title", "memory-entry-one"}),
NewOrderedMap(KV{"id", "m2"}, KV{"score", 0.84},
KV{"tags", []any{}}, KV{"title", "mémoire—café"}),
NewOrderedMap(KV{"id", "m3"}, KV{"score", 0.77},
KV{"tags", []any{"gamma"}}, KV{"title", "memory-entry-three"}),
NewOrderedMap(KV{"id", "m4"}, KV{"score", 0.7},
KV{"tags", []any{}}, KV{"title", "memory-entry-four"}),
NewOrderedMap(KV{"id", "m5"}, KV{"score", 0.62},
KV{"tags", []any{"delta"}}, KV{"title", "memory-entry-five"}),
NewOrderedMap(KV{"id", "m6"}, KV{"score", 0.55},
KV{"tags", []any{}}, KV{"title", "memory-entry-six"}),
NewOrderedMap(KV{"id", "m7"}, KV{"score", 0.41},
KV{"tags", []any{"epsilon"}}, KV{"title", "memory-entry-seven"}),
NewOrderedMap(KV{"id", "m8"}, KV{"score", 0.2},
KV{"tags", []any{}}, KV{"title", "memory-entry-eight"}),
}
// canonicalFromRaw — canonical string of a raw JSON document.
func canonicalFromRaw(raw []byte) (string, error) {
var v any
dec := json.NewDecoder(bytes.NewReader(raw))
dec.UseNumber()
if err := dec.Decode(&v); err != nil {
return "", err
}
return canonical(normalize(v)), nil
}
// canonical — sorted-key deterministic rendering of a decoded JSON value.
func canonical(v any) string {
switch t := v.(type) {
case nil:
return "null"
case bool:
if t {
return "true"
}
return "false"
case string:
return pythonEscapeString(t)
case float64:
if t == float64(int64(t)) {
return fmt.Sprintf("%d", int64(t))
}
return fmt.Sprintf("%v", t)
case json.Number:
f, _ := t.Float64()
return canonical(f)
case []any:
parts := make([]string, len(t))
for i, item := range t {
parts[i] = canonical(normalize(item))
}
return "[" + strings.Join(parts, ", ") + "]"
case map[string]any:
keys := make([]string, 0, len(t))
for k := range t {
keys = append(keys, k)
}
sort.Strings(keys)
parts := make([]string, 0, len(keys))
for _, k := range keys {
parts = append(parts, pythonEscapeString(k)+": "+canonical(normalize(t[k])))
}
return "{" + strings.Join(parts, ", ") + "}"
case *OrderedMap:
m := map[string]any{}
for _, kv := range t.Entries() {
m[kv.Key] = kv.Value
}
return canonical(m)
default:
return fmt.Sprintf("«%v»", t)
}
}
// normalize — convert OrderedMap/map trees into plain map[string]any with
// json.Number→float64 so canonical() sees one representation.
func normalize(v any) any {
switch t := v.(type) {
case *OrderedMap:
m := map[string]any{}
for _, kv := range t.Entries() {
m[kv.Key] = normalize(kv.Value)
}
return m
case map[string]any:
m := map[string]any{}
for k, val := range t {
m[k] = normalize(val)
}
return m
case []any:
out := make([]any, len(t))
for i, item := range t {
out[i] = normalize(item)
}
return out
case json.Number:
f, _ := t.Float64()
return f
default:
return v
}
}
func mustJSON(v any) string {
b, err := json.Marshal(normalize(v))
if err != nil {
return "null"
}
return string(b)
}
File diff suppressed because it is too large. Load diff
+264
View File
@@ -0,0 +1,264 @@
package main
// Battery — the HARD parity gate plus per-function batteries replaying the
// frozen oracle truth (truth.json, generated against the LIVE Python
// oracle with the clock frozen and env-driven stub scripts pinned)
// VERBATIM. Any divergence fails; the truth file rides the repo so the
// gate holds in CI without /tmp.
import (
"encoding/json"
"os"
"strings"
"testing"
)
func loadTruth(t *testing.T) *TruthDoc {
t.Helper()
raw, err := os.ReadFile("truth.json")
if err != nil {
t.Fatalf("truth.json missing (frozen oracle battery): %v", err)
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
t.Fatalf("parse truth.json: %v", err)
}
if len(doc.Rows) == 0 {
t.Fatal("truth.json contained no rows")
}
return &doc
}
func findRow(t *testing.T, name string) TruthRow {
t.Helper()
doc := loadTruth(t)
for _, row := range doc.Rows {
if row.Name == name {
return row
}
}
t.Fatalf("frozen row %q missing from truth.json — battery/truth drift", name)
return TruthRow{}
}
func mustReplay(t *testing.T, name string) {
t.Helper()
doc := loadTruth(t)
row := findRow(t, name)
if msg := compareOne(doc, &row); msg != "" {
t.Fatalf("DIVERGED %s: %s", name, msg)
}
}
func replayOp(t *testing.T, op string) int {
t.Helper()
doc := loadTruth(t)
n := 0
for _, row := range doc.Rows {
if row.Op != op {
continue
}
n++
if msg := compareOne(doc, &row); msg != "" {
t.Fatalf("DIVERGED %s: %s", row.Name, msg)
}
}
if n == 0 {
t.Fatalf("no %s rows in truth.json", op)
}
return n
}
// rowWant decodes a row's want JSON into a generic map.
func rowWant(t *testing.T, name string) map[string]any {
t.Helper()
row := findRow(t, name)
var want map[string]any
if err := json.Unmarshal(row.Want, &want); err != nil {
t.Fatalf("decode want of %s: %v", name, err)
}
return want
}
// TestTruthParity — the whole frozen battery, one shot.
func TestTruthParity(t *testing.T) {
doc := loadTruth(t)
var fails []string
for _, row := range doc.Rows {
if msg := compareOne(doc, &row); msg != "" {
fails = append(fails, row.Name+": "+msg)
}
}
if len(fails) > 0 {
t.Fatalf("parity replay diverged after %d clean rows:\n%s",
len(doc.Rows)-len(fails), strings.Join(fails, "\n"))
}
t.Logf("PARITY OK: %d cases exact-equal vs the Python oracle", len(doc.Rows))
}
// TestBatteryRunWarmRouter — ok / nonzero-exit / bad-json / empty-stdout /
// JSON-list payload / empty-list payload; the ok row pins the exact
// message the port hands to the seam.
func TestBatteryRunWarmRouter(t *testing.T) {
if n := replayOp(t, "warm_router"); n != 6 {
t.Fatalf("expected 6 frozen warm_router rows, got %d", n)
}
want := rowWant(t, "warm:ok")
if want["echo_message"] != "hello warm router" || want["matched"] != float64(3) {
t.Fatalf("frozen echo row changed: %v", want)
}
if got := len(want["fired"].([]any)); got != 3 {
t.Fatalf("fired payload changed: %v", want["fired"])
}
mustReplay(t, "warm:list")
mustReplay(t, "warm:emptylist")
}
// TestBatteryRunMemoryRecall — ok (echo pins the query string), budget
// truncation, non-list payload, and every degrade branch.
func TestBatteryRunMemoryRecall(t *testing.T) {
if n := replayOp(t, "recall"); n != 6 {
t.Fatalf("expected 6 frozen recall rows, got %d", n)
}
// recall:ok's want is the returned LIST — element 0 echoes the query
rawWant := findRow(t, "recall:ok").Want
var list []map[string]any
if err := json.Unmarshal(rawWant, &list); err != nil {
t.Fatalf("decode recall:ok want: %v", err)
}
if len(list) != 5 || list[0]["echo_query"] != "alpha beta gamma" {
t.Fatalf("frozen recall:ok changed: %v", list)
}
mustReplay(t, "recall:budget2")
mustReplay(t, "recall:nonlist")
mustReplay(t, "recall:emptyout")
}
// TestBatteryRunAlwaysDigest — ok / fail / bad-json / empty.
func TestBatteryRunAlwaysDigest(t *testing.T) {
if n := replayOp(t, "always"); n != 4 {
t.Fatalf("expected 4 frozen always rows, got %d", n)
}
want := rowWant(t, "always:ok")
if want["digest"] != "daily-summary" || want["items"] != float64(7) {
t.Fatalf("frozen digest changed: %v", want)
}
}
// TestBatteryLoadActiveTasks — missing file / mixed statuses (only
// open+in_progress survive) / malformed / empty / no-tasks-key.
func TestBatteryLoadActiveTasks(t *testing.T) {
if n := replayOp(t, "load_tasks"); n != 5 {
t.Fatalf("expected 5 frozen load_tasks rows, got %d", n)
}
mustReplay(t, "tasks:missing")
mustReplay(t, "tasks:malformed")
}
// TestBatteryBuild — every build_auto_context branch: full context (the
// recall echo pins stopword filtering + isalpha + first-5 join), short
// message, stopwords-only, non-alpha, unicode 200-rune preview truncation,
// empty message, recall-fail, warm-emptylist, warm-fail; frozen timestamp.
func TestBatteryBuild(t *testing.T) {
if n := replayOp(t, "build"); n != 9 {
t.Fatalf("expected 9 frozen build rows, got %d", n)
}
want := rowWant(t, "build:full")
recallList, _ := want["memory_recall"].([]any)
echo, _ := recallList[0].(map[string]any)
if echo["echo_query"] != "remember config parser override discussion" {
t.Fatalf("recall query extraction changed: %v", echo["echo_query"])
}
if want["timestamp"] != "2026-10-11T12:00:00.123456+00:00" {
t.Fatalf("frozen timestamp changed: %v", want["timestamp"])
}
want = rowWant(t, "build:unicode-preview")
preview, _ := want["message_preview"].(string)
if n := len([]rune(preview)); n != 200 {
t.Fatalf("preview must be 200 runes, got %d", n)
}
mustReplay(t, "build:short-message")
mustReplay(t, "build:stopwords")
mustReplay(t, "build:nonalpha")
mustReplay(t, "build:empty")
mustReplay(t, "build:recall-fail")
mustReplay(t, "build:warm-emptylist")
mustReplay(t, "build:warm-fail")
}
// TestBatterySave — save_context byte-parity: ensure_ascii escapes,
// indent=2 layout, and the tmp+rename overwrite (no .tmp leftover).
func TestBatterySave(t *testing.T) {
if n := replayOp(t, "save"); n != 2 {
t.Fatalf("expected 2 frozen save rows, got %d", n)
}
mustReplay(t, "save:full")
mustReplay(t, "save:overwrite")
}
// TestBatteryHandleHook — full counts, text fallback, str(data) fallback
// (Python dict repr!), short message, other event, no data, zero counts,
// and both best-effort error dicts verbatim.
func TestBatteryHandleHook(t *testing.T) {
if n := replayOp(t, "hook"); n != 9 {
t.Fatalf("expected 9 frozen hook rows, got %d", n)
}
want := rowWant(t, "hook:full")
if want["neurons_fired"] != float64(3) || want["memories_recalled"] != float64(5) ||
want["active_tasks"] != float64(2) {
t.Fatalf("frozen hook counts changed: %v", want)
}
if cf, _ := want["context_file"].(string); !strings.HasSuffix(cf, "@MEMDIR@/index/auto_context.json") {
t.Fatalf("context_file normalization changed: %v", cf)
}
errWant := rowWant(t, "hook:warm-list-error")
if errWant["error"] != "'list' object has no attribute 'get'" {
t.Fatalf("list.get error message changed: %v", errWant["error"])
}
errWant = rowWant(t, "hook:fired-int-error")
if errWant["error"] != "object of type 'int' has no len()" {
t.Fatalf("len(int) error message changed: %v", errWant["error"])
}
mustReplay(t, "hook:text-fallback")
mustReplay(t, "hook:str-fallback")
mustReplay(t, "hook:short-message")
mustReplay(t, "hook:other-event")
mustReplay(t, "hook:no-data")
mustReplay(t, "hook:counts-zero")
}
// TestBatteryCLI — main() byte-parity stdout + exit codes: build (plain +
// --json + default message), show (hit + missing), hook (event+data /
// defaults / error dict), argparse exits.
func TestBatteryCLI(t *testing.T) {
if n := replayOp(t, "cli"); n != 10 {
t.Fatalf("expected 10 frozen cli rows, got %d", n)
}
for name, wantExit := range map[string]float64{
"cli:build": 0,
"cli:build-json": 0,
"cli:build-default": 0,
"cli:show-missing": 0,
"cli:show-hit": 0,
"cli:hook": 0,
"cli:hook-default": 0,
"cli:hook-error": 0,
"cli:bad-cmd": 2,
"cli:no-cmd": 2,
} {
want := rowWant(t, name)
if want["exit"] != wantExit {
t.Fatalf("%s: frozen exit code %v != %v", name, want["exit"], wantExit)
}
}
}
// TestBatteryPlugin — the PLUGIN manifest row.
func TestBatteryPlugin(t *testing.T) {
mustReplay(t, "plugin")
want := rowWant(t, "plugin")
if want["name"] != "auto_context" || want["priority"] != float64(5) ||
want["auto_activate"] != true {
t.Fatalf("frozen PLUGIN manifest changed: %v", want)
}
}