diff --git a/docs/second-brain/brief-sb-s3a.md b/docs/second-brain/brief-sb-s3a.md new file mode 100644 index 0000000..5dfe4a5 --- /dev/null +++ b/docs/second-brain/brief-sb-s3a.md @@ -0,0 +1,79 @@ +# Brief — SB-S3a: the first profile.md READER + +> **Slice:** SB-S3a (first sub-slice of SB-S3, the cross-silo + ops-centre arc step). +> **Status:** DRAFT — awaiting operator "go" before any code. Light-Voyage hardening (brief-review → plan-critic → scope-guardian) pending. +> **Predecessors:** SB-S0 (id/profile substrate) · SB-S1 (published-gold ingest) · SB-S2 (consolidation motor — *motor-only, no reader*). + +## 1. Operator decision (2026-06-23) + +SB-S3 is the arc's largest slice and was decomposed into four sub-slices (value-first / risk-managed): **S3a reader · S3b supersede · S3c cross-silo id-threading · S3d hygiene+ops**. The operator picked **S3a — the profile.md reader — first**, and within S3a the first reader is **one agent: `strategy-advisor`**, wired **READ-only**. + +## 2. Why a reader, and why first + +The whole arc was built *"so S3's reader inherits rich data"* (`consolidation-loop.md:67`). S2 grows `brain/profile.md` but **no agent or command consumes it today** — verified: 0 references to `brain/profile.md` in `agents/`/`commands/`; every "profile" hit points at the *legacy* flat `profile/user-profile.md`, not the SB-S0 two-layer brain profile. The reader is the deferred payoff of S0→S2: it turns a motor-only system into one that **feeds content generation** — the first end-to-end proof of capture → consolidate → read-back-into-generation. + +It is first because it has **no dependency on id-threading** (it reads the profile *facts*, not the cross-silo graph), it is **additive / non-breaking**, and it is the **lowest-risk** of the four (read-only consumption of an existing file). + +## 3. Scope — what is IN (S3a) + +1. **Wire `strategy-advisor` to read `brain/profile.md`.** Add the brain profile to the agent's existing **Step 0: Load Context** list: + `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/brain/profile.md` + The agent reads the markdown in-context and reasons over it — no parser, consistent with the arc thesis (Claude *is* the retrieval engine; `architecture.md:14`). + +2. **Consumption contract (how the agent uses it).** A short subsection in the agent prompt that tells it: + - The profile has two layers — `## Static` (stable, high-confidence) and `## Dynamic` (emerging) — and each fact line carries `evidence_count` + `last_seen`. Higher `evidence_count` / more recent `last_seen` = stronger/fresher signal; weight accordingly. + - **Anti-sycophancy (binding, `architecture.md:58`):** treat every profile fact as *evidence to TEST, not flatter*. Counter-pressure it against analytics/state each time it is used (e.g. "the profile says you lean X, but your last imports show Y — test that"). The profile informs, it never dictates or flatters. + +3. **Graceful absence.** `brain/profile.md` does not exist until `brain init` runs (fresh installs have no brain). The agent must **degrade silently** when the file is missing or empty — no error, no "I couldn't find your profile" noise; it simply proceeds on its other context sources. + +4. **A deterministic wiring test** in the structure-lint / `test-runner.sh` gate: two UNCONDITIONAL assertions — (a) `strategy-advisor.md`'s context-load declares `brain/profile.md`; (b) the agent carries the anti-sycophancy sentinel **literal `evidence to TEST`** (exact-literal grep + a non-vacuity self-test, per the repo lint idiom in `test-runner.sh` Sections 13/16c — loose patterns match vacuously). This is the TDD anchor (see §6) and protects the wiring against future agent-file edits. + - **Assertion-floor lockstep (binding):** the two new checks are UNCONDITIONAL, so `ASSERT_BASELINE_FLOOR` in `test-runner.sh` (Section 18; currently **78** — verify at plan time) must be bumped by exactly +2 → **80** in the same change, per the assertion-erosion guard. Do NOT pin to a deps-present TOTAL. + +5. **Doc/count reconciliation.** Update the binding counts/docs touched (CLAUDE.md agent table note if needed, STATE telling, `consolidation-loop.md` reader-status line: "S3a wires the first reader — `strategy-advisor`"). + +## 4. Non-goals — what is OUT (deferred to later S3 sub-slices) + +- **More than one reader.** `content-optimizer` is the obvious second reader and the pattern generalizes trivially, but S3a wires **exactly one** agent — the smallest valuable proof. Follow-on readers are separate work. +- **A hook-level / `user-prompt-context.mjs` digest reader.** A broad "inject a profile digest into every prompt" reader is attractive but carries a twin `.mjs` parser (twin-sync with `profile.ts`) + per-prompt context cost — *not* a good first proof. Deferred. +- **Cross-silo id-threading (S3c).** The reader consumes profile facts, not the post↔specific↔trend↔analytics graph. No `mintEntityId` threading here. +- **Supersede arm (S3b)**, **content-history retirement / triple-post reconciliation + operations.md (S3d)** — separate sub-slices. +- **Any WRITE to `brain/profile.md`.** The profile is mutated ONLY via `brain consolidate --apply --confirm` (operator-gated). S3a is strictly read-only; it adds no write path. +- **A new parser or new `.mjs`.** S3a is markdown-agent edits + a lint assertion only. + +## 5. Boundaries / invariants (must hold) + +- **READ-only** — S3a never writes the profile; the `--apply --confirm` gate stays the sole writer. +- **Anti-sycophancy is a built-in default, not a toggle** (`architecture.md:58`). +- **Provenance spine intact** — the profile already encodes provenance; the reader must not undermine the published-only learning guard. +- **No tributary schema changes** (that's S3c). +- **Fresh-clone safe** — missing brain → silent degrade, no crash, no nag from the agent. +- **TDD iron law** — the failing wiring test lands before the agent edit. + +## 6. Success criteria (testable) + +- **SC1 — wired:** `strategy-advisor.md` Step 0 context-load includes `brain/profile.md`. *(lint assertion, deterministic)* +- **SC2 — anti-sycophancy framed:** `strategy-advisor.md`'s profile-consumption subsection contains the exact durable sentinel literal **`evidence to TEST`**, asserted by an exact-literal grep + a non-vacuity self-test (repo idiom, `test-runner.sh` §§13/16c). *(lint assertion)* +- **SC3 — gate green:** `scripts/test-runner.sh` (the structure lint) stays green with the new assertions; its `BRAIN_TESTS_FLOOR` (82) is unchanged — no brain TS is touched. The hook suite (~136) runs under the **separate** `node --test hooks/scripts/__tests__/*.test.mjs` runner (NOT part of `test-runner.sh`) and is untouched because no hook code path changes. +- **SC6 — assertion floor honoured:** `ASSERT_BASELINE_FLOOR` bumped by exactly the number of new unconditional checks (+2 → 80); the gate's self-count check passes. +- **SC4 — graceful absence (manual/behavioural):** running `strategy-advisor` with **no** `brain/profile.md` produces normal output, no error/noise about the missing file. +- **SC5 — read-back works (manual/behavioural):** running `strategy-advisor` with a **populated** `brain/profile.md` surfaces ≥1 profile fact as *evidence-to-test* in its recommendation, counter-pressured against analytics/state — not parroted. + +## 7. Verification + +- **Deterministic (gate):** SC1–SC3 via the structure-lint assertion in `test-runner.sh`. `cd scripts/brain` not required — this is an agent-file + lint change; brain TS is untouched. +- **Behavioural (manual, documented):** SC4 + SC5 — the honest limit. Agent-prompt behaviour is not unit-testable (the plugin's command-testing workstream is still open). The plan must include a documented manual run: (a) empty-brain run → clean output; (b) populated-brain run (seed via `brain init` + a `--apply --confirm` cycle, or a fixture profile) → confirm a profile fact appears as tested evidence. Record the result in STATE/changelog at land. + +## 8. Open questions for brief-review / the operator + +1. **Agent choice:** `strategy-advisor` recommended; operator may redirect to `content-optimizer` (post-level grounding) — confirm at the brief gate. +2. **Lint location:** which existing lint file in the `test-runner.sh` gate hosts the SC1/SC2 assertion (plan resolves precisely). +3. **Behavioural test honesty:** is the documented manual verification (SC4/SC5) acceptable for S3a's land, given agent-behaviour is not unit-testable — or does the operator want a fixture-driven harness scoped in (larger)? + +## 9. Brief-review (light-Voyage) — folded + +`voyage:brief-reviewer` verdict: **APPROVE** (scope crisp, non-goals correct, invariants inherited, deterministic-vs-behavioural split honest). Three [FIX]es folded above: +- SC3 no longer conflates two runners (brain-82 floor in `test-runner.sh` vs the separate hook runner). ✅ +- `ASSERT_BASELINE_FLOOR` lockstep bump (+2 → 80) made binding in §3.4 + SC6. ✅ +- SC2 pinned to the exact literal `evidence to TEST` + non-vacuity self-test (anti-vacuity idiom). ✅ + +Reviewer confirmed: `strategy-advisor` is the better first reader than `content-optimizer`; SC4/SC5 manual split is correct; do NOT scope a behavioural harness into S3a (keeps "smallest valuable proof"). diff --git a/docs/second-brain/plan-sb-s3a.md b/docs/second-brain/plan-sb-s3a.md new file mode 100644 index 0000000..ffd0a3b --- /dev/null +++ b/docs/second-brain/plan-sb-s3a.md @@ -0,0 +1,83 @@ +# Plan — SB-S3a: wire the first profile.md READER (`strategy-advisor`) + +> Brief: `docs/second-brain/brief-sb-s3a.md` (APPROVE, FIXes folded). Slice: SB-S3a. +> Operator confirmed: **`strategy-advisor`**, READ-only, **manual SC4/SC5** (no behavioural harness). +> Order is TDD: the failing lint check lands BEFORE the agent edit (iron law). + +## Goal + +`strategy-advisor` consumes `brain/profile.md` as *evidence-to-test*, guarded by a deterministic wiring lint. No write path, no parser, no tributary/hook change. Smallest end-to-end proof that the second brain feeds generation. + +## Files touched (exhaustive — for scope-guardian) + +| File | Change | Why | +|------|--------|-----| +| `scripts/test-runner.sh` | **NEW Section 16d** (2 unconditional checks) + bump `ASSERT_BASELINE_FLOOR` 78→80 + extend floor-history comment + header enumeration | SC1/SC2/SC6 — the TDD anchor | +| `agents/strategy-advisor.md` | add `brain/profile.md` to Step 0 Load Context + a short consumption subsection (anti-sycophancy literal + graceful-absence) | SC1/SC2/SC5 — the wiring itself | +| `docs/second-brain/consolidation-loop.md` | **append** an "SB-S3a landed" reader-status line (do NOT rewrite the existing :67 contract prose) | doc reconciliation | + +**Not touched (scope fence):** any `scripts/brain/**` TS · any `hooks/scripts/**` · any tributary JSON · `architecture.md`/`brief.md` (arc-level "motor-only until S3" stays true until all of S3 lands) · `content-optimizer.md` (second reader, not S3a) · CLAUDE.md agent table (responsibility text unchanged — the count/model lint is unaffected; a note is optional and deferred to avoid churn). + +## Step 1 — (RED) Lint: Section 16d + floor bump + +Insert a new section after Section 16c (`test-runner.sh:780`, before Section 17 at `:782`), modelled byte-for-byte on 16c's idiom (literals + `*_wired()` + non-vacuity self-test + real-file grep with `grep -qF`): + +- **Literals:** `READER_LIT='brain/profile.md'`, `ANTISYC_LIT='evidence to TEST'`. +- **`reader_wired()`** — text wired iff BOTH literals present (`grep -qF` each; echo twice as 16c does). +- **Check A (self-test, unconditional):** a fully-wired probe (`"reads brain/profile.md as evidence to TEST"`) MUST be detected; three under-wired probes MUST be rejected: + 1. `"reads brain/profile.md but never frames how to weigh it"` (has reader lit, missing anti-syc) + 2. `"treats facts as evidence to TEST but names no source file"` (has anti-syc, missing reader lit) + 3. `"reads profile/user-profile.md and tests the evidence"` (decoy: legacy path + lowercase "evidence" — must NOT match either exact literal) + → `pass`/`fail` "profile-reader self-test: full wiring detected; 3 under-wired forms rejected". +- **Check B (real-file grep, unconditional):** `grep -qF "$READER_LIT" agents/strategy-advisor.md && grep -qF "$ANTISYC_LIT" agents/strategy-advisor.md` → `pass` "strategy-advisor.md wired to brain profile reader (names '…profile.md', frames 'evidence to TEST')" else `fail`. +- **Header enumeration (`:19-35`):** add a sentence — "the brain profile-reader guard (SB-S3a: `strategy-advisor` names `brain/profile.md` AND carries the anti-sycophancy literal `evidence to TEST`, with a non-vacuity self-test) in Section 16d". Range "Sections 8–18" unchanged (16d is in range). +- **Floor (`:849-865`):** `ASSERT_BASELINE_FLOOR=78` → `80`; extend the history comment: "+2 for SB-S3a's two UNCONDITIONAL Section-16d checks (profile-reader self-test + strategy-advisor wiring grep) = 80." Both new checks are deps-free → they lift the deps-absent minimum, so the +2 bump is correct (NOT pinned to deps-present TOTAL). + +- **Non-collisions (verified by plan-critic):** the new prose carries no algorithm-magnitude token (Section 8 stat-consistency safe) and no `Significant?(` column (Section 11 render-chain safe); `brain` is absent from Section 13's `BARE_DATA` regex (`test-runner.sh:514`) AND the edit uses the exempt `${LINKEDIN_STUDIO_DATA:-…}` external form → Section 13 data-dir guard safe. Agent count (19) + `strategy-advisor` model/frontmatter unchanged → Sections 2/9/10 unaffected. + +**RED gate:** `bash scripts/test-runner.sh` → Check A passes, **Check B FAILS** (agent not yet wired), floor check passes (TOTAL 95 ≥ 80) → exit 1. This is the required failing test. + +## Step 2 — (GREEN) Wire `strategy-advisor.md` + +1. **Step 0 Load Context** (`strategy-advisor.md:41-47`): insert one line **inside** the fenced code block, immediately BEFORE the closing ``` fence (currently `:47`) — i.e. the new path becomes `:47` and the fence shifts to `:48`. Do NOT place it after the fence (that breaks the code block). The line: + `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/brain/profile.md → evolving second-brain profile (consolidated, evidence-weighted facts about the user)` +2. **New subsection** immediately after the Load Context block (after `:49`), e.g. `### Consuming the evolving profile (brain/profile.md)`: + - Two layers: `## Static` (stable, high-confidence) vs `## Dynamic` (emerging); each fact carries `evidence_count` + `last_seen` — weight by strength/recency. + - **Anti-sycophancy (binding):** "Treat every profile fact as **evidence to TEST**, not flatter — counter-pressure it against analytics/state each time (e.g. *the profile says you lean X, but your imports show Y — test that*). The profile informs; it never dictates or flatters." ← carries the exact literal `evidence to TEST`. + - **Graceful absence:** "If `brain/profile.md` is missing or empty (fresh installs have no brain yet), proceed silently on the other context sources — no error, no note about a missing profile." + +**GREEN gate:** `bash scripts/test-runner.sh` → Check A + Check B pass, FAIL=0, **95/0/0**, brain floor 82 untouched. + +## Step 3 — Doc reconciliation + +`consolidation-loop.md:67-68` currently reads *"`brain/profile.md` has no reader yet … wiring content agents/commands to consume it is SB-S3."* — which becomes **false** the moment S3a lands a reader. **No lint guards `docs/`** (Section 11 scans `references/ commands/ skills/ hooks/prompts/ agents/ assets/` only, and `PROP_FORBIDDEN` matches only the significance-verdict column — verified by plan-critic; `grep consolidation-loop test-runner.sh` = 0 hits). So **EDIT the bullet** (it lives under "## Honest limits" — keep it a limit) to the true partial state, e.g.: +"**`brain/profile.md` has one reader as of SB-S3a.** S2 evolved the profile motor-only; SB-S3a wired the first consumer — `strategy-advisor` reads it as *evidence-to-test*. Broader consumption (more content agents, a hook-level digest) remains later S3 work, and the profile is still mutated only via `brain consolidate --apply --confirm`." +This removes the stale claim rather than appending a self-contradicting line beneath it. + +## Step 4 — Behavioural verification (manual, recorded — SC4/SC5) + +Agent-prompt behaviour is not unit-testable (operator-accepted). Honest procedure, result recorded in STATE/changelog at land: +- **SC4 (graceful absence):** with no/empty `brain/profile.md` at the data-root, the wiring degrades silently — verified by inspection of the subsection's graceful-absence clause + (best-effort) a strategy-advisor run on the empty real root → no missing-file noise. +- **SC5 (read-back works):** seed a temp profile (e.g. `LINKEDIN_STUDIO_DATA= brain init` + a couple of hand-written facts) and run the agent → confirm ≥1 fact surfaces as tested evidence, counter-pressured, not parroted. If the harness cannot fully exercise a per-call data-root, record that honestly and fall back to inspection of the wiring + framing (do NOT overclaim a behavioural pass we did not run). + +## Step 5 — Land + +STATE "Telling" + "👉 NESTE" updated (S3a done → S3b/c/d remain); commit (Conventional, `[skip-docs]` on the code/feat parts; brief+plan+consolidation-loop are docs → judge suffix per the mixed change); **push only inside the window** (`date '+%u %H:%M'` first; `origin` is the PUBLIC `open/` remote → confirm before push). No version bump (S3a is additive within v0.5.2 dev; a release bump is a separate decision). + +## Verification (testable) + +| SC | Check | Command | Expected | +|----|-------|---------|----------| +| SC1+SC2 | reader wired + literal | `bash scripts/test-runner.sh` | Section 16d Check B passes | +| SC3 | gate green, brain floor intact | same run | 95/0/0; `BRAIN_TESTS_FLOOR=82` unchanged; hook suite (separate runner) untouched | +| SC6 | assertion floor honoured | same run | "assertion-count anti-erosion: 95 … >= floor 80" passes | +| (red proof) | failing-test-first | gate run BETWEEN Step 1 and Step 2 | exit 1, exactly Check B fails | +| SC4 | graceful absence | manual run, empty root | clean output, no missing-file noise | +| SC5 | read-back | manual run, seeded profile | ≥1 fact surfaced as evidence-to-test (or honest fallback recorded) | + +## Risks + +- **R1 — vacuous lint.** Mitigated by the 16c-style non-vacuity self-test with the legacy-path/lowercase decoy. +- **R2 — floor false-fail on fresh clone.** Mitigated: both new checks are deps-free (lift the deps-absent minimum), so +2 → 80 is the correct floor; not pinned to deps-present TOTAL. +- **R3 — behavioural overclaim.** Mitigated by Step 4's honest-fallback clause (verifiseringsplikt): never report a behavioural pass we did not run. +- **R4 — stale doc claim.** `consolidation-loop.md:67` ("no reader yet") becomes false once S3a lands; **no automated guard protects `docs/`** (Section 11 scans non-docs dirs + only the significance column — verified). Mitigated by EDITING :67 to the true partial state (one reader, broader consumption deferred) within the "Honest limits" framing — by discipline, not by a blind append.