New:Socket for Asana Is Now Available.Learn more
Sign In

brainllm

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

brainllm - npm Package Compare versions

Comparing version
10.2.0
to
10.4.0
+1
-1
manifest.json

@@ -5,3 +5,3 @@ {

"display_name": "BrainLLM",
"version": "10.2.0",
"version": "10.4.0",
"description": "Turns TriliumNext Notes into a persistent, graph-structured second brain for Claude and other LLM clients.",

@@ -8,0 +8,0 @@ "long_description": "BrainLLM is an MCP server that organises memory into five purpose-built areas (Master, LLM, Memory, Knowledge, Insights). The model supplies content while the server owns placement, format, lifecycle, dates and backups. Includes a full raw ETAPI surface for advanced use. Requires a running TriliumNext instance and the Bun runtime.",

{
"name": "brainllm",
"version": "10.2.0",
"version": "10.4.0",
"description": "MCP server that turns TriliumNext Notes into a persistent, graph-structured second brain for Claude and other LLM clients.",

@@ -5,0 +5,0 @@ "author": "Kevin Miiso Novo <miisodev@gmail.com>",

@@ -13,3 +13,3 @@ <div align="center">

[![Version](https://img.shields.io/badge/version-10.2.0-f59e0b?style=flat-square)](https://github.com/miisodev/BrainLLM/releases)
[![Version](https://img.shields.io/badge/version-10.4.0-f59e0b?style=flat-square)](https://github.com/miisodev/BrainLLM/releases)
[![CI](https://img.shields.io/github/actions/workflow/status/miisodev/BrainLLM/ci.yml?branch=main&style=flat-square&label=CI&color=f59e0b)](https://github.com/miisodev/BrainLLM/actions/workflows/ci.yml)

@@ -196,3 +196,3 @@ [![License: MIT](https://img.shields.io/badge/license-MIT-a1a1aa?style=flat-square)](./LICENSE)

### Core — universal verbs (29)
### Core — universal verbs (32)

@@ -203,6 +203,6 @@ | Group | Tools |

| Writing | `remember` · `diary` · `revise` · `resolve` · `withdraw` · `recover` |
| Reading & search | `recall` · `domain` · `brain` · `outline` · `inspect` · `template` · `consistency` |
| Reading & search | `recall` · `domain` · `brain` · `assembly` · `outline` · `inspect` · `diff` · `template` · `consistency` |
| Graph | `connect` · `explore` · `graph` |
| Attachments & labels | `attach` · `detach` · `label` |
| Maintenance & system | `addendum` · `maintain` · `forget` · `bootstrap` |
| Maintenance & system | `addendum` · `maintain` · `claim` · `forget` · `bootstrap` |

@@ -209,0 +209,0 @@ ### Core — surface reads (10, dual-mode)

@@ -5,3 +5,3 @@ {

"description": "Persistent, graph-structured memory for LLMs, stored in TriliumNext Notes. Enforced note structure, a session protocol with a durable pre-close gate, a typed knowledge graph, and a maintenance lifecycle — in a note application the human can read and edit too.",
"version": "10.2.0",
"version": "10.4.0",
"websiteUrl": "https://github.com/miisodev/BrainLLM#readme",

@@ -16,3 +16,3 @@ "repository": {

"identifier": "brainllm",
"version": "10.2.0",
"version": "10.4.0",
"transport": {

@@ -19,0 +19,0 @@ "type": "stdio"

@@ -30,8 +30,10 @@ ---

template(kind) ← the canonical structure for a kind — read before first write of a kind, then match a sibling's shape
<surface> / _recall ← read a surface in full, or skim it
<surface> / _recall ← read a surface in full, or skim it (section= reads ONE section — the read twin of revise(section=))
domain(name) ← surface all content for a domain/topic/project — the RELIABLE retrieval path; reach here first
recall(...) ← ranked search; pass domain= whenever you know the area
consistency(pattern) ← does the brain agree with itself? run after correcting a fact recorded in >1 note
revise(...) ← edit a note (section= for whole sections, find=/edits= for surgery, mode=before/after to insert)
claim(...) ← does the brain still agree with the WORLD? register a checkable assertion; verify on a schedule
revise(...) ← edit a note (section= for whole sections, find=/edits= for surgery, mode=before/after to insert, mode=remove to delete)
outline(noteId) ← the heading tree + structural check — read BEFORE a section= edit you're guessing at
diff(noteId) ← what your last write actually changed, against the revision it snapshotted
resolve(...) ← close a thread with its outcome

@@ -57,5 +59,7 @@ withdraw(noteId) ← pull an archived/resolved thread back to active

maintain(ack=[noteId]) ← a flag you've judged correct: quiet until that note's content changes
ANYTIME brain() ← surface the full content tree (all areas, sub-containers)
maintain(repair=[noteId]) ← fix an entity-corrupted body in place; revision taken first
ANYTIME assembly() ← what the brain HOLDS: titles by surface, each surface's purpose alongside — the awareness read
brain() ← the full inventory: id/kind/status/parent/dates per note (auditing, locating)
day(recap=true) ← everything written TODAY across every surface, chronologically
inspect(noteId, content?, find?) ← every label/relation/attachment on one note; content=true adds the body; find= counts a literal flag
inspect(noteId, content?, section?, find?) ← every label/relation/attachment; content=true adds the body (section= narrows it); find= counts a literal flag and shows the nearest text on a miss
```

@@ -65,3 +69,3 @@

**Singletons arrive as section headings, not full bodies.** Each comes back as `{slot, sections[], preview, size}` — enough to know what the brain holds. Pull the part that matters with `master(which)` / `llm(which)`; most sessions need one section, not five documents. `start(depth="full")` serves everything inline and is the right call for a strategy review, a singleton rewrite, or a first session on a new machine — but it is genuinely expensive, and paying it for a one-line question is the habit this default exists to break.
**Master preferences and LLM protocols always arrive in full. The other three arrive as section headings.** Preferences carries the schedule and working style; protocols carries the rules governing the session itself — a session needs both whole to orient correctly from its *first* message, before it knows enough to decide it needed them, which is exactly when a digest costs a second round-trip. Biography, goals and responsibilities come back as `{slot, sections[], preview, size}`: enough to know what the brain holds, and rarely load-bearing turn to turn. Pull one with `master(which)` / `llm(which)`, or a single section with their `section=` parameter. `start(depth="full")` serves all five inline — right for a strategy review or a singleton rewrite, wasteful for a one-line question.

@@ -72,4 +76,6 @@ `day()` is the new-day sweep in one call: the previous session in full, that day's change log, the notes touched since, and the current month's deliverables note in full — plus a `next[]` driving the sweep (advance statuses with `revise(find=)`, present findings in the first message, grounded strictly in what the touched notes evidence).

`session()` is the mandatory pre-close step — call it before `close()` when the session is wrapping. It fetches the **master singletons** (biography/goals/preferences) and **LLM singletons** (responsibilities/protocols) as `{id, lastModified, relations}` stubs (**light by default** — `start()` already served them all in full; fetch current content via `master()`/`llm()` only for the ones you intend to revise, or pass `full=true` to inline everything), today's **diary entry** as `{id, blocks, size}` (the id is all `diary()` needs — `full=true` inlines the body), and runs the **lightweight maintenance sweep**. Returns a `next[]` array covering the full end-of-session protocol: update master singletons → update LLM singletons → `addendum()` → `maintain()` → `remarks()` → `diary()` → `close()`. The goal is to evolve the singletons from this session's observations *before* the log is committed — ensuring logs are factual and singletons stay current — and to write the diary last, with the remarks cues in hand. Idempotent: all reads are safe to repeat.
`session()` is the mandatory pre-close step — call it before `close()` when the session is wrapping. It fetches the **master singletons** (biography/goals/preferences) and **LLM singletons** (responsibilities/protocols) as `{id, lastModified, relations}` stubs (**light by default** — `start()` already served them all in full; fetch current content via `master()`/`llm()` only for the ones you intend to revise, or pass `full=true` to inline everything), today's **diary entry** as `{id, blocks, size}` (the id is all `diary()` needs — `full=true` inlines the body), and runs the **lightweight maintenance sweep**. Returns a `next[]` array covering the full end-of-session protocol: update master singletons → update LLM singletons → **audit the singletons** → `addendum()` → `maintain()` → `remarks()` → `diary()` → `close()`. The goal is to evolve the singletons from this session's observations *before* the log is committed — ensuring logs are factual and singletons stay current — and to write the diary last, with the remarks cues in hand. Idempotent: all reads are safe to repeat.
**`session()` also returns `pending` and `audit`.** `pending` says how much each remaining step actually has to do — addendum markers outstanding, flags already raised, diary blocks written today, which singletons were written today — and `next[]` is written from those numbers rather than reciting steps with nothing behind them. `audit` is the cross-singleton check nothing else performs, in two parts: whether the five singletons **agree** with each other, and whether responsibilities and protocols still **serve** what biography, goals and preferences describe. The second is a semantic question — a protocol can be perfectly consistent and still be serving a goal that has moved — so `consistency()` and `maintain()` cannot answer it. Fix what you find *before* `close()`, so the log records a brain that already agrees with itself.
**Scoped and autonomous runs pass `session(scope="agent")`.** Steps 1–2 touch the *user's* personal singletons and belong to an interactive session; with `scope="agent"` they're omitted from `next[]` entirely and the `addendum()`/`maintain()` steps are reworded for lane-scoped work. If your brief says the personal singletons are out of scope, say so to the tool rather than working around a list that contradicts it.

@@ -97,3 +103,3 @@

├── Knowledge Master · Domains/[domain]/{ sources, info } (learned info beyond/contra training)
└── Insights Logs/ · Graph ([yyyy-mm-dd] log/day, auto-generated by close · the graph() mermaid view)
└── Insights Logs/ · Graph · Claims/ ([yyyy-mm-dd] log/day, auto-generated by close · the graph() mermaid view · registered claims)
```

@@ -151,6 +157,10 @@

**Every one of those takes `section="<heading>"` — read ONE section instead of the whole note.** It is the read twin of `revise(section=)` and goes through the same matching contract, so a heading name that reads also writes. Reach for it on anything large: a note past the read ceiling cannot be returned whole *at all*, which makes the notes carrying the most the ones you can least afford to open blind. `outline(id)` lists the headings; `occurrence=` disambiguates repeated ones; a miss returns `available[]` so you re-target instead of falling back to the full read you were avoiding.
`recall(query, kinds?, domain?, includeArchived?, regex?, fuzzy?)` searches the **whole** brain — use it when you don't know the surface, or for cross-surface lookups. A fuzzy pass runs automatically when the exact strategies come back thin; those hits are marked `fuzzy: true` and are leads to verify, not answers. `regex=` matches a real regular expression against stored bodies — the tool for structural questions keyword search cannot express.
`brain(includeArchived?)` surfaces **every content note** across all five areas — id, title, kind, status, dates — grouped by area. Use to audit the full picture or locate a note.
`brain(includeArchived?)` surfaces **every content note** across all five areas — id, title, kind, status, dates, **and `parent`** — grouped by area. Use to audit the full picture or locate a note. Each group is a **flat list of every descendant**, so a domain book and its information/sources children arrive interleaved and the ordering *looks* nested when it is not; read `parent`, never position. (Inferring parentage from the ordering once cost an 8,259-byte note, overwritten by a "fix" for a gap that was not there.)
**When you need to find where a claim lives inside a big note, reach for `outline()` first — not `recall()` or `consistency()`.** Those two answer "which notes mention this" and "do the notes agree"; neither tells you *which section of one note* to edit, which is the question you actually have before a surgical write. The heading tree costs one call and turns a guessed `section=` into a chosen one.
---

@@ -169,4 +179,6 @@

`kind="diary"`, `"session"`, `"log"`, and `"domain"` are rejected — each has a dedicated path (`diary()`, `close()`, auto-generated, and `information` respectively).
`kind="diary"`, `"session"`, `"log"`, `"domain"` and `"claim"` are rejected — each has a dedicated path (`diary()`, `close()`, auto-generated, `information`, and `claim()` respectively).
**Dedup-by-title is what makes `remember()` idempotent, and on a generic title it is a loaded weapon.** *Current State*, *Sources*, *Technology Stack* and *Product and Business* each exist in four or more domains, so a call you believe is creating a note can silently **replace** one — with a clean receipt reading `action: "updated"`. **Pass `mustCreate=true` whenever you intend to create rather than add**, and it refuses instead, naming the note that already exists. Read `action` on every receipt regardless: `updated` where you expected `created` means something was overwritten.
**Wire at creation, not later.** Pass `connect=[{relation, toNoteId}, …]` on the same `remember()` call — same semantics as `connect()` (idempotent, `worksWith` wired both ways). A new information/user/thread note left unconnected is an orphan until the next `maintain(deep)` pass catches it; the tool returns an explicit `hint` when a freshly-created note has no relations.

@@ -213,6 +225,10 @@

- `find="<exact text>"` — targeted string surgery: every occurrence of the exact raw string is replaced with `body` (raw, no markdown conversion), no read+full-replace needed. When the exact string misses, a **tolerant** pass retries ignoring CKEditor-injected tag attributes (spellcheck, data-list-item-id, …) and whitespace between elements — the receipt's `matchMode` says which pass matched. `nth=N` replaces one occurrence only. Returns `replaced` (a count; `0` with a cause-specific hint). Takes precedence over `section`/`mode`. `title=` composes with every mode.
- `section=` + `mode="remove"` — delete the heading and its whole section. Needs no `body=`. A miss leaves the body **untouched** rather than creating the heading you asked to delete, and reports `matched: false` with `available[]`.
- `find="<exact text>"` — targeted string surgery: every occurrence of the exact raw string is replaced with `body` (raw, no markdown conversion), no read+full-replace needed. When the exact string misses, a **tolerant** pass retries ignoring CKEditor-injected tag attributes (spellcheck, data-list-item-id, …) and whitespace between elements — the receipt's `matchMode` says which pass matched. `nth=N` replaces one occurrence only. Returns `replaced` (a count; `0` with a cause-specific hint). Takes precedence over `section`/`mode`. `title=` composes with every mode.
- `edits=[{find, body, nth?}, …]` — several surgeries in one call, applied in order against **one read and one write**. Per-edit `results[]`; an edit that matches nothing is reported without blocking the ones that did.
**Section replace is whole-section, not per-paragraph.** `section=` + `mode=replace` swaps *everything* under that heading — targeting one paragraph inside a multi-paragraph section silently wipes its siblings. For a word, phrase, or single paragraph, use `find=`; to add content beside a heading, `mode="before"/"after"`.
**Section replace is whole-section, not per-paragraph.** `section=` + `mode=replace` swaps *everything* under that heading — targeting one paragraph inside a multi-paragraph section silently wipes its siblings. For a word, phrase, or single paragraph, use `find=`; to add content beside a heading, `mode="before"/"after"`. When a replace *does* displace nested headings, the receipt names them in `replacedSubsections[]` — read it, because taking child sections along is correct behaviour and easy to not notice.
**Bodies accept escaped markup; `find=` does not — and that asymmetry is the single most common surgery failure.** You may write a body containing `&lt;h3&gt;` and it will be decoded to real markup on the way in. A `find=` string is matched against **stored HTML**, where that heading is a real `<h3>` tag, so searching for the escaped spelling matches nothing. Pass tags literally in `find=` (`"<h3>Typography</h3>"`), and take the same care with anything `outline()` printed: outline reports rendered **text**, and a heading carrying inline markup stores something different — which is why it also returns `raw` for exactly those headings. `section=` matches text (use `text`), `find=` matches storage (use `raw`).
A revision snapshot is always taken first. Containers are refused; the maintained singletons are editable.

@@ -224,4 +240,6 @@

**A `find=` miss names its own cause — read the hint rather than trying variants.** Three distinct causes, and they need different fixes: an **entity-escaped search string** (`&lt;h3&gt;` against stored real tags — note that *bodies* accept escaped markup and `find=` does not, which is exactly the trap), a find that **spans a block boundary** (anchor inside one element, or use `section=`), or the text genuinely differing. Whitespace between elements is no longer a cause — the tolerant pass handles it.
**A `find=` miss names its own cause AND shows you the stored text — read the hint rather than trying variants.** Four distinct causes, each with a different fix: an **entity-escaped search string** (see the asymmetry above), a find that **spans a block boundary** (anchor inside one element, or use `section=`), a string **already consumed by an earlier edit in the same `edits=` array** (the expected result, not a failure), or the text genuinely differing. In that last case the receipt carries `matchedUpTo` — the longest fragment of your string that *is* present — and `storedNearby`, the stored text around it, so you can see how it really differs instead of guessing a variant. Whitespace between elements is never the cause; the tolerant pass handles it.
**Verify a run of surgical edits with `diff(noteId)`, not another full read.** It compares the note against the revision your last write snapshotted, so "what did that actually change" is one call. Trusting receipts alone is how a section replace that displaced four subsections went unnoticed.
**Merge, don't stack — the mechanics.** Master, LLM singletons, and Knowledge notes (including every per-domain Sources note and information note) are clean structured documents, so `revise()`'s *default* append mode is the wrong one for them: reach for `section=` or `mode=replace` to absorb the content into the body, and `find=`/`edits=` for anything smaller than a section. Default append is correct only on sessions, diary entries, logs, and thread day-children. The rule and its rationale are stated in full under Writing.

@@ -269,3 +287,3 @@

|---|---|---|
| `entity-corrupted` | The body stores doubly-escaped markup (`&amp;lt;`) that renders as visible literal text. The write path no longer produces this, but existing damage does not heal itself | `revise(mode="replace")` with the decoded content — deliberately, since nothing decodes it as a side effect |
| `entity-corrupted` | The body stores doubly-escaped markup (`&amp;lt;`, `&amp;nbsp;`) that renders as visible literal text. The producer — a blanket escape on the markdown path — is fixed as of V10.3, but existing damage does not heal itself | `maintain(repair=[noteId])` — one level unwound, revision taken first. Compose with `dryRun` to preview |
| `dated title` | A date or run number in a title defeats dedup-by-title, so the note is minted fresh every run instead of updating the one that exists | `revise(title=…)` and fold the content into the note it should have updated |

@@ -294,3 +312,4 @@ | `long title` | Past the four-word rule | Usually the signal to split the **content**, not to shorten the words |

| `close(summary, title?, identity, learned?, date?, backup?, continuing?, force?)` | Commit the session log ([yyyy-mm-dd] note; identity= REQUIRED — the canonical h3 identification line; title param above Summary) + backup + daily log (regenerated in place) + idempotent session↔log wiring. **Refuses unless `session()`, `addendum()`, `maintain()`, `remarks()`, and `diary()` each ran this session AND `session() → remarks() → diary()` held (last calls)** — returns `{error, detail, hint}` naming what's missing or out of order; `force=true` bypasses the gate (reported back as `bypassed`) but never the identity requirement. `continuing=true` is the SECOND close of a day: skips the ceremonial re-run, and is verified against today's note so it can't stand in for a first close. On success returns `{action, noteId, date, backup, log}` and resets the gate. Once, last. |
| `brain(includeArchived?)` | Full content tree: every typed note across all five areas, grouped. |
| `assembly(area?, includeArchived?)` | **What the brain HOLDS**, shaped for awareness rather than audit: every note by title grouped under its surface, with each surface's engraved purpose alongside. Dated collections (sessions, diary, logs) collapse to a count and a date span — listing sixty `[yyyy-mm-dd]` titles is the bulk of the brain and none of its meaning. Domains nest under their book; threads group by status. `area=` zooms into one. Read this before deciding whether you need to look something up. |
| `brain(includeArchived?)` | Full content tree: every typed note across all five areas, grouped, with id/kind/status/parent/dates. The **inventory** — reach for it to audit or locate, and for `assembly()` when the question is what you know. |
| `bootstrap()` | Initialize the structure if uninitialized, or verify and refresh config if it already exists. Only creates a new tree when the stored root note is confirmed deleted in Trilium (404). Any other error (network, auth, timeout) is surfaced rather than silently creating a duplicate tree. |

@@ -302,6 +321,8 @@ | `remember(kind, …)` | Write a note — routed, formatted, deduped server-side. New threads REQUIRE `goal=` (queried from the user); thread appends REQUIRE `identity=`; thread bodies must not carry a Resolution. `connect=[{relation, toNoteId}]` wires relations in the same call; a new connectable note without them returns an orphan-prevention hint. Sources/information receipts include `domainId`. `icon=` sets the display icon. Rejects diary/session/log/domain — each has a dedicated path. |

| `recall(query, …)` | BrainLLM-wide ranked search. **Scope it — pass `domain=` whenever you know the area.** Title matches score per-token with an exact-title bonus, but an unscoped query across a large brain still ranks full-text noise alongside the note you want. `orderBy` / `orderDirection` for temporal ordering; `fastSearch` for title/label-only; `regex=` for a body pattern instead of keywords; `fuzzy=` to force the typo-tolerant pass on or off. A thin or odd result is evidence about the **query**, not the brain. |
| `consistency(pattern, domain?, kinds?)` | **Does the brain agree with itself?** Give a regex with one capture group naming the value that should match across notes — `consistency("(\\d+) Titan mailboxes")` — and it returns every note asserting a value, grouped, with `agreement: "unanimous"` or `"DISAGREEMENT"`. This is the check nothing else performs: `recall()` ranks by relevance and `maintain()` checks structure, so a correction applied to one note leaves its siblings silently wrong. **Run it after correcting any fact that could be recorded in more than one place.** Matches stored HTML — anchor on tags and entities, not rendered text. |
| `<surface>` / `<surface>_recall` | Read a surface in full / skim it (master, llm, memory, knowledge, insights). |
| `consistency(pattern, domain?, kinds?)` | **Does the brain agree with itself?** Give a regex with one capture group naming the value that should match across notes — `consistency("(\\d+) Titan mailboxes")` — and it returns every note asserting a value, grouped, with `agreement: "unanimous"` or `"DISAGREEMENT"`. This is the check nothing else performs: `recall()` ranks by relevance and `maintain()` checks structure, so a correction applied to one note leaves its siblings silently wrong. **Run it after correcting any fact that could be recorded in more than one place.** Matched against **both** the stored HTML and a tag-stripped projection, so a phrase split by an inline `<strong>` or `<code>` is found and so is a pattern anchored on tags. Scans every in-scope note by default — Trilium's `%=` pre-filter reads a striptags'd copy and silently drops notes, and on a contradiction sweep a falsely clean result is worse than a slow one; `fast=true` opts back into it. |
| `claim(...)` | **Does the brain still agree with the WORLD?** `consistency()` compares notes to each other; nothing asked whether an assertion is still true of the codebase, config or live surface it describes — so a claim that quietly stopped being true stayed authoritative until something downstream broke. Register one with `assertion=` + `check=`, record an outcome with `claimId=` + `holds=` + `evidence=`, read one with `claimId=` alone, list with no arguments. **BrainLLM never executes the check** — it has no shell, and note content is data rather than instructions; you run it and report back. `maintain(deep=true)` surfaces lapsed, unverified and broken claims. |
| `diff(noteId, revisionId?)` | What a write actually changed: the revision snapshot against current content, defaulting to the most recent. Every content write already took a revision and nothing could read one back, so verifying surgical edits meant re-reading the whole note or trusting the receipts. Returns the revision index on every call. |
| `<surface>` / `<surface>_recall` | Read a surface in full (`section=` for one section) / skim it (master, llm, memory, knowledge, insights). |
| `revise(noteId, …)` | Append / replace / section-edit / insert / find-replace a note. Thread appends REQUIRE `identity=`. Section: h2/h3/h4 tolerant match, `occurrence=` to disambiguate, `mode="before"/"after"` to insert around a heading; returns `matched`/`headingCount`/`available[]` — check them. `find=` + `body=`: exact-string surgery with an attribute- and whitespace-tolerant fallback (`matchMode` in the receipt), `nth=` for one occurrence; `edits=[{find, body}]` for several in one read/write. Misses return a cause-specific hint. `title=` composes with every mode. Retitling a domain book cascades `#domain` to its children. `Last updated` lines bumped server-side. Snapshot taken on content writes (not metadata-only). |
| `outline(noteId)` | The heading tree without the body: every h2–h4 with level and occurrence index, the note's section level, any table's key column, size, and structural findings (duplicate headings, unbalanced tags). Read it before a `section=` edit you're guessing at, and after a run of surgical edits to confirm the note is still sound. |
| `outline(noteId)` | The heading tree without the body: every h2–h4 with level, occurrence index, and — for headings carrying inline markup — `raw`, their stored form (`section=` matches `text`, `find=` matches `raw`). Plus the note's section level, any table's key column, size, and structural findings (duplicate headings, unbalanced tags). Read it before a `section=` edit you're guessing at, and after a run of surgical edits to confirm the note is still sound. |
| `resolve(noteId, outcome, …)` | Close a thread: outcome + terminal status + archive-in-place. |

@@ -317,5 +338,5 @@ | `withdraw(noteId, reason?, …)` | Pull an archived/resolved thread back to active (thread kind only — use recover() for other note kinds). |

| `explore(noteId, mode, …)` | Graph: links / backlinks / neighborhood / path. |
| `inspect(noteId, content?, find?)` | Full raw read of one note: every label (not just noteType/status), every outbound relation, the attachment inventory (id/title/mime/role/size), type/mime/parent/child ids, dates — the raw body when content=true, and `find=` counts a literal string (total + per-addendum-block) for flag-staleness tracking. The deep-dive counterpart to explore() and the surface reads. |
| `inspect(noteId, content?, section?, find?)` | Full raw read of one note: every label (not just noteType/status), every outbound relation, the attachment inventory (id/title/mime/role/size), type/mime/parent/child ids, dates — the raw body when content=true, `section=` narrows `content=true` to one section, and `find=` counts a literal string (total + per-addendum-block) for flag-staleness tracking — returning the nearest present fragment and the stored text around it when the count is zero. The deep-dive counterpart to explore() and the surface reads. |
| `addendum()` | Search Master, LLM singletons (responsibilities + protocols, not diary), and Knowledge for pending addendum blocks. These notes must be clean and structured — fold each block into the relevant section with revise(section=…, mode=replace), then leave no addendum marker. Only sessions, diary, and logs accumulate addendum history. Scoped/autonomous agents fold only what's in their lane — leaving personal/out-of-scope addendums for the next interactive session is correct; the call itself satisfies the gate. |
| `maintain(deep?, dryRun?, domain?, ack?)` | Lite: thread aging + unlabeled-node check per typed container. Deep adds: unlabeled thread-children check (each thread's own day-children, one level deeper than the lite pass) + stale-review + orphan/sink report (Memory/Threads + Knowledge, brain-wide inbound detection) + structural lint + duplicate-title detection + exact-duplicate relation-edge cleanup. `ack=[noteId]` marks a note reviewed-and-correct — quiet until its content changes (`suppressed` counts what was withheld). `domain=` narrows the deep passes to one lane. `coverage[]` names any capped pass. Report includes `policy` (active thresholds). |
| `maintain(deep?, dryRun?, domain?, ack?)` | Lite: thread aging + unlabeled-node check per typed container. Deep adds: unlabeled thread-children check (each thread's own day-children, one level deeper than the lite pass) + stale-review + orphan/sink report (Memory/Threads + Knowledge, brain-wide inbound detection) + structural lint + duplicate-title detection + exact-duplicate relation-edge cleanup. `ack=[noteId]` marks a note reviewed-and-correct — quiet until its content changes (`suppressed` counts what was withheld). `repair=[noteId]` unwinds one level of entity corruption in place, revision first. `domain=` narrows the deep passes to one lane — and a scoped run says so in `coverage`, so a clean scoped report is never mistaken for a clean brain. An unscoped run that finds things returns `laneHint`: **leave flags that are not yours rather than acking them** — an acknowledgement asserts you read the note. `coverage[]` names any capped pass. Report includes `policy` (active thresholds). |
| `forget(noteId, reason?, hard?)` | Archive (default) or hard-delete (blocked while backlinked). Undo with recover(). |

@@ -322,0 +343,0 @@

Sorry, the diff of this file is too big to display