diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index f20cb50..cb24347 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "second-brain", "source": "./", "description": "Self-evolving AI second brain. Auto-learns from sessions, discovers tools, maintains a local knowledge base, and self-critiques code quality.", - "version": "0.22.4" + "version": "0.22.5" } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 9e85d60..904ebd6 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "second-brain", "description": "Self-evolving AI second brain. Automatically learns from sessions, discovers tools, maintains a local knowledge base, and self-critiques code quality — getting smarter with every interaction.", - "version": "0.22.4", + "version": "0.22.5", "author": { "name": "second-brain" }, diff --git a/skills/upgrade/SKILL.md b/skills/upgrade/SKILL.md index d543ab4..1d4046f 100644 --- a/skills/upgrade/SKILL.md +++ b/skills/upgrade/SKILL.md @@ -35,6 +35,7 @@ Each migration is identified by its target version. Run only migrations whose ta | To version | Migration | Idempotent check | |---|---|---| | **vector-deps health** (re-runs every upgrade) | Smoke-import `@huggingface/transformers` from `$CLAUDE_PLUGIN_ROOT/mcp`. On failure, run `bash $CLAUDE_PLUGIN_ROOT/bin/install-vector-deps.sh`. **Why**: mcp bundles mark `@huggingface/transformers` as esbuild `--external` because its native binaries (`onnxruntime-node`, `sharp`) can't be statically packed. A plugin cache refresh ships `dist/` but does not touch `node_modules/`, so vector search silently degrades to text-only on every fresh install or cache wipe. Without this gate the user sees no error — just empty embeddings and degraded recall. Idempotent: the script is a no-op when the package and import smoke-check both succeed. | `cd "$CLAUDE_PLUGIN_ROOT/mcp" && node --input-type=module -e 'await import("@huggingface/transformers"); console.log("ok")' >/dev/null 2>&1` — if exit 0, skip. Otherwise run `bash "$CLAUDE_PLUGIN_ROOT/bin/install-vector-deps.sh"`; report the ~70MB network requirement before installing. | +| **0.22.5** | Persona graph-capability awareness (read-only boundary preserved). The persona-as-collaborator protocol (`skills/using-second-brain/SKILL.md`) knew the **read** tools (`knowledge_search`/`episodic_search`/`knowledge_neighbors`) and the wingman graph-check, but never referenced `knowledge_relate`, so the persona-driven loop didn't close the loop on a relationship it confirmed mid-session. The wingman section now does — but as a **surface-only** step: when the work confirms (or retires) a typed relationship, the persona surfaces it once as a *suggested* `knowledge_relate` (confirmed/retired edges only, never speculative). It does **not** call the tool — edge curation stays owned by the three sanctioned writers (capture-time **extractor**, the user's manual `knowledge_relate`, the `knowledge-maintainer`), and the extractor records the relationship from the session transcript at Stop, so the graph still accrues **with no user interaction**. Deliberately keeps the persona a graph READER, not a 4th writer (the boundary `agents/dream-runner.md` enumerates). New `tests/test-persona-capability-awareness.sh` guards both halves: awareness of the capability AND the read-only `allowed-tools` boundary. Prompt/test-only — no state migration. | No precondition — bumping the marker is sufficient. | | **0.22.4** | Post-0.22.3 completeness-audit cleanup (no functional regression in 0.22.2/0.22.3 — all four features audited `complete`; these are the surfaced gaps). (a) **`graph-migrate.sh` junk-edge root-cause fix**: the one-shot importer scraped `[[..]]` with a raw grep that also caught bash `[[ test ]]` expressions inside fenced code blocks, kept `[[target\|alias]]` display noise, and emitted edges with **no endpoint guard** — so the live graph accreted 6 `migration:v1` junk edges (shell fragments / aliases as `to`) that re-project as broken links. It now mirrors the sibling write path `merge-edges.sh`: skips fenced code blocks, reduces `[[target\|alias]]` to its target, and emits **only** edges whose target resolves to a real wiki page (prebuilt slug index → `resolves()` guard; case-preserving, so mixed-case slugs survive). (b) **Dream inline/background parity**: `skills/dream/SKILL.md` 2c RELATE gains the read-only `conflicts.jsonl` open-conflict echo that `agents/dream-runner.md` already had, so both dream paths report identically (guarded by new `tests/test-dream-conflict-echo.sh`). (c) Corrected stale `0.23.0` references in the write-time spec + two plans to the `0.22.2` version that actually shipped. Script/prompt/doc/test-only — no state migration. | No precondition — bumping the marker is sufficient. **Optional live cleanup** (the importer fix is forward-only; it does not retroactively remove the 6 existing junk edges): invalidate them append-only (never deleted, reversible) — back up `~/knowledge/graph/edges.jsonl`, then for each `source:"migration:v1"` assert whose `to` is a non-slug shell fragment/alias, append an `op:"invalidate"` line with `valid_to=` and reason, then `knowledge_reindex`. | | **0.22.3** | Dream-dogfood fixes (caught on the first production dream, 2026-06-02). (a) **`wiki-forget-score.sh` mawk-safe**: an empty/sparse `access-counts.json` made `acount` return empty → `awk "BEGIN{a=$acc;…}"` threw `syntax error at or near ;` on mawk (Pi default), degrading the FORGET access signal; now values pass via `-v` + numeric coercion (`x=x+0`) and `acount` defaults empty→0 (same bug class as the 0.21.4 lint-awk fix). (b) **Dream SUMMARIZE theme pages are slugged `theme-`** (was `` = the smallest member slug, which is usually the cluster's *anchor page* → `duplicate_slug` error). (c) **Dream ENRICH no longer hand-edits `related:`** (it is projected from the edge log → overwritten at reindex); it surfaces relationship gaps as `knowledge_relate` suggestions instead. Script/prompt-only — no state migration. | No precondition — bumping the marker is sufficient. Any `wiki/themes/.md` from a pre-0.22.3 dream can be renamed to `wiki/themes/theme-.md` to avoid a collision (or left — theme pages regenerate next dream). | | **0.22.2** | Graphiti/GraphRAG adoption — three additive, flag-gated features (all back-compat; design in `docs/specs/2026-06-01-*`, decision in wiki `decisions/graphiti-graphrag-evaluation-2026-06-01`). (a) **Write-time contradiction detector** in `merge-edges.sh` — pure-bash R1 reintroduce / R2 opposing / R3 multi_parent (opt-in) flags structural edge collisions to a new append-only `~/knowledge/graph/conflicts.jsonl` sidecar; never blocks the edge append, fail-open, kill switch `SB_CONFLICT_DETECT=off`. Surfaced by a high-priority `session-load.sh` banner (`sb_conflicts_open_count`); drained by the **live** `knowledge-maintainer` Phase 3 (the dream stays read-only re: `graph/`). (b) **Dream SUMMARIZE phase** (7th-phase cycle; `SB_DREAM_SUMMARIZE=off` to skip) — deterministic label-propagation clustering (new `graph-cluster.ts` + bundled `graph-cluster-cli` + `scripts/graph-cluster.sh` shim) writes FORGET-protected `wiki/themes/` pages, staged and reviewed at `dream_accept`. (c) **Retrieval-grounded reconciliation** in `knowledge-maintainer` Phase 2/3 (`SB_RECONCILE=off` to disable) — `knowledge_search` top-k → ADD/UPDATE/NOOP/SUPERSEDE with a deterministic enumerated SUPERSEDE edge loop. Additive on disk: `conflicts.jsonl` and `wiki/themes/` appear lazily; with no `~/knowledge/graph/` and flags at defaults, behaviour is unchanged. No destructive migration. | No precondition — bumping the marker is sufficient. The `graph-cluster-cli` bundle ships in `dist/` (no native deps; the vector-deps health check is unaffected). To verify the detector is wired: `grep -q SB_CONFLICT_DETECT "$CLAUDE_PLUGIN_ROOT/scripts/merge-edges.sh"`. | diff --git a/skills/using-second-brain/SKILL.md b/skills/using-second-brain/SKILL.md index 2a86f7e..503b363 100644 --- a/skills/using-second-brain/SKILL.md +++ b/skills/using-second-brain/SKILL.md @@ -48,6 +48,8 @@ This **refines** "silence is the default" (point 4) — it does not replace it. Example: user says "let's refactor the router daemon" → `knowledge_neighbors router-daemon` → "Heads up: graph says router-daemon **affects** vps-collector + supplychain-monitor and **supersedes** pi-ip-ufw-sync (retired) — want me to check those dependents first?" → then refactor. +**Surface what the work confirms (close the loop — but you don't write the graph).** The wingman *reads* the graph; you are **not** a graph writer. Edge curation is owned by three paths: the capture-time **extractor** (it records relationships from this session's transcript at Stop — so the graph accrues with **no user interaction**), the user's manual `knowledge_relate`, and the `knowledge-maintainer`. So when the work **confirms** a typed relationship — the user says "X depends on / affects / is part of Y", or explicitly retires/replaces something — *surface it ONCE* as a suggested `knowledge_relate` (`type: requires|affects|relates|part_of|supersedes`; `invalidate: true` + `valid_to` for a retired edge), brief, per silence-default — then move on. The extractor picks it up from the transcript; you do not call the tool yourself. **Only confirmed/retired relationships — never speculative ones.** Edge *contradictions* are likewise the maintainer's to drain (surfaced by the SessionStart conflict banner), not yours. + **Native-memory cross-check.** Claude Code's built-in auto-memory (`MEMORY.md`) is already in your context at session start — you don't fetch it. If a fact there **contradicts** the second-brain graph/wiki on something load-bearing to the current action, flag the conflict ONCE; don't silently pick a side. The second-brain (graph + wiki) is the structured source of truth; native auto-memory is a flat scratchpad. (Only relevant while both memory systems run; inert once native is disabled — see `/second-brain:status`.) ## Engagement gate diff --git a/tests/test-persona-capability-awareness.sh b/tests/test-persona-capability-awareness.sh new file mode 100755 index 0000000..015b7f7 --- /dev/null +++ b/tests/test-persona-capability-awareness.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# The persona-as-collaborator protocol (skills/using-second-brain/SKILL.md) must stay +# AWARE of the second-brain toolkit so it uses each capability without the user manually +# invoking a tool ("persona aware what + how to use", no user interaction) — WHILE keeping +# the deliberate write boundary: the persona is a graph READER, not a writer. +# +# Edge curation is owned by three paths only (capture-time extractor, the user's manual +# knowledge_relate, the knowledge-maintainer). The always-on persona must NOT become a +# fourth graph writer (history-review regression class, 0.22.5). Instead the wingman READS +# (knowledge_neighbors) and SURFACES a suggested knowledge_relate when the work confirms a +# relationship — the extractor then records it from the transcript, so the graph still +# accrues with no user interaction. +# +# This guards both halves: awareness of the capability AND the read-only boundary. +set -u +ROOT="$(cd "$(dirname "$0")"/.. && pwd)" +SK="$ROOT/skills/using-second-brain/SKILL.md" +P=0; F=0; ok(){ P=$((P+1)); echo " PASS $1"; }; bad(){ F=$((F+1)); echo " FAIL $1"; } +[ -f "$SK" ] || { echo "FAIL: skills/using-second-brain/SKILL.md missing"; exit 1; } + +ALLOWED=$(grep -m1 '^allowed-tools:' "$SK") + +# Read-side tools the persona drives autonomously — must be granted. +for t in knowledge_search episodic_search knowledge_neighbors; do + printf '%s' "$ALLOWED" | grep -q "$t" && ok "read tool granted: $t" || bad "read tool missing from allowed-tools: $t" +done + +# BOUNDARY: the persona stays READ-ONLY. knowledge_relate must NOT be in allowed-tools — +# granting it makes the always-on persona a 4th, ungoverned graph writer. +printf '%s' "$ALLOWED" | grep -q 'knowledge_relate' \ + && bad "knowledge_relate in allowed-tools — persona must stay read-only (4th-writer regression)" \ + || ok "allowed-tools is read-only (no knowledge_relate write grant)" + +# AWARENESS: the protocol still references knowledge_relate so the persona can SURFACE it. +grep -q 'knowledge_relate' "$SK" \ + && ok "protocol aware of knowledge_relate (as a suggestion)" \ + || bad "protocol never mentions knowledge_relate — persona unaware of the capability" + +# FRAMING: it must be a SURFACE/SUGGEST, and the real writers (extractor + maintainer) named, +# so the persona knows it is not the one writing. +grep -qiE 'surface|suggest' "$SK" && grep -qi 'extractor' "$SK" && grep -qi 'maintainer' "$SK" \ + && ok "framed as surface-only; extractor + maintainer named as the writers" \ + || bad "knowledge_relate not framed as surface-only with the real writers named" + +# ANTI-SPAM: the load-bearing discipline clause must be present (not just the heading word). +grep -qiE 'never[^.]*speculative|only confirmed/retired' "$SK" \ + && ok "scoped to confirmed/retired only (never speculative)" \ + || bad "missing the 'never speculative' discipline clause (over-assertion risk)" + +echo "PASS:$P FAIL:$F"; [ "$F" -eq 0 ]