Skip to main content

preserve

Single write point for the vault. Centralizes entity detection, textual matching, entity creation/update, and bidirectional linking. Accepts structured input (list of entities), free-form input (text, meeting notes, session context), or graphify output (graph.json + obsidian markdown from /graphify pipeline). Use when: "bedrock preserve", "bedrock-preserve", "save to vault", "record in vault", "/bedrock:preserve", or when another skill (e.g., /bedrock:teach) needs to persist entities in the vault.

معلومات المصدر

المستودع
ccplugins/awesome-claude-code-plugins
آخر نشاط في المصدر
٢١ أبريل ٢٠٢٦ في ٢١:٠٧
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٩٦٨
التفرعات
٦٣٩

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
preserve
description
Single write point for the vault. Centralizes entity detection, textual matching, entity creation/update, and bidirectional linking. Accepts structured input (list of entities), free-form input (text, meeting notes, session context), or graphify output (graph.json + obsidian markdown from /graphify pipeline). Use when: "bedrock preserve", "bedrock-preserve", "save to vault", "record in vault", "/bedrock:preserve", or when another skill (e.g., /bedrock:teach) needs to persist entities in the vault.
user_invocable
true
allowed-tools
Bash, Read, Write, Edit, Glob, Grep, Agent, mcp__plugin_github_github__*, mcp__plugin_atlassian_atlassian__*
# /bedrock:preserve — Single Write Point for the Vault ## Plugin Paths Entity definitions and templates are in the plugin directory, not in the vault root. Use the "Base directory for this skill" provided at invocation to resolve paths: - Entity definitions: `<base_dir>/../../entities/` - Templates: `<base_dir>/../../templates/{type}/_template.md` - Plugin CLAUDE.md: `<base_dir>/../../CLAUDE.md` (already injected automatically into context) Where `<base_dir>` is the path provided in "Base directory for this skill". --- ## Vault Resolution Resolve which vault to operate on. This skill can be invoked from any directory. **Step 1 — Parse `--vault` flag:** Check if the input arguments include `--vault <name>`. If found, extract the vault name and remove it from the arguments before further parsing. **Step 2 — Resolve vault path:** 1. **If `--vault <name>` was provided:** Read the vault registry at `<base_dir>/../../vaults.json`. Find the entry matching the name. If not found: error — "Vault `<name>` is not registered. Run `/bedrock:vaults` to see available vaults." If found: set `VAULT_PATH` to the entry's `path` value. 2. **If no `--vault` flag — CWD detection:** Read `<base_dir>/../../vaults.json`. Check if the current working directory is inside any registered vault path (CWD starts with a registered vault's absolute path). If multiple match, use the longest path (most specific). If found: set `VAULT_PATH` to the matching vault's `path`. 3. **If CWD detection fails — default vault:** From the registry, find the vault with `"default": true`. If found: set `VAULT_PATH` to the default vault's `path`. 4. **If no resolution:** Error — "No vault resolved. Available vaults:" followed by the registry listing. "Use `--vault <name>` to specify, or run `/bedrock:setup` to register a vault." **Step 3 — Validate vault path:** ```bash test -d "<VAULT_PATH>" && echo "exists" || echo "missing" ``` If missing: error — "Vault path `<VAULT_PATH>` does not exist on disk. Run `/bedrock:setup` to re-register." **Step 4 — Read vault config:** ```bash cat <VAULT_PATH>/.bedrock/config.json 2>/dev/null ``` Extract `language`, `git.strategy`, and other relevant fields for use in later phases. **From this point forward, ALL vault file operations use `<VAULT_PATH>` as the root.** - Entity directories: `<VAULT_PATH>/actors/`, `<VAULT_PATH>/people/`, etc. - Vault config: `<VAULT_PATH>/.bedrock/config.json` - Git operations: `git -C <VAULT_PATH> <command>` --- ## Overview This skill centralizes ALL write logic for the vault. It receives input (structured, free-form, or graphify output), identifies entities, correlates with the existing vault, proposes changes to the user, and executes after confirmation. It is the only path to create or update entities in the vault (except `/sync-people` which handles people/teams via GitHub API). **You are an execution agent.** Follow the phases below in order, without skipping steps. --- ## Phase 0 — Pre-Write Setup Two pre-flight steps run before any input parsing: synchronize the vault with its remote, then (when applicable) merge an incoming graphify output directory into the vault's cumulative `graphify-out/`. ### 0.1 Vault Sync Execute: ```bash git -C <VAULT_PATH> pull --rebase origin main ``` If the pull fails: - No remote configured: warn "No remote configured. Working locally." and proceed. - Pull conflict: `git -C <VAULT_PATH> rebase --abort` and warn the user. DO NOT proceed without resolving. - Otherwise: proceed. ### 0.2 Merge Incoming Graphify Output **When this runs:** Only when the skill was invoked with a `graphify_output_path` argument pointing at a graphify output directory (e.g., `/bedrock:teach` passes `$TEACH_TMP/graphify-out-new/`). Free-form text input and structured entity-list input skip this sub-phase entirely. **Skip condition (backward compat):** If the input's `graphify_output_path` resolves to the same absolute path as `<VAULT_PATH>/graphify-out/`, skip this sub-phase. Legacy callers (and `/bedrock:sync` in its current form) point at the vault's own output directory — there is nothing to merge. Use `realpath` (or equivalent) to compare: ```bash incoming_real=$(cd "<graphify_output_path>" 2>/dev/null && pwd -P) vault_real=$(cd "<VAULT_PATH>/graphify-out" 2>/dev/null && pwd -P) if [ "$incoming_real" = "$vault_real" ]; then echo "Phase 0.2: graphify_output_path already points at the vault — skipping merge." # proceed to Phase 1 with graphify_output_path unchanged fi ``` **Skip condition (no graphify input):** If the input is free-form text, structured entity list, or otherwise does not include `graphify_output_path`, skip. --- **Step 1 — Validate incoming directory.** Verify that `<graphify_output_path>/graph.json` exists, is non-empty, and parses as valid JSON. If invalid, abort with a clear error and do NOT mutate the vault: ```bash if [ ! -s "<graphify_output_path>/graph.json" ]; then echo "ERROR: graph.json missing or empty in <graphify_output_path>. Aborting before vault mutation." exit 1 fi python3 -c "import json,sys; json.load(open('<graphify_output_path>/graph.json'))" || { echo "ERROR: graph.json is not valid JSON."; exit 1; } ``` **Step 2 — First-ingestion edge case.** If `<VAULT_PATH>/graphify-out/` does not exist, promote the incoming directory wholesale (no re-merge pass) and record stats, then skip to Step 7: ```bash if [ ! -d "<VAULT_PATH>/graphify-out" ]; then mkdir -p "<VAULT_PATH>" cp -R "<graphify_output_path>" "<VAULT_PATH>/graphify-out" echo "Phase 0.2: first ingestion — promoted incoming graphify output to <VAULT_PATH>/graphify-out/." # record: nodes_added = <count of nodes in graph.json>, nodes_merged = 0, edges_added = <count of edges>, stale_flag_set = false # skip to Step 7 (record stats) then exit sub-phase fi ``` **Step 3 — Merge `graph.json` (nodes + edges).** Both files follow NetworkX node-link format (`{"nodes": [...], "edges": [...]}` or `"links"` — accept either key). Run the merge via an inline Python block to avoid hand-merging JSON in the prompt. Write the merged graph to a staging file, then atomically swap: ```bash python3 - <<'PY' import json, os, pathlib, shutil, sys existing_path = pathlib.Path("<VAULT_PATH>/graphify-out/graph.json") incoming_path = pathlib.Path("<graphify_output_path>/graph.json") staging_path = existing_path.with_suffix(".json.staging") with existing_path.open() as f: existing = json.load(f) with incoming_path.open() as f: incoming = json.load(f) # Accept both "edges" and "links" keys — normalize to "edges". def _edges(g): return g.get("edges", g.get("links", [])) # --- Node merge keyed by id --- def _union(a, b): # Preserve order; dedup by string representation. seen, out = set(), [] for item in (a or []) + (b or []): key = json.dumps(item, sort_keys=True) if not isinstance(item, str) else item if key not in seen: seen.add(key) out.append(item) return out def _dedup_sources_by_url(a, b): seen, out = set(), [] for item in (a or []) + (b or []): if isinstance(item, dict) and "url" in item: if item["url"] in seen: continue seen.add(item["url"]) out.append(item) return out existing_nodes = {n["id"]: n for n in existing.get("nodes", [])} nodes_added = 0 nodes_merged = 0 for inc in incoming.get("nodes", []): nid = inc["id"] if nid not in existing_nodes: existing_nodes[nid] = inc nodes_added += 1 else: cur = existing_nodes[nid] # Union sources by URL if "sources" in inc or "sources" in cur: cur["sources"] = _dedup_sources_by_url(cur.get("sources"), inc.get("sources")) # Most-recent updated_at (YYYY-MM-DD lexical compare works) cur_ua, inc_ua = cur.get("updated_at"), inc.get("updated_at") if inc_ua and (not cur_ua or inc_ua > cur_ua): cur["updated_at"] = inc_ua # Union labels and tags for key in ("labels", "tags"): if key in inc or key in cur: cur[key] = _union(cur.get(key), inc.get(key)) nodes_merged += 1 # --- Edge dedup keyed by (source, target, type/relation) --- def _edge_key(e): return (e.get("source"), e.get("target"), e.get("type") or e.get("relation")) existing_edges = _edges(existing) seen_edges = {_edge_key(e) for e in existing_edges} edges_added = 0 for inc_edge in _edges(incoming): k = _edge_key(inc_edge) if k in seen_edges: continue existing_edges.append(inc_edge) seen_edges.add(k) edges_added += 1 merged = dict(existing) merged["nodes"] = list(existing_nodes.values()) # Preserve the key naming the existing file used. merged_key = "edges" if "edges" in existing else ("links" if "links" in existing else "edges") merged[merged_key] = existing_edges with staging_path.open("w") as f: json.dump(merged, f, indent=2, ensure_ascii=False) # Emit stats to stdout for capture. print(json.dumps({"nodes_added": nodes_added, "nodes_merged": nodes_merged, "edges_added": edges_added})) PY ``` Atomic swap after the Python block succeeds: ```bash mv "<VAULT_PATH>/graphify-out/graph.json.staging" "<VAULT_PATH>/graphify-out/graph.json" ``` If the Python block exits non-zero, abort without running the `mv` — the vault's `graph.json` stays untouched. **Step 4 — Append `obsidian/*.md` files.** For each markdown file in `<graphify_output_path>/obsidian/`: - If the corresponding file exists in `<VAULT_PATH>/graphify-out/obsidian/`: append the incoming content to the existing file, separated by `\n\n---\n\n`. Existing content is preserved verbatim. - If it does not exist: copy the file into `<VAULT_PATH>/graphify-out/obsidian/`. ```bash mkdir -p "<VAULT_PATH>/graphify-out/obsidian" for src in "<graphify_output_path>/obsidian/"*.md; do [ -e "$src" ] || continue dest="<VAULT_PATH>/graphify-out/obsidian/$(basename "$src")" if [ -e "$dest" ]; then printf '\n\n---\n\n' >> "$dest" cat "$src" >> "$dest" else cp "$src" "$dest" fi done ``` **Step 5 — Append `GRAPH_REPORT.md`.** If `<graphify_output_path>/GRAPH_REPORT.md` exists: - If `<VAULT_PATH>/graphify-out/GRAPH_REPORT.md` exists: append a new dated section. - If it does not exist: copy. ```bash if [ -f "<graphify_output_path>/GRAPH_REPORT.md" ]; then dest="<VAULT_PATH>/graphify-out/GRAPH_REPORT.md" if [ -e "$dest" ]; then { printf '\n\n---\n\n# Merge on %s\n\n' "$(date +%Y-%m-%d)" cat "<graphify_output_path>/GRAPH_REPORT.md" } >> "$dest" else cp "<graphify_output_path>/GRAPH_REPORT.md" "$dest" fi fi ``` **Step 6 — Mark `.graphify_analysis.json` stale.** If `<VAULT_PATH>/graphify-out/.graphify_analysis.json` exists, set a top-level `"stale": true` field. Other content is untouched: ```bash analysis="<VAULT_PATH>/graphify-out/.graphify_analysis.json" stale_flag_set=false if [ -f "$analysis" ]; then python3 - <<PY import json, pathlib p = pathlib.Path("$analysis") with p.open() as f: data = json.load(f) data["stale"] = True with p.open("w") as f: json.dump(data, f, indent=2, ensure_ascii=False) PY stale_flag_set=true fi ``` If the file does not exist, skip (nothing to mark).
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub