Files
mesh-catalog/modules/mesh-knowledge/cmd/mesh-knowledge/answers.go
T
jochen 9f893b6305
mesh/merge-gate pass: builds mesh-knowledge → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
mesh-knowledge: let a record change a drafted clause, and never redraft a record whose clauses others link by number
A changes link onto a drafted clause was refused, so records that really
change such a rule could only cite it (ADR 0294, 0301, 0320); and return
renumbered ADR 0281's clauses, pointing other records' links at other
clauses. The changed drafted clause keeps its number and stays unserved;
return refuses a record whose clauses another record changed or links by
number. Issue 494, ADR 0321.
2026-10-12 14:05:37 +02:00

783 lines
25 KiB
Go

package main
// The answers (novox/hq ADR 0290 rule 6, to-be 55 §2 and §3): ask, get, holds, links and history, each the shape
// an agent needs. ask, get and holds stay under a bound they state, 4 KiB unless the caller asks for more, and say
// when they were cut and how to read the rest. A drafted clause is marked as such and never served as in force.
import (
"context"
"encoding/json"
"fmt"
"sort"
"strconv"
"strings"
)
// MostQuestion is the most characters a question to ask may hold.
const MostQuestion = 500
// The bound of an answer.
const (
DefaultBound = 4 << 10
MostBound = 64 << 10
LeastBound = 1 << 10
)
func boundOf(b int) int {
switch {
case b <= 0:
return DefaultBound
case b < LeastBound:
return LeastBound
case b > MostBound:
return MostBound
}
return b
}
func size(v any) int {
raw, _ := json.Marshal(v)
return len(raw)
}
// fit is the largest n (from 0 to total) whose answer stays within the bound.
func fit(total, bound int, build func(n int) any) (any, int) {
lo, hi := 0, total
for lo < hi {
mid := (lo + hi + 1) / 2
if size(build(mid)) <= bound {
lo = mid
} else {
hi = mid - 1
}
}
return build(lo), lo
}
func clip(s string, n int) string {
s = strings.Join(strings.Fields(s), " ")
if len([]rune(s)) <= n {
return s
}
return string([]rune(s)[:n-1]) + "…"
}
// held says, for a clause, whether it holds and what changed it, in the words an agent reads.
func held(r *Record, c Clause) string {
switch {
case c.Source == SourceDrafted:
s := "drafted, not confirmed: not the record's word, never in force"
if c.Unconfirmed != "" {
s += " (the second judgement did not confirm it: " + c.Unconfirmed + ")"
}
if len(c.ChangedBy) > 0 {
if c.Status == ClauseSuperseded {
s += "; superseded by " + strings.Join(c.ChangedBy, ", ")
} else {
s += "; changed by " + strings.Join(c.ChangedBy, ", ") + ": read both"
}
}
return s
case r.Status == StatusProposed || c.Status == ClauseProposed:
return "proposed: not in force"
case c.Status == ClauseChanged:
return "changed by " + strings.Join(c.ChangedBy, ", ") + ": read both"
case c.Status == ClauseSuperseded:
if len(c.ChangedBy) > 0 {
return "superseded by " + strings.Join(c.ChangedBy, ", ")
}
return "superseded"
case c.Source == SourceConfirmed:
return "in force (drafted from prose, confirmed)"
}
return "in force"
}
// Served says whether a clause may be served as holding: in force or changed, and not drafted.
func Served(r *Record, c Clause) bool {
return r.Accepted() && c.Source != SourceDrafted && (c.Status == ClauseInForce || c.Status == ClauseChanged)
}
// AnswerClause is one clause in an answer.
type AnswerClause struct {
N int `json:"n"`
Text string `json:"text"`
Holds string `json:"holds"`
Source string `json:"source,omitempty"`
}
// AnswerRecord is one record in an answer to ask.
type AnswerRecord struct {
Record string `json:"record"`
Title string `json:"title"`
Status string `json:"status"`
Clauses []AnswerClause `json:"clauses,omitempty"`
Note string `json:"note,omitempty"`
Since []string `json:"changed-since,omitempty"`
Read string `json:"read"`
}
// Asked is the answer to ask.
type Asked struct {
Question string `json:"question"`
Records []AnswerRecord `json:"records"`
Cut string `json:"cut,omitempty"`
Bound int `json:"bound"`
Says string `json:"says,omitempty"`
}
func readHow(k Key) string {
return fmt.Sprintf("get {\"kind\": %q, \"number\": %d, \"reasoning\": true}", k.Kind, k.Number)
}
// since is what changed a record since it was accepted: the accepted records that supersede or change it.
func (k *KB) since(ctx context.Context, key Key) []string {
in, err := k.Store.Inbound(ctx, key)
if err != nil {
return nil
}
var out []string
for _, x := range in {
if x.Link.Kind != LinkSupersedes && x.Link.Kind != LinkChanges {
continue
}
from, err := k.Store.Get(ctx, x.From)
if err != nil || from == nil || !from.Current.Accepted() {
continue
}
s := x.From.String()
if x.Link.From > 0 {
s += fmt.Sprintf(" §%d", x.Link.From)
}
switch {
case x.Link.Kind == LinkSupersedes:
s += " supersedes it"
case x.Link.To.Clause > 0:
s += fmt.Sprintf(" changes §%d", x.Link.To.Clause)
default:
s += " changes it in part; which clause is not judged yet"
}
out = append(out, s)
}
return out
}
// Ask is the records and clauses bearing on a question, ranked, each saying whether it holds or what changed it.
func (k *KB) Ask(question string, bound int) (*Asked, error) {
ctx := context.Background()
question = strings.TrimSpace(question)
if question == "" {
return nil, fmt.Errorf("ask needs a question, in plain words")
}
if len([]rune(question)) > MostQuestion {
return nil, fmt.Errorf("a question is at most %d characters; this one is %d: ask it in fewer words", MostQuestion, len([]rune(question)))
}
bound = boundOf(bound)
hits, err := k.Store.Search(ctx, question, 80)
if err != nil {
return nil, fmt.Errorf("the knowledge base cannot be searched (%v)", err)
}
type group struct {
key Key
clauses []int
record bool
// score is the record's own rank, its best clause's, and a little of each other clause's.
score, best, rest float64
}
var order []*group
byKey := map[Key]*group{}
for _, h := range hits {
g := byKey[h.Key]
if g == nil {
g = &group{key: h.Key}
byKey[h.Key] = g
order = append(order, g)
}
if h.Clause > 0 {
g.clauses = append(g.clauses, h.Clause)
if h.Rank > g.best {
g.rest += g.best
g.best = h.Rank
} else {
g.rest += h.Rank
}
} else {
g.record = true
g.score += h.Rank
}
}
for _, g := range order {
g.score += g.best + g.rest/4
}
sort.SliceStable(order, func(i, j int) bool { return order[i].score > order[j].score })
var records []AnswerRecord
for _, g := range order {
e, err := k.Store.Get(ctx, g.key)
if err != nil || e == nil {
continue
}
r := e.Current
ar := AnswerRecord{Record: g.key.String(), Title: r.Title, Status: r.Status, Read: readHow(g.key), Since: k.since(ctx, g.key)}
sort.Ints(g.clauses)
for _, n := range g.clauses {
if c := r.Clause(n); c != nil {
ar.Clauses = append(ar.Clauses, AnswerClause{N: n, Text: clip(c.Text, 220), Holds: held(r, *c), Source: c.Source})
}
}
if len(ar.Clauses) == 0 {
drafted := 0
for _, c := range r.Clauses {
if c.Source == SourceDrafted {
drafted++
}
}
switch {
case len(r.Clauses) == 0 && r.Kind == KindDecision:
ar.Note = "its decision is written as prose and its clauses are not drafted yet: read its Decision section"
case drafted > 0:
ar.Note = fmt.Sprintf("%d of its clauses are drafted and not confirmed: none of them is served as in force", drafted)
}
}
records = append(records, ar)
}
out := &Asked{Question: question, Bound: bound}
if len(records) == 0 {
out.Records = []AnswerRecord{}
out.Says = "nothing the knowledge base holds bears on that; ask in other words"
return out, nil
}
// Within the bound: the most records that fit; when not even one fits whole, its clauses shortened.
build := func(n int) any {
a := *out
a.Records = records[:n]
if n < len(records) {
names := []string{}
for _, r := range records[n:min(len(records), n+6)] {
names = append(names, r.Record)
}
a.Cut = fmt.Sprintf("%d more record(s) bear on it and are not shown within %d bytes (%s…): ask with a larger bound, or narrower words",
len(records)-n, bound, strings.Join(names, ", "))
}
return &a
}
v, n := fit(len(records), bound, build)
if n == 0 {
first := records[0]
for i := range first.Clauses {
first.Clauses[i].Text = clip(first.Clauses[i].Text, 80)
}
records[0] = first
v, _ = fit(len(records), bound, build)
}
return v.(*Asked), nil
}
// ---- get -----------------------------------------------------------------------------------------------------
// GetAsk is what get takes.
type GetAsk struct {
Kind string
Number int
Reasoning bool
Section string
Version int
Bound int
}
// Got is the answer to get.
type Got struct {
Record string `json:"record"`
Title string `json:"title"`
Status string `json:"status"`
Topic string `json:"topic,omitempty"`
Date string `json:"date,omitempty"`
Deciders []string `json:"deciders,omitempty"`
Version int `json:"version"`
Versions int `json:"versions"`
Clauses []AnswerClause `json:"clauses,omitempty"`
Links []string `json:"links,omitempty"`
LinkedFrom []string `json:"linked-from,omitempty"`
Since []string `json:"changed-since,omitempty"`
Sections []string `json:"sections,omitempty"`
Reasoning []Section `json:"reasoning,omitempty"`
Insights []Insight `json:"insights,omitempty"`
Pending string `json:"pending,omitempty"`
Waiting string `json:"waiting,omitempty"`
Reviews []Review `json:"reviews,omitempty"`
Drafting *Drafting `json:"drafting,omitempty"`
Review string `json:"review,omitempty"`
Source string `json:"source,omitempty"`
Reserved string `json:"reserved,omitempty"`
NeedsOperat bool `json:"needs-operator,omitempty"`
Cut string `json:"cut,omitempty"`
Bound int `json:"bound"`
Read string `json:"read,omitempty"`
// Reconstructed, Amended and Touches are the record's fields of those names; Fields, any other it holds as
// written (hq issue 487).
Reconstructed *bool `json:"reconstructed,omitempty"`
Amended []string `json:"amended,omitempty"`
Touches []string `json:"touches,omitempty"`
Fields map[string]string `json:"fields,omitempty"`
}
// Get is one record: its clauses, its links both ways, what changed it since; its reasoning only when asked.
func (k *KB) Get(a GetAsk) (*Got, error) {
ctx := context.Background()
if a.Kind == "" {
a.Kind = KindDecision
}
key := Key{a.Kind, a.Number}
e, err := k.Store.Get(ctx, key)
if err != nil {
return nil, fmt.Errorf("the knowledge base cannot be read (%v)", err)
}
if e == nil {
return nil, fmt.Errorf("the knowledge base holds no %s", key)
}
r, version := e.Current, e.CurrentVersion
if a.Version > 0 {
vs, err := k.Store.Versions(ctx, key)
if err != nil {
return nil, err
}
if a.Version > len(vs) {
return nil, fmt.Errorf("%s has %d version(s); there is no version %d", key, len(vs), a.Version)
}
r, version = vs[a.Version-1].Record, a.Version
}
bound := boundOf(a.Bound)
g := &Got{Record: key.String(), Title: r.Title, Status: r.Status, Topic: r.Topic, Date: r.Date, Deciders: r.Deciders,
Version: version, Versions: e.Versions, Insights: r.Insights, Reviews: r.Reviews, Drafting: r.Drafting, Source: r.Source,
NeedsOperat: r.NeedsOperator, Bound: bound, Since: k.since(ctx, key),
Reconstructed: r.Reconstructed, Amended: r.Amended, Touches: r.Touches, Fields: r.Extra}
if r.Status == StatusReserved || r.Status == StatusWithdrawn {
g.Reserved = fmt.Sprintf("reserved by %s at %s for %q", r.ReservedBy, r.ReservedAt.Format("2006-01-02 15:04 UTC"), r.ReservedFor)
}
if e.Pending != nil && a.Version == 0 {
g.Pending = fmt.Sprintf("version %d, a progressive insight, waits for its review: history shows what it changes", e.PendingVersion)
}
if r.Ask != nil {
g.Waiting = "the operator's ask: " + r.Ask.Said
}
if a.Version == 0 {
g.Review = reviewSays(e.Working())
}
for _, c := range r.Clauses {
g.Clauses = append(g.Clauses, AnswerClause{N: c.N, Text: c.Text, Holds: held(r, c), Source: c.Source})
}
for _, l := range r.Links {
g.Links = append(g.Links, l.String())
}
if in, err := k.Store.Inbound(ctx, key); err == nil {
for _, x := range in {
s := x.From.String() + " "
if x.Link.From > 0 {
s += fmt.Sprintf("§%d ", x.Link.From)
}
s += x.Link.Kind
if x.Link.To.Clause > 0 {
s += fmt.Sprintf(" §%d", x.Link.To.Clause)
}
g.LinkedFrom = append(g.LinkedFrom, s)
}
}
for _, s := range r.Sections {
g.Sections = append(g.Sections, s.Name)
if a.Reasoning || (a.Section != "" && strings.EqualFold(a.Section, s.Name)) {
g.Reasoning = append(g.Reasoning, s)
}
}
if a.Section != "" && len(g.Reasoning) == 0 {
return nil, fmt.Errorf("%s has no section %q; it has %s", key, a.Section, strings.Join(g.Sections, ", "))
}
if !a.Reasoning && a.Section == "" {
g.Read = "its reasoning: " + readHow(key) + ", or one section with section"
}
if size(g) <= bound {
return g, nil
}
// Cut: the reasoning first, section by section from the end, then the reviews but the last, then the links
// from the end, then the clauses' text, then clauses; each cut is said. Room is kept for saying it.
room := bound - 600
var cut []string
if len(g.Reasoning) > 0 {
reasoning := g.Reasoning
v, n := fit(len(reasoning), room, func(n int) any { c := *g; c.Reasoning = reasoning[:n]; return &c })
*g = *(v.(*Got))
if n < len(reasoning) {
left := []string{}
for _, s := range reasoning[n:] {
left = append(left, s.Name)
}
cut = append(cut, fmt.Sprintf("the sections %s are not shown within %d bytes: ask for one with section, or raise bound", strings.Join(left, ", "), bound))
if spare := room - size(g); n == 0 && spare > 400 {
s := reasoning[0]
s.Text = clip(s.Text, spare/2)
g.Reasoning = []Section{s}
cut[len(cut)-1] = fmt.Sprintf("the section %s is shortened, and %s not shown: ask for one with section, or raise bound",
s.Name, strings.Join(append([]string{}, left[1:]...), ", "))
}
}
}
if size(g) > room && len(g.Reviews) > 1 {
cut = append(cut, fmt.Sprintf("%d earlier review(s) are not shown: history lists every version", len(g.Reviews)-1))
g.Reviews = g.Reviews[len(g.Reviews)-1:]
}
for _, which := range []string{"linked-from", "links"} {
if size(g) <= room {
break
}
list := func(c *Got) *[]string {
if which == "links" {
return &c.Links
}
return &c.LinkedFrom
}
full := *list(g)
v, n := fit(len(full), room, func(n int) any { c := *g; *list(&c) = full[:n]; return &c })
if n < len(full) {
*g = *(v.(*Got))
cut = append(cut, fmt.Sprintf("%d of its %s are not shown: links lists them all", len(full)-n, which))
}
}
if size(g) > room {
for i := range g.Clauses {
g.Clauses[i].Text = clip(g.Clauses[i].Text, 160)
}
cut = append(cut, "the clauses are shortened: raise bound to read them whole")
}
if size(g) > room {
clauses := g.Clauses
v, n := fit(len(clauses), room, func(n int) any { c := *g; c.Clauses = clauses[:n]; return &c })
*g = *(v.(*Got))
cut = append(cut, fmt.Sprintf("%d clause(s) are not shown: raise bound", len(clauses)-n))
}
g.Cut = strings.Join(cut, "; ")
return g, nil
}
// ---- holds ---------------------------------------------------------------------------------------------------
// HeldClause is one clause in force, with the record it comes from.
type HeldClause struct {
Record string `json:"record"`
N int `json:"n"`
Text string `json:"text"`
Holds string `json:"holds"`
}
// Holding is the answer to holds.
type Holding struct {
Of string `json:"of"`
Clauses []HeldClause `json:"clauses"`
NotYet string `json:"not-served,omitempty"`
Cut string `json:"cut,omitempty"`
Bound int `json:"bound"`
}
// Holds is the clauses in force for a topic or a record: never a drafted one, and a confirmed one marked.
func (k *KB) Holds(topic string, key *Key, bound int) (*Holding, error) {
ctx := context.Background()
bound = boundOf(bound)
var records []*Record
of := ""
switch {
case key != nil:
e, err := k.Store.Get(ctx, *key)
if err != nil {
return nil, err
}
if e == nil {
return nil, fmt.Errorf("the knowledge base holds no %s", key)
}
records, of = []*Record{e.Current}, key.String()
case strings.TrimSpace(topic) != "":
all, err := k.Store.All(ctx)
if err != nil {
return nil, err
}
for _, e := range all {
if e.Current.Topic == topic {
records = append(records, e.Current)
}
}
of = "the topic " + topic
if len(records) == 0 {
return nil, fmt.Errorf("no record has the topic %q", topic)
}
default:
return nil, fmt.Errorf("holds takes a topic or a record's number")
}
out := &Holding{Of: of, Bound: bound, Clauses: []HeldClause{}}
drafted, prose := 0, 0
for _, r := range records {
if r.Kind == KindDecision && r.Accepted() && len(r.Clauses) == 0 {
prose++
}
for _, c := range r.Clauses {
if c.Source == SourceDrafted && r.Accepted() && c.Status != ClauseSuperseded {
drafted++
}
if Served(r, c) {
out.Clauses = append(out.Clauses, HeldClause{Record: r.Key().String(), N: c.N, Text: c.Text, Holds: held(r, c)})
}
}
}
var notes []string
if drafted > 0 {
notes = append(notes, fmt.Sprintf("%d drafted clause(s) are not served: they are not confirmed as the record's word", drafted))
}
if prose > 0 {
notes = append(notes, fmt.Sprintf("%d accepted decision(s) write their decision as prose and have no clauses yet", prose))
}
out.NotYet = strings.Join(notes, "; ")
clauses := out.Clauses
v, n := fit(len(clauses), bound, func(n int) any {
c := *out
c.Clauses = clauses[:n]
if n < len(clauses) {
c.Cut = fmt.Sprintf("%d more clause(s) are in force and not shown within %d bytes: raise bound, or ask holds of one record", len(clauses)-n, bound)
}
return &c
})
if n == 0 && len(clauses) > 0 {
for i := range clauses {
clauses[i].Text = clip(clauses[i].Text, 160)
}
v, _ = fit(len(clauses), bound, func(n int) any {
c := *out
c.Clauses = clauses[:n]
c.Cut = fmt.Sprintf("the clauses are shortened, and %d not shown within %d bytes: raise bound", len(clauses)-n, bound)
return &c
})
}
return v.(*Holding), nil
}
// ---- links ---------------------------------------------------------------------------------------------------
// Linked is the answer to links.
type Linked struct {
Record string `json:"record"`
Out []LinkRow `json:"out"`
In []LinkRow `json:"in"`
}
// LinkRow is one link, read from either end.
type LinkRow struct {
Kind string `json:"kind"`
From string `json:"from"`
To string `json:"to"`
Source string `json:"source,omitempty"`
Unresolved bool `json:"unresolved,omitempty"`
}
// Links is a record's links, both ways.
func (k *KB) Links(key Key) (*Linked, error) {
ctx := context.Background()
e, err := k.Store.Get(ctx, key)
if err != nil {
return nil, err
}
if e == nil {
return nil, fmt.Errorf("the knowledge base holds no %s", key)
}
out := &Linked{Record: key.String(), Out: []LinkRow{}, In: []LinkRow{}}
from := func(k Key, clause int) string {
if clause > 0 {
return k.String() + " §" + strconv.Itoa(clause)
}
return k.String()
}
for _, l := range e.Current.Links {
out.Out = append(out.Out, LinkRow{Kind: l.Kind, From: from(key, l.From), To: l.To.String(), Source: l.Source, Unresolved: l.Unresolved})
}
in, err := k.Store.Inbound(ctx, key)
if err != nil {
return nil, err
}
for _, x := range in {
out.In = append(out.In, LinkRow{Kind: x.Link.Kind, From: from(x.From, x.Link.From), To: x.Link.To.String(), Source: x.Link.Source})
}
return out, nil
}
// ---- history -------------------------------------------------------------------------------------------------
// VersionRow is one version in a history.
type VersionRow struct {
N int `json:"n"`
By string `json:"by"`
At string `json:"at"`
Why string `json:"why"`
Status string `json:"status"`
Insight bool `json:"insight,omitempty"`
Served bool `json:"served,omitempty"`
Pending bool `json:"pending,omitempty"`
}
// Historied is the answer to history.
type Historied struct {
Record string `json:"record"`
Versions []VersionRow `json:"versions"`
From int `json:"from,omitempty"`
To int `json:"to,omitempty"`
Diff []string `json:"diff,omitempty"`
Same bool `json:"same,omitempty"`
Cut string `json:"cut,omitempty"`
Bound int `json:"bound,omitempty"`
}
// History is a record's versions, and the difference between two of them, line by line.
func (k *KB) History(key Key, from, to, bound int) (*Historied, error) {
ctx := context.Background()
e, err := k.Store.Get(ctx, key)
if err != nil {
return nil, err
}
if e == nil {
return nil, fmt.Errorf("the knowledge base holds no %s", key)
}
vs, err := k.Store.Versions(ctx, key)
if err != nil {
return nil, err
}
out := &Historied{Record: key.String()}
for _, v := range vs {
out.Versions = append(out.Versions, VersionRow{N: v.N, By: v.By, At: v.At.UTC().Format("2006-01-02 15:04:05 UTC"), Why: v.Why,
Status: v.Record.Status, Insight: v.Insight, Served: v.N == e.CurrentVersion, Pending: v.N == e.PendingVersion})
}
if from == 0 && to == 0 {
out.Bound = boundOf(bound)
versions := out.Versions
v, m := fit(len(versions), out.Bound-300, func(m int) any { c := *out; c.Versions = versions[len(versions)-m:]; return &c })
*out = *(v.(*Historied))
if m < len(versions) {
out.Cut = fmt.Sprintf("the %d oldest versions are not listed within %d bytes: raise bound", len(versions)-m, out.Bound)
}
return out, nil
}
if from < 1 || to < 1 || from > len(vs) || to > len(vs) {
return nil, fmt.Errorf("%s has versions 1 to %d; from and to name two of them", key, len(vs))
}
out.From, out.To, out.Bound = from, to, boundOf(bound)
diff := Diff(Render(vs[from-1].Record), Render(vs[to-1].Record))
out.Same = len(diff) == 0
// Within the bound: the most of the difference's lines that fit, cut at a hunk's line and said; and when the
// versions alone do not fit, the oldest of them left out first.
versions := out.Versions
v, n := fit(len(diff), out.Bound-300, func(n int) any { c := *out; c.Diff = diff[:n]; return &c })
*out = *(v.(*Historied))
var cut []string
if n < len(diff) {
cut = append(cut, fmt.Sprintf("%d of the difference's %d lines are not shown within %d bytes: raise bound, or compare nearer versions",
len(diff)-n, len(diff), out.Bound))
}
if size(out) > out.Bound-300 {
v, m := fit(len(versions), out.Bound-300, func(m int) any { c := *out; c.Versions = versions[len(versions)-m:]; return &c })
*out = *(v.(*Historied))
cut = append(cut, fmt.Sprintf("the %d oldest versions are not listed", len(versions)-m))
}
out.Cut = strings.Join(cut, "; ")
return out, nil
}
// MostDiffWork bounds the work of a difference: the lines of the two texts' changed middles multiplied. Past it,
// the middle is given whole as removed and added — still exact, only less narrow.
const MostDiffWork = 4_000_000
// Diff is the difference of two texts, line by line: each hunk headed "@@ -a,n +b,m @@", its lines "-" removed
// and "+" added, by the longest common subsequence of their lines.
func Diff(a, b string) []string {
x, y := strings.Split(a, "\n"), strings.Split(b, "\n")
// The common head and tail cost nothing to find; only the middle between them is compared.
head := 0
for head < len(x) && head < len(y) && x[head] == y[head] {
head++
}
tail := 0
for tail < len(x)-head && tail < len(y)-head && x[len(x)-1-tail] == y[len(y)-1-tail] {
tail++
}
x, y = x[head:len(x)-tail], y[head:len(y)-tail]
n, m := len(x), len(y)
if n*m > MostDiffWork {
var out []string
if n+m > 0 {
out = append(out, fmt.Sprintf("@@ -%d,%d +%d,%d @@", head+1, n, head+1, m))
}
for _, l := range x {
out = append(out, "-"+l)
}
for _, l := range y {
out = append(out, "+"+l)
}
return out
}
lcs := make([][]int, n+1)
for i := range lcs {
lcs[i] = make([]int, m+1)
}
for i := n - 1; i >= 0; i-- {
for j := m - 1; j >= 0; j-- {
if x[i] == y[j] {
lcs[i][j] = lcs[i+1][j+1] + 1
} else {
lcs[i][j] = max(lcs[i+1][j], lcs[i][j+1])
}
}
}
var out []string
var hunk []string
ha, hb, ca, cb := 0, 0, 0, 0
flush := func() {
if len(hunk) > 0 {
out = append(out, fmt.Sprintf("@@ -%d,%d +%d,%d @@", ha, ca, hb, cb))
out = append(out, hunk...)
hunk = nil
}
}
i, j := 0, 0
for i < n || j < m {
switch {
case i < n && j < m && x[i] == y[j]:
flush()
i++
j++
case i < n && (j == m || lcs[i+1][j] >= lcs[i][j+1]):
if len(hunk) == 0 {
ha, hb, ca, cb = head+i+1, head+j+1, 0, 0
}
hunk = append(hunk, "-"+x[i])
ca++
i++
default:
if len(hunk) == 0 {
ha, hb, ca, cb = head+i+1, head+j+1, 0, 0
}
hunk = append(hunk, "+"+y[j])
cb++
j++
}
}
flush()
return out
}
// reviewSays is where a proposed version stands in the knowledge base's own review, in words; "" when nothing waits.
func reviewSays(r *Record) string {
j := r.Judging
if j == nil || r.Status != StatusProposed {
return ""
}
switch j.State {
case JudgingDue:
return "waiting for the knowledge base's review"
case JudgingUnproven:
return "not reviewed: " + j.Reason
}
// When it is asked again is said, so a pause is not taken for a review lost (novox/hq issue 497).
return fmt.Sprintf("not reviewed: %s (%s; no judgement that could not be had counts as an approval", j.Reason, j.owedSays())
}