docs: tier the tree by lifetime, so staleness shows in the path (V-446)

Seventeen markdown files at the repo root, twelve of them dated one-shot
reports sitting next to CLAUDE.md. That is why stale docs read as
current: nothing in the path said which was which.

Root now keeps CLAUDE.md and AGENTS.md. Living docs move under docs/
and carry a Last verified line. Dated measurements move to docs/evals/
ISO-prefixed, and are never edited after the day, so a newer number is
a new file. The senior review moves to docs/archive/.

Every reference was rewritten across markdown, Go comments, the Makefile
and the recall fixture. The touched Go packages still build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 03:28:49 +04:00
parent 7079a240f7
commit 93987f2dfc
38 changed files with 99 additions and 85 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
// Package auth is maven's authority layer — the 4-layer cascade and the
// "surface caps authority" invariant.
//
// Spec contract (from DESIGN.md § Auth):
// Spec contract (from docs/design.md § Auth):
//
// a cascade, not a pick-one — each layer answers a different question:
//
+1 -1
View File
@@ -649,7 +649,7 @@ type VoiceConfig struct {
// LLMRouter — route with the resident model instead of the embedding
// classifier. On by default since Vikunja #320.
//
// Measured on the held-out fixture (ROUTING-EVAL-31-07-2026.md): 63.2% of
// Measured on the held-out fixture (docs/evals/2026-07-31-routing.md): 63.2% of
// intents right against the classifier's 50.0%, and no route errors. It
// costs about 1s per turn instead of 30ms.
//
+1 -1
View File
@@ -1,6 +1,6 @@
// Package delivery is maven's channel-routing + dispatch layer.
//
// Spec contract (from DESIGN.md § Delivery / channel routing):
// Spec contract (from docs/design.md § Delivery / channel routing):
//
// - routing = f(severity, presence). presence decides REACHABILITY; severity
// decides INSISTENCE. need both.
+2 -2
View File
@@ -9,7 +9,7 @@ import (
"github.com/kami/maven/internal/store"
)
// This file walks every cell of the DESIGN.md § "Delivery / channel routing"
// This file walks every cell of the docs/design.md § "Delivery / channel routing"
// table, once as the pure table and once through the dispatcher, so a change
// to either side has to break a named cell.
//
@@ -205,7 +205,7 @@ func TestAwayChannelsGetMinimalBody(t *testing.T) {
// An empty Summary no longer means "send the whole body" — it means a short
// generic line — so the old expectation here was wrong as well as duplicated.
// TestCareAwayDropIsRecorded — DESIGN.md's drop is a decision ("a missed water
// TestCareAwayDropIsRecorded — docs/design.md's drop is a decision ("a missed water
// nudge is noise, a missed backup failure isn't"), so it should be visible
// rather than vanish. Today drop is a bare `continue`: no nudge row, no outbox
// attempt, no log — nothing an operator can see afterwards. now it leaves a
+1 -1
View File
@@ -31,7 +31,7 @@ type Completer interface {
// or reply in Russian.
//
// Why the JSON wrapper: this model always thinks out loud and this llama-server
// build ignores the thinking switch (see ROUTING-EVAL-31-07-2026.md). A bare
// build ignores the thinking switch (see docs/evals/2026-07-31-routing.md). A bare
// word-list grammar just captured the reasoning — every case came back as
// "Let me analyze this request carefully". Demanding JSON, like routeGrammar and
// responseGrammar already do, gives the reasoning nowhere to go.
+5 -5
View File
@@ -10,12 +10,12 @@ import (
// Tests for the universal restraint gate.
//
// DESIGN.md § Trigger model: "the gate is universal, applied by the loop, never
// docs/design.md § Trigger model: "the gate is universal, applied by the loop, never
// per-rule — quiet-hours, presence, cooldown, snooze, calendar-busy all live in
// one fires()." These tests pin the CONSERVATIVE side of that: the cases where
// Maven must stay quiet. They exist so nobody loosens the gate by accident.
//
// Where the code does not yet do what DESIGN.md promises, the test is written to
// Where the code does not yet do what docs/design.md promises, the test is written to
// show the gap and then skipped, with the file and line to fix. Behaviour is not
// changed to make a test pass.
@@ -54,7 +54,7 @@ func TestGateQuietHoursSuppressesCareOnly(t *testing.T) {
// ---------------------------- presence ---------------------------------------
// DESIGN.md § Delivery: "sev <= 2 drops on away, sev >= 3 holds: a missed water
// docs/design.md § Delivery: "sev <= 2 drops on away, sev >= 3 holds: a missed water
// nudge is noise, a missed backup failure isn't."
func TestGateAwayDropsCareHoldsOps(t *testing.T) {
cases := []struct {
@@ -250,7 +250,7 @@ func TestTickOrderOfRulesDoesNotMatter(t *testing.T) {
// ---------------------------- reminders bypass the gate ----------------------
// DESIGN.md § User reminders: "bypasses the restraint gate — 'wake me 7' fires
// docs/design.md § User reminders: "bypasses the restraint gate — 'wake me 7' fires
// in quiet hours; that's the point." Every suppressor set at once, and the
// reminder still comes through.
func TestRemindersBypassEverySuppressor(t *testing.T) {
@@ -269,7 +269,7 @@ func TestRemindersBypassEverySuppressor(t *testing.T) {
}
}
// GAP — DESIGN.md § User reminders ends "Snooze still applies." RemindDecisions
// GAP — docs/design.md § User reminders ends "Snooze still applies." RemindDecisions
// passes every due reminder straight through with no snooze check, so a snoozed
// reminder fires anyway. The test below is what the contract asks for.
func TestRemindersStillHonourSnooze(t *testing.T) {
+4 -4
View File
@@ -18,7 +18,7 @@ import (
// - does it stay quiet when it should?
// - is it silent when the key it needs has no data at all?
//
// The last one is load-bearing. DESIGN.md: "since(key)==null → don't fire.
// The last one is load-bearing. docs/design.md: "since(key)==null → don't fire.
// Silence on no-data is 'shuts up when uncertain'."
// stateWith builds a snapshot at refTime() holding just the given facts.
@@ -179,7 +179,7 @@ func TestCareRulePredicates(t *testing.T) {
// ---------------------------- ops rules --------------------------------------
// The two ops rules match on a value AND on which poller wrote it. DESIGN.md:
// The two ops rules match on a value AND on which poller wrote it. docs/design.md:
// "a compromised poller must not be able to forge a trigger." Half of this
// table is forgery attempts; all of them must be refused.
func TestOpsRulePredicates(t *testing.T) {
@@ -331,7 +331,7 @@ func TestNoDefaultRuleFiresOnEmptyState(t *testing.T) {
}
}
// Severities are the delivery contract (DESIGN.md § Delivery / channel
// Severities are the delivery contract (docs/design.md § Delivery / channel
// routing): care is sev1-2 and drops when away, ops is sev3-4 and holds. Pin
// them so a change to a rule's insistence has to be deliberate.
func TestDefaultRuleSeverities(t *testing.T) {
@@ -356,7 +356,7 @@ func TestDefaultRuleSeverities(t *testing.T) {
}
}
// Cooldown bounds keep the feedback tuner honest — DESIGN.md wants
// Cooldown bounds keep the feedback tuner honest — docs/design.md wants
// `cooldown in [min,max]` "so a weird week can't mutate Maven silent or
// stalker". A base outside its own envelope would make that meaningless.
func TestDefaultRuleCooldownsAreBounded(t *testing.T) {
+1 -1
View File
@@ -41,7 +41,7 @@
"tags": ["preference", "homelab", "paraphrase", "hard"],
"query": "когда запускать резервное копирование",
"want": "n1",
"note": "The DESIGN.md preference-seam example, phrased as the operator would ask it later.",
"note": "The docs/design.md preference-seam example, phrased as the operator would ask it later.",
"notes": [
{"id": "n1", "text": "бэкапы лучше делать ночью в три часа", "kind": "note"},
{"id": "n2", "text": "обновления ставлю по субботам", "kind": "note"},
+3 -3
View File
@@ -15,7 +15,7 @@ const (
CheckLang = "lang" // the operator's language, not the prompt's
CheckLength = "length" // a nudge is one sentence, not a paragraph
CheckFeminine = "feminine" // her self-reference is feminine (hard constraint)
CheckCringe = "cringe" // DESIGN.md § Non-goals, "not a relationship"
CheckCringe = "cringe" // docs/design.md § Non-goals, "not a relationship"
CheckOnTopic = "ontopic" // says the thing the rule is about
// CheckHisGender — the other half of the persona rule: SHE is feminine, HE
@@ -105,7 +105,7 @@ func checkLength(body string) Result {
// --- feminine self-reference ---------------------------------------------
//
// The hard constraint (CLAUDE.md, DESIGN.md § Identity): Maven's Russian
// The hard constraint (CLAUDE.md, docs/design.md § Identity): Maven's Russian
// self-reference is feminine. The operator is male, so second-person forms
// addressed to him are MASCULINE and must not be flagged — "ты не пил воду" is
// correct, "я напомнил" is not. Both directions matter, which is why this is a
@@ -497,7 +497,7 @@ func isLatinWord(w string) bool {
// --- the cringe checks ---------------------------------------------------
//
// "Think Jarvis without the cringe part". DESIGN.md § Non-goals: "Not a
// "Think Jarvis without the cringe part". docs/design.md § Non-goals: "Not a
// relationship — mom-tone is a function that makes nudges land, not emotional
// company. Names the drift a warm small model falls into." Each pattern below
// is one shape of that drift. They are deliberately specific: a check that
+2 -2
View File
@@ -11,7 +11,7 @@
// length test that a human can read and disagree with. A score here is a claim
// about measurable properties, not about whether a sentence is good.
//
// DESIGN.md § "Rules decide, LLM phrases" is why there is no send/veto signal
// docs/design.md § "Rules decide, LLM phrases" is why there is no send/veto signal
// anywhere in this package: the rule already decided she speaks. The phraser
// only words it, so a nudge the model refuses to write is a failure, never a
// legitimate outcome.
@@ -38,7 +38,7 @@ var fixtureJSON []byte
const SchemaVersion = 1
// Case — one nudge situation, as a real tick would present it. The fields are
// the (rule, severity, context) input DESIGN.md names, flattened to JSON.
// the (rule, severity, context) input docs/design.md names, flattened to JSON.
//
// WantAny is the on-topic contract: at least one of these lowercased fragments
// must appear in the message. A water nudge that never mentions water is a
+1 -1
View File
@@ -692,7 +692,7 @@ func (p *LLMPhraser) chatWithSystem(ctx context.Context, system, user string, ma
// Written as filled-in examples, not as a schema with "..." in it. A 0.8B
// copies whatever sits in the response slot, so a literal placeholder there
// teaches it to answer with the placeholder. Measured: 7/15 nudges came back
// as "..." before this. See PHRASING-EVAL-31-07-2026.md.
// as "..." before this. See docs/evals/2026-07-31-phrasing.md.
//
// Russian only, feminine self-reference, second person masculine (the owner is
// a man). She talks TO him, informally, singular — never "вы", never "он".
+2 -2
View File
@@ -1,9 +1,9 @@
// Package phraser is maven's "rules decide, llm phrases" seam — the layer
// that turns a loop decision into the body + summary the delivery module ships.
//
// Per DESIGN.md § Resident language model: the phraser is the resident model
// Per docs/design.md § Resident language model: the phraser is the resident model
// (Qwen3-1.7B — RU continued pretraining plus joint persona/router SFT, not a
// sub-1b prompted-only model as the retired spec claimed; see DESIGN.md
// sub-1b prompted-only model as the retired spec claimed; see docs/design.md
// § Superseded, "small-model phrasing claim"). It takes
// (rule, severity, context) and produces Body (full voice message, local — no
// shoulder-surf concern beyond who's in the room) + Summary (minimal body for
+1 -1
View File
@@ -39,7 +39,7 @@ import (
// Re-measured with everything else held equal, thinking off scores exactly the
// same, case for case — and a direct probe shows this llama-server build ignores
// enable_thinking / reasoning_budget for this model anyway, so there was nothing
// to turn off. Full write-up in ROUTING-EVAL-31-07-2026.md (Vikunja #376).
// to turn off. Full write-up in docs/evals/2026-07-31-routing.md (Vikunja #376).
func TestLLMRouterBaseline(t *testing.T) {
base := os.Getenv("MAVEN_LLM_URL")
if base == "" {
+3 -3
View File
@@ -1,13 +1,13 @@
// Package router is maven's reactive path — the cascade that turns a free-form
// utterance into a deterministic Decision.
//
// Spec contract (from DESIGN.md § Reactive path — routing):
// Spec contract (from docs/design.md § Reactive path — routing):
//
// - the TARGET design is LLM-as-router: the resident model (Qwen3-1.7B)
// emits GBNF-constrained structured JSON for the route, and the same
// model phrases replies; the embedder is a RAG hint, not a routing gate.
// the classifier/embedder cascade below is the committed default today,
// but it is an interim stopgap (DESIGN.md § Superseded, "classifier-owns-
// but it is an interim stopgap (docs/design.md § Superseded, "classifier-owns-
// the-route") and the known cause of weak RU query handling — not a
// design to extend.
// - a CASCADE, not one decider — layers:
@@ -35,7 +35,7 @@ package router
import "time"
// Intent — the seven save-where labels from DESIGN.md's routing table. The
// Intent — the seven save-where labels from docs/design.md's routing table. The
// discriminator is "does the loop evaluate a predicate against it?":
//
// - act: command now, not stored (function call into the allowlist)
+2 -2
View File
@@ -18,9 +18,9 @@
// returns a canned string the router + action path operate on); with a
// worker socket configured, it wires Remote.
//
// Per DESIGN.md § Voice pipeline (STT / TTS): whisper.cpp (CGo, Vulkan) in
// Per docs/design.md § Voice pipeline (STT / TTS): whisper.cpp (CGo, Vulkan) in
// cmd/mavsttd is the production stt — the older faster-whisper/vosk picks are
// retired (DESIGN.md § Superseded, "named STT/TTS model picks"). The
// retired (docs/design.md § Superseded, "named STT/TTS model picks"). The
// server-side stt module is the heavy multilingual path; the client's
// wake-word + stage-0 command grammar (cmd/mavwaked) hits the router directly
// and never crosses this seam. Today's Remote + Stub both return plain text
+2 -2
View File
@@ -2,7 +2,7 @@
// store's allowlist, and drafts 'proposed' scaffolds for acts that aren't on
// it yet.
//
// Boundary discipline (DESIGN.md § "Tool registration — drafting is suggest,
// Boundary discipline (docs/design.md § "Tool registration — drafting is suggest,
// enabling is act"):
//
// - The store is the allowlist. Only status='enabled' rows run. A verb not
@@ -26,7 +26,7 @@
// - Destructive tools don't run on first hearing: Exec returns ErrNeedsConfirm
// and the handler runs a confirm turn ("выполнить X? да/нет"); only a
// confirmed re-Exec runs them. A gate assumes a fully-formed action, which
// an enabled+matched act is (DESIGN.md § "Confirmation is not one
// an enabled+matched act is (docs/design.md § "Confirmation is not one
// mechanism").
package tool
+2 -2
View File
@@ -5,9 +5,9 @@
// PCM, headerless per the audio package; the voice sink + reference client
// wrap it in a WAV at the disk edge.
//
// Per DESIGN.md § Voice pipeline (STT / TTS): piper is the production tts
// Per docs/design.md § Voice pipeline (STT / TTS): piper is the production tts
// (subprocess + espeak-ng, CPU-only on the ryzen box, driven by cmd/mavttsd);
// the older silero pick is retired (DESIGN.md § Superseded, "named STT/TTS
// the older silero pick is retired (docs/design.md § Superseded, "named STT/TTS
// model picks"). A different voice is a model-file swap, not a code change.
// The daemon wires one impl — Remote pointing at the worker socket if
// configured, Stub otherwise.