v2: 7-persona virtual team + headless MCP + paste-install UX#4
v2: 7-persona virtual team + headless MCP + paste-install UX#4
Conversation
…ath traversal guard compiler: extract shared wikilink/frontmatter/strip_noise parser from concept_graph.py and link_discovery.py into _md_parse.py. Eliminates duplicate parser drift risk (frontmatter CRLF, wikilink false positives inside code blocks). mcp-server: add realpath guard in fs-transport.resolvePath() to defend against symlink/junction escapes. Lexical path.relative() check stays for the cheap path; realpath only invoked when guard triggers. Tests added for Windows junction escape. Tests: +52 assertions across concept_graph + link_discovery + fs-transport.
… 5 worker specs v1 was an MCP server. v2 is a virtual team of 6 vault-* personas (librarian, architect, curator, teacher, historian, janitor) that wrap the MCP server as slash commands and KT creatures. REQUIREMENTS.md: benchmarked against garrytan/gstack (persona framing, paste-to-install, 30-second gate). Sections P-01..06, I-01..04, D-01..04, R-01..06, S-01..04. specs/spec-A..E: 5 worker specs with input/output contracts, acceptance checklists, reject signals. Executed by 5 KT creatures on MiniMax-M2.7- highspeed in 3 waves via direct-tools dispatch. Outputs staged under .compile/ (gitignored) for HMS review before integration. progress.txt: session log including M2.7+KT sub-agent incompatibility (tool_id bug) and direct-tools guardrail discovery.
spec-A output. 12 files (6 skills + 6 creatures), dual-host ready:
- skills/vault-{librarian,architect,curator,teacher,historian,janitor}.md
-> Claude Code slash commands (`/vault-librarian`, etc.)
- creatures/vault-*/config.yaml + prompts/system.md
-> KT creature configs for MiniMax-M2.7-highspeed controller
Each persona wraps a scoped subset of vault.* MCP tools. Librarian reads
and cites; architect compiles graph; curator finds orphans/duplicates;
teacher explains in context; historian answers time-indexed queries;
janitor proposes cleanups with dry-run default.
v1 skills kept in place (non-destructive). Pruning deferred until v2
smoke proves out.
spec-B output. Ships a ready-to-demo vault pointing at the MCP server. 10 markdown notes (150-400 words each), 40 internal wikilink edges + 30 tag edges, 3 tag clusters (inference/training/meta). Deliberate defects drive the persona demos: - 1 dangling [[sparse-mixture-experts]] (curator demo) - 2 near-duplicate attention notes (janitor demo) - 1 stale note from Oct 2024 (historian demo) karpathy-llm-wiki-concept.md is the anchor: landing note users hit first, links 5+ other notes, states the thesis (notes as AI-readable structure). graph.json force-added (inside gitignored .compile/) so end users see the compiled structure without running the compiler.
spec-C output. README.md: new v2 top-level. Tagline "Your markdown vault, compiled into a 6-persona team for any agent." Top-of-fold 12 lines, under the 40-line cap. 4 example prompts (cold/warm/specific/iterate) mirror gstack structure. docs/WHY_NOT_JUST_GREP.md: 332 words, addresses 4 objections (concepts vs substrings / compiled vs stateless / MCP tools vs text / agent vs you). docs/INSTALL.md: full install matrix moved out of README top-of-fold so the 30-second gate stays clean. docs/gif-script.md: 10-second recording plan for the README hero GIF. docs/legacy/README-v1.md: previous product-spec README preserved under the v1.0.0 product direction.
spec-D output. setup (bash): detects --host flag (claude/codex/opencode/gemini) and installs skills to the right host dir (~/.claude/skills/vault-wiki/ etc). Prints paste-able .mcp.json + CLAUDE.md snippet on success. Never sudo. POSIX-safe for Git Bash on Windows. setup.ps1: PowerShell sibling for Windows users who prefer native. viewer/: single-file static page. cytoscape.js + cose-bilkent loaded from unpkg CDN (no npm install for end user). Paste-your-graph.json textarea swaps rendering. Tag edges colored differently from wikilink; unresolved dashed. Opens standalone in Chrome/Firefox. vercel.json: static-only deploy pointing at viewer/. Zero serverless functions. Old setup.sh (v1.0 install) left in place for backwards compat; prune in a later commit after v2 smoke proves out.
spec-E output.
terrariums/vault-wiki-team.yaml: wires the 6 creatures into a team.
Root dispatches by intent ("what do I know about" -> librarian;
"clean up" -> curator; "explain" -> teacher; ...). All creatures
listen on team_chat broadcast; architect+janitor coordinate on
compile_request/graph_update. Tokens via env-ref (ANTHROPIC_AUTH_TOKEN,
ANTHROPIC_BASE_URL), zero literal sk- secrets in yaml.
smoke/01-install-prompt.md: ONE paste-able sentence for Claude Code.
smoke/02-expected-outputs.md: step-by-step success criteria.
smoke/03-smoke-script.sh: 7-step end-to-end harness (setup -> MCP up ->
terrarium up -> librarian query -> assert citation -> teardown). Manual
checklist where full automation not feasible on first pass.
smoke/04-recording-notes.md: GIF shot list for docs hero.
docs/KT_DOGFOOD.md: 383 words, 0 buzzwords. "This repo was authored
by a 5-creature KT terrarium run over ~10 minutes on 2026-04-20."
Honest, dated, reproducible.
Worker built the original graph.json before the _md_parse refactor landed, so unresolved was 0 (dangling wikilink misclassified as wikilink). Rebuilt with latest compiler -> unresolved=1 correctly catches [[sparse-mixture-experts]]. nodes=11 edges=70 (wikilink=40 tag=30) unresolved=1.
…ntegration Wave-by-wave dispatch summary, 7-commit integration log, 5 known gaps carried forward (P2: setup leanness + GIF; P3: terrarium nesting, smoke automation, M2.7 sub-agent bug).
Install size 64 MB -> 1.6 MB (97.5% shrink), boot 76 ms.
- mcp-server: esbuild --bundle dist/index.js -> bundle.js (1.5 MB,
self-contained, ESM with createRequire shim for ws CJS interop).
Add npm run bundle + npm run rebuild scripts. Switch package main
to bundle.js. Set files allowlist for any future npm publish.
- setup, setup.ps1: replace cp -r . with explicit allowlist (skills,
examples, docs, terrariums, viewer, smoke, top-level docs, vercel.json,
mcp-server/{bundle.js, package.json}). Fail fast if bundle missing.
PS variant uses -VaultHost (not -Host -- $Host is reserved in PS).
- README: fix quick-start. Old command cloned full repo into the install
dir, bypassing setup entirely. New command clones to a sibling dir,
runs setup, leaves a 1.6 MB curated install.
- docs/INSTALL.md: rewrite from v1 (npm install + tsc + yaml prompt +
~/.claude.json registration + smoketest -- none of which the current
setup does) to v2 (per-host install, manual install, verify probe,
troubleshooting for the four real failure modes).
- LICENSE: restore MIT (Copyright 2026 Curry) from first commit d0f212c.
Was lost when v2 work started on a fresh branch. Fix mismatch -- the
package.json said GPL-3.0 (stray from early scaffold) while README and
legacy LICENSE both said MIT. README/intent wins; correct package.json
to license: MIT.
- .gitignore: ignore mcp-server/smoke.{in,out,err} test artifacts.
Verified end-to-end:
- npm run bundle reproducible (1.5 MB, banner clean, no warnings)
- Real install: 1.6 MB / 406 ms (51 files, no .git/.compile/node_modules)
- Boot: 76 ms cold start, MCP initialize + tools/list + vault.list all
return valid JSON-RPC.
…blank canvas
Before: stats bar said "nodes: 10 / edges: 70" but the canvas in
the middle of the page was empty. Tested in headless playwright
(1440x900 + 390x844). After this commit: full graph with concept
nodes (blue), tag nodes (purple rounded), wikilink edges, and the
unresolved 'sparse-mixture-experts' link rendered as a dashed-red
ghost. No console errors.
1. Drop the cose-bilkent CDN chain. v4.1.0 needs layout-base +
cose-base loaded first; the viewer only loaded cose-bilkent and
threw 'Cannot read properties of undefined (reading layoutBase)'
at registration. Switched to cytoscape's built-in 'cose' layout --
single CDN script, no extension graph. Quality is fine for the
demo's 10-100 node range.
2. Materialize ghost nodes for missing edge targets. Cytoscape
throws 'Can not create edge X->Y with nonexistant target Y' if
any edge references an absent node. The compiler emits tags only
as edges (kind='tag', not as nodes) plus one wikilink to a real
missing note. buildElements() now scans edges, classifies missing
targets by edge.kind, and adds them as either kind='tag' or
kind='unresolved' nodes so cytoscape doesn't crash.
3. Stop swallowing render errors. The fetch chain ended in
.catch(() => {}) which masked every cytoscape failure as 'no
sample graph, that's fine'. Now distinguishes a real 404 (silent)
from any other error (surfaces to #error-msg + console).
4. Add styling for kind='unresolved' ghost nodes (dark fill, dashed
red border) so unresolved wikilinks are visually distinct from
tags and concepts.
Verified end-to-end in playwright: 0 console errors, canvas
1440x769 (was stuck at 300x150 default before fix #2), file size
147 KB desktop / 80 KB mobile (was 19 KB / 14 KB blank).
…ad-only) 12 P0/P1/P2 findings across 6 persona skills + 25 MCP tool descriptions. Strategic decisions captured: - Kill proposed vault.import; bulk ingest via Obsidian native CLI + Importer - Keep vault-gardener persona (headless, vault.create/append) - Reposition obsidian-vault-bridge as multi-agent + corpus-landing bus (not Obsidian plugin adapter). See memory: project_bridge_architecture_v2.md No code changes this session. Fixes execute next session starting P0 #1.
- vault-curator: strip 'Stale Notes (90+ days)' section — vault.lint does not produce mtime data; section was promising output the handler could never deliver (skills/ + creatures/ copies) - vault.graph: wire 'type' param (resolved|unresolved|both) into the handler; previously declared in operations.ts schema but ignored, lying to the LLM. Filters edges by whether target note exists. - vault.externalSearch: remove entirely from operations + dispatch; the tool always threw 'no engine configured' and degraded tool-list quality. Re-register conditionally if/when an external adapter ships. - skills/mcp-tools-reference.md: delete drifted hand-written doc (op 'neq'/'not_exists', vault.delete force, vault.search context, vault.graph backlinks/outgoing were all documented but not implemented). operations.ts descriptions are SSOT; auto-generator deferred until needed. Tests: 86/86 pass. Typecheck 0 errors.
P1 fixes: - #5 verbose tool descriptions for vault.search, vault.lint, vault.graph, query.unified, query.search, query.explain so the LLM can disambiguate overlapping-but-different tools (filesystem grep vs multi-adapter ranked search vs prose-summary fan-out) without reading source. - #6 vault.graph now returns unresolvedLinks count so the vault-architect template 'Nodes/Edges/Unresolved: K' is backed by data (previously claimed a field the handler never produced). - #7 vault.searchByFrontmatter now includes mtime on each result, eliminating the N-roundtrip vault.stat fan-out the historian persona previously needed to sort by recency. - #8 MCP-adapter-unavailable fallback added to architect/historian/ janitor/teacher/librarian personas (curator already had one). Each names a specific fallback tool chain rather than 'fail open'. P2 fixes: - #10 startup banner: stderr now emits a first-prompt hint (try 'what do I know about <topic>' to invoke vault-librarian) after the adapter list, so new users see a concrete entry point. - #11 vault-janitor 10-deletion cap: clarified as self-enforced norm (not server-side cap) per per audit decision 'accept as soft norm and document'. Applied to both skills/ and creatures/ copies of each persona. Tests: 86/86 pass. Typecheck 0 errors.
Headless-only persona for conversational seeding of empty vaults. Tool budget: vault.list / vault.init / vault.create / vault.append. Scope per progress.txt audit decision: - Conversational empty-vault seeding ONLY; does NOT overlap with bulk corpus ingest (killed proposal vault.import -- that goes through the Obsidian native CLI + Importer plugin instead). - dryRun=true default like janitor, max 5 starter notes per session (overwhelming an empty vault is worse than leaving it empty). - vault.init fallback to vault.create+minimal _index.md when no filesystem adapter. Mirrors the 6-persona template (skills/ + creatures/config.yaml + creatures/prompts/system.md). Tests: 86/86 pass.
…dener DONE Handoff block for three code commits landed this session: - b5ddf83 P0 #1-#4 - b0d4515 P1 #5-#8 + P2 #10-#11 - 765e4ca vault-gardener persona (#9) Records the NULL-byte edge-key gotcha in VaultFs.dispatch so the next editor does not repeat the same Edit-tool discovery. Next-step block now points at Deferred items (push, bridge v2, demo gif, tools-doc generator) which require user confirmation.
…reference.md Resolves deferred P0 #2 audit item (single source of truth for LLM tool descriptions). operations.ts was already the runtime authority after last session deleted the hand-maintained skills/mcp-tools-reference.md; this restores a human-readable reference without drift risk. - mcp-server/src/scripts/generate-tools-doc.ts: imports makeAllOperations, passes null-cast stub deps (handlers are never invoked), groups by namespace in fixed order (vault, query, compile, recipe, agent), emits markdown. 100 lines, zero runtime deps beyond node:fs. - package.json: 'npm run generate-tools-doc' after build. - docs/mcp-tools-reference.md: first generated output. 38 operations across 5 namespaces, 418 lines, auto-banner forbids hand edits. Stub-deps trick works because Operation metadata (name/description/ params/mutating/namespace) is plain data; only handler() closures capture CompileTrigger/AdapterRegistry, and we never call them.
Asserts on-disk docs/mcp-tools-reference.md equals what generate() produces right now. Failure message points at 'npm run generate-tools-doc' so next editor knows the fix is one command. Second test scans the output for each of the 5 namespace headers -- catches regressions where a namespace is accidentally filtered out of NAMESPACE_ORDER. 88 tests green (86 existing + 2 new).
Technical slug (@obsidian-llm-wiki/mcp, github.com/2233admin/obsidian-llm-wiki,
serverInfo.name, clone paths, kb_meta path) unchanged. Prose flipped in
README H1+pitch, CLAUDE.md H1, setup/setup.ps1 top+echo banners,
REQUIREMENTS H1 + install prefix, .outreach/{show-hn,x-thread},
viewer title+h1, demo-vault + creatures READMEs, WHY_NOT_JUST_GREP,
gif-script, dogfood-log.sh header, vault-mind.example.yaml header.
Closes step 5/6 of rebrand workflow (see feedback_rebrand_workflow memory).
Negative grep verified: 30 residual slug mentions, all technical
(URLs, npm scope, clone paths, serverInfo.name, script ROOT).
Karpathy concept attribution preserved (23 mentions untouched).
There was a problem hiding this comment.
Code Review
This pull request transitions the project to 'LLM Wiki Bridge v2', introducing a persona-based architecture with six specialized virtual team roles for AI agents. Key changes include a pre-bundled MCP server for easier installation, refactored Python compiler logic for robust markdown parsing, a synthetic demo vault, and a static graph viewer. Feedback was provided regarding the vault.searchByFrontmatter implementation in the MCP server, where a call to statSync lacks error handling, potentially leading to execution failures if files are deleted or inaccessible during the vault walk.
| const pushWithMtime = (relPath: string, v: unknown): void => { | ||
| const st = statSync(this.resolve(relPath)); | ||
| results.push({ path: relPath, value: v, mtime: st.mtimeMs }); | ||
| }; |
There was a problem hiding this comment.
statSync is called without error handling inside the pushWithMtime helper. If a file is deleted or becomes inaccessible between the vault walk and this call, the entire tool execution will fail. It is safer to wrap this in a try-catch block to skip files that cannot be accessed.
const pushWithMtime = (relPath: string, v: unknown): void => {
try {
const st = statSync(this.resolve(relPath));
results.push({ path: relPath, value: v, mtime: st.mtimeMs });
} catch {
// Skip files that disappeared or are inaccessible
}
};Persona outputs now sediment into {vault}/00-Inbox/AI-Output/{persona}/
with a 6-field provenance frontmatter (generated-by / generated-at /
agent / parent-query / source-nodes / status). Gardener can
subsequently flip aged drafts to stale with a backlink-source test.
vault.writeAIOutput
- Typed params with persona regex ^vault-[a-z]+$ validation
- Auto-slug from parentQuery (first 6 words, kebab-case, fs-safe);
YYYY-MM-DD-slug.md under per-persona subdir
- Collision loop appends -2 through -99
- YAML-subset serialization round-trips through the existing
parseFrontmatter (no reformatting drift)
- dryRun default true (matches project convention); on dry run
returns the computed path + frontmatter without writing
- Status hardcoded `draft` on write
vault.sweepAIOutput
- Hardcoded per-persona thresholds: architect=45d, gardener=30d,
historian=180d, librarian=60d, catch-all=60d (MVP; graduate to
yaml config in Step 1.5 once usage justifies)
- Stale rule = age-past-threshold AND zero non-AI-Output backlinks
(source files whose own frontmatter carries `generated-by` do
NOT anchor -- AI->AI references would be self-anchoring
hallucination chains)
- Supersede candidates reported on same-persona reviewed pairs with
source-nodes Jaccard >= 0.6 and newer generated-at; human
confirms, never auto-applies -- AI self-grading is a second-layer
hallucination deliberately avoided
- dry_run=false rewrites frontmatter `status: draft` to `stale`
in-place via narrow regex substitution (body + other fm fields
untouched)
- `now` param provides ISO timestamp injection seam for tests
Tests: 10 unit tests in mcp-server/src/ai-output.test.ts covering
the write contract (all 6 fields, dryRun default, collision,
persona validation, empty sourceNodes serialization) and the sweep
contract (threshold detection with injected now, backlink filter,
AI-Output->AI-Output non-anchoring, in-place status flip preserving
body, supersede candidate Jaccard gate). node:test + tmpdir
isolation, zero new deps.
Incidental fix: added an `import.meta.url === process.argv[1]` guard
around main() in index.ts so importing VaultFs from tests does not
start the stdio MCP server. Without the guard, StdioServerTransport
keeps the process alive and subsequent test files never run.
Also exported `class VaultFs` (was previously private to the module)
so ai-output.test.ts can construct a fresh instance per test against
a tmpdir vault.
Total: 98 tests pass (88 baseline + 10 new). tsc 0 errors.
See docs/ai-output-convention.md for the human-readable schema
contract + FAQ. Closes Step 1 of the AI-Output sediment system.
Every persona now instructs its runtime to persist meaningful
analyses via vault.writeAIOutput, so the vault sediments both the
human's notes and the AI collaboration history.
- skills/vault-{architect,curator,teacher,historian,janitor,
librarian,gardener}.md: appended `## Sediment convention` block
with a per-persona vault.writeAIOutput invocation template +
status lifecycle pointer.
- skills/vault-gardener.md: additionally appended `## Sweep
convention (gardener-only responsibility)` -- dry-run first,
require explicit user confirmation before dry_run:false flips,
never auto-apply supersede candidates.
- docs/ai-output-convention.md (NEW, ~120 lines): schema table,
legal status transitions, per-persona thresholds, FAQ covering
"who flips reviewed", "what counts as a backlink", "why exclude
AI->AI references", "what happens to stale entries".
Non-trivial design reasoning (why B->A beats A->B for the
draft->stale/reviewed split, why the 6-field schema is minimal,
why supersede stays semi-automatic) lives in planning notes, not
in-tree.
Auto-generated via `npm run generate-tools-doc` after adding vault.writeAIOutput + vault.sweepAIOutput. Drift guard test now passes (mcp-server/src/scripts/generate-tools-doc.test.ts). Purely mechanical diff: 2 new ops appear under the `vault.*` namespace section with their param tables; no hand-edits.
…ess handoff - docs/GUIDE.md (NEW, 268 lines): user-journey oriented walkthrough -- pitch, 30-second install, first useful 5-minute session with 5 real prompt examples, 7-persona table, AI-Output sediment plain- English explanation, common prompts cheat sheet, troubleshooting, FAQ. Links to INSTALL.md for install-depth, ai-output-convention.md for sediment schema, mcp-tools-reference.md for tool catalog. - docs/GUIDE.zh-CN.md (NEW, 268 lines): structure-mirrored Chinese translation. Idiomatic (not literal) rendering of the same content for the Chinese dev community. Top-of-page language switch link. - README.md: single-line language-switch header pointing at both GUIDE files. Keeps README lean; GUIDE.md carries the depth. - progress.txt: Step 1 (AI-Output sediment) shipping summary, engineering notes for next editor, updated known-gaps roster.
Step 2 of AI-Output provenance (Appendix D of governance plan). - vault.writeAIOutput now accepts optional scope (project|global| cross-project|host-local) and quarantineState (new|reviewed| promoted|discarded), written to frontmatter. Defaults preserve Step 1 behaviour (scope=project, quarantine-state=new). - Zero logic change in vault.sweepAIOutput: it still keys off status only; governance fields are passthrough for this commit. - 8-field schema documented in docs/ai-output-convention.md with the three-axis model (scope = namespace, status = content timeliness, quarantine-state = trust gate). - 4 new tests: default values, explicit values, invalid scope rejected, invalid quarantineState rejected. 14/14 pass. - mcp-tools-reference.md regenerated (+4 lines).
Completes Appendix D of the Step 2 governance extension. - vault.sweepAIOutput now appends a structured history entry whenever it flips draft -> stale. Entry is YAML flow-style so the Step-1 scalar-array frontmatter parser round-trips it as an opaque string, avoiding a parser rewrite until Step 3 needs structured reads. - New helper appendHistoryInYaml in index.ts: initialises the history: key if absent, otherwise inserts a new item after the last existing flow-style entry. - Schema doc explains the flow-style choice, lists required fields (ts / from / to / trigger / evidence_level / human_in_loop / note) with the trigger enum from the governance plan, and notes that human-driven transitions must also append. - 2 new tests: first-flip appends a new history: block; a file seeded with an older history item keeps both entries in order. 16/16 ai-output pass; full mcp-server suite 104/104.
Follow-up to the external governance review. Adds a 9th frontmatter field that caches the human-vs-machine signal, informed by agent-memory-runtime's split of quarantine_state (machine) and review_status (human). review-status enum: none | user-confirmed Design choice: the enum deliberately omits the value "reviewed" that quarantine-state already uses. Two clean axes: - quarantine-state: new | reviewed | promoted | discarded (gardener's model of candidate maturity) - review-status: none | user-confirmed (Curry's explicit signature — never auto-advanced by the sweep) Why cache at all instead of deriving from history: - history is source of truth (flow-style items survive parser) - but flow-style means searchByFrontmatter can't index it - frontmatter field exists purely as the indexable cache over latest history entry with trigger=manual-user-confirmed-write - sweep never writes user-confirmed; only explicit reviewStatus param or a manual history append can (Step 2.6 gardener skill) Changes: - vault.writeAIOutput accepts optional reviewStatus param - frontmatter serialises review-status: none by default - schema tool description bumped to "9-field" in operations.ts - convention doc adds the 4-axis explanation + cache semantics - mcp-tools-reference.md regenerated (+1 line) - 3 new tests (default, explicit user-confirmed, invalid enum) — 19/19 ai-output pass; no regression in 104 full-suite run
Informed by Eridanus117/agent-memory-runtime external review: borrow the default-reject rules (decision-table §七) and the promotion-ready metrics (governance-layer §P0.5), adapted to a vault-level base. Skipped: capture/observation layer and the durable-memory+injection path (wrong base — our recall is a librarian skill, not a Claude Code hook). Input gate in vault.writeAIOutput (after schema/enum validation, so those errors still surface first): - body >= 50 chars required - parent-query must not be a single shell command (pwd/ls/cd/cat/ rg/grep/echo/git status|diff) - at least one of parent-query or sourceNodes must be non-empty Sweep metrics on every vault.sweepAIOutput response: - totalEntries, byPersona, byStatus, byQuarantineState - realBacklinkHitRate (fraction of entries with a non-AI-Output wikilink anchoring them) Purpose: future threshold tuning needs evidence; a vault where <10% of entries are ever cited is a persona-prompt problem, not a staleness-threshold problem. Tests: 22/22 ai-output pass (6 new: 3 gate rejections, 1 gate allow, 2 metrics cases). Full mcp-server suite 110/110 green. Convention doc gains two new sections (input gate, sweep metrics) with rationale grounded in the external governance plan.
…y tag Previously (PR #6 commit 136c40c): added review-status as a 9th frontmatter field as a cache over history[].trigger == manual-user-confirmed-write. Two design bugs surfaced during session-2 external-review audit: - P1 drift: sweep had no sync code from history appends back to the cache; any hand-edit of history or a future write-path emission of manual-user-confirmed-write would leave the cache wrong. - P2 adapt-over-build violation: Obsidian ships with native #tag infrastructure that indexes for free via vault.searchByTag. Re-inventing that as a frontmatter cache is exactly the kind of substrate-duplication the adapt-over-build rule forbids. Fix: drop review-status frontmatter field. When caller passes reviewStatus: user-confirmed, append '#user-confirmed' at body end (idempotent: skip if already present). Enum validation + API param shape preserved so internal callers (librarian skill) don't need updates. Files: - index.ts: drop frontmatter field + YAML line, add body-tag injection - operations.ts: update description (9-field → 8-field) + param doc - ai-output.test.ts: delete default-frontmatter test, rewrite user-confirmed test to assert body tag (not frontmatter), add idempotency test - docs/ai-output-convention.md: schema header (9 → 8 fields), remove the review-status row, reframe axes section as '3 axes + body tag' and describe the Obsidian-native routing - docs/mcp-tools-reference.md: regenerated (drift guard green) Verification: npm run build clean, 108/108 tests green (was 107; +1 net for new idempotency test, -1 for removed default test, ±0 for rewrite). Net diff +22 lines (projection was -30). Overrun comes from two new tests (idempotency + stronger body-tag assertion) and the kept enum validator. Trade accepted: preserves API surface + pins a real invariant (tag dedup), at the cost of the line-count target.
Step 2.5 (commit 4374ce3) chose three thresholds as guesses — body >= 50 chars, single-shell-cmd reject, query+sourceNodes both-empty reject — and hard-rejected on any miss. Step 2.6 converts the three throws to structured warnings collected into a warnings[] array on the write result, plus a stderr log line. The write still lands on disk. Rationale: - Thresholds were estimates, not measurements. 'body >= 50' was picked to block obvious noise but also blocks short-but-legitimate analyses (one-paragraph librarian lookups, stub references). - Hard reject means we never build up distribution data to retune from. - Warnings preserve the signal-quality instinct, make it observable (stderr + response field), and keep writes unblocked for 2-4 weeks of data collection. Retune or re-promote to reject after that. Response shape gain: writeAIOutput now always returns warnings: string[] (empty when clean) Values: 'body-too-short' / 'query-looks-like-shell-cmd' / 'no-anchor'. Files: - index.ts: swap 3 throws for warnings.push() + stderr log + include warnings in both dryRun and real-write response - ai-output.test.ts: 3 'rejects' tests -> 'warns but still writes', plus a clean-write test asserting warnings=[]; WriteOk interface gets warnings?: string[] - docs/ai-output-convention.md: 'Input gate (Step 2.5)' section rewritten as 'Input gate (Step 2.5 -> Step 2.6: warning mode)' Verification: npm run build clean, 114/114 tests green (same count as before; test count preserved, semantics flipped).
Step 2 history entries carried {from, to} without naming which axis
moved. Unambiguous for draft->stale (status always), but future
manual-promote entries {from: reviewed, to: promoted} are ambiguous —
both status and quarantine-state have a 'reviewed' value. Adding an
'axis: status' | 'axis: quarantine-state' sub-key resolves it.
Backward compat: existing entries are parsed as opaque strings by the
scalar-only frontmatter parser, so unchanged entries keep round-tripping.
New entries get axis:. No migration pass needed — entries without
axis are treated as 'axis unknown' (human reviews them manually if
ever audited).
Files:
- index.ts sweep path: historyEntry template gets 'axis: status'
inserted right after ts, before from/to (gardener only auto-flips
status). Comment block explains why.
- ai-output.test.ts: first-transition test asserts both
'axis: status' is present and that it sits immediately before from/to.
- docs/ai-output-convention.md: history example shows three entries
covering both axis values; required-fields table gains an axis row
with the Step 2.7 rationale; from/to row simplified to 'before/after
value on axis'.
Verification: 114/114 green, build clean.
Step 2.5 metrics gave a point-in-time snapshot per sweep but no
time-series, so tuning decisions had to eyeball the latest sweep and
trust memory for the trend. This commit appends one flow-style YAML
line per real sweep to 00-Inbox/AI-Output/sweep.log.md so a future
vault.lint or vault.query can reconstruct the trend.
Line shape:
- {ts: "...", totalEntries: N, staleHits: M, supersedeHits: K,
realBacklinkHitRate: 0.XXX}
Gates:
- Skipped on dry_run (no-write-on-dry-run invariant).
- Skipped when entries.length === 0 (no point spamming empty-vault
rows into the log).
- File created with a '# Sweep trend log' header on first non-empty
real sweep; subsequent sweeps append only the data line.
Files:
- index.ts sweepAIOutput: add the conditional append block right
before return. Reuses appendFileSync (already imported) and mkdirSync
(already imported). ~14 real lines of code + comment.
- ai-output.test.ts: 3 new tests pinning the semantics — dry_run=true
must not create the file, two !dry_run sweeps produce two lines
under the header, empty-vault !dry_run sweeps leave no file.
- docs/ai-output-convention.md: new 'Trend log (Step 2.8)' subsection
under 'Sweep metrics' with rationale + example entries + gate list.
Verification: 117/117 green (+3 new tests), build clean.
…w handoff Records two-PR (#6 governance + #7 Step 2.5 input gate/metrics) outcome, external-review absorption from agent-memory-runtime, and an explicit self-audited backlog for next session: P1: review-status cache has no sync code (drift lurking); history from/to axis is ambiguous (needs axis: sub-key) P2: review-status should be an Obsidian tag not a fm field (adapt-over-build rule violation); input gate thresholds are guesses (should downgrade to warnings); metrics is snapshot without trend log; no e2e MCP smoke test P3: MUST-append-history undocumented enforcement; borrow-3/skip-3 was inference not measurement; PR should have been single Next-session priority: migrate review-status frontmatter -> Obsidian tag (~-30 lines, kills two gaps at once), then downgrade input gate to warnings (~10 lines), then history axis sub-key (~20 lines), then metrics trend log (~15 lines). Total ~70 lines closes all P1 + half of P2.
Records the four-item close-out (items 1-4 from session-2's next-step list): review-status migration, input-gate warning downgrade, axis sub-key, sweep metrics trend log. 117/117 tests green, +88 net lines (+25% over projection, traded for regression safety), explicit gap list, PR strategy decision queued for next session.
Post-Step-2.5 the input gate pushes to warnings: string[] instead of throwing. Every skills/vault-*.md example showed the call without reading the result, so warnings from a real run would have landed silently. Each example now assigns const result = vault.writeAIOutput and surfaces result.warnings via console.warn when non-empty. Pure doc change. No mcp-server code touched. Tests 117/117 green.
Adds a five-step Hand-test path section to both GUIDE.md and GUIDE.zh-CN.md, sitting between the AI-Output sediment convention and the Vault structure sections. The payoff for the sediment system is most concrete in Obsidian Local Graph: a #user-confirmed tag cluster visually connects a human-confirmed AI-Output to every source-node it cites. Section walks from install -> one real writeAIOutput call -> open in Obsidian -> Local Graph depth 2 -> round-trip through sweep to see an axis: status history entry. Screenshot placeholders (TODO comments) ship today; images go in after a first real run against a live vault.
Spawn mcp-server/bundle.js as a child process and drive it with the MCP SDK client. Three round-trips: tools/list returns the core vault ops, vault.list finds a seeded note, vault.exists agrees. Uses node --test + tmpdir isolation. Closes the three-session carry-forward from Step 1. Surfaces two latent issues documented in progress.txt: - loadConfig precedence: `./vault-mind.yaml` and `../vault-mind.yaml` shadow $VAULT_MIND_VAULT_PATH silently. - bundled pglite/vector resolves `vector.tar.gz` relative to bundle.js, not to pglite; vaultbrain init crashes when enabled in a deployed (non-dev-workspace) build. Smoke test sidesteps both by writing a minimal yaml in the temp vault with `adapters: filesystem`. Bundle rebuilt so the shipped artifact tracks src. 120/120 tests green.
Problem: `@electric-sql/pglite/vector` resolves its extension bundle via
`new URL('../vector.tar.gz', import.meta.url)`. When esbuild inlined
the vector module into bundle.js, `import.meta.url` pointed at
bundle.js and the URL resolved to
`D:/projects/obsidian-llm-wiki/vector.tar.gz` (non-existent) instead
of `pglite/dist/vector.tar.gz`. Any deploy enabling the default
adapter list crashed on VaultBrain init with:
Error: Extension bundle not found: file:///.../vector.tar.gz
Fix: add `--external:@electric-sql/pglite --external:@electric-sql/pglite/*`
to the esbuild bundle script. pglite is a regular dependency so
consumers already install it alongside bundle.js; at runtime pglite
loads from node_modules/ and `import.meta.url` points to pglite's
own dist/ where `../vector.tar.gz` correctly resolves.
Side effect: bundle.js shrinks from ~1.54MB to ~963KB (-38%).
Also: extend the smoke test with a regression guard that spawns a
second server with the default adapter list (vaultbrain enabled).
Drop the workaround `adapters: filesystem` from the main smoke
block now that the default path is safe.
121/121 tests green.
…NGELOG Prepares feat/step2-6-tag-migration for squash-merge into main as v2.0.0. loadConfig precedence flipped from yaml-first to env-first (env > ./vault-mind.yaml > ../vault-mind.yaml). Previously an abandoned parent-dir yaml could silently shadow VAULT_MIND_VAULT_PATH. Smoke-test comments updated; 121/121 green. compiler ruff now clean (was 23 errors blocking PR #4 CI): - drop unused strip_noise import in concept_graph.py - bump line-length 100 -> 120 (LLM prompts + CLI help strings) - per-file ignore E501 in tests/*.py (long assert literals) - wrap remaining 3 long report strings in frontmatter_generator - isort auto-fix on link_discovery.py docs/ICEBOX.md: 2026-04-20 persona+MCP audit 12 items deferred to v3 (item #2 mcp-tools-reference drift already closed via generate-tools-doc); bridge v2 architecture noted as separate-repo concern; P2/P3 carry-forward items (sweep.log rotation, screenshots, CRLF, smoke payload tolerance) parked. CHANGELOG.md: v2.0.0 entry enumerates breaking changes and features. Bundle regenerated after loadConfig edit.
What's in v2
v2 pivots from "MCP plumbing" to "6-persona virtual team". Your markdown vault becomes a set of
/vault-*skills any MCP agent (Claude Code, Codex, OpenCode, Gemini) drives directly.Install in 30 seconds
PowerShell:
Headline changes
docs/mcp-tools-reference.mdwith a drift guard test--host <name>)obsidian-llm-wikiunchanged (npm scope, repo URL, clone paths,serverInfo.name)Verification
npm run build-- clean (tsc 0 errors)npm test-- 88/88 passnpm run generate-tools-doc-- doc is deterministic, drift test enforces freshnessv2.0.0-rc1already pushed on this headReview notes
mainproject_obsidian_llm_wiki_brandingandfeedback_rebrand_workflow(short form: npm scope was registered first, slug has to stay canonical across npm/URL/path for GEO + install consistency)Ready for merge review. Release notes live on the Releases tab.