feat: what earns its keep (v1.51.0) - #177
Conversation
Phase-0 artifacts for the eight-task wave: consultant variants, design document, and implementation plan. Variant B (shared envelope spine, additive adoption) accepted with two amendments: deterministic-only salience gate, and an explicit refusal of the transformer embedding unit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
Three lanes already spoke the needs-llm-step grammar with three independent declarations of it - the durable write-session envelope, the diarization profile step, the rollup ladder envelope - and the next lane that needed generated text would have written a fourth. src/core/brain/ llm-step.ts is the one declaration they share: the fields all three carry (status, step, prompt, schema_hints, target_path) and nothing else, plus buildNeedsLlmStep, which stamps the status literal so no construction site can spell it a fourth way and copies the hint list so the envelope cannot change under a reference the caller kept. The builder refuses a blank step, prompt, or target_path by name: an envelope missing one of the three cannot be answered, and it is worth more as a refusal at the construction site than as an unanswerable request downstream. Adoption is additive and type-level. The diarization step IS the spine; the rollup envelope is the spine plus the two fields only a ladder rung has. WriteSessionEnvelope is the durable superset - its status widens to the whole session lifecycle - so it inherits the spine's generation fields and keeps its session-backed ones; the session kernel's runtime is untouched, and both envelopes serialize byte-for-byte as before, key order included, which the new suite pins against the pre-spine JSON. Beside it, src/core/brain/response-checks.ts holds the constraints the deliberately-shallow ShapeDescriptor language cannot express: cardinality and cross-item rules, which need to read more than one value at a time. Deepening the descriptor language to fit them would trade the flatness the shape layer exists to keep, so they live next to it instead - following the precedent of the research report's citation check, which is left where it is because moving it would change its error class and its messages. The registry is fail-closed: asserting a surface nobody registered throws a named refusal rather than reading as a clean pass, registering a surface twice throws rather than silently replacing the first check, and a check returning a code outside the frozen vocabulary is itself refused so no refusal reaches a caller unnamed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
The architecture overview described a project's structure without ever saying what that description rested on. A reader could not tell whether the notes were backed by a real code graph or by nothing but the deterministic file scan, and the codegraph partner report - which has answered exactly that question since the operational-readability work - had no consumer inside the generator. A new `codegraph` sentinel region in the overview states the verdict. All five `CodegraphIndexState` values render distinctly: `indexed` stamps `graph-present` with node, file and edge counts plus the health summary (and one line per health finding, by code); the other four share the `graph-absent, lexical-only` verdict but each keeps its own state name and its own reason string, because their remediations differ and `codegraph init` is actively the wrong advice for a partner that timed out. The verdict comes from the existing `buildCodegraphReport`. No partner CLI vocabulary is added: `CODEGRAPH_CLI`'s four tokens remain the entire surface. The report is asked with `limit: 1` and the scanned project as `cwd`, which scopes the question to the project the overview is about - the default scan widens to the vault parent's siblings and would otherwise stamp a verdict about a different repository into this repository's notes. A project root that is not a code project is reported as `no_project`, which is true of it. The region body is its own provenance stamp (state, counts, health), so a region left behind by an index that has since moved describes the reading it was made from. Because the verdict is a fact about the machine rather than about the tree, the region is a declared exception to this module's unchanged-project byte-identity guarantee, named as such in the generator docstring; the exception is scoped to the region and nothing else, and the new suite pins that every other region and all operator prose still survive a moving verdict byte-for-byte. The partner read happens before the sync lock is taken: it touches neither the vault nor the notes, so holding the critical section across it would only lengthen the window a concurrent run waits on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
…isions note
Two additions to the generated architecture notes, both deterministic and
both grounded in data that already exists.
A `module-map` region in the overview renders the module layout as a
Mermaid flowchart. It is CONTAINMENT ONLY, and the region says so in
prose above the block: the scanner records no import graph at all
("Import-graph analysis is explicitly out of scope"), so every edge runs
from the project root to a module it holds, and a module-to-module edge
would be a relation nothing measured. Each node carries the module name,
its file count, and its dominant language. Node ids are positional
(`mod0`, `mod1`, ...) rather than derived from directory names, which
removes the need for a collision-free mangling from arbitrary bytes to a
Mermaid identifier; labels are entity-escaped for `#`, `"`, `<` and `>`,
with `#` first so the escapes are not themselves rewritten. Ordering runs
through `compareStable`, never a locale collator, so the block is
byte-identical for the same facts on any host.
A key-decisions note (`decisions.md`, `kind: arch-decisions`) is planned
beside the overview on every run. It lists the repository's ADR
candidates - title, sha, matched signals, and a wikilink to the draft -
read from the vault-global `Brain/decisions/candidates/` store and
filtered by the `repo_key` the architect already derives, which is the
same key the commit miner stamps. Without that filter one repository's
note would list another's drafts. A repo with no candidates gets an
explicit empty-state sentence rather than an empty region, because a
blank region is indistinguishable from a generator that failed to run.
A candidate whose frontmatter lost its sha is listed with "sha
unrecorded" - a hand-edited draft is reported, never silently dropped.
The key-decisions note is the second declared exception to the
unchanged-project byte-identity guarantee, named in the generator
docstring beside the codegraph region: mining new commits moves its bytes
with no change to the repository at all. The exception is scoped - the
new suite pins that an unchanged project AND an unchanged vault
regenerate every note byte-identically, and that a new candidate moves
the decisions note and nothing else. The `module-map` diagram is
explicitly NOT an exception; the docstring says so.
Ride-alongs in the files touched: `compareStable` moves to `scan.ts`, the
leaf of the scan/render pair, because the new candidate reader orders its
output with it too and the two modules may not import each other; the
language sort is hoisted into `sortedLanguages` so the summary line and
the diagram's dominant-language label cannot disagree about which
language leads a module; the summary line's magic 8 becomes
`LANGUAGES_LINE_CAP`. The run tally and the render stage's denominator go
from `1 + modules` to `2 + modules`, and the CLI's JSON envelope gains
`decisions_path`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
The dream pass has no per-item model lane. Its entire model delegation is the count-triggered rollup ladder, which reads one number - how many facts exist - and emits one envelope per fired rung. So there is exactly one place a salience judgement can change anything, and this is it: which facts are counted into that fold set. `salience-gate.ts` scores each fact by combining three signals the vault already computes as pure functions, reused verbatim rather than reimplemented: the dominant decayed outcome mass from `lessons.ts`, the Wilson-bound confidence from `confidence.ts`, and the observed-reuse rate from `observed-use.ts`. Mass is the only unbounded one, so it is mapped into [0, 1) by a hyperbolic saturation with a named half-saturation constant; the three weighted terms sum to 1, so the score is bounded and each weight reads as "how much of the score this signal can buy". No model, no cache, no prompt version - recomputation is free and a cached verdict would be a second source of truth about a number three pure functions already answer. `dream.salience_threshold` is optional and unset by default. Unset, the gate is OPEN: nothing is scored, so neither the log nor the continuity store is read, the ladder counts the unfiltered fold set, and the run is what it always was. That is the documented default, not a silent fallback - the summary still reports the gate explicitly, with `threshold: null`, because a missing field could not tell an absent gate from a gate that excluded nothing. A malformed threshold is refused by name at the config seam and again at the resolve seam, the doctrine `dream-gates.ts` states for a malformed gate override. Every excluded fact is named in the summary with its score and its three raw signals; the gate never drops anything silently. `o2b brain dream retriage <run-id>` (and MCP `brain_dream` `action: "retriage"`) is the read-only answer to "what would move": a staged bundle records the partition it was staged with, and retriage re-runs the gate against the CURRENT threshold and names every fact that would newly enter or leave the fold set, with both scores. It rewrites nothing, so adopting the new partition stays a deliberate re-stage. A bundle staged before the gate shipped is refused by name rather than compared against an assumed-open partition. No new DREAM_PHASE: the gate fits inside rollup planning, so the phase refusal vocabulary is untouched. The salience partition rides beside the plan in the bundle manifest rather than inside `DreamStagePlan`, because that projection is what `validate` diffs to prove the vault has not drifted and it models what the pass WRITES - the ladder's envelopes are not in it either. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
…dius rule A note delete removed one file and reported the inbound references it stranded. When the note was an imported source, several of those references were not navigation aids: a per-source summary page, a signal citing it, a preference folded from that signal - files whose whole content restates the note and which mean nothing once it is gone. An operator had to hand-chase every one. `--delete-linked` extends the delete to exactly that class and no further. The rule deciding membership is not a new one: `deleteBySource` already has it - a single-purpose derived page in a derivation directory, citing no second source, folded from no foreign signal - and this commit generalises that tracer over a spelling set rather than copying it, so a note delete and a source delete cannot come to disagree about what "derived solely from" means. `traceNoteDerivations` is the note-side entry point; `deleteBySource`'s own behaviour is unchanged (its identity carries one spelling, exactly as before). The module header's commitment stands and the implementation says so where the two sets diverge: a delete still rewrites nothing, and everything outside the solely-derived set is REPORTED. A page citing a second source, and every user note, land in `reportedFiles` and survive. The response returns both lists, disjoint, on the dry run and on the confirmed run, so a caller never subtracts one from the other. It also names the scope the fold walked (`Brain/`), because an empty derived set has two readings and only one is a measurement. The destructive ladder is the existing one with one deliberate change: under the flag the count guard asserts over the DELETION SET rather than the inbound-reference count, so `--expect` counts the paths the response just printed. All unlinks sit inside ONE `withDestructiveSnapshot` call - the set is one decision, and a per-file recovery point would leave the operator choosing among archives. The note goes first, so a failure mid-cascade leaves dangling derivations (the state every delete before this flag left behind) rather than derivations whose note survived. The Brain half of the set IS covered by the archive, which is why a cascade reports `partial` where a bare delete reports `unproven`; the `destructive-sites.ts` entry for the module records that. Surfaces: `deleteLinked` on the core input, `--delete-linked` on the CLI verb (which prints both lists in full rather than counting them), `delete_linked` on the MCP tool, plus help-text and command-manifest. The flag is refused by name on any action that removes nothing, rather than being silently ignored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
Two halves of one gap. A capture could say what was captured and nothing about what to do with it, and the only way to make one at all was the inbound Telegram bot - `writeCaptureNote` had exactly one caller, so the staging area, the drain and the `/catchup` watermark were reachable only by an install that had configured a chat channel. `guidance` is an optional string on WriteCaptureInput, rendered as a `## Guidance` body section and read back verbatim on CaptureNote. It is deliberately not merged into the body: the two are written in one breath and mean different things, and a drain that cannot tell them apart would route on an instruction as though it were content. The hash shape changes, and only in one direction. Guidance joins the id hash input, because two captures differing only in it are two captures and hashing the body alone would collapse them onto one stem and hand the second to the `-2` allocator, where the id stops describing the content. The segment is appended ONLY when guidance is present, so the hash input for a capture without it is the historical three-part string byte for byte and every id the Telegram bot would have produced is unchanged. The same conditionality governs the frontmatter: the `capture_guidance` marker is emitted only alongside real guidance. That marker is what makes the body split safe. A captured message may itself end in a `## Guidance` heading - people quote documents - so the parser splits only when the writer said it wrote a section, and a marked page whose body carries no section is refused as corrupted rather than read as guidance-less. Telegram is unchanged, and that is a reading of its payload rather than an omission: the update carries a chat id and one text body, with nothing in it distinguishing content from instruction, so splitting on a convention this bot invented would put words in the sender's mouth. The call site says so. `o2b brain capture` is the ingress: body as an argument or on stdin, `--source`, `--sender` (defaulting to the configured agent), `--at` for a replayed capture's own instant, `--guidance`, and `--json`. Every refusal is named and exits 2 on both surfaces. It writes through the same contract function the bot uses, so no second set of rules exists. No MCP tool in this unit. No STATE_SURFACES change either: the registry models ledgers and machine state, not Markdown content trees, and `Brain/captures/` has no row to update - the surface contract is where captures live and what they are, and neither moved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
`expiration_date` shipped with a validator, a writer parameter on both artifact kinds, and a fully-wired read side - and no surface anywhere that set it. A caller could declare a memory's lifetime only by writing straight to the core API, and could never change one at all: nothing in the tree rewrote that field after the write. This closes both halves. Creation. `--expires` on `o2b brain feedback` and `expires` on `brain_feedback` plumb into the existing WriteSignalInput and WritePreferenceInput slots. Both surfaces validate BEFORE the signal write, so an unparseable date refuses the whole call rather than landing a signal and then failing on the force-confirmed preference beside it. A force-confirmed rule inherits the signal's lifetime: a preference confirmed from an observation that expires does not outlive it. Mutation. `o2b brain expire <id> --expires <date|none>` and the `brain_expire` MCP tool, over a new `expiration-set.ts` core that addresses signals and preferences BY ID and never accepts a path. That routing is the design decision the brainstorm left open, and it settles the way the note surfaces force: `resolveNoteTarget` refuses the `Brain/` machinery root for every path it is given, which is what keeps a note-editing verb from rewriting a preference, so reaching these artifacts from there would mean carving an exception into the one guard that makes those surfaces safe. `brain_expire` is a tool rather than a `brain_lifecycle` action for the same reason - every action there addresses its subject by a note path. Clearing is the explicit word `none` and it REMOVES the key. `--expires ""` is what a shell produces when a variable did not expand, so an empty value must never read as "un-expire this"; and an empty key on disk parses to nothing, fails open on read, and looks like a lifetime somebody meant to set. A re-set to the value already there is reported as `changed: false` and writes nothing - the audit log records changes, not calls, and both sides of each change are in it because "cleared" and "moved to a later date" are different decisions the frontmatter afterwards cannot tell apart. The one chokepoint is now enforced rather than observed. `tests/core/architecture/expiration-chokepoint.test.ts` sweeps `src/` and requires every module that stamps the field into a frontmatter map to name `normalizeExpirationDate`. That matters because the READ side deliberately fails open on an unparseable value so corruption surfaces - a tolerance that is only safe while every write is validated. The census states its two narrowings rather than implying completeness. Ride-along: `signal.ts` and `preference.ts` now stamp through `EXPIRATION_DATE_FIELD` instead of repeating the literal. Pins moved, each for the one tool added: the MCP tool count (110 -> 111) in the parity, listing, stdio and removed-tools suites, the agent-scope matrix classification and its probe counts (96/220 -> 97/221, quoted in docs/architecture.md), the caller-supplied-agent tool count (21 -> 22), and the write-site census's shared-helper rows (97 -> 98) for the new module writing through `writeFrontmatterAtomic`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
The regex fact extractor runs inside the live capture hook, which is why it only recognises structure - a URL, an address, a unit-bound number. Every preference an operator states in prose is invisible to it. This adds the batch counterpart: `o2b brain extract-signals <session-ref>` (CLI and `brain_extract_signals` MCP), diarization-shaped and two-phase. Phase one reads an already-imported session's USER turns - the import path's own rule - and returns a report carrying exactly one needs-llm-step envelope built through the wave's spine builder. Phase two validates the answer structurally against a new `extracted_signals` response shape and semantically against a per-session cap and a per-item confidence floor, both registered on the semantic-check registry because neither is expressible in a descriptor, then routes the accepted items through the existing helper chain in the deterministic extractor's order: capture boundary, dedup, durability denylist, `Brain/pending/` staging when write approval is on, `writeSignal`. Refusals are named and total. An over-cap payload names the cap and the count; an under-floor item names the floor and the value; a session with no imported turns, or none authored by the user, or one the capture boundary ignores, is refused rather than reported as an empty mine. Nothing partial ever lands. `source_type` gains `auto_extract`, a member of its own rather than a reuse of `extracted`: both mine a session, but one is a regex over structure and the other is a model reading prose, and their trust profiles differ forever. The vocabulary stays closed and fail-closed, so a reader from an older build refuses a file carrying the new member rather than misreading it. `import-session` is untouched and now pinned: every signal it writes still stamps `session`, and a re-import over the same log leaves the inbox bytes unchanged. Also fixed in passing, in the files this commit touches: - `fact-extract.ts` documented "the seven families" over a table that has held three since the language-agnostic rewrite. - Both `source_type` refusals in `signal.ts` hand-wrote "'live', 'inline', or 'session'", a list already stale on `extracted`. They now render the vocabulary, so a new member cannot leave a refusal naming a set the guard no longer enforces. - `session-recall.ts` gained `listSessionRawTurns`: `describe` counted turns and `expand` paged them from one node outward, and nothing handed a caller one session's turns as a list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
`skill-proposals.ts` mines continuity telemetry - what the agent did, repeatedly, in a shape a detector recognises. It has never read a vault page, so the material an operator already wrote down, verified and reused was the one obvious skill source the queue could not see. And in a tree whose whole subject is skills, nothing had ever written a SKILL.md. This adds `skill-page-drafts.ts` and two operations on the existing surface (`o2b brain skill-proposals page-candidates | page-draft`, and the matching `brain_skill_proposals` operations) rather than a parallel pipeline. Candidacy is gated three ways before a model is asked for anything: the page-meta trio as the cheap filter (core tier, non-stale lifecycle, high confidence - the only three fields that apply to arbitrary user pages), observed reuse as the evidence floor (the one signal saying the material was actually pulled back out), and installed skill coverage as the negative signal. Every page the gate turns down is reported with the reason and the measured value behind it; a report that listed only winners could not be told apart from a vault nobody tagged. Drafting is envelope-mediated, one spine envelope per admitted page, carrying that page's content. The returned draft is validated structurally by a new `skill_page_draft` response shape and semantically by a check that its `name` is a legal skill directory name - the descriptor language has no pattern key and deliberately keeps none. Acceptance is where the one out-of-vault write happens. `skills_dir` may point anywhere, so everything automatic stays in `Brain/skill-proposals/pending/`, and only an explicit accept materializes `SKILL.md` under the configured root - through the same write-ahead journal the procedure branch already used. The journal now records the ABSOLUTE path the materialize step is about to write, because the two branches write into different trees and a rollback that re-derived a procedure path from the slug would leave the real artifact behind while reporting it removed. The rollback also removes the skill directory it created, via `rmdirSync`, which refuses a non-empty one. Sticky rejection, dedup and one-name-one-proposal are the declared path's, unchanged: the planner is now parameterised by pattern kind rather than copied. The write-site and destructive-site censuses are updated in this commit with the reason `rmdirSync` is there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
… and truth records `o2b brain panel` deliberates: a durable write session, personas walked over several turns, a note committed under `Brain/decisions/panels/`. That is the right shape when a decision is worth several rounds and the wrong one when somebody wants a design note now, and there was no other shape on offer. This adds the one-shot sibling - `o2b brain design-note <topic>` and `brain_design_note` - built like `diarization.ts`: read-only grounding, no session, no model, exactly one needs-llm-step envelope. What "grounded" means here is the point. The pass reads three stores that already know what this project has argued about, decided and asserted - tension records, decision records, and the truth projection with its conflicts - and matches each to the topic with the vault's own deterministic token overlap, the `tokenise`/`jaccard` pair `findSimilarDecisions` already ranks with, so a topic reaches the same records from every surface and no model decides relevance. The decision store's matcher is called rather than reimplemented, and its similarity floor is reused for all three stores so "related" means one thing across the report. An empty store is NAMED, not implied. A vault with no tensions yet and a vault whose tensions all missed the topic are different facts leading to different next moves, and rendering both as an empty list would be exactly the misleading silence this project forbids - so `emptyStores` carries the stores holding nothing at all, and the prompt says which of the two happened per section. An empty vault produces a grounded report, never a refusal. The payload contract is one recommendation, and it is the case the semantic-check registry exists for: "exactly one alternative sets recommended: true" reads the whole array at once, which the deliberately shallow descriptor language cannot state. Zero and two-plus are both refused and the refusal carries the count found, because those are different mistakes with different fixes. The validated note commits as `Brain/decisions/design-<date>-<topic>.md`, the panel outputs' filename shape one directory up, through an exclusive create: a second note for the same topic on the same day is refused rather than overwriting the first, since two design notes that disagree are worth keeping. The write-site census's shared-helper count moves by one, with the reason: this module commits through `writeFrontmatterAtomic` exclusively, so it lands in the class the census wants modules to land in and needs no exclusion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
…richment wave Thirteen findings from an independent review of this branch, plus one failing gate the review did not name. - owner-scope-refusal: AGENT_ARGUMENT_TOOL_COUNT is 24, not 23 - the wave added `agent` to brain_expire, brain_extract_signals and brain_design_note. The prose beside it no longer states a number of its own, so the count lives in the constant alone. - brain_design_note and brain_extract_signals returned raw absolute host paths; both now route through `vaultRelativeSafe`, as every other Brain tool does. An MCP response lands in model context. - readSkillDraft re-applies the skill-name charset at MATERIALIZE time. That write is the one in the accept sequence that passes no vault confinement (the skills root may sit outside the vault), and a hand-edited `skill_name` reached `join()` unchecked - a proposal naming `../escaped` wrote a SKILL.md into the skills root's parent. SKILL_NAME_RE moves to skill-proposals.ts so both ends share one pattern. - commitSkillPageDraft confines the caller-supplied page path with `ensureInsideVault` before probing it, and refuses an outside path by name instead of accepting it as provenance. - The `--delete-linked` cascade now requires DECLARED provenance as well as the solely-derived rule. That rule was written for an imported source, where "cites no second source" is decisive; for a note it is vacuously true of any Brain page that writes `[[Note]]` in prose and declares no `source:` array, so such a page could enter the deletion set. It is reported instead. `deleteBySource` is untouched, and its payload keeps the shape its golden test pins: the new `structuredProvenance` field rides on `NoteDerivationEntry`, which only `traceNoteDerivations` returns. CLI help and the MCP descriptions say the enforced rule. - The dream retriage validates the manifest's `considered` and `admitted` counts the way it already validates the threshold and the exclusion list. `Number(<junk>)` is NaN, which compares unequal to everything and reported a phantom "fold set changed size" on every retriage. - `o2b brain dream` names the salience gate on the human stream when a threshold is configured - admitted of considered, and the threshold. Exclusions were visible only under `--json`, which is unchanged. - setExpiration compares the stored value through the same normalizer the incoming one goes through, so `changed: false` means what its docstring says. An equivalent spelling used to rewrite the file and append an audit event describing no change. - buildNeedsLlmStep drops a runtime `status` on its fields instead of letting it displace the stamped literal. It is dropped rather than re-stamped last because the envelope's key order is observable. - commitExtractedSignals reports a mid-loop write failure as the partial write it is: `ExtractSignalsWriteError` names the signals already on disk and the item that failed, following ArchWriteError's precedent. Previously the call threw a bare cause with no accounting. - brain_design_note's entry in the agent-scope matrix moves back into alphabetical order; the parity docblock's merge artifact is rewritten so the three added tools are each introduced grammatically. - commitDesignNote takes the vault-identity write guard. It was the one write-capable module in `src/core/brain/` without it, and `tests/core/brain/vault-guard-census.test.ts` was failing on the branch because of it. The reported greedy-split defect in capture-note's `splitGuidance` did not reproduce: the leading `[\s\S]*` is greedy, so the separator already matches at its LAST occurrence. A regression test now pins that, and the comment says why in terms a reader cannot invert. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
CHANGELOG entry for the salience-lifecycle-enrichment wave, the README what-is-new rewrite, and the version bump propagated to all mirrored manifests via sync-version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
|
Important Review skippedToo many files! This PR contains 112 files, which is 12 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (112)
You can disable this status message by setting the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
CodeRabbit skipped this pull request (111 files exceeds the 100-file limit, and usage credits are exhausted), so no automated review pass will arrive. An equivalent independent review was run inside the branch before it was opened: a reviewer with no build context examined all eleven implementation commits against main, confirmed the security-sensitive paths (SKILL.md materializer, model-authored slugs, delete cascade, Mermaid labels) are correctly gated, and returned one blocking and eleven non-blocking findings plus one false positive. All findings are applied in commit 28ab585 and the false positive is pinned by a regression test. Full-suite evidence after the fixes: 11453 pass, 0 fail. |
…hanges brain_feedback gained an `expires` parameter (creation-time expiration, unit 3c) that never made it into the vendored copy in plugins/hermes/_schemas.py, so the anti-drift test (test_static_schemas_match_live_tools_list) failed CI's "Python plugin tests" job. Fetched a fresh tools/list from the branch's own `o2b mcp` against a throwaway vault and confirmed the other nine curated tools (brain_apply_evidence, brain_note, brain_pinned_context, brain_query, brain_search, brain_recall_gate, brain_context, brain_context_pack, brain_pre_compact_extract) already matched byte-for-byte; only brain_feedback needed re-vendoring. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dYpaSgWTK5L6o6AzcLcBd
Summary
Eight tracker cards shipped as one wave (v1.51.0): the dream pass gains a deterministic salience gate that decides which facts earn expensive synthesis and names every exclusion; model-mined session signals, staged Agent Skill drafts, and one-shot grounded design notes become three new model lanes that all write through one shared needs-llm-step envelope grammar and one fail-closed semantic-check registry; a note delete can follow structured provenance under a snapshot and an exact count guard; expiration becomes settable at creation and mutable afterwards from every writing surface; and
o2b brain architectoutput now states its codegraph verdict, draws a containment diagram it can prove, and lists the repository's mined decision candidates. A ninth card, the local transformer embedding provider, is refused with a recorded rationale rather than shipped as a runtime-less stub.Why this matters
Every lane where a model writes into the vault now passes the same three checkpoints before a byte lands, and everything expensive or destructive states what it considered and what it refused.
flowchart LR A[Model-authored payload] --> B{Declared response shape} B -->|violation named| R1[Refused] B --> C{Semantic checks: cap, floor, cardinality} C -->|limit named| R2[Refused] C --> D{Staging: inbox trial or pending accept} D -->|operator accepts| V[(Vault)] S[Existing signals: mass, confidence, reuse] --> G{Salience gate} G -->|admitted| E[Dream synthesis] G -->|excluded, named in summary| N[Run summary]Concrete benefits
retriagepreview before any re-stage.--delete-linkeddeletes only files whose structured provenance traces solely to the target, reports everything else, and runs under the snapshot gate, count guard, and destructive-sites census.graph-presentwith counts, orgraph-absent, lexical-onlywith the reason) and the containment diagram draws only what the scanner truthfully knows.What ships
src/core/brain/llm-step.ts,src/core/brain/response-checks.tssrc/core/brain/salience-gate.ts,dream.salience_threshold,dream retriageo2b brain extract-signals,brain_extract_signals,source_type: auto_extract--delete-linkedon note delete (CLI + MCP)guidanceon captures,o2b brain capture--expiresat creation,o2b brain expire/brain_expiremature_pageproposals, SKILL.md materializer on acceptcodegraphandmodule-mapoverview regions, key-decisions noteo2b brain design-note,brain_design_noteTest plan
env HOME=$(mktemp -d) bun test- 11453 pass, 0 fail (1175 files)bun x tsc --noEmit- cleanbun run lint- 0 errors, no new warningsbun run scripts/sync-version.ts --check- all 7 manifests at 1.51.0bun run build:openclawunder Bun 1.4.0 - zero diffdream.salience_thresholdreproduces pre-wave behavior byte-for-byte (pinned by test)import-sessionoutput pinned byte-identical (the extraction lane stayed out of the hook path)