# v0.10.10 — pull-channels: Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
>
> **Git policy for this project (operator-imposed):** the executing
> agent MUST NOT run `git commit`, `git push`, branch creation, or any
> other state-changing git command without an explicit per-action
> confirmation from the operator. Every "Commit" step below ends with
> `Pause: ask operator for permission to run the git command, paste
> the exact command, wait for an explicit yes`. Do not auto-stage and
> do not piggyback unrelated changes.

**Goal:** Ship four small "pull channels" so that runtimes / cron jobs /
operators without a `SessionStart` hook can still reach the agent's
working memory: a `brain_context` MCP tool, a Most-applied (30d)
section in `Brain/active.md`, a `o2b brain note` CLI verb, and a
semantic-search hint line in `o2b status`.

**Architecture:** Each feature reuses an existing module rather than
introducing a new abstraction. `brain_context` is a thin read wrapper
over `regenerateActive`. Most-applied lives in its own pure module
(`src/core/brain/most-applied.ts`) and plugs into the existing
`renderBody`. `o2b brain note` extracts the body of `toolBrainNote`
into `src/core/brain/note.ts` so the MCP handler and the CLI share
exactly one code path. The semantic hint lifts the existing truthy
predicate from `writeSearchInitBlock` into
`src/cli/helpers.ts:resolveSemanticConfigState` so both `o2b init` and
`o2b status` consume the same source.

**Tech Stack:** TypeScript (Node-style, run by Bun), `bun test`, no
new runtime dependencies. Tests live under `tests/`, mirroring the
existing structure (`tests/core/brain/`, `tests/cli/`, `tests/mcp/`,
`tests/hooks/`, `tests/e2e/`). The full test suite is run with
`bun test` from the repo root.

**Spec:** `docs/plans/2026-05-20-v0.10.10-design.md`. Read it before
starting — every section number below (`§5.2`, `§6.1`, …) refers to
that document.

---

## Pre-flight

These run once before Task 1.

- [ ] **Verify the working tree is clean.**

```bash
cd /srv/projects/open-second-brain
git status
```

Expected: branch is `master` or an existing feature branch, no unstaged
changes, no untracked files in `src/` or `tests/`. If dirty: stop and
ask the operator how to proceed.

- [ ] **Verify baseline tests pass.**

```bash
cd /srv/projects/open-second-brain
bun test
```

Expected: full suite green. If anything fails: stop and ask the
operator — implementing on top of red tests would mask new
regressions.

- [ ] **Verify typecheck passes.**

```bash
cd /srv/projects/open-second-brain
bun run typecheck
```

Expected: zero errors.

- [ ] **Read the spec.**

Read `docs/plans/2026-05-20-v0.10.10-design.md` end-to-end. Every
later step relies on section references from it.

---

## Task 1: `computeMostApplied` core function

**Files:**
- Create: `src/core/brain/most-applied.ts`
- Test: `tests/core/brain/most-applied.test.ts`

Implements §6.1 (window scan over `Brain/log/`, ranking by
`apply-evidence (result: applied)` count, keyed by preference id).

- [ ] **Step 1.1: Write the failing test file.**

```ts
// tests/core/brain/most-applied.test.ts
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { computeMostApplied } from "../../../src/core/brain/most-applied.ts";
import type { BrainPreference } from "../../../src/core/brain/types.ts";
import { BRAIN_PREFERENCE_STATUS } from "../../../src/core/brain/types.ts";

function buildPref(id: string, overrides: Partial<BrainPreference> = {}): BrainPreference {
  return {
    id,
    topic: id.replace(/^pref-/, ""),
    principle: `principle for ${id}`,
    signal: "positive",
    scope: null,
    pinned: false,
    status: BRAIN_PREFERENCE_STATUS.confirmed,
    confidence: "medium",
    confidence_value: 0.5,
    applied_count: 0,
    violated_count: 0,
    created_at: "2026-04-01T00:00:00Z",
    last_evidence_at: null,
    evidenced_by: [],
    contradicted_by: [],
    unconfirmed_until: null,
    primary_agent: null,
    retired_reason: null,
    ...overrides,
  } as BrainPreference;
}

function writeLogDay(vault: string, date: string, body: string): void {
  mkdirSync(join(vault, "Brain", "log"), { recursive: true });
  writeFileSync(join(vault, "Brain", "log", `${date}.md`), body, "utf8");
}

const APPLY_BLOCK = (ts: string, prefWikilink: string) =>
  [
    `## ${ts.slice(11, 19)}Z - apply-evidence`,
    `- timestamp: ${ts}`,
    `- preference: ${prefWikilink}`,
    `- artifact: [[src/foo.ts]]`,
    `- agent: tester`,
    `- result: applied`,
    "",
  ].join("\n");

const LOG_HEADER = (date: string) =>
  ["---", "kind: brain-log", `date: ${date}`, "tags: [project/open-second-brain, log]", "---", "", `# ${date}`, ""].join("\n");

describe("computeMostApplied", () => {
  let vault: string;
  beforeEach(() => {
    vault = mkdtempSync(join(tmpdir(), "osb-most-applied-"));
  });
  afterEach(() => {
    rmSync(vault, { recursive: true, force: true });
  });

  test("returns empty when log directory is missing", () => {
    const result = computeMostApplied(vault, [], { now: new Date("2026-05-20T00:00:00Z") });
    expect(result).toEqual([]);
  });

  test("counts only result=applied events inside the 30d window", () => {
    const now = new Date("2026-05-20T00:00:00Z");
    writeLogDay(
      vault,
      "2026-05-15",
      LOG_HEADER("2026-05-15")
        + APPLY_BLOCK("2026-05-15T10:00:00Z", "[[pref-a]]")
        + APPLY_BLOCK("2026-05-15T10:05:00Z", "[[pref-a]]"),
    );
    // 31 days ago — out of window.
    writeLogDay(
      vault,
      "2026-04-19",
      LOG_HEADER("2026-04-19")
        + APPLY_BLOCK("2026-04-19T10:00:00Z", "[[pref-a]]"),
    );
    const prefs = [buildPref("pref-a")];
    const result = computeMostApplied(vault, prefs, { now });
    expect(result.length).toBe(1);
    expect(result[0]!.applied_30d).toBe(2);
  });

  test("ignores violated and outdated results", () => {
    const now = new Date("2026-05-20T00:00:00Z");
    const VIOL = APPLY_BLOCK("2026-05-15T10:00:00Z", "[[pref-a]]").replace("applied", "violated");
    const OUTD = APPLY_BLOCK("2026-05-15T10:05:00Z", "[[pref-a]]").replace("applied", "outdated");
    writeLogDay(vault, "2026-05-15", LOG_HEADER("2026-05-15") + VIOL + OUTD);
    const result = computeMostApplied(vault, [buildPref("pref-a")], { now });
    expect(result).toEqual([]);
  });

  test("excludes preferences whose status is not confirmed or quarantine (input contract)", () => {
    const now = new Date("2026-05-20T00:00:00Z");
    writeLogDay(
      vault,
      "2026-05-15",
      LOG_HEADER("2026-05-15") + APPLY_BLOCK("2026-05-15T10:00:00Z", "[[pref-a]]"),
    );
    // Caller passes ONLY confirmed/quarantine preferences. Retired
    // preferences are dropped by the caller before reaching this
    // function, so an event referencing pref-a with no matching
    // entry in the map is silently ignored.
    const result = computeMostApplied(vault, [], { now });
    expect(result).toEqual([]);
  });

  test("includes quarantine preferences", () => {
    const now = new Date("2026-05-20T00:00:00Z");
    writeLogDay(
      vault,
      "2026-05-15",
      LOG_HEADER("2026-05-15") + APPLY_BLOCK("2026-05-15T10:00:00Z", "[[pref-q]]"),
    );
    const result = computeMostApplied(
      vault,
      [buildPref("pref-q", { status: BRAIN_PREFERENCE_STATUS.quarantine })],
      { now },
    );
    expect(result.length).toBe(1);
    expect(result[0]!.preference.id).toBe("pref-q");
    expect(result[0]!.applied_30d).toBe(1);
  });

  test("sorts by count desc, then by id asc", () => {
    const now = new Date("2026-05-20T00:00:00Z");
    writeLogDay(
      vault,
      "2026-05-15",
      LOG_HEADER("2026-05-15")
        + APPLY_BLOCK("2026-05-15T10:00:00Z", "[[pref-a]]")
        + APPLY_BLOCK("2026-05-15T10:01:00Z", "[[pref-a]]")
        + APPLY_BLOCK("2026-05-15T10:02:00Z", "[[pref-b]]")
        + APPLY_BLOCK("2026-05-15T10:03:00Z", "[[pref-c]]"),
    );
    const result = computeMostApplied(
      vault,
      [buildPref("pref-a"), buildPref("pref-b"), buildPref("pref-c")],
      { now },
    );
    expect(result.map((r) => r.preference.id)).toEqual(["pref-a", "pref-b", "pref-c"]);
    expect(result[0]!.applied_30d).toBe(2);
  });

  test("honours limit", () => {
    const now = new Date("2026-05-20T00:00:00Z");
    let body = LOG_HEADER("2026-05-15");
    for (const id of ["a", "b", "c"]) {
      body += APPLY_BLOCK("2026-05-15T10:00:00Z", `[[pref-${id}]]`);
    }
    writeLogDay(vault, "2026-05-15", body);
    const result = computeMostApplied(
      vault,
      [buildPref("pref-a"), buildPref("pref-b"), buildPref("pref-c")],
      { now, limit: 2 },
    );
    expect(result.length).toBe(2);
  });

  test("normalises wikilinks with line ranges", () => {
    const now = new Date("2026-05-20T00:00:00Z");
    writeLogDay(
      vault,
      "2026-05-15",
      LOG_HEADER("2026-05-15") + APPLY_BLOCK("2026-05-15T10:00:00Z", "[[pref-a:42-58]]"),
    );
    const result = computeMostApplied(vault, [buildPref("pref-a")], { now });
    expect(result.length).toBe(1);
    expect(result[0]!.applied_30d).toBe(1);
  });

  test("survives a malformed log day", () => {
    const now = new Date("2026-05-20T00:00:00Z");
    writeLogDay(vault, "2026-05-14", "this is not a valid log file");
    writeLogDay(
      vault,
      "2026-05-15",
      LOG_HEADER("2026-05-15") + APPLY_BLOCK("2026-05-15T10:00:00Z", "[[pref-a]]"),
    );
    const result = computeMostApplied(vault, [buildPref("pref-a")], { now });
    expect(result.length).toBe(1);
    expect(result[0]!.applied_30d).toBe(1);
  });

  test("event exactly on window edge is included", () => {
    const now = new Date("2026-05-20T00:00:00Z");
    const windowStart = new Date(now.getTime() - 30 * 24 * 3600 * 1000);
    const stamp = windowStart.toISOString().replace(/\.\d{3}Z$/, "Z");
    const date = stamp.slice(0, 10);
    writeLogDay(vault, date, LOG_HEADER(date) + APPLY_BLOCK(stamp, "[[pref-a]]"));
    const result = computeMostApplied(vault, [buildPref("pref-a")], { now });
    expect(result.length).toBe(1);
  });
});
```

- [ ] **Step 1.2: Run the test, confirm it fails.**

```bash
cd /srv/projects/open-second-brain
bun test tests/core/brain/most-applied.test.ts
```

Expected: every test fails because the module does not exist yet.

- [ ] **Step 1.3: Implement the module.**

Create `src/core/brain/most-applied.ts` with:

```ts
/**
 * `Brain/active.md` Most-applied (30d) section computation.
 *
 * Reads the last 30 days of `Brain/log/`, counts `apply-evidence`
 * events with `result: applied` whose `timestamp` lies inside the
 * window, attributes them to the matching preference, and returns
 * a ranked list capped at `limit` entries.
 *
 * Pure read. The caller passes the candidate preferences (already
 * filtered to `confirmed | quarantine` in `regenerateActive`), so
 * this module does not re-read the preferences directory.
 *
 * Anchored in design doc §6.1 ("Most-applied (30d) section in
 * `active.md`").
 */

import { existsSync, readdirSync } from "node:fs";

import { brainDirs } from "./paths.ts";
import { readLogDay } from "./log-jsonl.ts";
import { normaliseWikilinkTarget } from "./wikilink.ts";
import {
  BRAIN_APPLY_RESULT,
  BRAIN_LOG_EVENT_KIND,
  BRAIN_PREFERENCE_STATUS,
  type BrainPreference,
} from "./types.ts";

const WINDOW_MS = 30 * 24 * 3600 * 1000;
const DAY_MS = 24 * 3600 * 1000;
const DEFAULT_LIMIT = 10;
const LOG_FILENAME_RE = /^(\d{4}-\d{2}-\d{2})\.md$/;

export interface MostAppliedEntry {
  readonly preference: BrainPreference;
  readonly applied_30d: number;
}

export interface ComputeMostAppliedOptions {
  /** Window anchor. Defaults to `new Date()`. */
  readonly now?: Date;
  /** Max entries returned. Defaults to 10. */
  readonly limit?: number;
}

/**
 * Compute the Most-applied list for the `active.md` section.
 *
 * @param vault       Vault root.
 * @param preferences Candidate preferences (confirmed | quarantine).
 *                    Events referencing preferences outside this list
 *                    are silently ignored by design — retired rules
 *                    do not appear in `active.md`.
 */
export function computeMostApplied(
  vault: string,
  preferences: ReadonlyArray<BrainPreference>,
  opts: ComputeMostAppliedOptions = {},
): ReadonlyArray<MostAppliedEntry> {
  const now = opts.now ?? new Date();
  const limit = opts.limit ?? DEFAULT_LIMIT;
  const dirs = brainDirs(vault);
  if (!existsSync(dirs.log)) return [];

  // `preferences` is filtered upstream to confirmed | quarantine, but
  // we sanity-check the status here so a future caller passing a
  // wider list cannot accidentally surface retired rules.
  const prefByKey = new Map<string, BrainPreference>();
  for (const p of preferences) {
    if (
      p.status !== BRAIN_PREFERENCE_STATUS.confirmed &&
      p.status !== BRAIN_PREFERENCE_STATUS.quarantine
    ) {
      continue;
    }
    prefByKey.set(normaliseWikilinkTarget(p.id), p);
  }
  if (prefByKey.size === 0) return [];

  const windowEndMs = now.getTime();
  const windowStartMs = windowEndMs - WINDOW_MS;
  // Day-level fence: include the day before window start to absorb
  // UTC drift between the event timestamp and the file's date prefix.
  const earliestDayPrefix = isoDayPrefix(new Date(windowStartMs - DAY_MS));

  const counts = new Map<string, number>();
  for (const name of readdirSync(dirs.log)) {
    const m = LOG_FILENAME_RE.exec(name);
    if (!m) continue;
    const datePrefix = m[1]!;
    if (datePrefix < earliestDayPrefix) continue;

    let parsed;
    try {
      parsed = readLogDay(vault, datePrefix);
    } catch (err) {
      process.stderr.write(
        `warning: most-applied: failed to read ${datePrefix}: ${(err as Error).message}\n`,
      );
      continue;
    }
    for (const e of parsed.entries) {
      if (e.eventType !== BRAIN_LOG_EVENT_KIND.applyEvidence) continue;
      const result = e.body["result"];
      if (result !== BRAIN_APPLY_RESULT.applied) continue;
      const tsMs = Date.parse(e.timestamp);
      if (!Number.isFinite(tsMs)) continue;
      if (tsMs < windowStartMs || tsMs > windowEndMs) continue;
      const prefField = e.body["preference"];
      if (typeof prefField !== "string") continue;
      const key = normaliseWikilinkTarget(prefField);
      if (!prefByKey.has(key)) continue;
      counts.set(key, (counts.get(key) ?? 0) + 1);
    }
  }

  const entries: MostAppliedEntry[] = [];
  for (const [key, count] of counts) {
    const pref = prefByKey.get(key)!;
    entries.push({ preference: pref, applied_30d: count });
  }
  entries.sort((a, b) => {
    if (b.applied_30d !== a.applied_30d) return b.applied_30d - a.applied_30d;
    return a.preference.id.localeCompare(b.preference.id);
  });
  return entries.slice(0, limit);
}

function isoDayPrefix(d: Date): string {
  return d.toISOString().slice(0, 10);
}
```

> **DRY check:** `BRAIN_APPLY_RESULT.applied` is the canonical literal
> for `"applied"`. If `src/core/brain/types.ts` does not export this
> constant, use `e.body["result"] === "applied"` inline — do NOT
> introduce a new constant for it.

- [ ] **Step 1.4: Run the test, confirm it passes.**

```bash
cd /srv/projects/open-second-brain
bun test tests/core/brain/most-applied.test.ts
```

Expected: all tests green.

- [ ] **Step 1.5: Typecheck.**

```bash
cd /srv/projects/open-second-brain
bun run typecheck
```

Expected: zero errors.

- [ ] **Step 1.6: Commit.**

```bash
git add src/core/brain/most-applied.ts tests/core/brain/most-applied.test.ts
git commit -m "feat(brain): computeMostApplied — 30d apply-evidence ranker"
```

Pause: ask operator for permission to run the git command, paste the
exact command, wait for an explicit yes.

---

## Task 2: Render Most-applied section in `active.md`

**Files:**
- Modify: `src/core/brain/active.ts`
- Modify: `tests/core/brain/active.test.ts` (extend; create if missing)

Implements §6.2, §6.3, §6.4.

- [ ] **Step 2.1: Check whether `tests/core/brain/active.test.ts` exists.**

```bash
ls -1 /srv/projects/open-second-brain/tests/core/brain/active.test.ts 2>&1 || echo "absent"
```

If absent: create it in step 2.2 with the full scaffolding shown below.
If present: append the new `describe("Most-applied (30d)", ...)` block
without disturbing existing tests.

- [ ] **Step 2.2: Write the failing Most-applied test block.**

Add (or create the file with) the following block. If creating from
scratch, prepend the same imports `most-applied.test.ts` uses, plus
`regenerateActive` from the module under test.

```ts
// In tests/core/brain/active.test.ts
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { regenerateActive } from "../../../src/core/brain/active.ts";
import { writePreference } from "../../../src/core/brain/preference.ts";
import {
  BRAIN_PREFERENCE_STATUS,
  BRAIN_SIGNAL_SIGN,
} from "../../../src/core/brain/types.ts";

function bootstrap(vault: string): void {
  mkdirSync(join(vault, "Brain", "preferences"), { recursive: true });
  mkdirSync(join(vault, "Brain", "retired"), { recursive: true });
  mkdirSync(join(vault, "Brain", "log"), { recursive: true });
  writeFileSync(
    join(vault, "Brain", "_brain.yaml"),
    "dream:\n  unconfirmed_window_days: 7\n",
    "utf8",
  );
}

const LOG_HEADER = (date: string) =>
  ["---", "kind: brain-log", `date: ${date}`, "tags: [project/open-second-brain, log]", "---", "", `# ${date}`, ""].join("\n");

const APPLY_BLOCK = (ts: string, prefWikilink: string) =>
  [
    `## ${ts.slice(11, 19)}Z - apply-evidence`,
    `- timestamp: ${ts}`,
    `- preference: ${prefWikilink}`,
    `- artifact: [[src/foo.ts]]`,
    `- agent: tester`,
    `- result: applied`,
    "",
  ].join("\n");

describe("Most-applied (30d) section", () => {
  let vault: string;
  beforeEach(() => {
    vault = mkdtempSync(join(tmpdir(), "osb-active-most-applied-"));
    bootstrap(vault);
  });
  afterEach(() => {
    rmSync(vault, { recursive: true, force: true });
  });

  test("section absent when no applied events in window", () => {
    writePreference(vault, {
      id: "pref-a",
      topic: "a",
      principle: "principle a",
      signal: BRAIN_SIGNAL_SIGN.positive,
      scope: null,
      pinned: false,
      status: BRAIN_PREFERENCE_STATUS.confirmed,
      confidence: "medium",
      confidence_value: 0.5,
      applied_count: 0,
      violated_count: 0,
      created_at: "2026-05-01T00:00:00Z",
      last_evidence_at: null,
      evidenced_by: [],
      contradicted_by: [],
      unconfirmed_until: null,
      primary_agent: null,
      retired_reason: null,
    });
    const res = regenerateActive(vault, { now: new Date("2026-05-20T00:00:00Z") });
    const body = readFileSync(res.path, "utf8");
    expect(body).not.toContain("## Most-applied");
    expect(res.counts.most_applied_30d).toBe(0);
  });

  test("section rendered when applied events exist", () => {
    writePreference(vault, {
      id: "pref-a",
      topic: "a",
      principle: "principle a",
      signal: BRAIN_SIGNAL_SIGN.positive,
      scope: "writing",
      pinned: false,
      status: BRAIN_PREFERENCE_STATUS.confirmed,
      confidence: "medium",
      confidence_value: 0.5,
      applied_count: 0,
      violated_count: 0,
      created_at: "2026-05-01T00:00:00Z",
      last_evidence_at: null,
      evidenced_by: [],
      contradicted_by: [],
      unconfirmed_until: null,
      primary_agent: null,
      retired_reason: null,
    });
    writeFileSync(
      join(vault, "Brain", "log", "2026-05-15.md"),
      LOG_HEADER("2026-05-15") + APPLY_BLOCK("2026-05-15T10:00:00Z", "[[pref-a]]"),
      "utf8",
    );
    const res = regenerateActive(vault, { now: new Date("2026-05-20T00:00:00Z") });
    const body = readFileSync(res.path, "utf8");
    expect(body).toContain("## Most-applied (30d) (1)");
    expect(body).toContain("`pref-a`");
    expect(body).toContain("applied_30d: 1");
    expect(res.counts.most_applied_30d).toBe(1);
  });

  test("idempotent: re-render produces byte-equal body", () => {
    writePreference(vault, {
      id: "pref-a",
      topic: "a",
      principle: "p",
      signal: BRAIN_SIGNAL_SIGN.positive,
      scope: null,
      pinned: false,
      status: BRAIN_PREFERENCE_STATUS.confirmed,
      confidence: "medium",
      confidence_value: 0.5,
      applied_count: 0,
      violated_count: 0,
      created_at: "2026-05-01T00:00:00Z",
      last_evidence_at: null,
      evidenced_by: [],
      contradicted_by: [],
      unconfirmed_until: null,
      primary_agent: null,
      retired_reason: null,
    });
    writeFileSync(
      join(vault, "Brain", "log", "2026-05-15.md"),
      LOG_HEADER("2026-05-15") + APPLY_BLOCK("2026-05-15T10:00:00Z", "[[pref-a]]"),
      "utf8",
    );
    const r1 = regenerateActive(vault, { now: new Date("2026-05-20T00:00:00Z") });
    const body1 = readFileSync(r1.path, "utf8");
    const r2 = regenerateActive(vault, { now: new Date("2026-05-20T00:01:00Z") });
    const body2 = readFileSync(r2.path, "utf8");
    // frontmatter `generated_at` would differ if a rewrite happened;
    // because the body did not change, regenerateActive must keep the
    // file untouched.
    expect(body2).toBe(body1);
    expect(r2.changed).toBe(false);
  });
});
```

- [ ] **Step 2.3: Run the failing block.**

```bash
cd /srv/projects/open-second-brain
bun test tests/core/brain/active.test.ts
```

Expected: the Most-applied block fails because the field
`counts.most_applied_30d` does not exist yet and the section is not
rendered.

- [ ] **Step 2.4: Extend `RegenerateActiveResult.counts`.**

Edit `src/core/brain/active.ts`. Find the `RegenerateActiveResult`
interface (around line 57). Add the `most_applied_30d` field:

```ts
export interface RegenerateActiveResult {
  readonly path: string;
  readonly changed: boolean;
  readonly counts: {
    readonly confirmed: number;
    readonly quarantine: number;
    readonly retired_recent: number;
    readonly most_applied_30d: number;  // NEW
  };
}
```

- [ ] **Step 2.5: Call `computeMostApplied` from `regenerateActive`.**

Edit `src/core/brain/active.ts`. Add the import near the top:

```ts
import { computeMostApplied, type MostAppliedEntry } from "./most-applied.ts";
```

In the `regenerateActive` body, after computing `confirmed` and
`quarantine`:

```ts
  const mostApplied = computeMostApplied(
    vault,
    [...confirmed, ...quarantine],
    { now },
  );
```

Pass `mostApplied` into the render:

```ts
  const body = renderBody({
    confirmed,
    quarantine,
    retiredRecent,
    mostApplied,        // NEW
  });
```

Update the returned counts:

```ts
  return {
    path,
    changed,
    counts: {
      confirmed: confirmed.length,
      quarantine: quarantine.length,
      retired_recent: retiredRecent.length,
      most_applied_30d: mostApplied.length,   // NEW
    },
  };
```

- [ ] **Step 2.6: Extend `RenderInput` and `renderBody`.**

Edit `src/core/brain/active.ts`. Extend `RenderInput`:

```ts
interface RenderInput {
  readonly confirmed: ReadonlyArray<BrainPreference>;
  readonly quarantine: ReadonlyArray<BrainPreference>;
  readonly retiredRecent: ReadonlyArray<BrainRetired>;
  readonly mostApplied: ReadonlyArray<MostAppliedEntry>;   // NEW
}
```

In `renderBody`, between the `Confirmed` section and the `Quarantine`
section, insert:

```ts
  if (input.mostApplied.length > 0) {
    out.push(`## Most-applied (30d) (${input.mostApplied.length})`);
    out.push("");
    for (const m of input.mostApplied) out.push(renderMostAppliedLine(m));
    out.push("");
  }
```

Add the renderer near the other `render*Line` helpers:

```ts
function renderMostAppliedLine(m: MostAppliedEntry): string {
  const p = m.preference;
  const tags: string[] = [];
  if (p.scope) tags.push(`scope: ${p.scope}`);
  tags.push(`applied_30d: ${m.applied_30d}`);
  return `- \`${p.id}\` (${tags.join(", ")}) — ${p.principle}`;
}
```

- [ ] **Step 2.7: Run the active.md tests, confirm they pass.**

```bash
cd /srv/projects/open-second-brain
bun test tests/core/brain/active.test.ts tests/core/brain/most-applied.test.ts
```

Expected: green.

- [ ] **Step 2.8: Run the full suite.**

```bash
cd /srv/projects/open-second-brain
bun test
```

Expected: green. Any pre-existing test that asserted exact body bytes
of `active.md` will likely surface here — fix it by appending the
`Most-applied` expectation rather than weakening the assertion.

- [ ] **Step 2.9: Typecheck.**

```bash
cd /srv/projects/open-second-brain
bun run typecheck
```

Expected: zero errors.

- [ ] **Step 2.10: Commit.**

```bash
git add src/core/brain/active.ts tests/core/brain/active.test.ts
git commit -m "feat(brain): Most-applied (30d) section in active.md"
```

Pause: ask operator for permission, paste the command, wait for yes.

---

## Task 3: Extract `appendBrainNote` core

**Files:**
- Create: `src/core/brain/note.ts`
- Modify: `src/mcp/brain-tools.ts` (refactor `toolBrainNote`)
- Test: `tests/core/brain/note.test.ts`

Implements §7.1. No behaviour change for the MCP tool — existing tests
in `tests/mcp/brain.test.ts` must remain green.

- [ ] **Step 3.1: Write the failing test for the new core function.**

```ts
// tests/core/brain/note.test.ts
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { appendBrainNote } from "../../../src/core/brain/note.ts";

function bootstrap(vault: string): void {
  mkdirSync(join(vault, "Brain", "log"), { recursive: true });
  writeFileSync(
    join(vault, "Brain", "_brain.yaml"),
    "dream:\n  unconfirmed_window_days: 7\n",
    "utf8",
  );
}

describe("appendBrainNote", () => {
  let vault: string;
  beforeEach(() => {
    vault = mkdtempSync(join(tmpdir(), "osb-brain-note-"));
    bootstrap(vault);
  });
  afterEach(() => {
    rmSync(vault, { recursive: true, force: true });
  });

  test("writes a one-line note to today's log under kind=note", () => {
    const now = new Date("2026-05-20T12:34:56Z");
    const res = appendBrainNote({
      vault,
      text: "released v0.10.10",
      agent: "tester",
      now,
    });
    expect(res.agent).toBe("tester");
    expect(res.logged_at).toBe("2026-05-20T12:34:56Z");
    expect(res.log_path).toBe("Brain/log/2026-05-20.md");
    const body = readFileSync(res.absolute_log_path, "utf8");
    expect(body).toContain("## 12:34:56Z - note");
    expect(body).toContain("- text: released v0.10.10");
    expect(body).toContain("- agent: tester");
  });

  test("collapses multi-line text to one line", () => {
    const res = appendBrainNote({
      vault,
      text: "line one\nline two\r\nline three",
      agent: "tester",
      now: new Date("2026-05-20T00:00:00Z"),
    });
    const body = readFileSync(res.absolute_log_path, "utf8");
    expect(body).toContain("- text: line one line two line three");
  });

  test("rejects empty text", () => {
    expect(() =>
      appendBrainNote({ vault, text: "   ", agent: "tester", now: new Date() }),
    ).toThrow(/text is required/);
  });

  test("rejects whitespace-only text after sanitiser", () => {
    expect(() =>
      appendBrainNote({ vault, text: "\n\t  \n", agent: "tester", now: new Date() }),
    ).toThrow(/text is required/);
  });
});
```

- [ ] **Step 3.2: Run, confirm it fails.**

```bash
cd /srv/projects/open-second-brain
bun test tests/core/brain/note.test.ts
```

Expected: file not found → tests fail.

- [ ] **Step 3.3: Create the core module.**

Create `src/core/brain/note.ts`:

```ts
/**
 * `appendBrainNote` — single source of truth for the `brain_note`
 * event-kind write.
 *
 * Called by:
 *   - `src/mcp/brain-tools.ts:toolBrainNote` (MCP tool, agent path)
 *   - `src/cli/brain/verbs/note.ts:cmdBrainNote` (CLI verb, cron /
 *     shell path)
 *
 * Both surfaces share this body so the on-disk shape cannot drift
 * between them. Sanitiser, agent resolution, and log append happen
 * here; protocol-specific concerns (MCPError vs CLI exit code) stay
 * in the wrappers.
 *
 * Anchored in design doc §7.1 ("Core extraction").
 */

import { resolve } from "node:path";

import { sanitiseTextField } from "../redactor.ts";
import { resolveAgentName } from "../config.ts";
import { appendLogEvent } from "./log.ts";
import { isoSecond } from "./time.ts";
import { BRAIN_LOG_EVENT_KIND } from "./types.ts";
import { vaultRelativeSafe } from "./paths.ts";

const NOTE_TEXT_MAX_LEN = 4096;

export interface AppendBrainNoteInput {
  readonly vault: string;
  readonly text: string;
  readonly agent?: string;
  readonly now?: Date;
  readonly configPath?: string;
}

export interface AppendBrainNoteResult {
  readonly logged_at: string;
  /** Vault-relative POSIX path (`Brain/log/<date>.md`). */
  readonly log_path: string;
  /** Absolute filesystem path. */
  readonly absolute_log_path: string;
  /** Identity actually written into the log entry. */
  readonly agent: string;
}

/**
 * Append one `note` event to `Brain/log/<today>.md` (and the JSONL
 * sidecar via `appendLogEvent`). Throws on empty text — wrappers
 * translate the error to their protocol's idiom.
 */
export function appendBrainNote(
  input: AppendBrainNoteInput,
): AppendBrainNoteResult {
  const sanitised = sanitiseTextField(input.text, {
    maxLen: NOTE_TEXT_MAX_LEN,
    singleLine: true,
  }).trim();
  if (!sanitised) {
    throw new Error("brain_note: text is required");
  }
  const agent =
    (input.agent && input.agent.trim().length > 0
      ? input.agent
      : resolveAgentName(input.configPath));
  const timestamp = isoSecond(input.now ?? new Date());
  const entry = {
    timestamp,
    eventType: BRAIN_LOG_EVENT_KIND.note,
    body: { text: sanitised, agent },
  } as const;
  const res = appendLogEvent(input.vault, entry);
  return {
    logged_at: timestamp,
    log_path: vaultRelativeSafe(input.vault, res.logPath),
    absolute_log_path: resolve(res.logPath),
    agent,
  };
}
```

> **Heads-up:** `vaultRelativeSafe` currently lives in
> `src/mcp/brain-tools.ts:605`. The MCP layer should not be the home
> of a path helper that the core now needs. Move
> `vaultRelativeSafe` (and any tightly-coupled helper it uses) into
> `src/core/brain/paths.ts`, re-export it from `src/mcp/brain-tools.ts`
> for backward compatibility within the MCP layer, and update the
> import in `note.ts` to point at the new home. If the move is more
> than ~15 lines or pulls in unrelated MCP types, stop and ask the
> operator before proceeding — keep this PR small.

- [ ] **Step 3.4: Move `vaultRelativeSafe` to `paths.ts`.**

In `src/core/brain/paths.ts`, append:

```ts
import { isAbsolute, relative, resolve } from "node:path";

/**
 * Compute the vault-relative POSIX path of `target` (an absolute or
 * vault-relative filesystem path). Falls back to the absolute path
 * when `target` resolves outside the vault, so callers never silently
 * leak a `../` traversal through what looks like a relative path.
 */
export function vaultRelativeSafe(vault: string, target: string): string {
  const absVault = resolve(vault);
  const absTarget = isAbsolute(target) ? target : resolve(vault, target);
  const rel = relative(absVault, absTarget);
  if (rel.startsWith("..") || isAbsolute(rel)) return absTarget;
  return rel.split(/[\\/]/).join("/");
}
```

In `src/mcp/brain-tools.ts`, around line 605, replace the local
`vaultRelativeSafe` definition with a re-export:

```ts
export { vaultRelativeSafe } from "../core/brain/paths.ts";
```

If the local definition has additional logic this re-export does not
cover: stop and ask the operator before changing semantics.

- [ ] **Step 3.5: Run the new core tests, confirm green.**

```bash
cd /srv/projects/open-second-brain
bun test tests/core/brain/note.test.ts
```

Expected: green.

- [ ] **Step 3.6: Refactor `toolBrainNote` to delegate.**

Edit `src/mcp/brain-tools.ts:toolBrainNote` (around line 324).
Replace the body with:

```ts
async function toolBrainNote(
  ctx: ServerContext,
  args: Record<string, unknown>,
): Promise<Record<string, unknown>> {
  const rawText = coerceStr(args, "text", true)!;
  const agentArg = coerceStr(args, "agent", false);

  let res;
  try {
    res = appendBrainNote({
      vault: ctx.vault,
      text: rawText,
      ...(normalizeAgentArgument(agentArg) !== null
        ? { agent: normalizeAgentArgument(agentArg)! }
        : {}),
      ...(ctx.configPath ? { configPath: ctx.configPath } : {}),
    });
  } catch (err) {
    throw new MCPError(INVALID_PARAMS, (err as Error).message);
  }
  return {
    logged_at: res.logged_at,
    log_path: res.log_path,
    absolute_log_path: res.absolute_log_path,
    agent: res.agent,
  };
}
```

Add the import near the top:

```ts
import { appendBrainNote } from "../core/brain/note.ts";
```

Drop now-unused local symbols (`NOTE_TEXT_MAX_LEN`, the local
sanitiser invocation, the local `appendLogEvent` call) from this
file.

- [ ] **Step 3.7: Run the full MCP brain suite, confirm green.**

```bash
cd /srv/projects/open-second-brain
bun test tests/mcp/brain.test.ts tests/core/brain/note.test.ts
```

Expected: green. The existing MCP brain_note tests must still pass —
the surface contract is byte-identical.

- [ ] **Step 3.8: Full suite + typecheck.**

```bash
cd /srv/projects/open-second-brain
bun test
bun run typecheck
```

Expected: green / zero errors.

- [ ] **Step 3.9: Commit.**

```bash
git add src/core/brain/note.ts src/core/brain/paths.ts src/mcp/brain-tools.ts tests/core/brain/note.test.ts
git commit -m "refactor(brain): extract appendBrainNote core, share between MCP and CLI"
```

Pause: ask operator, wait for yes.

---

## Task 4: CLI verb `o2b brain note`

**Files:**
- Create: `src/cli/brain/verbs/note.ts`
- Modify: `src/cli/brain/verbs/index.ts` (export)
- Modify: `src/cli/brain.ts` (dispatch)
- Modify: `src/cli/brain/help-text.ts` (BRAIN_HELP + VERB_HELP)
- Test: `tests/cli/brain-note.test.ts`

Implements §7.2.

- [ ] **Step 4.1: Write the failing CLI test.**

```ts
// tests/cli/brain-note.test.ts
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { execFileSync } from "node:child_process";
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

const O2B = join(import.meta.dir, "..", "..", "scripts", "o2b");

function runO2b(argv: string[], env: Record<string, string> = {}) {
  return execFileSync(O2B, argv, {
    encoding: "utf8",
    env: { ...process.env, ...env },
  });
}

describe("o2b brain note", () => {
  let vault: string;
  beforeEach(() => {
    vault = mkdtempSync(join(tmpdir(), "osb-cli-brain-note-"));
    mkdirSync(join(vault, "Brain", "log"), { recursive: true });
    writeFileSync(
      join(vault, "Brain", "_brain.yaml"),
      "dream:\n  unconfirmed_window_days: 7\n",
      "utf8",
    );
  });
  afterEach(() => {
    rmSync(vault, { recursive: true, force: true });
  });

  test("writes a note with the positional text", () => {
    runO2b(["brain", "note", "released v0.10.10", "--vault", vault, "--agent", "tester"]);
    const today = new Date().toISOString().slice(0, 10);
    const body = readFileSync(join(vault, "Brain", "log", `${today}.md`), "utf8");
    expect(body).toContain("- note");
    expect(body).toContain("- text: released v0.10.10");
    expect(body).toContain("- agent: tester");
  });

  test("--json emits the structured result", () => {
    const out = runO2b([
      "brain", "note", "json output check",
      "--vault", vault, "--agent", "tester", "--json",
    ]);
    const parsed = JSON.parse(out);
    expect(parsed.agent).toBe("tester");
    expect(parsed.log_path).toMatch(/^Brain\/log\/\d{4}-\d{2}-\d{2}\.md$/);
    expect(parsed.logged_at).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/);
    expect(typeof parsed.absolute_log_path).toBe("string");
  });

  test("missing text exits non-zero with a clear message", () => {
    let failed = false;
    try {
      execFileSync(O2B, ["brain", "note", "--vault", vault], {
        encoding: "utf8",
        env: process.env,
      });
    } catch (exc: any) {
      failed = true;
      expect(exc.status).toBe(2);
      expect(String(exc.stderr ?? "")).toContain("brain note");
    }
    expect(failed).toBe(true);
  });

  test("empty positional text exits non-zero", () => {
    let failed = false;
    try {
      execFileSync(O2B, ["brain", "note", "   ", "--vault", vault, "--agent", "tester"], {
        encoding: "utf8",
        env: process.env,
      });
    } catch (exc: any) {
      failed = true;
      expect(exc.status).not.toBe(0);
    }
    expect(failed).toBe(true);
  });
});
```

- [ ] **Step 4.2: Run, confirm it fails.**

```bash
cd /srv/projects/open-second-brain
bun test tests/cli/brain-note.test.ts
```

Expected: every test fails because `brain note` is an unknown verb.

- [ ] **Step 4.3: Implement the verb handler.**

Create `src/cli/brain/verbs/note.ts`:

```ts
/**
 * `o2b brain note <text> [--agent <name>]` — CLI mirror of the MCP
 * `brain_note` tool.
 *
 * Same on-disk contract as the agent path: writes one
 * `Brain/log/<today>.md` entry under the `note` event kind plus the
 * JSONL sidecar. Intended for cron jobs and shell scripts that
 * cannot reach the MCP surface.
 *
 * Anchored in design doc §7.2 ("CLI surface").
 */

import { defaultConfigPath } from "../../../core/config.ts";
import { appendBrainNote } from "../../../core/brain/note.ts";
import { fail, ok, okJson, parse, resolveBrainVault } from "../helpers.ts";

export async function cmdBrainNote(argv: string[]): Promise<number> {
  const { flags, positional } = parse(argv, {
    vault: { type: "string" },
    config: { type: "string" },
    agent: { type: "string" },
    json: { type: "boolean" },
  });

  if (positional.length === 0) {
    return fail("brain note requires a text argument: o2b brain note \"<text>\"");
  }
  if (positional.length > 1) {
    return fail(
      "brain note takes exactly one positional argument — quote multi-word text",
    );
  }
  const text = positional[0]!;

  const config = (flags["config"] as string | undefined) ?? defaultConfigPath();
  const vault = resolveBrainVault(flags["vault"] as string | undefined, config);

  let res;
  try {
    res = appendBrainNote({
      vault,
      text,
      ...(typeof flags["agent"] === "string" && flags["agent"].trim().length > 0
        ? { agent: flags["agent"] as string }
        : {}),
      configPath: config,
    });
  } catch (exc) {
    return fail((exc as Error).message ?? String(exc));
  }

  if (flags["json"]) {
    return okJson({
      logged_at: res.logged_at,
      log_path: res.log_path,
      absolute_log_path: res.absolute_log_path,
      agent: res.agent,
    });
  }
  return ok(`logged note at ${res.log_path}`);
}
```

- [ ] **Step 4.4: Export from the verbs barrel.**

Edit `src/cli/brain/verbs/index.ts`. Add:

```ts
export { cmdBrainNote } from "./note.ts";
```

- [ ] **Step 4.5: Wire the dispatcher.**

Edit `src/cli/brain.ts`. Add `cmdBrainNote` to the import group at
the top:

```ts
import {
  // ... existing imports
  cmdBrainNote,
  // ...
} from "./brain/verbs/index.ts";
```

In the dispatch `switch`, add the case alphabetically near the other
plain verbs (between `cmdBrainMerge` and `cmdBrainPin` is fine):

```ts
      case "note": return await cmdBrainNote(rest);
```

- [ ] **Step 4.6: Document in BRAIN_HELP and VERB_HELP.**

Edit `src/cli/brain/help-text.ts`. Add a `note` line to `BRAIN_HELP`
near the related verbs (after `feedback`, `apply-evidence`):

```text
  note <text>             Append a one-line narrative milestone to Brain/log/today
```

Add a `VERB_HELP["note"]` entry:

```ts
  note: [
    "o2b brain note <text> [--agent <name>] [--vault <path>] [--config <path>] [--json]",
    "",
    "Append one narrative-milestone line to Brain/log/<today>.md under the",
    "`note` event kind. CLI mirror of the MCP `brain_note` tool — same on-disk",
    "contract. Use from cron jobs and shell scripts.",
    "",
    "Multi-line input is collapsed to a single line (same contract as MCP).",
    "",
  ].join("\n"),
```

- [ ] **Step 4.7: Re-run the CLI test, confirm green.**

```bash
cd /srv/projects/open-second-brain
bun test tests/cli/brain-note.test.ts
```

Expected: green.

- [ ] **Step 4.8: Full suite + typecheck.**

```bash
cd /srv/projects/open-second-brain
bun test
bun run typecheck
```

Expected: green / zero errors.

- [ ] **Step 4.9: Commit.**

```bash
git add src/cli/brain/verbs/note.ts src/cli/brain/verbs/index.ts src/cli/brain.ts src/cli/brain/help-text.ts tests/cli/brain-note.test.ts
git commit -m "feat(cli): o2b brain note — Brain-native milestone log for cron/shell"
```

Pause: ask operator, wait for yes.

---

## Task 5: Hook detector accepts `o2b brain note`

**Files:**
- Modify: `hooks/lib/detect.ts`
- Test: `tests/hooks/detect.test.ts` (extend; create if missing)

Implements §7.3.

- [ ] **Step 5.1: Locate the test file.**

```bash
ls -1 /srv/projects/open-second-brain/tests/hooks/ 2>&1
```

If `detect.test.ts` is absent: create it with the imports the other
hook tests use. Otherwise extend it.

- [ ] **Step 5.2: Add the failing assertions.**

The public surface in `hooks/lib/detect.ts` is `summarizeTurn` —
there is no public `isBrainEventBash`. Tests drive the bash-needle
list through `summarizeTurn` with an empty `toolCalls` array and the
candidate command in `bashCommandsThisTurn`. The existing needles
(`"o2b brain feedback"`, `"o2b brain apply-evidence"`) are bare
substrings matched via `String.prototype.includes`, so the new
needle MUST follow the same convention (no trailing space).

Append (or include in the new file):

```ts
import { describe, expect, test } from "bun:test";
import { summarizeTurn } from "../../hooks/lib/detect.ts";

describe("summarizeTurn — o2b brain note bash needle", () => {
  test("o2b brain note '...' counts as a brain event", () => {
    const s = summarizeTurn([], ["o2b brain note \"v0.10.10 released\""]);
    expect(s.hadBrainEvent).toBe(true);
  });
  test("o2b brain note (no args) still trips the needle", () => {
    // Mirrors the existing semantics for `o2b brain feedback` /
    // `o2b brain apply-evidence`: bare-substring match. The CLI
    // itself rejects empty text with exit 2; the hook side only
    // cares about intent.
    const s = summarizeTurn([], ["o2b brain note"]);
    expect(s.hadBrainEvent).toBe(true);
  });
  test("an unrelated command does not trip the needle", () => {
    const s = summarizeTurn([], ["echo hello"]);
    expect(s.hadBrainEvent).toBe(false);
  });
});
```

> The existing needle list does NOT defend against lookalikes such
> as `o2b brain notes` — the v0.10.6 needles (`o2b brain feedback`,
> `o2b brain apply-evidence`) accept the same substring shape. We
> deliberately do NOT introduce a stricter check for `note`; the
> behaviour stays uniform across the three CLI verbs. If a real
> CLI verb named `note-something` ever lands, revisit then.

- [ ] **Step 5.3: Run, confirm failure on the positive cases.**

```bash
cd /srv/projects/open-second-brain
bun test tests/hooks/detect.test.ts
```

Expected: at least the first two new tests fail.

- [ ] **Step 5.4: Add the needle.**

Edit `hooks/lib/detect.ts`. Find `BRAIN_EVENT_BASH_NEEDLES`. Add
`"o2b brain note"` to the array (bare substring, matching the existing
two entries' shape — `"o2b brain feedback"`, `"o2b brain apply-evidence"`).
Alphabetise relative to those.

Confirm `BRAIN_EVENT_NAME_SUFFIX` already includes `brain_note`
(it does, as of v0.10.8). No edit needed there.

- [ ] **Step 5.5: Re-run, confirm green.**

```bash
cd /srv/projects/open-second-brain
bun test tests/hooks/detect.test.ts
```

Expected: green.

- [ ] **Step 5.6: Full suite + typecheck.**

```bash
cd /srv/projects/open-second-brain
bun test
bun run typecheck
```

Expected: green / zero errors.

- [ ] **Step 5.7: Commit.**

```bash
git add hooks/lib/detect.ts tests/hooks/detect.test.ts
git commit -m "feat(hooks): detect o2b brain note as a brain event"
```

Pause: ask operator, wait for yes.

---

## Task 6: `brain_context` MCP tool

**Files:**
- Modify: `src/mcp/brain-tools.ts` (new `toolBrainContext`)
- Modify: `src/mcp/tools.ts` (add to `WRITER_TOOL_NAMES` + tool table)
- Modify: `src/mcp/instructions.ts` (document the new tool)
- Test: `tests/mcp/brain-context.test.ts`

Implements §5.

- [ ] **Step 6.1: Write the failing test.**

```ts
// tests/mcp/brain-context.test.ts
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { buildToolTable } from "../../src/mcp/tools.ts";

function findHandler(name: string) {
  const tool = buildToolTable("full").find((t) => t.name === name);
  if (!tool) throw new Error(`tool not in table: ${name}`);
  return tool.handler;
}

function bootstrap(vault: string): void {
  mkdirSync(join(vault, "Brain", "preferences"), { recursive: true });
  mkdirSync(join(vault, "Brain", "retired"), { recursive: true });
  mkdirSync(join(vault, "Brain", "log"), { recursive: true });
  writeFileSync(
    join(vault, "Brain", "_brain.yaml"),
    "dream:\n  unconfirmed_window_days: 7\n",
    "utf8",
  );
}

describe("brain_context tool", () => {
  let vault: string;
  beforeEach(() => {
    vault = mkdtempSync(join(tmpdir(), "osb-mcp-brain-context-"));
  });
  afterEach(() => {
    rmSync(vault, { recursive: true, force: true });
  });

  test("returns present:false when Brain/ does not exist", async () => {
    const handler = findHandler("brain_context");
    const out: any = await handler({ vault, configPath: null, repoRoot: null }, {});
    expect(out.present).toBe(false);
    expect(out.content).toBe("");
    expect(out.counts.confirmed).toBe(0);
    expect(out.generated_at).toBeNull();
  });

  test("self-heals active.md on first call when Brain/ exists", async () => {
    bootstrap(vault);
    const handler = findHandler("brain_context");
    const out: any = await handler({ vault, configPath: null, repoRoot: null }, {});
    expect(out.present).toBe(true);
    expect(out.content).toContain("# Active Brain Preferences");
    expect(out.generated_at).not.toBeNull();
    // file exists on disk
    const onDisk = readFileSync(join(vault, "Brain", "active.md"), "utf8");
    expect(out.content).toBe(onDisk);
  });

  test("payload includes most_applied_30d count", async () => {
    bootstrap(vault);
    const handler = findHandler("brain_context");
    const out: any = await handler({ vault, configPath: null, repoRoot: null }, {});
    expect(typeof out.counts.most_applied_30d).toBe("number");
  });

  test("two calls return byte-equal content when nothing changed", async () => {
    bootstrap(vault);
    const handler = findHandler("brain_context");
    const a: any = await handler({ vault, configPath: null, repoRoot: null }, {});
    const b: any = await handler({ vault, configPath: null, repoRoot: null }, {});
    expect(b.content).toBe(a.content);
    expect(b.generated_at).toBe(a.generated_at);
  });
});
```

- [ ] **Step 6.2: Run, confirm failure.**

```bash
cd /srv/projects/open-second-brain
bun test tests/mcp/brain-context.test.ts
```

Expected: `tool not in table: brain_context`.

- [ ] **Step 6.3: Implement the handler.**

Add to `src/mcp/brain-tools.ts` near the other reader handlers:

```ts
// ----- brain_context (v0.10.10) --------------------------------------------

import { existsSync, readFileSync } from "node:fs";

import { regenerateActive } from "../core/brain/active.ts";
import { brainActivePath, brainDirs } from "../core/brain/paths.ts";
import { computeBrainStatus } from "../core/brain/status.ts";
import { parseFrontmatter } from "../core/vault.ts";

interface BrainContextPayload {
  vault_path: string;
  present: boolean;
  active_path: string;
  content: string;
  counts: {
    confirmed: number;
    quarantine: number;
    retired_recent: number;
    most_applied_30d: number;
  };
  generated_at: string | null;
  error?: string;
}

async function toolBrainContext(
  ctx: ServerContext,
): Promise<Record<string, unknown>> {
  const dirs = brainDirs(ctx.vault);
  const activePath = brainActivePath(ctx.vault);
  if (!existsSync(dirs.brain)) {
    const empty: BrainContextPayload = {
      vault_path: ctx.vault,
      present: false,
      active_path: activePath,
      content: "",
      counts: {
        confirmed: 0,
        quarantine: 0,
        retired_recent: 0,
        most_applied_30d: 0,
      },
      generated_at: null,
    };
    return empty;
  }

  // Idempotent regen — writes only if the body differs from disk.
  // Counts come straight from the result so we never re-walk the
  // preferences/log twice.
  let counts;
  let error: string | undefined;
  try {
    const r = regenerateActive(ctx.vault);
    counts = r.counts;
  } catch (err) {
    error = (err as Error).message ?? String(err);
    counts = {
      confirmed: 0,
      quarantine: 0,
      retired_recent: 0,
      most_applied_30d: 0,
    };
  }

  let content = "";
  let generatedAt: string | null = null;
  if (!error && existsSync(activePath)) {
    content = readFileSync(activePath, "utf8");
    try {
      const [meta] = parseFrontmatter(activePath);
      const v = meta["generated_at"];
      if (typeof v === "string" && v.trim().length > 0) {
        generatedAt = v;
      }
    } catch {
      // Frontmatter parse failure is non-fatal — leave generated_at null.
    }
  }

  const payload: BrainContextPayload = {
    vault_path: ctx.vault,
    present: true,
    active_path: activePath,
    content,
    counts,
    generated_at: generatedAt,
    ...(error ? { error } : {}),
  };
  return payload;
}
```

> **DRY note:** the empty-counts literal repeats. If the reviewer
> flags it, hoist a `const EMPTY_COUNTS = Object.freeze({…})` at the
> top of the function — but do NOT export it; this counts shape is
> local to the tool.

- [ ] **Step 6.4: Register the tool.**

Two files to edit. Tool definitions for Brain live in `BRAIN_TOOLS`
(a const array exported from `src/mcp/brain-tools.ts`); the scope
filter lives in `WRITER_TOOL_NAMES` (in `src/mcp/tools.ts`).

**6.4a — add the entry to `BRAIN_TOOLS`.** In `src/mcp/brain-tools.ts`,
near the other Brain tools (after the `brain_apply_evidence` and
`brain_note` entries, before `brain_digest` — keep the writer trio
contiguous):

```ts
  {
    name: "brain_context",
    description:
      "Pull the current Brain/active.md body plus active-preference counts. Use at session start when SessionStart hook is unavailable (Cursor, Aider, raw Claude API). Read-only.",
    inputSchema: {
      type: "object",
      properties: {},
      additionalProperties: false,
    },
    handler: toolBrainContext,
  },
```

**6.4b — extend the writer scope filter.** In `src/mcp/tools.ts`,
update `WRITER_TOOL_NAMES`:

```ts
// The set is named after the original payload (mutating writers); as
// of v0.10.10 it also hosts `brain_context`, a reader that has to be
// always-loaded to be useful at session start. Renaming the server
// to a neutral noun is deferred — see docs/plans/2026-05-20-v0.10.10-design.md
// §12.
const WRITER_TOOL_NAMES: ReadonlySet<string> = new Set([
  "brain_apply_evidence",
  "brain_context",
  "brain_feedback",
  "brain_note",
]);
```

No new import is needed in `tools.ts` — `BRAIN_TOOLS` already
flows in via `import { BRAIN_TOOLS } from "./brain-tools.ts";` and
the new entry rides that wire.

- [ ] **Step 6.5: Run the test, confirm green.**

```bash
cd /srv/projects/open-second-brain
bun test tests/mcp/brain-context.test.ts
```

Expected: green.

- [ ] **Step 6.6: Update writer-server instructions.**

Edit `src/mcp/instructions.ts`. Find the writer-server description
block. Add a line for `brain_context`:

```text
  - brain_context        — pull the current Brain/active.md body plus
                           counts. Use at session start when the host
                           runtime lacks a SessionStart hook (Cursor,
                           Aider, raw Claude API). Read-only.
```

Update any surrounding sentence that says "the writer-server hosts the
three writer tools" to "hosts the writer tools plus the
`brain_context` reader".

- [ ] **Step 6.7: Verify the writer-scope tool table still loads four tools.**

```bash
cd /srv/projects/open-second-brain
bun test tests/cli/mcp-scope-arg.test.ts tests/mcp/scope-filter.test.ts
```

Expected: green. If these tests asserted the writer scope had exactly
three tools, update them to four and re-run.

- [ ] **Step 6.8: Full suite + typecheck.**

```bash
cd /srv/projects/open-second-brain
bun test
bun run typecheck
```

Expected: green / zero errors.

- [ ] **Step 6.9: Commit.**

```bash
git add src/mcp/brain-tools.ts src/mcp/tools.ts src/mcp/instructions.ts tests/mcp/brain-context.test.ts tests/cli/mcp-scope-arg.test.ts tests/mcp/scope-filter.test.ts
git commit -m "feat(mcp): brain_context tool for runtimes without SessionStart"
```

Pause: ask operator, wait for yes.

---

## Task 7: `o2b status` semantic hint

**Files:**
- Modify: `src/cli/helpers.ts` (new `resolveSemanticConfigState`)
- Modify: `src/cli/main.ts` (use the helper in both `cmdStatus` and `writeSearchInitBlock`)
- Test: `tests/cli/status.test.ts`

Implements §8.

- [ ] **Step 7.1: Write the failing CLI test.**

```ts
// tests/cli/status.test.ts
import { describe, expect, test, afterEach, beforeEach } from "bun:test";
import { execFileSync } from "node:child_process";
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

const O2B = join(import.meta.dir, "..", "..", "scripts", "o2b");

function runO2b(args: string[], env: Record<string, string> = {}) {
  return execFileSync(O2B, args, {
    encoding: "utf8",
    env: { ...process.env, ...env },
  });
}

function writeConfig(dir: string, body: string): string {
  mkdirSync(dir, { recursive: true });
  const p = join(dir, "config.yaml");
  writeFileSync(p, body, "utf8");
  return p;
}

describe("o2b status — semantic hint", () => {
  let cfgDir: string;
  beforeEach(() => {
    cfgDir = mkdtempSync(join(tmpdir(), "osb-status-"));
  });
  afterEach(() => {
    rmSync(cfgDir, { recursive: true, force: true });
  });

  test("hint absent when semantic_enabled is true and key is present", () => {
    const cfg = writeConfig(
      cfgDir,
      [
        "vault: /tmp/x",
        "search_semantic_enabled: \"true\"",
        "embedding_api_key: \"sk-test\"",
        "embedding_base_url: \"https://example.test/v1\"",
        "embedding_model: \"test-model\"",
      ].join("\n") + "\n",
    );
    const out = runO2b(["status", "--config", cfg], {
      // Defensive: nuke any operator env vars that could flip enabled.
      OPEN_SECOND_BRAIN_SEARCH_SEMANTIC: "",
      OPEN_SECOND_BRAIN_EMBEDDING_KEY: "",
    });
    expect(out).not.toContain("semantic:");
  });

  test("hint present when semantic_enabled is false", () => {
    const cfg = writeConfig(cfgDir, "vault: /tmp/x\n");
    const out = runO2b(["status", "--config", cfg], {
      OPEN_SECOND_BRAIN_SEARCH_SEMANTIC: "",
      OPEN_SECOND_BRAIN_EMBEDDING_KEY: "",
    });
    expect(out).toContain("semantic: off");
    expect(out).toContain("o2b search check");
  });

  test("hint present when key is missing even with enabled flag set", () => {
    const cfg = writeConfig(
      cfgDir,
      "vault: /tmp/x\nsearch_semantic_enabled: \"true\"\n",
    );
    const out = runO2b(["status", "--config", cfg], {
      OPEN_SECOND_BRAIN_SEARCH_SEMANTIC: "",
      OPEN_SECOND_BRAIN_EMBEDDING_KEY: "",
    });
    expect(out).toContain("semantic: off");
  });

  test("hint suppressed when search is disabled outright", () => {
    const cfg = writeConfig(cfgDir, "vault: /tmp/x\nsearch_enabled: \"false\"\n");
    const out = runO2b(["status", "--config", cfg]);
    expect(out).not.toContain("semantic:");
  });

  test("--json carries semantic flags and hint", () => {
    const cfg = writeConfig(cfgDir, "vault: /tmp/x\n");
    const out = runO2b(["status", "--config", cfg, "--json"], {
      OPEN_SECOND_BRAIN_SEARCH_SEMANTIC: "",
      OPEN_SECOND_BRAIN_EMBEDDING_KEY: "",
    });
    const parsed = JSON.parse(out);
    expect(parsed.semantic_enabled).toBe(false);
    expect(parsed.embedding_key_present).toBe(false);
    expect(typeof parsed.semantic_hint).toBe("string");
    expect(parsed.semantic_hint).toContain("o2b search check");
  });

  test("--json semantic_hint is null when everything is configured", () => {
    const cfg = writeConfig(
      cfgDir,
      [
        "vault: /tmp/x",
        "search_semantic_enabled: \"true\"",
        "embedding_api_key: \"sk-test\"",
      ].join("\n") + "\n",
    );
    const out = runO2b(["status", "--config", cfg, "--json"], {
      OPEN_SECOND_BRAIN_SEARCH_SEMANTIC: "",
      OPEN_SECOND_BRAIN_EMBEDDING_KEY: "",
    });
    const parsed = JSON.parse(out);
    expect(parsed.semantic_enabled).toBe(true);
    expect(parsed.embedding_key_present).toBe(true);
    expect(parsed.semantic_hint).toBeNull();
  });
});
```

- [ ] **Step 7.2: Run, confirm failure.**

```bash
cd /srv/projects/open-second-brain
bun test tests/cli/status.test.ts
```

Expected: every test fails (current `o2b status` never mentions
semantic).

- [ ] **Step 7.3: Add the helper.**

Edit `src/cli/helpers.ts`. Append:

```ts
const SEMANTIC_HINT_MESSAGE =
  "run 'o2b search check' for setup steps";

export interface SemanticConfigState {
  readonly semantic_enabled: boolean;
  readonly embedding_key_present: boolean;
  readonly search_disabled: boolean;
  readonly off: boolean;          // true when semantic is unusable AND search is enabled
  readonly hint: string | null;   // SEMANTIC_HINT_MESSAGE when off, else null
}

function truthyString(v: unknown): boolean {
  if (typeof v !== "string") return false;
  const s = v.trim().toLowerCase();
  return s === "true" || s === "1";
}

/**
 * Resolve whether semantic search is meaningfully usable, given a
 * flat config map and the current environment. Mirrors the boolean
 * logic already used by `writeSearchInitBlock` in `src/cli/main.ts`,
 * lifted here so `o2b status` shares the same source of truth.
 *
 * Anchored in design doc §8.1.
 */
export function resolveSemanticConfigState(
  configData: Readonly<Record<string, unknown>>,
  env: NodeJS.ProcessEnv,
): SemanticConfigState {
  const searchDisabled = configData["search_enabled"] === "false";
  const enabled =
    truthyString(configData["search_semantic_enabled"]) ||
    truthyString(env["OPEN_SECOND_BRAIN_SEARCH_SEMANTIC"]);
  const cfgKey = configData["embedding_api_key"];
  const keyPresent =
    (typeof cfgKey === "string" && cfgKey.length > 0) ||
    !!env["OPEN_SECOND_BRAIN_EMBEDDING_KEY"];

  const off = !searchDisabled && (!enabled || !keyPresent);
  return Object.freeze({
    semantic_enabled: enabled,
    embedding_key_present: keyPresent,
    search_disabled: searchDisabled,
    off,
    hint: off ? SEMANTIC_HINT_MESSAGE : null,
  });
}
```

- [ ] **Step 7.4: Wire `cmdStatus`.**

Edit `src/cli/main.ts`. Add the import:

```ts
import { resolveSemanticConfigState, /* existing */ } from "./helpers.ts";
```

In `cmdStatus`, after computing `result` from `discoverConfig`:

```ts
  const semantic = resolveSemanticConfigState(result.data, process.env);
```

In the `--json` branch, add the three keys to the output object (use
unconditional assignment so consumers see a stable shape):

```ts
    output["semantic_enabled"] = semantic.semantic_enabled;
    output["embedding_key_present"] = semantic.embedding_key_present;
    output["semantic_hint"] = semantic.hint;
```

In the human branch, after the `config_keys` block:

```ts
    if (semantic.off) {
      process.stdout.write(`semantic: off (${semantic.hint})\n`);
    }
```

- [ ] **Step 7.5: DRY: route `writeSearchInitBlock` through the helper.**

Still in `src/cli/main.ts`. Replace the inline truthy/key logic in
`writeSearchInitBlock` with a call to the new helper:

```ts
function writeSearchInitBlock(configPath: string): void {
  process.stdout.write("\nSearch:\n");
  process.stdout.write("  next: o2b search index   # build the vault search index\n");

  const data = discoverConfig(configPath).data;
  const semantic = resolveSemanticConfigState(data, process.env);
  if (!semantic.semantic_enabled || semantic.embedding_key_present) return;
  // … rest of the existing template unchanged …
}
```

This keeps the existing onboarding banner verbatim but removes the
duplicated logic.

- [ ] **Step 7.6: Re-run the CLI tests, confirm green.**

```bash
cd /srv/projects/open-second-brain
bun test tests/cli/status.test.ts
```

Expected: green.

- [ ] **Step 7.7: Run the existing init-related tests to make sure the DRY refactor did not regress.**

```bash
cd /srv/projects/open-second-brain
bun test tests/cli/cli.test.ts tests/cli/install-cli.test.ts
```

Expected: green.

- [ ] **Step 7.8: Full suite + typecheck.**

```bash
cd /srv/projects/open-second-brain
bun test
bun run typecheck
```

Expected: green / zero errors.

- [ ] **Step 7.9: Commit.**

```bash
git add src/cli/helpers.ts src/cli/main.ts tests/cli/status.test.ts
git commit -m "feat(cli): o2b status — semantic-search hint line"
```

Pause: ask operator, wait for yes.

---

## Task 8: E2E happy path

**Files:**
- Test: `tests/e2e/v0.10.10-pull-channels.test.ts`

End-to-end coverage for the most-load-bearing path: `o2b brain note`
lands in `Brain/log/<today>.md` AND the JSONL sidecar under
`note` kind with the correct agent identity, then `brain_context`
sees the same vault state in its payload.

- [ ] **Step 8.1: Write the e2e test.**

```ts
// tests/e2e/v0.10.10-pull-channels.test.ts
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { execFileSync } from "node:child_process";
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { buildToolTable } from "../../src/mcp/tools.ts";

const O2B = join(import.meta.dir, "..", "..", "scripts", "o2b");

describe("v0.10.10 pull-channels e2e", () => {
  let vault: string;
  beforeEach(() => {
    vault = mkdtempSync(join(tmpdir(), "osb-e2e-v0-10-10-"));
  });
  afterEach(() => {
    rmSync(vault, { recursive: true, force: true });
  });

  test("init → note → brain_context sees the milestone", async () => {
    execFileSync(O2B, ["brain", "init", "--vault", vault, "--agent-name", "tester"], {
      encoding: "utf8",
      env: process.env,
    });

    execFileSync(
      O2B,
      ["brain", "note", "v0.10.10 released", "--vault", vault, "--agent", "tester"],
      { encoding: "utf8", env: process.env },
    );

    const today = new Date().toISOString().slice(0, 10);
    const md = readFileSync(join(vault, "Brain", "log", `${today}.md`), "utf8");
    expect(md).toContain("- note");
    expect(md).toContain("- text: v0.10.10 released");
    expect(md).toContain("- agent: tester");

    const jsonl = readFileSync(join(vault, "Brain", "log", `${today}.jsonl`), "utf8");
    expect(jsonl).toContain("\"eventType\":\"note\"");
    expect(jsonl).toContain("\"text\":\"v0.10.10 released\"");

    const handler = buildToolTable("full").find((t) => t.name === "brain_context")!.handler;
    const ctx: any = await handler({ vault, configPath: null, repoRoot: null }, {});
    expect(ctx.present).toBe(true);
    expect(ctx.content).toContain("# Active Brain Preferences");
    expect(typeof ctx.counts.most_applied_30d).toBe("number");
  });
});
```

> If `o2b brain init` is not the right verb for bootstrapping a
> Brain/ layer in tests (look at how `tests/cli/brain.test.ts` does
> it): use the same pattern that other e2e tests use. Do not write a
> bespoke bootstrap helper.

- [ ] **Step 8.2: Run, confirm green.**

```bash
cd /srv/projects/open-second-brain
bun test tests/e2e/v0.10.10-pull-channels.test.ts
```

Expected: green.

- [ ] **Step 8.3: Commit.**

```bash
git add tests/e2e/v0.10.10-pull-channels.test.ts
git commit -m "test(e2e): v0.10.10 pull-channels happy path"
```

Pause: ask operator, wait for yes.

---

## Task 9: Documentation and CHANGELOG

**Files:**
- Modify: `CHANGELOG.md`
- Modify: `hooks/README.md`
- Modify: `docs/architecture.md`

- [ ] **Step 9.1: Add the v0.10.10 changelog entry.**

Edit `CHANGELOG.md`. Insert a new entry above `## [0.10.9]`:

```markdown
## [0.10.10] - <YYYY-MM-DD set on the day the release ships>

Pull channels for runtimes without SessionStart. Adds an MCP
`brain_context` tool, surfaces a `Most-applied (30d)` section in
`Brain/active.md`, ships the `o2b brain note` CLI verb, and prints
a semantic-search hint in `o2b status`. The always-loaded writer
MCP server now also hosts one read tool; renaming the server is
deferred (see `docs/plans/2026-05-20-v0.10.10-design.md` §12).

### Added

- `brain_context` MCP tool — returns the current `Brain/active.md`
  body plus active-preference counts. Always-loaded in the
  `open-second-brain-writer` MCP server. Intended for runtimes
  without a `SessionStart` hook (Cursor, Aider, raw Claude API).
- `Most-applied (30d)` section in `Brain/active.md` — top-ten
  `confirmed` / `quarantine` preferences ranked by
  `apply-evidence (result: applied)` events whose timestamp lies
  inside the trailing 30-day window. Section is omitted when the
  count is zero.
- `o2b brain note <text>` CLI verb — Brain-native milestone log
  for cron jobs and shell scripts. Same on-disk contract as the
  MCP `brain_note` tool.
- `o2b status` semantic-search hint — prints
  `semantic: off (run 'o2b search check' for setup steps)` when
  semantic search is enabled but unusable. Hint suppressed when
  `search_enabled` is `false`. `--json` payload gains
  `semantic_enabled`, `embedding_key_present`, and `semantic_hint`
  keys (always present).

### Changed

- `RegenerateActiveResult.counts` gains a `most_applied_30d`
  field. Internal consumers only — additive.
- `vaultRelativeSafe` moved from `src/mcp/brain-tools.ts` to
  `src/core/brain/paths.ts`. The MCP module re-exports the symbol
  to preserve external imports.

### Not changed

- The always-loaded MCP server is still named
  `open-second-brain-writer` even though it now hosts a read tool.
  A rename to a neutral noun is deferred to the next release that
  adds another reader.
```

- [ ] **Step 9.2: Update `hooks/README.md`.**

In the section describing brain-event detection (the same place that
lists `event_log_append` history and the v0.10.8 retirement), add a
sentence noting that `o2b brain note` joins
`o2b brain feedback` / `o2b brain apply-evidence` as bash-needle
brain events. Match the existing prose tone (no marketing voice).

- [ ] **Step 9.3: Update `docs/architecture.md`.**

In the MCP / writer-server subsection, add one paragraph:

> As of v0.10.10 the always-loaded `open-second-brain-writer` MCP
> server hosts one read tool (`brain_context`) alongside the
> three writers. The server name is preserved for backward
> compatibility with existing client `.mcp.json` entries; renaming
> is deferred until a second read tool joins the always-load
> scope.

- [ ] **Step 9.4: Run sync-version check (does NOT bump the version yet).**

```bash
cd /srv/projects/open-second-brain
bun run sync-version:check
```

This is informational — it confirms that `package.json` /
`pyproject.toml` / `openclaw.plugin.json` are in sync at whatever
version is currently checked in. We do NOT bump to `0.10.10` in this
plan; the operator owns the release-day version bump because it ties
to the actual release date.

- [ ] **Step 9.5: Full suite + typecheck.**

```bash
cd /srv/projects/open-second-brain
bun test
bun run typecheck
```

Expected: green / zero errors.

- [ ] **Step 9.6: Commit.**

```bash
git add CHANGELOG.md hooks/README.md docs/architecture.md
git commit -m "docs: v0.10.10 changelog, architecture, hooks README"
```

Pause: ask operator, wait for yes.

---

## Task 10: Vault summary update (operator-coordinated)

The vault edit is intentionally split from the code PR because
`/root/vault` is a Syncthing-replicated knowledge store, not part
of the project repo. It is also the only edit in this plan that
touches an operator-owned artifact rather than the project's
source tree.

- [ ] **Step 10.1: Confirm with the operator before writing.**

State to the operator: "About to edit
`/root/vault/Projects/OpenSecondBrain/Features/_summary.md` to mark
v0.10.10 shipped. Ask the operator: should I make the edit now, or
do you want to do it by hand?"

Wait for a yes.

- [ ] **Step 10.2: Apply the §11 spec edits.**

If yes:

1. In the `## Tier S` section, find the `### ✅ 1.` entry. Append to
   its `*(implemented in v0.9.1)*` parenthetical:
   `; + v0.10.10 Most-applied (30d) section`.
2. In the `## Disputed / deferred` section, remove the bullet
   starting with **Pull-bootstrap MCP-tool `brain_context`/`brain_principles`**.
3. In the `## Deferred work` section, find the bullet that starts
   with **§32B CLI verb `o2b brain note`** and replace it with a
   shipped reference pointing at the v0.10.10 release (mirroring
   the tone of the other already-shipped deferred items).

- [ ] **Step 10.3: Tell the operator.**

Report the three edits made. Do NOT commit anything — the vault is
not a git repo on this host; Syncthing replicates the markdown
verbatim.

---

## Final pass

- [ ] **Final.1: Full suite + typecheck one more time.**

```bash
cd /srv/projects/open-second-brain
bun test
bun run typecheck
```

Expected: green / zero errors.

- [ ] **Final.2: Show the operator the commit list.**

```bash
git log --oneline master..HEAD
```

Expected: roughly seven commits — one per task that produced code,
plus the docs commit and the e2e commit. Verify each commit is small
and reviewable on its own.

- [ ] **Final.3: Stop. Ask the operator how they want to proceed.**

Possible next steps the operator may choose (do NOT pick one
unilaterally):

- Open a PR via `gh pr create`
- Run a code review via CodeRabbit (operator-triggered only)
- Bump version manifests and push the release commit
- Continue iterating

Wait for the operator's choice.

---

## Implementation order recap

Sequencing follows the dependency graph:

1. **Task 1** (computeMostApplied) — pure module, zero dependencies.
2. **Task 2** (active.md render) — depends on Task 1.
3. **Task 3** (appendBrainNote core) — independent; touches MCP only via the refactor.
4. **Task 4** (CLI verb) — depends on Task 3.
5. **Task 5** (hook detector) — depends on Task 4 (the needle).
6. **Task 6** (brain_context tool) — depends on Task 2 (counts include `most_applied_30d`).
7. **Task 7** (o2b status hint) — independent of Brain work.
8. **Task 8** (e2e) — depends on Tasks 3, 4, 6.
9. **Task 9** (docs) — depends on Tasks 1-7.
10. **Task 10** (vault summary) — operator-side, depends on Task 9.

Tasks 3 and 7 are technically independent of Tasks 1-2 and could
ship in parallel branches. The single-branch order above is the
recommended one because Tasks 1-2 land the user-facing payload
(Most-applied) early, which lets the e2e test in Task 8 assert on
the same shape `brain_context` exposes.
