From ddb2ae942030bbb46e24ec7b83d3ed49438d5ab5 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 6 Nov 2026 15:39:16 +0100 Subject: [PATCH] knowledge-base get shows the findings of the pending version's reviews (issue 1970) Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- modules/mesh-knowledge/README.md | 2 +- .../cmd/mesh-knowledge/active_effort_test.go | 29 ++++++++++++++++ .../cmd/mesh-knowledge/answers.go | 33 ++++++++++++++----- .../mesh-knowledge/cmd/mesh-knowledge/main.go | 2 +- .../cmd/mesh-knowledge/review_test.go | 9 +++++ 5 files changed, 65 insertions(+), 10 deletions(-) diff --git a/modules/mesh-knowledge/README.md b/modules/mesh-knowledge/README.md index 5804bf92c..2c776eca5 100644 --- a/modules/mesh-knowledge/README.md +++ b/modules/mesh-knowledge/README.md @@ -16,7 +16,7 @@ operator, it is accepted only on the operator's Approve, given on a channel that | Verb | Takes | Answers | |---|---|---| | `ask` | `question` (at most 500 characters), `bound` | the records and clauses bearing on the question, ranked; each clause says whether it holds, what changed it, or that it is only drafted; each record says what changed it since and how to read its reasoning | -| `get` | `number`, `kind`, `reasoning`, `section`, `version`, `bound` | one record: clauses, links both ways, what changed it since, versions, reviews, the operator's ask; its reasoning only when asked | +| `get` | `number`, `kind`, `reasoning`, `section`, `version`, `bound` | one record: clauses, links both ways, what changed it since, versions, reviews, the pending version's reviews with their findings (`pending-reviews`), the operator's ask; its reasoning only when asked | | `list` | `kind`, `status`, `topic`, `flag` (`research-done` or `proposed-old`), `before` (a number, the cursor), `limit` | short rows, newest first, number by number: number, kind, title, status, topic, date, versions, and whether a proposal is pending; `next`, the `before` that reads on, when more match | | `findings` | `number` (optional) | the whole-set findings kept — each a rule's failure, the records it names, what it says, and when; with `number`, only the findings naming that record | | `holds` | `topic` or `number`, `bound` | the clauses in force for a topic of the reading order or for one record; never a drafted clause | diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/active_effort_test.go b/modules/mesh-knowledge/cmd/mesh-knowledge/active_effort_test.go index 3302dec07..3058781b8 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/active_effort_test.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/active_effort_test.go @@ -1,6 +1,8 @@ package main import ( + "context" + "fmt" "strings" "testing" "time" @@ -118,6 +120,33 @@ func TestAChangesVerdictLeavesTheServedVersionServedAndGraduatingNeedsABecameLin e.Current.Status != StatusActive || e.Current.Section("01-findings.md") != "" { t.Fatalf("after changes: pending %v, current %+v", e.Pending != nil, e.Current.Sections) } + // 1970/AC1: get with no version shows the pending version's findings, and says where the version stands. + g, err := w.k.Get(GetAsk{Kind: KindResearch, Number: r.Number}) + if err != nil { + t.Fatal(err) + } + if len(g.PendingReviews) != 1 || g.PendingReviews[0].Verdict != VerdictChanges || g.PendingReviews[0].Version == 0 || + !strings.Contains(g.PendingReviews[0].Findings, "say where the finding comes from") || len(g.Reviews) != 1 || + !strings.Contains(g.Pending, "changes asked") || !strings.Contains(g.Pending, "pending-reviews") { + t.Fatalf("pending findings not shown: %+v, pending %q, reviews %+v", g.PendingReviews, g.Pending, g.Reviews) + } + // 1970/AC3: under a small bound the last pending review is kept, and the cut is said. + e2 := mustGet(t, w.k.Store, key) + more := e2.Pending.Clone() + for i := 0; i < 3; i++ { + more.Reviews = append(more.Reviews, Review{By: "r", Verdict: VerdictChanges, Version: 9, Findings: strings.Repeat("a long finding ", 60) + fmt.Sprint(i)}) + } + if err := w.k.Store.Save(context.Background(), Version{By: "author@g14", Why: "test", Record: more}, false, false, e2.Revision); err != nil { + t.Fatal(err) + } + g, err = w.k.Get(GetAsk{Kind: KindResearch, Number: r.Number, Bound: LeastBound}) + if err != nil { + t.Fatal(err) + } + if len(g.PendingReviews) != 1 || !strings.HasSuffix(g.PendingReviews[0].Findings, "2") || + !strings.Contains(g.Cut, "earlier pending review") { + t.Fatalf("pending cut: %d kept, cut %q", len(g.PendingReviews), g.Cut) + } grad := effortAsk(r.Number, StatusGraduated, overview) if _, err := w.k.Propose("author@g14", grad); err == nil || !strings.Contains(err.Error(), "became") { t.Fatalf("graduated without a became link: %v", err) diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/answers.go b/modules/mesh-knowledge/cmd/mesh-knowledge/answers.go index 7c42b1f43..4a9941fb3 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/answers.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/answers.go @@ -312,14 +312,17 @@ type Got struct { Idea string `json:"idea,omitempty"` Waiting string `json:"waiting,omitempty"` // Retracted are the retractions the operator approved; Retracting, one waiting for the operator. - Retracted []RetractionRow `json:"retracted,omitempty"` - Retracting string `json:"retracting,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"` + Retracted []RetractionRow `json:"retracted,omitempty"` + Retracting string `json:"retracting,omitempty"` + Reviews []Review `json:"reviews,omitempty"` + // PendingReviews are the reviews of the pending version, with their findings, when get is asked without a + // version (issue 1970): the version waits for its author, and these are what it must answer. + PendingReviews []Review `json:"pending-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"` // Brief is what the operator's ask shows of it (issue 639). Brief *Brief `json:"brief,omitempty"` Cut string `json:"cut,omitempty"` @@ -371,7 +374,17 @@ func (k *KB) Get(a GetAsk) (*Got, error) { g.Idea = "an idea: research it again before anything rests on it" } if e.Pending != nil && a.Version == 0 { + g.PendingReviews = e.Pending.Reviews g.Pending = fmt.Sprintf("version %d, a progressive insight or a new version of an active effort, waits for its review: history shows what it changes", e.PendingVersion) + if n := len(e.Pending.Reviews); n > 0 && e.Pending.Reviews[n-1].Verdict == VerdictChanges { + last := e.Pending.Reviews[n-1] + k := len(strings.Split(strings.TrimSpace(last.Findings), "\n")) + if strings.TrimSpace(last.Findings) == "" { + k = 0 + } + g.Pending = fmt.Sprintf("version %d: changes asked (%d finding(s)) on version %d, shown in pending-reviews; it waits for its author's revision", + e.PendingVersion, k, last.Version) + } } if r.Ask != nil { g.Waiting = "the operator's ask: " + r.Ask.Said @@ -452,6 +465,10 @@ func (k *KB) Get(a GetAsk) (*Got, error) { 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:] } + if size(g) > room && len(g.PendingReviews) > 1 { + cut = append(cut, fmt.Sprintf("%d earlier pending review(s) are not shown: get with the pending version's number shows them", len(g.PendingReviews)-1)) + g.PendingReviews = g.PendingReviews[len(g.PendingReviews)-1:] + } for _, which := range []string{"linked-from", "links"} { if size(g) <= room { break diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/main.go b/modules/mesh-knowledge/cmd/mesh-knowledge/main.go index 5882e6af3..38c37131c 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/main.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/main.go @@ -783,7 +783,7 @@ func Tools(k *KB, caller func(map[string]any) string) []stdio.Tool { }}, {Name: Seat + ".get", Description: "One record by its number: its title, status, topic, date and deciders; its clauses, each with whether it holds; " + - "its links both ways; what changed it since; its versions; its reviews, and the operator's ask when it waits for one. " + + "its links both ways; what changed it since; its versions; its reviews, and the findings of the pending version's reviews (pending-reviews) when one waits for its author; the operator's ask when it waits for one. " + "Its reasoning (context, options, consequences, how it is checked; a research effort's documents) only with reasoning, " + "or one section by name; an earlier version with version. Within a bound it states, 4 KiB unless asked for more, " + "saying what it cut.", diff --git a/modules/mesh-knowledge/cmd/mesh-knowledge/review_test.go b/modules/mesh-knowledge/cmd/mesh-knowledge/review_test.go index 51221ab22..3d6e79d4d 100644 --- a/modules/mesh-knowledge/cmd/mesh-knowledge/review_test.go +++ b/modules/mesh-knowledge/cmd/mesh-knowledge/review_test.go @@ -361,6 +361,15 @@ func TestAnAcceptedRecordChangesOnlyByAnInsightReviewedLikeAProposal(t *testing. e.Pending.Reviews[0].Verdict != VerdictChanges { t.Fatalf("the insight was served before its review approved it: %+v", e.Current.Sections) } + // 1970/AC2: the same for a pending insight. + g, err := w.k.Get(GetAsk{Kind: KindDecision, Number: n}) + if err != nil { + t.Fatal(err) + } + if len(g.PendingReviews) != 1 || !strings.Contains(g.PendingReviews[0].Findings, "what a minute is measured from") || + !strings.Contains(g.Pending, "changes asked") { + t.Fatalf("pending insight findings not shown: %+v, %q", g.PendingReviews, g.Pending) + } w.judge.review = "" p.Sections[2].Text = "Merges land sooner, by about a minute from the last merge." if _, err := w.k.Propose("author@g14", p); err != nil {