diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 6f29b6b..0c9ea39 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1670,6 +1670,28 @@ "security", "compliance" ] + }, + { + "name": "bedrock", + "description": "Second Brain automation for Obsidian vaults — entity management, ingestion, compression, and sync via Claude Code skills", + "version": "1.2.0", + "author": { + "name": "Iury Krieger", + "url": "https://github.com/iurykrieger" + }, + "source": "./plugins/bedrock", + "category": "Knowledge Management", + "homepage": "https://github.com/ccplugins/awesome-claude-code-plugins/tree/main/plugins/bedrock", + "keywords": [ + "obsidian", + "second-brain", + "knowledge-base", + "zettelkasten", + "vault", + "knowledge-management", + "knowledge-graph", + "note-taking" + ] } ] } \ No newline at end of file diff --git a/README.md b/README.md index e4de615..2690210 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Awesome Claude Code plugins — a curated list of slash commands, subagents, MCP - [Development Engineering](#development-engineering) - [Documentation](#documentation) - [Git Workflow](#git-workflow) + - [Knowledge Management](#knowledge-management) - [Marketing Growth](#marketing-growth) - [Project & Product Management](#project--product-management) - [Security, Compliance, & Legal](#security-compliance--legal) @@ -159,6 +160,9 @@ Install or disable them dynamically with the `/plugin` command — enabling you - [pr-review](./plugins/pr-review) - [update-branch-name](./plugins/update-branch-name) +### Knowledge Management +- [bedrock](./plugins/bedrock) + ### Marketing Growth - [app-store-optimizer](./plugins/app-store-optimizer) - [content-creator](./plugins/content-creator) diff --git a/plugins/bedrock/.claude-plugin/plugin.json b/plugins/bedrock/.claude-plugin/plugin.json new file mode 100644 index 0000000..034a5ac --- /dev/null +++ b/plugins/bedrock/.claude-plugin/plugin.json @@ -0,0 +1,23 @@ +{ + "name": "bedrock", + "description": "Second Brain automation for Obsidian vaults — entity management, ingestion, compression, and sync via Claude Code skills", + "version": "1.2.0", + "author": { + "name": "Iury Krieger", + "url": "https://github.com/iurykrieger" + }, + "homepage": "https://github.com/ccplugins/awesome-claude-code-plugins/tree/main/plugins/bedrock", + "repository": "https://github.com/iurykrieger/claude-bedrock", + "license": "MIT", + "category": "knowledge-management", + "keywords": [ + "obsidian", + "second-brain", + "knowledge-base", + "zettelkasten", + "vault", + "knowledge-management", + "knowledge-graph", + "note-taking" + ] +} diff --git a/plugins/bedrock/CLAUDE.md b/plugins/bedrock/CLAUDE.md new file mode 100644 index 0000000..9d72edc --- /dev/null +++ b/plugins/bedrock/CLAUDE.md @@ -0,0 +1,227 @@ +# Bedrock — CLAUDE.md + +Instructions for AI agents working on Obsidian vaults powered by the Bedrock plugin. + +--- + +## What is Bedrock? + +**Bedrock** is a Claude Code plugin that turns any Obsidian vault into a structured Second Brain. It provides entity management, ingestion, compression, and sync automation — all via Claude Code skills. + +This is **not a codebase**. The target vault is markdown-only — no build system, no tests, no deployable artifacts. The primary consumers are humans reading in Obsidian and AI agents writing via skills. + +--- + +## Entity Types + +The vault organizes knowledge into 8 entity types, each in its own directory: + +| Entity | Directory | Filename pattern | Example | +|---|---|---|---| +| Actors | `actors/` | `repo-name.md` | `billing-api.md` | +| People | `people/` | `first-last.md` | `alice-smith.md` | +| Teams | `teams/` | `squad-name.md` | `squad-payments.md` | +| Concepts | `concepts/` | `slug.md` | `event-sourcing.md` | +| Topics | `topics/` | `YYYY-MM-category-slug.md` | `2026-04-feature-new-checkout.md` | +| Discussions | `discussions/` | `YYYY-MM-DD-slug.md` | `2026-04-02-daily-payments.md` | +| Projects | `projects/` | `slug.md` | `processing-3-0.md` | +| Fleeting | `fleeting/` | `YYYY-MM-DD-slug.md` | `2026-04-09-new-tokenization-service.md` | + +Each entity type has a `_template.md` defining the required frontmatter and structure. **Always follow the template when creating new entities.** + +Entity semantic definitions live in the plugin's `entities/` directory — used by `/bedrock:teach` and `/bedrock:preserve` to classify content. + +--- + +## Writing Rules + +### Language +- **English (en-US)** for all content by default (configurable via `/bedrock:setup`) +- Technical terms in English are accepted (PCI, API, Kafka, etc.) + +### Frontmatter +- YAML between `---` delimiters +- **Keys always in English** (`type`, `name`, `status`, `updated_at`, `updated_by`) +- **Values in the vault's configured language** (`description: "Billing and invoicing API"`) +- Array references use wikilink syntax: `["[[name1]]", "[[name2]]"]` +- Every entity must have `updated_at` (YYYY-MM-DD) and `updated_by` (person or `name@agent`) + +### Wikilinks +- Bare names only: `[[notification-service]]`, never `[[actors/notification-service]]` +- Bidirectional links expected (see template for link table per entity type) +- Add new links, **never remove** existing ones +- Links to non-existent files are fine — Obsidian shows them as creation invitations + +### Tags (hierarchical) +Tags use `/` separator for multi-dimensional filtering in Obsidian graph view: + +| Dimension | Prefix | Values | +|---|---|---| +| Type | `type/` | `actor`, `person`, `team`, `concept`, `topic`, `discussion`, `project`, `fleeting` | +| Status | `status/` | `active`, `deprecated`, `planning`, `blocked`, `done`, `in-progress`, `open`, `completed`, `cancelled`, `raw`, `reviewing`, `promoted`, `archived` | +| Domain | `domain/` | `payments`, `finance`, `notifications`, `checkout`, `orders`, `integrations`, `compliance`, `core`, `data`, `infra`, `marketplace`, `internal-tools`, `platform`, `security` | +| Scope | `scope/` | `pci`, `sox`, `lgpd` (fintech), `hipaa` (health), `gdpr` (Europe), `soc2` (SaaS) | +| Category | `category/` | `deprecation`, `bugfix`, `troubleshooting`, `rfc`, `incident`, `feature`, `compliance` | + +These are examples — both domains and scopes are extensible. Add new values as your organization grows (e.g. new teams, new compliance requirements). + +Rules: +- `type/*` mandatory on all entities +- `status/*` mandatory on actors and topics +- `domain/*` mandatory on actors and teams +- `scope/*` and `category/*` only when applicable + +### Aliases +- Minimum 1 alias per entity (Obsidian `aliases` field) +- Must not duplicate the filename +- Format: `aliases: ["Readable Name", "Acronym"]` + +### Callouts +| Callout | When | Mandatory? | +|---|---|---| +| `> [!warning] Deprecated` | Actor/topic with status deprecated | Yes | +| `> [!danger] PCI Scope` | Actor with `pci: true` | Yes | +| `> [!danger] SOX Scope` | Actor with SOX scope | Yes | +| `> [!info]`, `> [!todo]`, `> [!bug]` | Contextual highlights | No — use sparingly | + +### Filenames +- Kebab-case, no accents, lowercase +- Actor filenames = GitHub repository name (canonical identifier) + +--- + +## Update Rules + +| Entity | Body | Frontmatter | +|---|---|---| +| **Actors** | May modify and merge — new data replaces stale content | Merge new data, never delete fields | +| **People, Teams, Concepts, Topics** | Append-only — never delete content from another agent/human | Merge new data, never delete fields | +| **All** | Never remove existing wikilinks | Always update `updated_at` and `updated_by` | + +--- + +## Skills + +These are the Claude Code skills provided by the Bedrock plugin: + +| Skill | Purpose | +|---|---| +| `/bedrock:ask` | Orchestrated vault reader — decomposes questions, searches graph and vault, cross-references entities | +| `/bedrock:teach` | Ingest external sources (Confluence, Google Docs, GitHub repositories, remote URLs, and any file format supported by docling — DOCX, PPTX, XLSX, PDF, HTML, EPUB, images, and more) — extracts entities — delegates to `/bedrock:preserve` | +| `/bedrock:preserve` | Single write point — entity detection, matching, create/update, bidirectional links, git commit | +| `/bedrock:compress` | Vault alignment engine — fixes broken backlinks, concept fragmentation, entity miscategorization, duplicated entities, misnamed entities. Supports `--mode cron` for scheduled execution | +| `/bedrock:healthcheck` | Read-only vault health diagnostic — checks graphify-out integrity, setup, orphan entities, dangling content, old content (>15 days). Safe to run at any frequency | +| `/bedrock:sync` | Re-sync entities with external sources. Flags: `--people` (sync contributors), `--github` (sync PRs/activity) | +| `/bedrock:vaults` | Manage registered vaults — list, set default (`--set-default `), remove (`--remove `) | + +--- + +## Vault Resolution + +Bedrock supports multiple vaults. Each vault is registered by name in a global registry +(`vaults.json` in the plugin directory) during `/bedrock:setup`. Skills can target any +registered vault using the `--vault ` flag, regardless of the current working directory. + +### Registry + +The vault registry lives at `/vaults.json` with this schema: + +```json +{ + "vaults": [ + { "name": "my-vault", "path": "/absolute/path/to/vault", "default": true }, + { "name": "team-vault", "path": "/absolute/path/to/team-vault", "default": false } + ] +} +``` + +- Vault names are **kebab-case**, lowercase, unique +- Exactly one vault is marked as `"default": true` +- The registry is created automatically during `/bedrock:setup` +- Manage vaults with `/bedrock:vaults` (list, set-default, remove) + +### Resolution Precedence + +When a skill needs to determine which vault to operate on, it follows this chain: + +1. **Explicit flag** — `--vault ` targets the named vault from the registry +2. **CWD detection** — if the current directory is inside a registered vault path, use that vault +3. **Default vault** — use the vault marked as default in the registry +4. **Error** — no vault resolved; display available vaults and ask the user to specify + +This keeps full backward compatibility — users already working inside a vault directory +don't need to change anything. + +### Plugin Reinstall Note + +If the Bedrock plugin is reinstalled, the `vaults.json` registry file may be lost. +Vault data on disk is unaffected. Re-run `/bedrock:setup` inside each vault to +re-register it. + +--- + +## Git Workflow + +Bedrock supports 3 git strategies, configured via `.bedrock/config.json` (`git.strategy` field): + +| Strategy | Behavior | When to use | +|---|---|---| +| `commit-push` (default) | Commit + push to `main` + rebase retry (max 2 attempts) | Solo vaults, trusted contributors | +| `commit-push-pr` | Commit to branch + push + open PR targeting `main` via `gh` CLI | Team vaults requiring review | +| `commit-only` | Commit locally, no push | Offline or local-only vaults | + +When `git.strategy` is absent from config (or `.bedrock/config.json` does not exist), all skills default to `commit-push` for backwards compatibility. + +**Branch naming for `commit-push-pr`:** `vault/-` where `` is derived from the commit message (entity name, `batch-N-entities`, `compress-N-entities`, etc.). If the branch already exists, a counter is appended (e.g., `-2`). + +- **Pull before write**: `git pull --rebase origin main` +- **Commit convention**: `vault(): [source: ]` + +| Field | Values | +|---|---| +| `` | `person`, `team`, `actor`, `concept`, `topic`, `discussion`, `project`, `note` | +| `` | `creates`, `updates`, `links`, `compresses` | +| `` | `memory`, `github`, `jira`, `confluence`, `gdoc`, `sheets`, `manual`, `compress` | + +Examples: +``` +vault(actor): updates billing-api [source: github] +vault: teaches roadmap-26q1, creates 7 topics [source: confluence] +vault: compresses 25 entities across 8 clusters [source: compress] +``` + +--- + +## Zettelkasten Principles + +The vault follows adapted Zettelkasten principles. Each entity type has a **role** in the knowledge graph: + +| Role | Entity types | Behavior | +|---|---|---| +| **Permanent notes** | `actors/`, `people/`, `teams/`, `concepts/` | Consolidated, stable knowledge. Self-contained. | +| **Bridge notes** | `topics/`, `discussions/` | Connect permanents, explaining *why* they relate. | +| **Index notes** | `projects/` | Curation — organize reading paths (thematic MOCs). | +| **Fleeting notes** | `fleeting/` | Inbox — raw ideas, forming concepts. Temporary by design. | + +### Linking Rules + +1. **Frontmatter = structural.** Arrays in frontmatter define organizational relationships (team, members, actors). Feed Dataview queries. +2. **Body = semantic.** Wikilinks in the body must have textual context: "processes payments via [[billing-api]]", not just "[[billing-api]]". +3. **Bridges are the connective tissue.** If two actors relate, the explanation lives in a topic or discussion — not duplicated in both. +4. **Index notes point, they don't explain.** Projects direct the reader to bridges and permanents. +5. **Fleeting notes are temporary.** They should be promoted (to permanent/bridge) or archived. +6. **Provenance via `sources` field.** Every entity can record where its data came from in the `sources` frontmatter field (list of `{url, type, synced_at}`). See `entities/sources-field.md` in the plugin. + +Details in `entities/*.md` (section "Zettelkasten Role" per type) within the plugin directory. + +--- + +## Don'ts + +- **Never** use flat tags (`[actor]`) — always hierarchical (`[type/actor]`) +- **Never** use path-qualified wikilinks — `[[name]]`, not `[[dir/name]]` +- **Never** use display names in wikilinks — `[[notification-service]]`, not `[[NotificationService]]` +- **Never** delete content in people/teams/concepts/topics written by another agent or human +- **Never** delete existing wikilinks or frontmatter fields +- **Never** commit credentials, tokens, PANs, CVVs, or any sensitive data +- **Never** log raw card data (PAN, CVV, tracks, EMV) in documentation examples diff --git a/plugins/bedrock/LICENSE b/plugins/bedrock/LICENSE new file mode 100644 index 0000000..61cf1eb --- /dev/null +++ b/plugins/bedrock/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Iury Krieger + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/bedrock/README.md b/plugins/bedrock/README.md new file mode 100644 index 0000000..c593ac2 --- /dev/null +++ b/plugins/bedrock/README.md @@ -0,0 +1,46 @@ +# Bedrock + +Second Brain automation for Obsidian vaults — entity management, ingestion, compression, and sync via Claude Code skills. + +**Upstream repository:** https://github.com/iurykrieger/claude-bedrock +**Homepage:** https://claude-bedrock.vercel.app + +## What it does + +Bedrock turns any Obsidian vault into a structured Second Brain following adapted Zettelkasten principles. Knowledge is organized into **8 entity types** (actors, people, teams, concepts, topics, discussions, projects, fleeting notes), each with YAML frontmatter, hierarchical tags (`type/`, `status/`, `domain/`, `scope/`), and bidirectional wikilinks. + +No build system. No runtime. Just markdown files, AI agents, and your Obsidian vault. + +## Skills + +| Skill | Purpose | +|---|---| +| `/bedrock:setup` | Interactive vault initialization and configuration | +| `/bedrock:ask` | Orchestrated vault reader — decomposes questions, searches graph and vault, cross-references entities | +| `/bedrock:teach` | Ingest external sources (Confluence, Google Docs, GitHub, DOCX/PPTX/XLSX/PDF/HTML/EPUB via docling) and extract entities | +| `/bedrock:preserve` | Single write point — detect, match, create/update entities with bidirectional links | +| `/bedrock:compress` | Deduplication and vault health — fix broken backlinks, merge duplicates, consolidate fragmented concepts | +| `/bedrock:sync` | Re-sync entities with external sources (GitHub PRs/activity, contributors) | +| `/bedrock:healthcheck` | Read-only vault health diagnostic — graphify-out integrity, orphans, dangling content | +| `/bedrock:vaults` | Manage registered vaults — list, set default, remove | + +## Dependencies + +- [graphify](https://github.com/iurykrieger/graphify) — semantic code extraction and knowledge-graph pipeline (auto-installed by `/bedrock:setup`) +- [docling](https://github.com/docling-project/docling) — universal file → markdown converter (auto-installed by `/bedrock:setup`) + +## Install + +```bash +/plugin install bedrock@awesome-claude-code-plugins +``` + +Then scaffold a vault: + +``` +/bedrock:setup +``` + +## License + +[MIT](LICENSE) — Iury Krieger diff --git a/plugins/bedrock/entities/actor.md b/plugins/bedrock/entities/actor.md new file mode 100644 index 0000000..48abbd7 --- /dev/null +++ b/plugins/bedrock/entities/actor.md @@ -0,0 +1,76 @@ +# Entity: Actor + +> Source of truth for required fields: `actors/_template.md` + +## What it is + +An **actor** is a system, service, API, or application with its own lifecycle — it has a GitHub repository, an independent deployment process, and is operated by a specific team. Actors are the fundamental infrastructure unit in the Second Brain: each actor represents something that runs in production (or has run, if deprecated). + +Actors can be HTTP APIs, queue workers/consumers, cronjobs, lambdas, or monoliths. The key criterion is: **has its own repository and independent deployment**. + +## When to create + +- The content mentions a system/service with its own GitHub repository that does not yet exist in `actors/` +- The content describes a new application being developed (status: `in-development`) +- The content references an organization GitHub repository not yet cataloged + +## When NOT to create + +- It is an internal library/SDK used by other actors (e.g., `opentelemetry-golang-lib`) — that is a dependency, not an actor +- It is a module within another repository (e.g., `orders-cdc` within the `orders-api` workspace) — the actor is the root repository +- It is a CI/CD tool or shared infrastructure (e.g., ArgoCD, Karavela, Terraform modules) +- It is an external/third-party service (e.g., DataDog, New Relic, AWS SQS) — mention it as a dependency of an actor, not as its own actor + +## How to distinguish from other types + +| Looks like... | But is... | Key difference | +|---|---|---| +| Actor | Topic (deprecation) | If the focus is "this system is going to be shut down", it is a deprecation topic that **references** the actor. The actor is the system; the topic is the subject about it | +| Actor | Project | If the focus is "we are building a new system", it is a project until the system has a repo and deployment. Once created, the system becomes an actor | +| Actor | Person | Repo names can look like people's names (e.g., `ralph`). If it has a GitHub repo and deploys, it is an actor | + +## Required fields (frontmatter) + +| Field | Type | Description | +|---|---|---| +| `type` | string | Always `"actor"` | +| `name` | string | Repository name (kebab-case) | +| `category` | string | `api`, `worker`, `consumer`, `producer`, `cronjob`, `lambda`, `monolith` | +| `description` | string | Description of the system's function | +| `repository` | string | GitHub repository URL | +| `stack` | string | Tech stack separated by ` · ` | +| `status` | string | `active`, `deprecated`, `in-development` | +| `team` | wikilink | `"[[squad-name]]"` | +| `criticality` | string | `very-high`, `high`, `medium`, `low` | +| `pci` | boolean | Whether it operates under PCI DSS scope | +| `updated_at` | date | YYYY-MM-DD | +| `updated_by` | string | Who updated it | + +## Zettelkasten Role + +**Classification:** permanent note +**Purpose in the graph:** Represent consolidated facts about systems and services that run in production. + +### Linking Rules + +**Structural links (frontmatter):** `team` (wikilink to the responsible squad). These define the organizational structure — who operates the system. +**Semantic links (body):** Wikilinks in the body must have textual context explaining the relationship. E.g., "receives authorizations from [[payment-gateway]] via gRPC" instead of just "[[payment-gateway]]". Body links explain technical dependencies, data flows, and integrations — the *why* of the connection. +**Relationship with other roles:** Actors are referenced by bridge notes (topics, discussions) that explain what is happening with the system. Do not duplicate in the actor explanations that belong in a topic — the actor describes the system, the topic describes the subject about it. + +### Completeness Criteria + +An actor is complete when: it has an identified repository, documented stack, defined status, assigned responsible team, and a self-contained description (understandable without reading other notes). If fundamental data is missing (no repo, no team, no description), the content should go to `fleeting/` until consolidated. + +## Examples + +### This IS an actor + +1. "The `billing-api` is a .NET 8 API that processes charges and invoices. It runs on EKS via ArgoCD in the `runtime-payments-prd` namespace." — System with repo, deployment, runtime. It is an actor. + +2. "We are spinning up `health-checker` in Go to replace the old probe. It already has a GitHub repo and CI pipeline." — New system with its own repo. It is an actor (status: `in-development`). + +### This is NOT an actor + +1. "We use the `opentelemetry-golang-lib` library for instrumentation." — Shared library, has no independent deployment. It is a dependency of actors. + +2. "ArgoCD handles deployment for all squad services." — Shared infrastructure tool, does not have independent deployment as a product. It is not an actor. diff --git a/plugins/bedrock/entities/code.md b/plugins/bedrock/entities/code.md new file mode 100644 index 0000000..364c05d --- /dev/null +++ b/plugins/bedrock/entities/code.md @@ -0,0 +1,93 @@ +# Entity: Code + +> Source of truth for required fields: `actors/_template_node.md` + +## What it is + +A **code** entity is a granular unit of knowledge automatically extracted by graphify from an actor's source code or documentation. It represents functions, classes, modules, concepts, decisions, interfaces, or endpoints that were identified by semantic analysis (AST + LLM) of the repository. + +Code entities are sub-entities of actors — each code entity belongs to exactly one actor and lives inside the actor's folder at `actors//nodes/`. They form the fine-grained detail layer of the knowledge graph, connecting the vault to information that exists in the code but would not be captured by high-level descriptions. + +## When to create + +- Graphify extracted a node from an actor's repository (function, class, module, interface, endpoint) with semantic relevance +- Graphify extracted a concept or architectural decision from documentation linked to an actor +- The node has `confidence` EXTRACTED or INFERRED (not purely AMBIGUOUS) +- The corresponding actor already exists in the vault + +## When NOT to create + +- The node is trivial (generic getter/setter, boilerplate, auto-generated code) — filter by relevance +- The node already exists as another entity in the vault (e.g., a concept that is already a topic) +- The corresponding actor does not exist in the vault — create the actor first +- The node has confidence AMBIGUOUS without edges connecting it to other nodes — isolated information without value +- The content is sensitive (credentials, tokens, PANs, CVVs) — never persist sensitive data + +## How to distinguish from other types + +| Looks like... | But is... | Key difference | +|---|---|---| +| Code | Topic | If the content is a broad architectural decision affecting multiple actors, it is a topic. If it is specific to a function/class of one actor, it is a code entity | +| Code | Actor | If it has its own repository and independent deployment, it is an actor. Code entities are internal parts of an actor | +| Code | Fleeting | If it came from graphify with confidence EXTRACTED/INFERRED and has a `graphify_node_id`, it is a code entity. If it is a loose idea without a link to the graph, it is fleeting | +| Code | Discussion | If it describes a decision made in a meeting/debate, it is a discussion. If it describes a design decision found in the code, it is a code entity | +| Code | Concept | If the content describes a cross-cutting pattern, principle, or abstraction that is actor-independent, it is a concept. If it is specific to a single actor's implementation, it is a code entity | + +## Required fields (frontmatter) + +| Field | Type | Description | +|---|---|---| +| `type` | string | Always `"code"` | +| `name` | string | Human-readable name of the node (e.g., `"ProcessTransaction"`, `"KafkaEventPublisher"`) | +| `aliases` | array | Alternative names (min 1). E.g., `["Process Transaction", "processTransaction"]` | +| `actor` | wikilink | `"[[actor-name]]"` — parent actor to which this code entity belongs | +| `node_type` | string | `function`, `class`, `module`, `concept`, `decision`, `interface`, `endpoint` | +| `source_file` | string | Relative path in the actor's repo (e.g., `src/Controllers/PaymentController.cs`) | +| `description` | string | Description of this node's function/role | +| `graphify_node_id` | string | Unique node ID in graph.json (e.g., `billing_api_processTransaction`) | +| `confidence` | string | `EXTRACTED`, `INFERRED`, or `AMBIGUOUS` — extraction confidence level | +| `updated_at` | date | YYYY-MM-DD | +| `updated_by` | string | Who updated it | +| `tags` | array | Hierarchical tags: `[type/code]` + `domain/*` inherited from the actor | + +### Optional fields + +| Field | Type | Description | +|---|---|---| +| `relations` | array | Wikilinks to other code entities or related entities | +| `source_location` | string | Line or range in the source_file (e.g., `L42-L85`) | + +## Zettelkasten Role + +**Classification:** permanent note extension (sub-entity of actor) +**Purpose in the graph:** Represent granular implementation details of actors — functions, classes, design decisions — that enrich the knowledge graph without polluting the high-level permanent notes. + +### Linking Rules + +**Structural links (frontmatter):** `actor` (wikilink to the parent actor). Defines the hierarchy — every code entity belongs to exactly one actor. +**Semantic links (body):** Wikilinks in the body should have textual context when possible. E.g., "calls [[ProcessPayment]] to execute the transaction" instead of just "[[ProcessPayment]]". For code entities with many relations, links in the frontmatter (`relations`) are acceptable without textual context. +**Relationship with other roles:** Code entities are referenced by the parent actor ("Knowledge Nodes" section) and can be referenced by bridge notes (topics, discussions) when relevant. Code entities reference each other via `relations` and edges from graph.json. + +### Completeness Criteria + +A code entity is complete when: it has a valid `graphify_node_id`, defined `actor`, defined `node_type`, identified `source_file`, and a self-contained `description`. If `graphify_node_id` or `actor` is missing, the content should go to `fleeting/`. + +## Examples + +### This IS a code entity + +1. "The function `ProcessTransaction` in `src/Controllers/PaymentController.cs` of `billing-api` is responsible for orchestrating the processing flow with the selected provider." — Specific function of an actor, extracted by AST. It is a code entity. + +2. "The class `KafkaEventPublisher` implements the event publishing pattern for Kafka topics following the orders contract." — Internal class of an actor. It is a code entity. + +3. "The endpoint `POST /v1/payments/authorize` receives authorization requests and delegates to `AuthorizationService`." — API endpoint of an actor. It is a code entity. + +### This is NOT a code entity + +1. "The billing-api is being refactored to support internationalization." — High-level information about the actor. It is a topic. + +2. "We decided in the daily that the retry pattern will change to exponential backoff." — Decision made in a meeting. It is a discussion. + +3. "There might be a race condition in the void worker." — Unconfirmed hypothesis. It is fleeting. + +4. "Event sourcing is a pattern where state changes are captured as a sequence of events." — Cross-cutting abstraction, not specific to one actor. It is a concept. diff --git a/plugins/bedrock/entities/concept.md b/plugins/bedrock/entities/concept.md new file mode 100644 index 0000000..2142d4d --- /dev/null +++ b/plugins/bedrock/entities/concept.md @@ -0,0 +1,86 @@ +# Entity: Concept + +> Source of truth for required fields: `concepts/_template.md` + +## What it is + +A **concept** is a timeless, definitional unit of knowledge — a pattern, principle, technique, protocol, or abstraction that is self-contained and actor-independent. Concepts describe *what something IS*, not what is happening with it. They are the canonical source of truth for ideas that recur across multiple entities in the vault. + +Concepts consolidate knowledge that would otherwise be scattered: instead of each actor and topic re-describing "event sourcing" or "circuit breaker pattern" in their own words, they reference the concept via wikilink. The concept page holds the stable definition; temporal evolution of how the concept is being adopted lives in topics that reference it. + +## When to create + +- The content defines a pattern, principle, or technique that is referenced by multiple actors or topics (e.g., "event sourcing", "saga pattern", "PCI tokenization flow") +- The content describes a protocol or standard that governs how systems interact (e.g., "mTLS authentication", "ISO 8583 message format") +- The content explains an abstraction or architectural paradigm used across the organization (e.g., "CQRS", "hexagonal architecture", "strangler fig pattern") +- Graphify extracted a concept node (`file_type: document` or `file_type: paper`) that is self-contained and not specific to a single actor's implementation + +## When NOT to create + +- The content describes a temporal initiative with status and lifecycle (e.g., "we are migrating to event sourcing") — that is a topic +- The content is specific to a single actor's implementation (e.g., "the retry logic in billing-api uses exponential backoff") — that is a code entity (sub-entity of the actor) +- The content is a vague or unconfirmed idea without a clear definition (e.g., "maybe we should look into CQRS") — that is fleeting +- The content is a meeting or conversation about a concept (e.g., "we discussed event sourcing in the daily") — that is a discussion + +## How to distinguish from other types + +| Looks like... | But is... | Key difference | +|---|---|---| +| Concept | Topic | A topic is temporal — it has a status, lifecycle, and tracks what is HAPPENING over time (e.g., "migration to event sourcing"). A concept is timeless — it defines what something IS (e.g., "event sourcing"). A topic can reference a concept. | +| Concept | Code | A code entity is a sub-entity of a specific actor — it describes an implementation detail (function, class, endpoint) inside one system. A concept is actor-independent — it describes a pattern or principle used across systems. | +| Concept | Fleeting | A fleeting note is a raw, unconfirmed fragment. A concept is self-contained and definitional — it has a clear name, description, and enough context to stand on its own. If the idea is vague, it belongs in fleeting until it matures. | +| Concept | Actor | An actor has a repository and deployment. A concept is abstract knowledge — it has no repo, no deployment, no infrastructure. If it deploys, it is an actor. | + +## Required fields (frontmatter) + +| Field | Type | Description | +|---|---|---| +| `type` | string | Always `"concept"` | +| `name` | string | Human-readable name of the concept (e.g., `"Event Sourcing"`, `"Circuit Breaker Pattern"`) | +| `aliases` | array | Alternative names (min 1). E.g., `["ES", "Event-Driven State"]` | +| `description` | string | One-line definition of the concept | +| `related_to` | array | Wikilinks to related entities of any type: `["[[entity-name]]"]` | +| `sources` | array | Provenance: `[{url, type, synced_at}]` | +| `updated_at` | date | YYYY-MM-DD | +| `updated_by` | string | Who updated it | +| `tags` | array | Hierarchical tags: `[type/concept]` + `domain/*` when applicable | + +### Optional fields + +| Field | Type | Description | +|---|---|---| +| `graphify_node_id` | string | Unique node ID in graph.json (when extracted by graphify) | +| `confidence` | string | `EXTRACTED`, `INFERRED`, or `AMBIGUOUS` — extraction confidence level (when extracted by graphify) | + +## Zettelkasten Role + +**Classification:** permanent note +**Purpose in the graph:** Represent stable, timeless, definitional knowledge — patterns, principles, techniques, protocols, and abstractions — that multiple entities reference instead of re-describing. + +### Linking Rules + +**Structural links (frontmatter):** `related_to` (wikilinks to any related entity). This is a flat list — the body provides the semantic context for each relationship. +**Semantic links (body):** Wikilinks in the body must have textual context. E.g., "commonly implemented in Go services like [[billing-api]] and [[notification-service]] using the circuit breaker library" — not just "[[billing-api]]". The concept body explains what the concept IS and how it connects to the vault's entities. +**Relationship with other roles:** Concepts are referenced by bridge notes (topics) that track temporal adoption or evolution. Concepts are referenced by permanent notes (actors) that implement them. Concepts do not duplicate information from topics — the concept defines the idea, the topic tracks what is happening with it. + +### Completeness Criteria + +A concept is complete when: it has a clear name, a self-contained description that defines what it IS, and at least 1 related entity referenced with context. If the idea is vague, lacks a clear definition, or cannot stand on its own without reading other entities, the content should go to `fleeting/` until it matures. + +## Examples + +### This IS a concept + +1. "Event sourcing is a pattern where state changes are captured as a sequence of events rather than storing only the current state. Events are immutable and append-only." — Timeless definition of a pattern. Self-contained. Not specific to one actor. It is a concept. + +2. "The circuit breaker pattern prevents cascading failures by detecting repeated errors and temporarily stopping calls to a failing service, returning a fallback response instead." — Defines a technique used across multiple systems. It is a concept. + +3. "mTLS (mutual TLS) is a protocol where both client and server authenticate each other using certificates, ensuring bidirectional identity verification." — Protocol definition, actor-independent. It is a concept. + +### This is NOT a concept + +1. "We are migrating all Go services from REST to gRPC by Q3." — Temporal initiative with deadline. This is a topic (category: `rfc` or `feature`). + +2. "The `CircuitBreakerMiddleware` class in notification-service wraps HTTP calls with a 5-second timeout and 3-retry threshold." — Implementation detail specific to one actor. This is a code entity of notification-service. + +3. "Someone mentioned we should try CQRS for the new orders system." — Vague, unconfirmed. This is a fleeting note until it has a clear definition and confirmed relevance. diff --git a/plugins/bedrock/entities/discussion.md b/plugins/bedrock/entities/discussion.md new file mode 100644 index 0000000..0b25dfd --- /dev/null +++ b/plugins/bedrock/entities/discussion.md @@ -0,0 +1,78 @@ +# Entity: Discussion + +> Source of truth for required fields: `discussions/_template.md` + +## What it is + +A **discussion** is the record of a conversation, meeting, or exchange of ideas that took place at a specific moment. Discussions capture: who participated, what was discussed, which decisions were made, and which actions remained pending. + +Discussions are **one-time events with a fixed date** — they do not evolve over time like topics. Once created, they are only updated to reflect progress on action items, never to add new subjects to the same discussion. + +## When to create + +- The content is meeting minutes or meeting notes with participants and decisions +- The content records a conversation that generated decisions or action items relevant to the vault +- The content describes an alignment/planning session with multiple people and impact on actors + +## When NOT to create + +- It is technical documentation, a spec, or a PRD — those are reference documents, not discussions +- It is a changelog or release notes — that is activity of an actor +- It is a Slack thread with one-off information without decisions — it only counts if there was a decision or action item +- It is a casual 1:1 conversation with no impact on the vault — discussions record relevant events + +## How to distinguish from other types + +| Looks like... | But is... | Key difference | +|---|---|---| +| Discussion | Topic | A topic is a subject that evolves (status, history). A discussion is an event with a fixed date. A meeting about deprecation is a discussion; the deprecation itself is a topic | +| Discussion | Project | A project has deliverables and a deadline. A discussion is the record of a conversation. A planning meeting can generate a discussion AND result in the creation of a project | +| Discussion | Source | If the content is meeting notes being ingested, the source is the source. The content extracted from the source can generate a discussion | + +## Required fields (frontmatter) + +| Field | Type | Description | +|---|---|---| +| `type` | string | Always `"discussion"` | +| `title` | string | Descriptive title of the conversation | +| `date` | date | YYYY-MM-DD of the conversation | +| `summary` | string | Summary in 1-2 sentences | +| `conclusions` | array | List of decisions made | +| `action_items` | array | List of pending actions | +| `related_topics` | array | Wikilinks to topics | +| `related_actors` | array | Wikilinks to actors | +| `related_people` | array | Wikilinks to persons | +| `related_projects` | array | Wikilinks to projects | +| `related_teams` | array | Wikilinks to teams | +| `source` | string | `session`, `meeting-notes`, `jira`, `confluence`, `manual` | +| `updated_at` | date | YYYY-MM-DD | +| `updated_by` | string | Who updated it | + +## Zettelkasten Role + +**Classification:** bridge note +**Purpose in the graph:** Record the moment when permanents (people, actors, teams) connected through a conversation, decision, or exchange of ideas. + +### Linking Rules + +**Structural links (frontmatter):** `related_people`, `related_actors`, `related_teams`, `related_topics`, `related_projects` (wikilinks). These define which entities participated in or were discussed during the event. +**Semantic links (body):** Links in the body must contextualize participation or mention. E.g., "[[bob-jones]] presented the proposal to migrate [[legacy-gateway]] to [[billing-api]]" instead of just listing names. The body of the discussion is the narrative of the event — who said what about which system and for what reason. +**Relationship with other roles:** Discussions are temporal bridges — they record when and how permanents connected at a specific moment. They differ from topics because they are one-time events, not subjects that evolve. A discussion can generate or update topics and projects. + +### Completeness Criteria + +A discussion is complete when: it has a date, at least 1 participant (person), a summary of what was discussed, and at least 1 conclusion or action item. If there is only a mention of "a meeting happened" without details, the content should go to `fleeting/` until enriched. + +## Examples + +### This IS a discussion + +1. "Q2 planning meeting (04/01/2026): Alice, Bob, Carol attended. Decision: prioritize migration of legacy-gateway. Action: Bob will map dependencies by Friday." — Meeting notes with participants, decision, and action item. It is a discussion. + +2. "Observability alignment (04/03/2026): we decided to migrate from DataDog to OpenTelemetry in the Go services. Responsible: squad Notifications starts with crypto-service." — Conversation with decision and action. It is a discussion. + +### This is NOT a discussion + +1. "Architecture document for orders-api describing the hexagonal flow." — Technical documentation. It is not a discussion. + +2. "Release notes v2.3.0 for billing-api: added PartnerPay support." — Changelog for an actor. It is not a discussion. diff --git a/plugins/bedrock/entities/fleeting.md b/plugins/bedrock/entities/fleeting.md new file mode 100644 index 0000000..2240f00 --- /dev/null +++ b/plugins/bedrock/entities/fleeting.md @@ -0,0 +1,116 @@ +# Entity: Fleeting + +> Source of truth for required fields: `fleeting/_template.md` + +## What it is + +A **fleeting note** is a capture of raw information — ideas, emerging concepts, vague mentions, fragments without full context. Fleeting notes are the vault's inbox: they receive content that has not yet reached the threshold of a permanent or bridge note. + +Fleeting notes are **temporary by design**. They should be promoted (to permanent or bridge) or archived within a reasonable period. They are not garbage — they are information in the process of maturing. + +## Zettelkasten Role + +**Classification:** fleeting note +**Purpose in the graph:** Capture information in formation that is not yet consolidated enough to be a permanent or bridge note. + +### Linking Rules + +**Structural links (frontmatter):** `source` (wikilink to the source it came from, or `"session"` if captured directly), `promoted_to` (wikilink to the destination note when promoted). +**Semantic links (body):** Links in the body are exploratory — they may reference existing permanents or bridges that seem related, but without the obligation of full textual context. Fleeting notes are drafts; the semantic linking requirement applies when they are promoted. +**Relationship with other roles:** Fleeting notes reference existing permanents and bridges as connection "clues". When promoted, the content migrates to a permanent (actor, person, team) or bridge (topic, discussion) following the linking rules of that type. + +### Completeness Criteria + +Fleeting notes **do not need** to be complete — that is the point. They exist precisely to capture incomplete information. The relevant criterion is **promotion** (see below). + +## When to create + +- Content ingested by `/teach` contains ideas, mentions, or fragments that do not meet the completeness criteria of any permanent or bridge type +- The content mentions something potentially useful but without enough data to create an entity (e.g., "someone mentioned a new tokenization service" — no name, repo, or team) +- The content captures a hypothesis, suggestion, or draft idea that needs refinement +- `/preserve` receives content that does not pass the completeness criteria of any type + +## When NOT to create + +- The content already has enough data to create a permanent or bridge entity — create directly in the correct type +- The content is irrelevant to the vault (noise, casual conversation, off-topic information) +- The content is a duplicate of something already captured in another fleeting note or existing entity + +## How to distinguish from other types + +| Looks like... | But is... | Key difference | +|---|---|---| +| Fleeting | Actor | If it has a repo, deployment, and team — it is an actor. If only "someone mentioned a new service" without details, it is fleeting | +| Fleeting | Person | If it has a full name and team — it is a person. If only "an engineer from payments", it is fleeting | +| Fleeting | Topic | If it has a clear objective and affected actors — it is a topic. If it is "maybe we need to deprecate X", it is fleeting | +| Fleeting | Discussion | If it has a date, participants, and decisions — it is a discussion. If it is "it seems there was a meeting about Y", it is fleeting | + +## Promotion Criteria + +A fleeting note should be promoted to permanent or bridge when **any** of the 3 criteria is met: + +### 1. Critical mass + +The fleeting note accumulates enough information to be self-contained: +- More than 3 paragraphs with verifiable sources +- Concrete data (names, dates, numbers, repositories) +- Sufficient context to meet the completeness criteria of the destination type + +### 2. Corroboration + +The information in the fleeting note is confirmed or supplemented by an existing permanent note: +- A new ingestion via `/teach` brings data that validates or expands the fleeting +- An existing permanent is updated with information that confirms the fleeting's content +- Two or more fleeting notes about the same subject can be consolidated into a permanent note + +### 3. Active relevance + +`/bedrock` references the fleeting note in response to a query, signaling that the information is useful: +- The fleeting's content is cited as an answer to a user question +- The fleeting contributes to the understanding of an active subject in the vault +- In this case, `/bedrock` signals the promotion opportunity + +## Promotion Pipeline + +1. **Detection** — `/preserve`, `/teach`, or `/bedrock` identifies that a promotion criterion has been met +2. **Signaling** — The skill signals with a callout: `> [!info] Suggested promotion: this note can be promoted to ` +3. **Promotion** — `/preserve` is invoked to create the destination entity, migrating the relevant content +4. **Update** — The fleeting note receives `status: promoted` and `promoted_to: [[destination-note]]` + +## Required fields (frontmatter) + +| Field | Type | Description | +|---|---|---| +| `type` | string | Always `"fleeting"` | +| `title` | string | Short descriptive title | +| `source` | wikilink/string | `"[[source-name]]"` or `"session"` | +| `captured_at` | date | YYYY-MM-DD of capture | +| `status` | string | `raw`, `reviewing`, `promoted`, `archived` | +| `promoted_to` | wikilink/string | `"[[destination-note]]"` or `""` (empty until promotion) | +| `updated_at` | date | YYYY-MM-DD | +| `updated_by` | string | Who updated it | + +## Possible statuses + +| Status | Description | +|---|---| +| `raw` | Newly captured, not yet reviewed | +| `reviewing` | Under analysis — someone or some skill is evaluating for promotion | +| `promoted` | Promoted — content migrated to a permanent/bridge note (see `promoted_to`) | +| `archived` | Archived — content was not relevant or became obsolete | + +## Examples + +### This IS a fleeting note + +1. "Someone mentioned a new tokenization service that will replace the legacy-gateway, but I don't know the name or the repo." — Useful but incomplete information. It is fleeting until it has concrete data. + +2. "It seems the Notifications squad is thinking about migrating crypto-service to Rust. Needs confirmation." — Unconfirmed hypothesis. It is fleeting until validated. + +3. "In some meeting they talked about changing the SMS provider for notifications. I don't know when or who was there." — Fragment without date, participants, or decision. It is fleeting. + +### This is NOT a fleeting note + +1. "The health-checker is a Go service that replaces MonitorAPI. Repo: acme-corp/health-checker. Squad Orders is responsible." — Enough concrete data to be an actor. + +2. "Planning meeting (04/01/2026): Alice, Bob, Carol. Decision: prioritize legacy-gateway migration." — Complete data for a discussion. diff --git a/plugins/bedrock/entities/person.md b/plugins/bedrock/entities/person.md new file mode 100644 index 0000000..8c585b5 --- /dev/null +++ b/plugins/bedrock/entities/person.md @@ -0,0 +1,94 @@ +# Entity: Person + +> Source of truth for required fields: `people/_template.md` + +## What it is + +A **person** is any internal collaborator of the organization who is identifiable and relevant to the vault's context — engineer, tech lead, manager, product manager, engineering manager, designer, DRI, or any other professional who participates in decisions, operations, or technical contributions. + +The primary identification is the **corporate email prefix** (e.g., `alice.smith@company.com` -> filename `alice-smith.md`), which ensures idempotency and universality — every collaborator has a corporate email, regardless of whether they have a GitHub account. + +Persons are connected to teams and actors via wikilinks. The vault tracks people to understand: who works on what, who is the focal point for which system, who participated in which decisions, and what each person's role is in the organization. + +## When to create + +- The content mentions a person by full name AND it is possible to associate them with a team or actor in the vault +- The content identifies an active contributor (commits, PRs, code reviews) in repositories of known actors +- The content names a DRI, focal point, or person responsible for an action/decision +- The content mentions a professional (PM, EM, designer, etc.) who actively participates in a team or project in the vault, even without direct code contribution + +## When NOT to create + +- It is a generic mention without a full name (e.g., "the notifications team folks", "someone from infra") — that is a reference to a team, not a person +- It is an end user or customer (e.g., "the merchant reported a bug") — persons are internal collaborators +- It is an external stakeholder without direct participation in the organization (e.g., "the PCI auditor") +- It is a person mentioned only once without team/actor context — probably not relevant to the vault + +## How to distinguish from other types + +| Looks like... | But is... | Key difference | +|---|---|---| +| Person | Team | If the content says "the payments folks", it is a reference to the team. A person is an individual with a name | +| Person | Actor | Names like `ralph` can be either a person or a repo. If it has a corporate email and participates in a team, it is a person. If it deploys, it is an actor | +| Person | Discussion participant | If the person only appears as a participant in a meeting, they can be created as a person AND referenced in the discussion | + +## Required fields (frontmatter) + +| Field | Type | Description | +|---|---|---| +| `type` | string | Always `"person"` | +| `name` | string | Person's full name | +| `role` | string | Job title/function | +| `team` | wikilink | `"[[squad-name]]"` | +| `focal_points` | array | Wikilinks to actors: `["[[repo-name]]"]` | +| `updated_at` | date | YYYY-MM-DD | +| `updated_by` | string | Who updated it | + +## Optional fields (frontmatter) + +| Field | Type | Description | +|---|---|---| +| `email` | string | Full corporate email (e.g., `alice.smith@company.com`) | +| `github` | string | GitHub login (when applicable) | +| `slack` | string | Slack handle (e.g., `@alice.smith`) | +| `jira` | string | Jira username | + +## Filename convention + +A person's filename is derived from the **corporate email prefix**, normalized: +- Dots become hyphens: `alice.smith` -> `alice-smith` +- Lowercase, no accents +- Example: `alice.smith@company.com` -> `alice-smith.md` + +When the email is not known, use `first-last.md` based on the full name (normalized). + +## Zettelkasten Role + +**Classification:** permanent note +**Purpose in the graph:** Represent consolidated facts about internal collaborators — who they are, where they work, and which systems they contribute to. + +### Linking Rules + +**Structural links (frontmatter):** `team` (wikilink to squad), `focal_points` (wikilinks to actors). These define the organizational position — which team and which systems the person works on. +**Semantic links (body):** Wikilinks in the body must have textual context. E.g., "leads the migration from [[legacy-gateway]] to [[billing-api]]" instead of just "[[legacy-gateway]]". Body links explain contributions, responsibilities, and involvement in decisions. +**Relationship with other roles:** Persons are referenced by bridge notes (topics, discussions) that record participation in events and subjects. Do not duplicate in the person the history of discussions — the person describes who they are, the topic/discussion describes what happened. + +### Completeness Criteria + +A person is complete when: they have a full name, assigned team, and at least 1 focal point or defined role. If only the name is mentioned without team or role context, the content should go to `fleeting/` until consolidated. + +## Examples + +### This IS a person + +1. "Bob Jones is an engineer on squad Payments and works primarily on billing-api." — Individual with name, team, and focal actor. It is a person. + +2. "PR #142 on notification-service was opened by `davewilson` (Dave Wilson)." — Identifiable contributor with GitHub login and actor. It is a person. + +3. "Eve Martin is a Product Manager on squad Orders and leads the V2 migration project." — Professional with name, team, and project. It is a person, even without commits. + +### This is NOT a person + +1. "The notifications team will handle the migration." — Reference to a team, not an individual. Do not create a person. + +2. "A merchant reported a timeout on card charges." — End user, not an internal collaborator. Do not create a person. diff --git a/plugins/bedrock/entities/project.md b/plugins/bedrock/entities/project.md new file mode 100644 index 0000000..89870da --- /dev/null +++ b/plugins/bedrock/entities/project.md @@ -0,0 +1,77 @@ +# Entity: Project + +> Source of truth for required fields: `projects/_template.md` + +## What it is + +A **project** is an initiative with a closed scope, a deadline (real or estimated), concrete deliverables, and responsible focal points. Projects aggregate multiple actors, people, and topics under a common objective. They are the "hub" of an initiative in the Second Brain. + +Projects have status (planning → active → blocked → completed), trackable progress, and explicit blockers. They differ from topics by being more concrete and delivery-oriented. + +## When to create + +- The content describes an initiative with an objective, scope, and at least 1 responsible person (focal point) +- The content mentions a migration, rewrite, or construction of a new system with a timeline +- The content defines deliverables and milestones of a cross-team effort + +## When NOT to create + +- It is a subject/theme without a deadline or concrete deliverables — that is a topic +- It is a conversation/meeting — that is a discussion (which may reference a project) +- It is an isolated task by 1 person on 1 actor — that is operational work, not a project +- It is the ongoing operation of a system — that is the actor itself + +## How to distinguish from other types + +| Looks like... | But is... | Key difference | +|---|---|---| +| Project | Topic | A topic is an open subject (e.g., "deprecation of legacy services"). A project is a closed initiative (e.g., "migrate legacy-gateway to billing-api by Q3"). A topic can exist without a deadline; a project always has one (even if estimated) | +| Project | Actor | If the end result is a new system, it starts as a project and becomes an actor once it has a repo and deploy. E.g., "project to create health-checker" → later becomes actor `health-checker` | +| Project | Discussion | A discussion is a one-time event. A project is an ongoing effort with progress. A discussion can create or update a project | + +## Required fields (frontmatter) + +| Field | Type | Description | +|---|---|---| +| `type` | string | Always `"project"` | +| `name` | string | Project name | +| `description` | string | Description | +| `status` | string | `planning`, `active`, `blocked`, `completed` | +| `deadline` | string | Deadline date or empty | +| `progress` | string | Description of current progress | +| `blockers` | array | List of blockers | +| `focal_points` | array | Wikilinks to persons: `["[[first-last]]"]` | +| `related_topics` | array | Wikilinks to topics | +| `related_actors` | array | Wikilinks to actors | +| `related_teams` | array | Wikilinks to teams | +| `updated_at` | date | YYYY-MM-DD | +| `updated_by` | string | Who last updated | + +## Zettelkasten Role + +**Classification:** index note +**Purpose in the graph:** Organize reading paths — aggregate bridges (topics, discussions) and permanents (actors, people, teams) under a common objective, functioning as a thematic Map of Content (MOC). + +### Linking Rules + +**Structural links (frontmatter):** `focal_points` (wikilinks to persons), `related_topics`, `related_actors`, `related_teams` (wikilinks). Define which entities compose this initiative. +**Semantic links (body):** Links in the body should point to where the knowledge lives, without repeating content. E.g., "the migration progress is documented in [[2026-06-deprecation-legacy-gateway]]" instead of replicating the history here. The project body is curation — it directs the reader to the right notes. +**Relationship with other roles:** Projects do not contain their own knowledge — they point to bridges (topics that detail the subjects) and permanents (actors and people involved). If a project is explaining something in detail, that detail should be in a topic. + +### Completeness Criteria + +A project is complete when: it has an objective, at least 1 focal point, and references to related topics or actors. If it is just an idea for an initiative without a responsible person or concrete scope, the content should go to `fleeting/` until it is defined. + +## Examples + +### This IS a project + +1. "Migration from legacy-gateway to billing-api: deadline Q3/2026. Responsible: Bob. Blocker: legacy system clients that still use the legacy-gateway." — Initiative with deadline, responsible person, blocker. It is a project. + +2. "V2 Orders API: we are building the new orders API. Squad Orders leads, go-live forecast in May. Involves orders-api, billing engine, integration engine." — Construction effort with timeline and multiple actors. It is a project. + +### This is NOT a project + +1. "We need to improve observability of the Go services." — Open subject without a deadline or concrete deliverable. It is a topic. + +2. "Fix the timeout bug in notification-service by Friday." — One-off task by 1 person. It is not a project. diff --git a/plugins/bedrock/entities/sources-field.md b/plugins/bedrock/entities/sources-field.md new file mode 100644 index 0000000..b29cc24 --- /dev/null +++ b/plugins/bedrock/entities/sources-field.md @@ -0,0 +1,94 @@ +# Field: sources (provenance) + +> Documentation for the `sources` field in entity frontmatter. + +## What it is + +The `sources` field is an array in the frontmatter of any entity that records where the information came from — a Confluence page, a Google Doc, a CSV, a GitHub repository. It is NOT an entity type; it is traceability metadata embedded in the entity itself. + +The field enables re-ingestion: by recording the URL/path and the date of the last sync, `/sync` knows which sources are outdated and can be re-queried. + +## Schema + +```yaml +sources: + - url: "https://mycompany.atlassian.net/wiki/spaces/PROC/pages/..." + type: "confluence" + synced_at: 2026-04-09 + - url: "https://github.com/acme-corp/billing-api" + type: "github-repo" + synced_at: 2026-04-10 +``` + +| Field | Type | Required | Description | +|---|---|---|---| +| `url` | string | yes | URL or local path of the source | +| `type` | string | yes | `confluence`, `gdoc`, `github-repo`, `csv`, `markdown`, `manual` | +| `synced_at` | date | yes | YYYY-MM-DD of the last sync | + +## Merge rules + +- **Append-only:** new sources are added, never removed +- **Dedup by URL:** if the URL already exists in the list, update `synced_at` (do not duplicate the entry) +- **Order:** most recent first (by `synced_at`) + +## When to populate + +- An external source was ingested via `/teach` and the content created or updated this entity +- `/sync` re-synchronized a source and updated `synced_at` +- The user wants to manually record the provenance of an entity + +## When NOT to populate + +- The content was typed directly by the user without reference to an external document — there is no source to record +- The source is the agent's session memory — that is implicit, no record needed +- The source is a GitHub commit or PR — that is already tracked by git history + +## How /sync uses this field + +1. Scans all vault entities extracting the `sources` field +2. Deduplicates by URL (a URL may appear in multiple entities) +3. For each unique URL with a syncable type (`confluence`, `gdoc`, `github-repo`, `markdown`): + - Re-fetches the updated content + - Compares with existing entities (incremental diff) + - Updates `synced_at` in all entities that reference that URL + +## Relationship with the `source` field (singular) + +Discussions and fleeting notes have a `source` field (singular string) that indicates the capture context: `session`, `meeting-notes`, `teach`, `manual`. This field has different semantics: + +| Field | Type | Semantics | Example | +|---|---|---|---| +| `source` (singular) | string | How the entity was captured | `"meeting-notes"` | +| `sources` (plural) | array | Where the external data came from | `[{url: "...", type: "confluence", synced_at: "..."}]` | + +Both can coexist in the same entity. They are independent. + +## Examples + +### Entity with a single source + +```yaml +sources: + - url: "https://mycompany.atlassian.net/wiki/spaces/PROC/pages/8675099062" + type: "confluence" + synced_at: 2026-04-09 +``` + +### Entity with multiple sources + +```yaml +sources: + - url: "https://github.com/acme-corp/billing-api" + type: "github-repo" + synced_at: 2026-04-10 + - url: "https://mycompany.atlassian.net/wiki/spaces/PROC/pages/123" + type: "confluence" + synced_at: 2026-04-05 +``` + +### Entity without an external source + +```yaml +sources: [] +``` diff --git a/plugins/bedrock/entities/team.md b/plugins/bedrock/entities/team.md new file mode 100644 index 0000000..28d7e98 --- /dev/null +++ b/plugins/bedrock/entities/team.md @@ -0,0 +1,72 @@ +# Entity: Team + +> Source of truth for required fields: `teams/_template.md` + +## What it is + +A **team** is a squad with a defined organizational scope, identifiable members, and ownership over a set of actors. Teams are the organizational unit of the Second Brain — they represent the real squad structure of the organization. + +Teams have a domain scope (e.g., "card transaction lifecycle"), listed members, and a set of actors under their responsibility. + +## When to create + +- The content references a squad/team with a formal name and at least 1 identifiable member or 1 actor under ownership +- The content describes the creation of a new squad with a defined scope +- The content lists members and responsibilities of an uncatalogued team + +## When NOT to create + +- It is an ad-hoc group assembled for a specific project (e.g., "migration task force") — that is a project with focal_points, not a team +- It is a reference to a Slack channel or communication group (e.g., "#payments-alerts") +- It is a generic mention ("the backend folks", "the infra crew") — without a formal scope, it is not a team +- It is a reference to a non-technical team without system ownership (e.g., "legal compliance team", "HR team") — the vault covers technology teams + +## How to distinguish from other types + +| Looks like... | But is... | Key difference | +|---|---|---| +| Team | Project | If it is a temporary group with a deadline and deliverables, it is a project. A team is permanent with ongoing ownership | +| Team | Person (plural) | If the content says "Carol and Bob from payments", those are persons referencing the team. The team already exists | +| Team | Actor | If the content says "payments", it could be the team (squad-payments) or a specific actor. Context defines: if talking about people/ownership → team; if talking about deploy/code → actor | + +## Required fields (frontmatter) + +| Field | Type | Description | +|---|---|---| +| `type` | string | Always `"team"` | +| `name` | string | Squad name (e.g., "Squad Payments") | +| `scope` | string | Area of responsibility | +| `purpose` | string | Team purpose/mission | +| `members` | array | Wikilinks to persons: `["[[first-last]]"]` | +| `actors` | array | Wikilinks to actors: `["[[repo-name]]"]` | +| `updated_at` | date | YYYY-MM-DD | +| `updated_by` | string | Who last updated | + +## Zettelkasten Role + +**Classification:** permanent note +**Purpose in the graph:** Represent consolidated facts about squads — organizational scope, members, and system ownership. + +### Linking Rules + +**Structural links (frontmatter):** `members` (wikilinks to persons), `actors` (wikilinks to actors). Define the team composition — who works there and which systems it operates. +**Semantic links (body):** Wikilinks in the body should have textual context. E.g., "responsible for the operation and evolution of [[billing-api]] and [[rate-limiter]]" instead of listing loose links. Links in the body explain the team's relationship with its systems and with other teams. +**Relationship with other roles:** Teams are referenced by bridge notes (topics, discussions) and index notes (projects). Do not duplicate project history in the team — the team describes the organizational structure, the project describes the initiative. + +### Completeness Criteria + +A team is complete when: it has a formal name, a defined scope, at least 1 listed member, and at least 1 actor under ownership. If only the squad name is mentioned without members or actors, the content should go to `fleeting/` until it is consolidated. + +## Examples + +### This IS a team + +1. "Squad Notifications is responsible for notification-service, retry-consumer, and crypto-service. It has 5 engineers." — Formal squad with actors and members. It is a team. + +2. "We are creating Squad Orders to manage the order lifecycle. Bob will lead." — New squad with a defined scope. It is a team. + +### This is NOT a team + +1. "We assembled a group with people from payments and notifications to resolve the incident." — Ad-hoc/temporary group. It could be a discussion or topic, not a team. + +2. "The HR team adjusted the meal voucher benefit." — Non-technical team without system ownership. Outside the vault's scope. diff --git a/plugins/bedrock/entities/topic.md b/plugins/bedrock/entities/topic.md new file mode 100644 index 0000000..b6bc2ee --- /dev/null +++ b/plugins/bedrock/entities/topic.md @@ -0,0 +1,75 @@ +# Entity: Topic + +> Source of truth for required fields: `topics/_template.md` + +## What it is + +A **topic** is a cross-cutting subject with its own lifecycle (open → in-progress → completed/cancelled). Topics represent initiatives, incidents, RFCs, deprecations, or any theme that evolves over time and affects multiple actors or people. They are the "tracker" of subjects in the Second Brain. + +Topics have status, event history, decisions made, and next steps. They are the place where you record **what is happening** with a subject over time. + +## When to create + +- The content describes a cross-cutting initiative that affects more than 1 actor (e.g., observability migration, service deprecation) +- The content reports an incident or systemic problem with cross-team impact +- The content proposes an RFC or architectural change that needs tracking +- The content describes a system deprecation process + +## When NOT to create + +- It is a one-off task without temporal evolution (e.g., "fix bug X in PR #123") — that is operational work, not a topic +- It is an isolated bug in a single actor without cross-team impact — record it as a known_issue in the actor +- It is a feature request without cross-cutting impact — it could be an item in a project, not a topic +- It is a conversation/meeting — that is a discussion. Topics are subjects; discussions are events + +## How to distinguish from other types + +| Looks like... | But is... | Key difference | +|---|---|---| +| Topic | Discussion | A discussion is a one-time event (meeting, conversation) with a fixed date. A topic is a subject that evolves over time with status and history | +| Topic | Project | A project has a deadline, deliverables, and focal points. A topic is more open — it may not have a defined deadline. E.g., "deprecation of legacy-gateway" is a topic; "migration to orders-api v2" is a project | +| Topic | Actor (known_issue) | If the problem affects only 1 actor and is technical, it goes as a known_issue in the actor. If it affects multiple actors or has organizational impact, it is a topic | + +## Required fields (frontmatter) + +| Field | Type | Description | +|---|---|---| +| `type` | string | Always `"topic"` | +| `title` | string | Descriptive title of the subject | +| `category` | string | `bugfix`, `troubleshooting`, `rfc`, `incident`, `feature`, `deprecation`, `compliance` | +| `status` | string | `open`, `in-progress`, `completed`, `cancelled` | +| `people` | array | Wikilinks to persons: `["[[first-last]]"]` | +| `actors` | array | Wikilinks to actors: `["[[repo-name]]"]` | +| `objective` | string | Topic objective | +| `created_at` | date | YYYY-MM-DD | +| `updated_at` | date | YYYY-MM-DD | +| `updated_by` | string | Who last updated | + +## Zettelkasten Role + +**Classification:** bridge note +**Purpose in the graph:** Connect permanent notes (actors, people, teams) explaining *why* they relate in the context of a subject that evolves over time. + +### Linking Rules + +**Structural links (frontmatter):** `people` (wikilinks to involved persons), `actors` (wikilinks to affected actors). Define which permanents this subject connects. +**Semantic links (body):** Links in the body are the central point of a topic — they should explain the relationship between permanents with rich context. E.g., "the deprecation of [[legacy-gateway]] is blocked because legacy system clients still depend on the tokenization provided by [[billing-api]]" instead of just "[[legacy-gateway]] and [[billing-api]]". The topic body is where the explanation of the connection between permanents lives. +**Relationship with other roles:** Topics are the connective tissue between permanents. If two actors relate, the explanation lives here — not duplicated in both actors. Topics are referenced by index notes (projects) that organize multiple subjects under an objective. + +### Completeness Criteria + +A topic is complete when: it has a defined objective, at least 1 actor or person referenced with context, and an updated status. If the subject is vague, without concrete actors or a clear objective, the content should go to `fleeting/` until it matures. + +## Examples + +### This IS a topic + +1. "We are migrating all Go services from dd-trace to OpenTelemetry. Affects notification-service, crypto-service, orders-api, and metrics-collector." — Cross-cutting initiative with multiple actors. It is a topic (category: `rfc`). + +2. "The deprecation of legacy-gateway is blocked by the migration of legacy system clients. Status: in progress since March." — Subject with lifecycle and status. It is a topic (category: `deprecation`). + +### This is NOT a topic + +1. "I need to fix the timeout on the /create endpoint of notification-service." — One-off bug in 1 actor. Goes as a known_issue in the actor, not as a topic. + +2. "We had a meeting about the deprecation plan on Monday." — That is a discussion (event). The deprecation plan itself may be a topic. diff --git a/plugins/bedrock/skills/ask/SKILL.md b/plugins/bedrock/skills/ask/SKILL.md new file mode 100644 index 0000000..efd8981 --- /dev/null +++ b/plugins/bedrock/skills/ask/SKILL.md @@ -0,0 +1,532 @@ +--- +name: ask +description: > + Adaptive vault reader skill. Receives a natural language question, + searches the vault first (Glob/Grep, entity reads, wikilink traversal), + then self-assesses whether more context is needed. Escalates to /graphify + for graph-level understanding or to /bedrock:teach for remote content ingestion + only when the vault alone is insufficient. Answers simple questions with zero + graphify calls. + Use when: "bedrock ask", "bedrock-ask", "/bedrock:ask", any question about the vault, + "what do we know about", "who owns", "what's the status of", "tell me about", + "how does it work", or any Second Brain query. +user_invocable: true +allowed-tools: Bash, Read, Glob, Grep, Skill, Agent +--- + +# /bedrock:ask — Adaptive Vault Reader + +## Plugin Paths + +Entity definitions and templates are in the plugin directory, not at the vault root. +Use the "Base directory for this skill" provided at invocation to resolve the paths: + +- Entity definitions: `/../../entities/` +- Templates: `/../../templates/{type}/_template.md` +- Plugin CLAUDE.md: `/../../CLAUDE.md` (already automatically injected into context) + +Where `` is the path provided in "Base directory for this skill". + +--- + +## Vault Resolution + +Resolve which vault to query. This skill can be invoked from any directory. + +**Step 1 — Parse `--vault` flag:** +Check if the input arguments include `--vault `. If found, extract the vault name and remove it from the arguments (the remaining text is the question). + +**Step 2 — Resolve vault path:** + +1. **If `--vault ` was provided:** + Read the vault registry at `/../../vaults.json`. Find the entry matching the name. + If not found: error — "Vault `` 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 `/../../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 ` to specify, or run `/bedrock:setup` to register a vault." + +**Step 3 — Validate vault path:** +```bash +test -d "" && echo "exists" || echo "missing" +``` +If missing: error — "Vault path `` does not exist on disk. Run `/bedrock:setup` to re-register." + +**Step 4 — Read vault config:** +```bash +cat /.bedrock/config.json 2>/dev/null +``` +Extract `language` and other relevant fields for use in later phases. + +**From this point forward, ALL vault file operations use `` as the root.** +- Entity directories: `/actors/`, `/people/`, etc. +- Graphify output: `/graphify-out/` + +--- + +## Overview + +This skill receives a natural language question and answers it using an adaptive, +vault-first approach. It always reads vault content first, then decides whether +to escalate to graphify or /teach based on what's actually needed — not what the +question looks like in isolation. + +**You are an adaptive context orchestrator agent. You only READ — never write, edit, or delete files directly.** + +Writes happen exclusively through `/bedrock:teach` delegation (which flows through `/bedrock:preserve`). +If the query reveals outdated or missing information and no remote source is available to ingest, +suggest that the user run `/bedrock:preserve` or `/bedrock:teach` to update the vault. + +--- + +## Phase 0 — Read Configuration + +### 0.1 Load config + +Read `.bedrock/config.json` from the vault root: + +```bash +if [ -f ".bedrock/config.json" ]; then + cat .bedrock/config.json +else + echo "config_not_found" +fi +``` + +- **If config exists:** extract the value of `query.max_graphify_calls`. Store as `max_graphify_calls`. +- **If config does not exist or field is absent:** set `max_graphify_calls = 3` (default). +- **Valid range:** 1–5. If the value is outside this range, clamp to the nearest bound and log a warning. + +--- + +## Phase 1 — Analyze the Question + +### 1.1 Classify the question + +Read the user's question and identify: + +1. **Mentioned entities** — names of systems, people, teams, topics, projects, or discussions. + They may appear as: + - Exact filename (e.g.: "billing-api", "squad-payments") + - Human-readable name (e.g.: "Billing API", "Squad Payments") + - Alias or acronym (e.g.: "BillingAPI", "BRB") + - Contextual reference (e.g.: "the billing service", "the notifications team") + +2. **Relevant domain(s)** — `payments`, `notifications`, `orders`, `integrations`, `checkout`, `compliance`, `internal-tools`. + Infer from the mentioned entities or the question context. + +3. **Type of information sought:** + - **Status/overview** — "what is X?", "what's the status of X?" + - **Architecture/stack** — "how does X work?", "what's the stack of X?" + - **People/teams** — "who owns X?", "who works with Y?" + - **History/decisions** — "what was decided about X?", "what happened with Y?" + - **Relationships** — "what depends on X?", "how does Y relate to Z?" + - **Deprecation** — "what is being deprecated?", "what's the deprecation plan for X?" + +### 1.2 Assess clarity + +If the question is too ambiguous to produce a targeted search (e.g.: "tell me everything", +"how does the system work?", "what's going on?"), ask for clarification: + +> "Your question is broad. Can you specify: which system, team, or topic would you like to know more about?" + +If the question mentions something that clearly isn't part of the vault (e.g.: something personal, +unrelated technology), inform: "I didn't find anything in the vault about this." + +### 1.3 Phase 1 classification result + +At the end, you should have: +- **search_terms**: list of names, aliases, and keywords to search for +- **domains**: list of relevant domains (may be empty if not identified) +- **info_type**: classification of the type of information sought +- **explicit_entities**: entities mentioned directly by name (if any) + +--- + +## Phase 2 — Vault-First Search + +This phase **always runs** for every question, regardless of graph availability. +It is the foundation of the adaptive approach — read vault content first, decide later. + +### 2.1 Read entity definitions + +Use Read to read the entity definition files from the plugin (see "Plugin Paths" section): +- If the question is about a system → read `/../../entities/actor.md` +- If the question is about a person → read `/../../entities/person.md` +- If the question is about a team → read `/../../entities/team.md` +- If the question is about a topic/deprecation → read `/../../entities/topic.md` +- If the question is about a meeting/decision → read `/../../entities/discussion.md` +- If the question is about a project/initiative → read `/../../entities/project.md` +- If you don't know the type → read all entity definitions from the plugin to classify correctly + +### 2.2 Search entities by name and alias + +For each search term identified in Phase 1: + +**Step 1 — Search by filename:** +``` +Glob: /actors/*.md, /people/*.md, /teams/*.md, + /topics/**.md, /discussions/**.md, /projects/*.md, + /fleeting/**.md +``` + +**Step 2 — Search by alias in frontmatter:** +``` +Grep: pattern="aliases:.*" in directories: /actors/, /people/, /teams/, + /topics/, /discussions/, /projects/ + (case-insensitive) +``` + +**Step 3 — Search by name in frontmatter:** +``` +Grep: pattern="name:.*" or pattern="title:.*" + in the same directories (case-insensitive) +``` + +**Step 4 — Search by content (fallback):** +If steps 1-3 did not return sufficient results: +``` +Grep: pattern="" in entity directories (case-insensitive) +``` + +### 2.3 Filter by domain + +If domains were identified in Phase 1, filter results: +``` +Grep: pattern="domain/" in the found files (tags field of frontmatter) +``` + +Keep all results, but prioritize those matching the domain. + +### 2.4 Read found entities + +For each entity found (limit: 15 entities): + +1. Read the frontmatter first (~first 30 lines) to confirm relevance +2. If relevant: read the full file +3. If not relevant (false positive from Grep): discard + +Record for each entity read: +- filename, type, name +- wikilinks found in frontmatter and body +- external URLs found in the content (Confluence, Google Docs, GitHub) +- Explicit date in the filename (if any) + +### 2.5 Follow wikilinks (1 level of depth) + +For each extracted wikilink that is relevant to the question: + +1. Resolve the file: search for `.md` in entity directories + ``` + Glob: /actors/.md, /people/.md, /teams/.md, + /topics/**.md, /discussions/**.md, /projects/.md + ``` + +2. Read the found file (frontmatter + body) + +3. **Do NOT follow wikilinks from this second level** — stop here to avoid context explosion + +**Relevance criteria for following a wikilink:** +- The question is about relationships ("who owns", "what depends on") → follow all +- The question is about status/overview → follow team, people (focal points) +- The question is about history → follow related discussions, topics +- The question is about architecture → follow dependent actors + +**Limit:** Do not read more than 15 entities total (2.4 + 2.5 combined). +If the limit is reached, prioritize entities directly mentioned in the question. + +### 2.6 Phase 2 output + +At the end of Phase 2, you have: +- A set of vault entities with their full content +- Wikilinks between them (structural relationships) +- External URLs found in their content (Confluence, GDocs, GitHub) +- A sense of whether the vault content covers the question + +--- + +## Phase 3 — Context Assessment + Conditional Escalation + +This is the core decision point. After reading vault content in Phase 2, +assess whether you have enough context to answer the question. + +### 3.1 Self-Assessment + +Evaluate the vault content you read in Phase 2 against the original question. +Determine one of three outcomes: + +**`vault_sufficient`** — You have enough information to compose a good answer. +Indicators: +- The question is factual/status/ownership and the vault entities contain a clear answer +- Examples: "who owns X", "what's the status of Y", "what team manages Z", "what is X" +- The entities read in Phase 2 directly address the question +- No significant gaps in the information + +**`needs_graphify`** — The vault content is partial but the knowledge graph could fill the gaps. +Indicators: +- The question involves code-level relationships, cross-domain dependencies, or architectural paths +- The vault entities reference systems whose connections aren't explicit in the markdown +- You feel you're missing structural context that the knowledge graph could provide +- Examples: "how does X connect to Y at the code level", "what are the dependencies of X", "trace the data flow from A to B" + +**`needs_remote_content`** — The vault entities reference external URLs that appear directly relevant +to the question, but the content behind those URLs isn't ingested in the vault. +Indicators: +- An entity's `sources` field or body text contains a URL (Confluence, GDocs, GitHub) that likely holds the answer +- The question asks about something documented externally (e.g., "what's the runbook for X" and the entity links to a Confluence page) +- The vault has a pointer to the answer but not the answer itself + +**Priority when multiple outcomes apply:** +`needs_remote_content` > `needs_graphify` > `vault_sufficient` + +Rationale: remote content must be internalized first for the vault to be complete. +Graphify can run on richer data after ingestion. If both apply, handle remote content first, +then re-assess whether graphify is still needed. + +**After determining the outcome:** +- `vault_sufficient` → skip directly to **Phase 4** (recency) then **Phase 5** (respond) +- `needs_graphify` → proceed to **Phase 3-G** +- `needs_remote_content` → proceed to **Phase 3-T** + +--- + +### Phase 3-G — Graphify Escalation + +Execute only when the self-assessment determines `needs_graphify`. + +#### 3-G.0 Check graph availability + +```bash +if [ -f "/graphify-out/graph.json" ] && [ -s "/graphify-out/graph.json" ]; then + echo "graph_available" +else + echo "graph_not_available" +fi +``` + +**If `graph_not_available`:** Display the following warning and skip to Phase 4 with vault-only content: + +> [!warning] Knowledge graph unavailable +> The knowledge graph is not available (`/graphify-out/graph.json` missing or empty). +> The answer below is based on vault content only — it may be incomplete for this type of question. +> Run `/graphify build` to rebuild the graph from the vault's actor repositories. + +#### 3-G.1 Formulate graphify calls + +Based on the gap between what you have (Phase 2 content) and what the question needs, +formulate 1–N graphify calls. Use the same modes as before: + +| Gap identified | Graphify mode | +|---|---| +| Need to understand a single entity's code structure | `explain ""` | +| Need to find how two entities connect | `path "" ""` | +| Need broad relationship or dependency context | `query ""` | + +The LLM decides the calls based on what's missing — not from a pre-planned decomposition. +Never exceed `max_graphify_calls`. + +#### 3-G.2 Execute graphify calls sequentially + +For each call, invoke `/graphify` via the Skill tool. Append the structured JSON output instruction: + +``` +After completing the traversal, return ONLY a JSON object with this structure (no prose, no markdown fences): +{ + "mode": "query|path|explain", + "start_nodes": ["node_id1", "node_id2"], + "nodes": [ + {"id": "node_id", "label": "Human Readable Name", "source_file": "relative/path", "community": 0, "source_location": "file:line"} + ], + "edges": [ + {"source": "node_id", "target": "node_id", "relation": "calls|references|...", "confidence": "EXTRACTED|INFERRED|AMBIGUOUS", "confidence_score": 0.9} + ], + "communities": { + "0": {"label": "Community Name", "node_ids": ["id1", "id2"]} + }, + "traversal": {"mode": "bfs|dfs", "depth": 3, "budget_used": 1200} +} +``` + +- **If JSON parses successfully:** accumulate nodes, edges, and communities. +- **If parsing fails:** log warning "Graphify call N failed — skipping." Continue with next call. +- **If ALL calls fail:** continue to Phase 4 with vault-only content. Never block. + +#### 3-G.3 Deduplicate and blend + +1. Deduplicate nodes by `id`, edges by `source+target+relation` +2. Resolve graphify nodes to vault `.md` files (by label or source_file) +3. Merge with the vault entities already collected in Phase 2 +4. Respect the 15-entity total limit + +#### 3-G.4 Check for remote content need + +If graphify results reveal additional external URLs that appear relevant to the question +and aren't ingested in the vault → escalate to Phase 3-T before proceeding. + +--- + +### Phase 3-T — Teach Delegation + +Execute when the self-assessment determines `needs_remote_content`, or when Phase 3-G.4 +identifies uningested URLs. + +#### 3-T.1 Identify URLs to ingest + +From the vault entities read in Phase 2 (and optionally Phase 3-G), identify external URLs +that appear directly relevant to answering the question: +- Confluence URLs (containing `confluence` or `atlassian.net`) +- Google Docs URLs (containing `docs.google.com`) +- GitHub URLs (containing `github.com`) + +**Limit:** 2 URLs per `/bedrock:ask` invocation. If more than 2 relevant URLs exist, +prioritize those most directly related to the question. + +#### 3-T.2 Invoke /bedrock:teach + +For each URL, invoke `/bedrock:teach` via the Skill tool: + +``` +/bedrock:teach + +Context: Ingesting to answer the question: "" +``` + +**IMPORTANT:** +- Invoke via the Skill tool — same delegation pattern as teach → preserve +- `/teach` handles its own flow: fetch content, extract entities, present to user for confirmation, delegate to `/preserve` +- `/ask` waits for `/teach` to complete + +#### 3-T.3 Re-read newly created entities + +After `/teach` completes successfully: +1. Search the vault for entities that were just created or updated (based on `/teach`'s output) +2. Read these new entities (frontmatter + body) +3. Add them to the working set of vault entities for response composition + +#### 3-T.4 Best-effort fallback + +If `/teach` fails or the user declines the confirmation: +- Log: "Teach delegation for did not complete. Continuing with available content." +- Continue to Phase 4 with whatever content is available +- **Never block the response** because of a failed teach delegation + +--- + +## Phase 4 — Prioritize by Recency + +### 4.1 Identify entities with explicit dates + +For discussions and topics, extract the date from the filename: +- Pattern `YYYY-MM-DD-slug.md` → full date (e.g.: `2026-04-02`) +- Pattern `YYYY-MM-slug.md` → partial date, assume day 01 (e.g.: `2026-04-01`) + +For consolidated entities (actors, people, teams, projects): +- Treat as equally up-to-date — do not apply temporal ranking +- Trust that content is up-to-date via `/bedrock:preserve` and `/bedrock:compress` + +### 4.2 Sort by recency + +When the response involves multiple dated discussions or topics: +- Sort by date descending (most recent first) +- If the question is explicitly about something recent ("what happened lately", + "latest decisions"), limit to entities from the last 30 days +- If the question is about history ("what happened with X over time"), + include all dates but present chronologically (most recent first) + +--- + +## Phase 5 — Respond to the User + +### 5.1 Compose the response + +Build the response following these rules: + +1. **Language:** Use the vault's configured language. Technical terms in English are accepted (PCI DSS, API, EKS, etc.) + +2. **Response structure:** + - Open with a direct answer to the question (1-3 sentences) + - If necessary, expand with details organized by topic + - Use headers (`##`, `###`) if the response is long (>5 paragraphs) + - Use tables when the information is comparative or inventory-like + +3. **Entity citations:** + - Cite ALL consulted entities as wikilinks: `[[entity-name]]` + - Use bare wikilinks (never `[[dir/entity-name]]`) + - Group citations at the end if there are many, or inline when natural + +4. **Escalation transparency:** + - If graphify was used, note: "I consulted the knowledge graph for deeper context." + - If /teach was invoked, note: "I ingested [source] into the vault to answer this question." + - If vault-only was sufficient, no special note needed + +5. **When nothing is found:** + - State explicitly: "I didn't find information about [X] in the vault." + - If relevant, suggest: "You can use `/bedrock:teach ` to ingest a source about this topic." + - **NEVER fabricate information.** Only respond with what was found. + +6. **Response prioritization (Zettelkasten hierarchy):** + When composing the response, apply weight by Zettelkasten role: + - **Permanent notes** (actors, people, teams) — maximum weight, consolidated information. Present as current facts. + - **Bridge notes** (topics, discussions) — high weight, contextualized information. Most recent discussions/topics first. + - **Index notes** (projects) — medium weight, organizational reference. Point to where the detail is. + - **Fleeting notes** — low weight, unconsolidated information. **ALWAYS** flag with disclaimer: + `(source: fleeting note — unconsolidated information)` + - If there is conflicting information between sources, point out the discrepancy. + +7. **Fleeting note promotion detection (criterion 3: active relevance):** + When a fleeting note is referenced in the response because it is relevant to the query: + - Check if it meets promotion criteria (see `/../../entities/fleeting.md`): + - Critical mass (>3 paragraphs with sources) + - Corroboration (confirmed by an existing permanent) + - If any criterion is met, add at the end of the response: + `> [!info] Promotion suggested: [[fleeting-note-name]] can be promoted to permanent/bridge` + - `/bedrock:ask` does NOT promote automatically — it only flags. Promotion happens when + `/bedrock:preserve` is invoked with the instruction to promote. + +### 5.2 Post-response suggestions + +When appropriate, suggest actions to the user: + +- If information is outdated: "The vault may be outdated about [X]. Consider running `/bedrock:teach ` to update." +- If the question revealed gaps: "I didn't find [Y] in the vault. If you have this information, you can use `/bedrock:preserve` to record it." +- If the question is complex and the response incomplete: "For a more complete view, you may also want to run `/bedrock:teach ` to ingest additional sources." + +--- + +## Critical Rules + +| Rule | Detail | +|---|---| +| Vault-first principle | Phase 2 ALWAYS runs before any escalation. Read vault content first, decide later. Never skip Phase 2. | +| LLM self-assessment | The decision to escalate is made by the LLM after reading vault content (Phase 3.1), not by a heuristic rule table. Use the guidance provided, but the LLM makes the final call. | +| Escalation priority | When multiple outcomes apply: `needs_remote_content` > `needs_graphify` > `vault_sufficient`. Internalize first, then analyze. | +| No direct writes | `/ask` NEVER writes, edits, or deletes files directly. All writes are delegated through `/bedrock:teach` → `/bedrock:preserve`. | +| Teach delegation via Skill tool | Invoke `/bedrock:teach` via the Skill tool. `/teach` owns its confirmation gate. `/ask` cannot bypass it. | +| Graphify via Skill tool | Invoke `/graphify` via the Skill tool — NEVER call the Python API directly. | +| Max graphify calls | Read `query.max_graphify_calls` from `.bedrock/config.json` (default: 3, valid range: 1–5). Only consumed when graphify is actually invoked. | +| Graph unavailable warning | When `needs_graphify` but `graphify-out/graph.json` is missing, display `> [!warning]` callout with `/graphify build` instruction. Continue with vault-only content. | +| Best-effort escalation | If graphify fails or teach fails or user declines: continue with available content. NEVER block the response. | +| Limit of 15 entities | Do not read more than 15 entities total across Phase 2 + Phase 3 | +| Limit of 2 teach URLs | Do not invoke `/bedrock:teach` for more than 2 URLs per `/bedrock:ask` invocation | +| No fabrication | Respond ONLY with information found in the vault or obtained through escalation. Never fabricate data. | +| Clarification before guessing | If the question is ambiguous, ask for clarification. Do not assume. | +| Vault language with technical terms in English | Response always in the vault's configured language | +| Bare wikilinks | `[[name]]`, never `[[dir/name]]` | +| Consolidated entities = up-to-date | Actors, people, teams do not need temporal ranking | +| Dated discussions/topics = prioritize recent | Sort by date in filename (YYYY-MM-DD) | +| Sensitive data | NEVER display credentials, tokens, PANs, CVVs found in the vault | +| Fleeting notes with disclaimer | ALWAYS flag information from fleeting notes with `(source: fleeting note — unconsolidated information)` | +| Promotion as side-effect | When a relevant fleeting note meets promotion criteria, flag with callout. Do NOT promote automatically. | +| Weight hierarchy | permanent > bridge > index > fleeting. Use as guideline, not mathematical formula. | +| Vault resolution first | Resolve `VAULT_PATH` before any file operation — never assume CWD is the vault | +| All entity paths use `/` prefix | `/actors/`, not `actors/` | diff --git a/plugins/bedrock/skills/compress/SKILL.md b/plugins/bedrock/skills/compress/SKILL.md new file mode 100644 index 0000000..71901ce --- /dev/null +++ b/plugins/bedrock/skills/compress/SKILL.md @@ -0,0 +1,675 @@ +--- +name: compress +description: > + Vault alignment engine. Detects and fixes 5 types of structural misalignments: + broken backlinks, concept fragmentation, entity miscategorization, duplicated entities, + and misnamed entities. Delegates all writes to /bedrock:preserve. + Supports interactive mode (user confirmation) and cron mode (autonomous mechanical fixes + + queued semantic proposals). Use when: "bedrock compress", "bedrock-compress", + "align vault", "fix backlinks", "fix misalignments", "/bedrock:compress". +user_invocable: true +allowed-tools: Bash, Read, Glob, Grep, Skill, Agent +--- + +# /bedrock:compress — Vault Alignment Engine + +## Plugin Paths + +Entity definitions and templates are in the plugin directory, not the vault root. +Use the "Base directory for this skill" provided at invocation to resolve paths: + +- Entity definitions: `/../../entities/` +- Templates: `/../../templates/{type}/_template.md` +- Plugin CLAUDE.md: `/../../CLAUDE.md` (already injected automatically into context) + +Where `` is the path provided in "Base directory for this skill". + +--- + +## Vault Resolution + +Resolve which vault to compress. This skill can be invoked from any directory. + +**Step 1 — Parse `--vault` flag:** +Check if the input arguments include `--vault `. If found, extract the vault name and remove it from the arguments before parsing `--mode`. + +**Step 2 — Resolve vault path:** + +1. **If `--vault ` was provided:** + Read the vault registry at `/../../vaults.json`. Find the entry matching the name. + If not found: error — "Vault `` is not registered. Run `/bedrock:vaults` to see available vaults." + If found: set `VAULT_PATH` to the entry's `path` value. Store the resolved vault name as `VAULT_NAME`. + +2. **If no `--vault` flag — CWD detection:** + Read `/../../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`. Store its name as `VAULT_NAME`. + +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`. Store its name as `VAULT_NAME`. + +4. **If no resolution:** + Error — "No vault resolved. Available vaults:" followed by the registry listing. + "Use `--vault ` to specify, or run `/bedrock:setup` to register a vault." + +**Step 3 — Validate vault path:** +```bash +test -d "" && echo "exists" || echo "missing" +``` +If missing: error — "Vault path `` does not exist on disk. Run `/bedrock:setup` to re-register." + +**Step 4 — Read vault config:** +```bash +cat /.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 `` as the root.** +- Entity directories: `/actors/`, `/people/`, etc. +- Git operations: `git -C ` +- When delegating to `/bedrock:preserve`, pass `--vault ` + +--- + +## Overview + +This skill scans all entities in the vault, detects 5 types of structural misalignments, +proposes fixes to the user, and delegates all writes to `/bedrock:preserve`. + +**You are an execution agent.** Follow the phases below in order, without skipping steps. + +### Execution modes + +The skill accepts an optional `--mode` argument: + +- **`interactive`** (default): all 5 capabilities prompt the user for confirmation before execution. +- **`cron`**: capabilities 1 and 4 (mechanical, deterministic) execute autonomously without confirmation. + Capabilities 2, 3, and 5 (semantic, judgment-dependent) are detected but written as a proposal to a + fleeting note for human review — they are NOT executed. + +Parse the mode from the invocation arguments. If no `--mode` is specified, default to `interactive`. + +### Five alignment capabilities + +| # | Capability | Type | Cron behavior | +|---|---|---|---| +| 1 | Broken backlinks | Mechanical | Autonomous — fix without confirmation | +| 2 | Concept match | Semantic | Queued — write proposal to fleeting note | +| 3 | Entity misalignment | Semantic | Queued — write proposal to fleeting note | +| 4 | Duplicated entities | Mechanical | Autonomous — fix without confirmation | +| 5 | Misnamed entities | Semantic | Queued — write proposal to fleeting note | + +**Critical rules:** +- **NEVER** write entity files directly — all mutations go through `/bedrock:preserve` +- **NEVER** execute semantic capabilities (2, 3, 5) without confirmation in interactive mode +- **NEVER** execute semantic capabilities (2, 3, 5) autonomously in cron mode — always queue +- **NEVER** remove existing wikilinks +- **NEVER** delete entities (compress aligns, it does not delete) +- People/Teams/Concepts/Topics: **append-only** — never delete content +- Actors: **free merge** — may edit body freely + +--- + +## Phase 0 — Sync the Vault + +Execute: +```bash +git -C pull --rebase origin main +``` + +If it fails: +- No remote: warn "No remote configured. Working locally." and proceed. +- Conflict: `git -C rebase --abort` and warn the user. Do NOT proceed without resolving. + +--- + +## Phase 1 — Scan and Detect + +Scan the entire vault and run all 5 detection algorithms. Store results for Phase 2. + +### 1.0 Load entity definitions + +Read the entity definitions from the plugin directory to understand classification criteria: +- `/../../entities/concept.md` — needed for capability 2 (concept match) +- `/../../entities/*.md` — needed for capability 3 (entity misalignment) + +Store the "When to create", "When NOT to create", and "How to distinguish" sections +from each entity definition for use in detection. + +### 1.1 Read all entities + +For each entity directory (`/actors/`, `/people/`, `/teams/`, `/concepts/`, `/topics/`, `/discussions/`, `/projects/`, `/fleeting/`): + +1. List all `.md` files, **excluding `_template.md` and `_template_node.md`** + - For actors: include both `/actors/*.md` (flat) and `/actors/*/*.md` (folder) +2. For each entity, read frontmatter + body +3. Extract: + - `type` from frontmatter + - `name` from frontmatter (or filename as fallback) + - `aliases` from frontmatter (array) + - All wikilinks `[[target]]` from body AND frontmatter arrays + - All proper nouns, service names, team names, person names mentioned in the body (for capabilities 4 and 5) + +**Optimization for large vaults:** If the vault has more than 100 entities in a type, +use subagents via Agent tool to parallelize reading by entity type. + +**Output:** `vault_data` map: `entity_name → {type, name, aliases[], wikilinks[], body_mentions[], frontmatter, body}` + +### 1.2 Capability 1 — Detect broken backlinks + +For each entity A in `vault_data`: +1. For each wikilink `[[B]]` found in A (body or frontmatter arrays): + - Skip if B does not exist as an entity file in the vault (wikilinks to non-existent entities are valid in Obsidian) + - If B exists: check if B contains a wikilink `[[A]]` (body or frontmatter arrays) + - If B does NOT link back to A: register as **broken backlink** + +**Output:** `broken_backlinks[]` — list of `{source: A, target: B, direction: "A→B exists, B→A missing"}` + +### 1.3 Capability 2 — Detect concept fragmentation + +Scan all entity bodies for recurring terms or phrases that: +1. Appear in **3+ different entities** (across any types) +2. Do NOT have a corresponding entity file in `/concepts/` (or any other entity directory) +3. Are NOT already wrapped in a wikilink `[[term]]` + +For each candidate term, evaluate against the concept entity definition (`entities/concept.md`): +- Is it **timeless and definitional**? (not temporal, not an initiative) +- Is it **actor-independent**? (not specific to one system's implementation) +- Does it match "When to create" criteria? +- Does it NOT match "When NOT to create" criteria? + +Filter out: +- Common English words and generic terms +- Terms that are already entity filenames or aliases +- Terms shorter than 2 words (unless they are well-known patterns like "CQRS", "mTLS") + +**Output:** `concept_candidates[]` — list of `{term, occurrences: [{entity, context_snippet}], meets_concept_criteria: bool}` + +### 1.4 Capability 3 — Detect entity misalignment + +For each entity in `vault_data`: +1. Read the entity's frontmatter `type` field +2. Read the corresponding entity definition from `entities/.md` +3. Evaluate the entity's content against: + - "When to create" criteria for the current type → does the entity still qualify? + - "When NOT to create" criteria for the current type → does the entity violate any? + - "How to distinguish" table → does the entity look like another type? +4. If a different type is a better fit: + - Score the entity against "When to create" criteria of the proposed new type + - Score the entity against "When NOT to create" criteria of the proposed new type + - If the new type scores higher: flag as **misaligned** + +Focus on these common misalignments: +- Fleeting notes that have matured into topics, actors, or concepts (critical mass, corroboration) +- Topics that are actually concepts (timeless definition vs. temporal initiative) +- Actors that are actually projects (no repo/deployment yet) + +**Output:** `misaligned_entities[]` — list of `{entity, current_type, proposed_type, reason}` + +### 1.5 Capability 4 — Detect duplicated entities + +Scan all entity bodies for proper nouns, service names, team names, and person names that: +1. Are mentioned in **3+ different entity files** +2. Do NOT have a corresponding entity file anywhere in the vault +3. Are NOT already wrapped in a wikilink `[[name]]` + +Identification heuristics: +- Capitalized multi-word phrases (e.g., "Payment Gateway", "Alice Smith") +- Kebab-case or camelCase terms that look like service names (e.g., "billing-api", "notificationService") +- Terms following patterns like "the X team", "the X service", "X squad" + +Filter out: +- Terms that are already entity filenames or aliases (existing entities) +- Generic organizational terms ("the team", "the service", "the API") +- Terms that appear only within wikilinks (already linked) + +**Output:** `missing_entities[]` — list of `{name, inferred_type, mentions: [{entity, context_snippet}]}` + +### 1.6 Capability 5 — Detect misnamed entities + +Scan for name variants of the same real-world entity: +1. For each entity, collect all known names: filename (kebab-case), `name` field, `aliases[]` +2. For each proper noun/service name found in body text across the vault: + - Check if it is a variant of an existing entity name (case-insensitive, with/without hyphens, abbreviated forms) + - Example matches: "Iury" ↔ "Iury Krieger", "billing-api" ↔ "BillingAPI" ↔ "Billing API" +3. If a mention is a variant of an existing entity but NOT wrapped in a wikilink AND + the variant is NOT in the entity's `aliases[]`: flag as **misnamed** +4. If two distinct entity files refer to the same real-world entity (e.g., `iury.md` and `iury-krieger.md`): + flag as **duplicate entity files** requiring merge + +**Output:** `misnamed_entities[]` — list of `{canonical_entity, variant_name, found_in: [{entity, context_snippet}], action: "add_alias" | "merge_entities"}` + +--- + +## Phase 2 — Build Proposal + +Present all findings to the user in a structured report, grouped by capability. + +### 2.1 Summary table + +```markdown +## /bedrock:compress — Alignment Proposal + +| # | Capability | Findings | Mode | +|---|---|---|---| +| 1 | Broken backlinks | N found | Autonomous / Interactive | +| 2 | Concept match | N candidates | Queued / Interactive | +| 3 | Entity misalignment | N misaligned | Queued / Interactive | +| 4 | Duplicated entities | N missing | Autonomous / Interactive | +| 5 | Misnamed entities | N variants | Queued / Interactive | + +**Total findings:** N +**Mode:** interactive / cron +``` + +### 2.2 Capability 1 — Broken backlinks + +```markdown +### Capability 1: Broken Backlinks + +| # | Source | Target | Missing direction | +|---|---|---|---| +| 1 | [[entity-a]] | [[entity-b]] | entity-b → entity-a | +| 2 | [[entity-c]] | [[entity-d]] | entity-d → entity-c | + +**Fix:** Add missing backlinks in target entities via /bedrock:preserve. +``` + +If no broken backlinks found: "No broken backlinks found." + +### 2.3 Capability 2 — Concept match + +```markdown +### Capability 2: Concept Fragmentation + +| # | Candidate concept | Occurrences | Entities | +|---|---|---|---| +| 1 | "event sourcing" | 5 | [[actor-a]], [[topic-b]], [[actor-c]], ... | +| 2 | "circuit breaker" | 3 | [[actor-d]], [[actor-e]], [[topic-f]] | + +**Fix:** Create concept entities and add wikilinks in referencing entities via /bedrock:preserve. +``` + +If no candidates found: "No concept fragmentation found." + +### 2.4 Capability 3 — Entity misalignment + +```markdown +### Capability 3: Entity Misalignment + +| # | Entity | Current type | Proposed type | Reason | +|---|---|---|---|---| +| 1 | [[note-about-cqrs]] | fleeting | concept | Meets critical mass: >3 paragraphs, timeless definition | +| 2 | [[new-checkout-system]] | actor | project | No repo or deployment yet | + +**Fix:** Recategorize via /bedrock:preserve (create under new type, mark original as promoted/consolidated). +``` + +If no misalignments found: "No entity misalignments found." + +### 2.5 Capability 4 — Duplicated entities + +```markdown +### Capability 4: Missing Entities (Mentioned but Not Created) + +| # | Name | Inferred type | Mentions | +|---|---|---|---| +| 1 | "Payment Gateway" | actor | 4 mentions in [[topic-a]], [[actor-b]], [[discussion-c]], [[actor-d]] | +| 2 | "Alice Smith" | person | 3 mentions in [[discussion-e]], [[topic-f]], [[discussion-g]] | + +**Fix:** Create missing entities and establish backlinks via /bedrock:preserve. +``` + +If no missing entities found: "No duplicated entity mentions found." + +### 2.6 Capability 5 — Misnamed entities + +```markdown +### Capability 5: Misnamed Entities + +| # | Canonical entity | Variant found | Found in | Action | +|---|---|---|---|---| +| 1 | [[iury-krieger]] | "Iury" | [[discussion-a]], [[topic-b]] | Add alias + wikilink | +| 2 | [[billing-api]] | "BillingAPI" | [[actor-c]] | Add alias + wikilink | +| 3 | [[iury.md]] + [[iury-krieger.md]] | Same person | — | Merge entities | + +**Fix:** Add aliases and wikilinks, or merge duplicate entity files via /bedrock:preserve. +``` + +If no misnamed entities found: "No misnamed entities found." + +### 2.7 No findings + +If ALL 5 capabilities found 0 issues: +Report "Vault is aligned. No misalignments detected." and end (skip Phases 3-5). + +--- + +## Phase 3 — Confirmation and Mode Handling + +### Interactive mode (`--mode interactive` or default) + +Present the full proposal from Phase 2 and ask: + +```markdown +Confirm execution? (yes / no / partial) +- **yes**: execute all findings +- **no**: abort +- **partial**: specify which capabilities or individual findings to execute (e.g., "only capability 1 and 4", "all except finding 3 in capability 5") +``` + +**STOP HERE and wait for user confirmation.** + +If the user says "no": report "No changes made." and end. +If the user partially confirms: filter the execution list accordingly. + +### Cron mode (`--mode cron`) + +No user confirmation needed for mechanical capabilities. Split findings: + +**Autonomous execution (capabilities 1 and 4):** +- Proceed directly to Phase 4 with all findings from capabilities 1 and 4. +- When invoking `/bedrock:preserve`, include in the prompt: + "Autonomous mode — do not ask for confirmation, process directly." + +**Queued proposals (capabilities 2, 3, and 5):** +- If there are findings in capabilities 2, 3, or 5: compile them into a single fleeting note + and delegate creation to `/bedrock:preserve`: + +```yaml +entities: + - type: fleeting + name: "-compress-proposals" + action: create + content: | + ## Compress Alignment Proposals — + + The following alignment issues were detected by `/bedrock:compress` running in cron mode. + Review each proposal and run `/bedrock:compress` in interactive mode to execute. + + ### Concept Fragmentation (Capability 2) + + + ### Entity Misalignment (Capability 3) + + + ### Misnamed Entities (Capability 5) + + relations: {} + source: "compress" + metadata: + status: "raw" + source: "session" + captured_at: "" +``` + +- Include in the `/bedrock:preserve` invocation: + "Autonomous mode — do not ask for confirmation, process directly." + +--- + +## Phase 4 — Delegate to /bedrock:preserve + +### 4.1 Compile structured entity list + +Build the entity list in the format accepted by `/bedrock:preserve`, grouping all confirmed fixes: + +#### Capability 1 fixes (broken backlinks) + +For each broken backlink `{source: A, target: B}`: +```yaml +- type: + name: "" + action: update + content: "" + relations: + : [""] + source: "compress" +``` + +The content field is empty because the fix is adding a relation (backlink), not body content. +`/bedrock:preserve` handles adding the wikilink in B's body or frontmatter. + +#### Capability 2 fixes (concept creation) + +For each confirmed concept candidate: +```yaml +- type: concept + name: "" + action: create + content: "" + relations: + : ["", "", ...] + source: "compress" +``` + +Plus, for each referencing entity that should link to the new concept: +```yaml +- type: + name: "" + action: update + content: "" + relations: + concepts: [""] + source: "compress" +``` + +#### Capability 3 fixes (entity misalignment) + +For each misaligned entity: + +**If current type is `fleeting` (promotion):** +```yaml +- type: + name: "" + action: create + content: "" + relations: + : [...] + source: "compress" +- type: fleeting + name: "" + action: update + content: "" + relations: {} + source: "compress" + metadata: + status: "promoted" + promoted_to: "[[]]" +``` + +**If current type is NOT fleeting (recategorization):** +```yaml +- type: + name: "" + action: create + content: "" + relations: + : [...] + source: "compress" +``` + +Add a consolidation callout in the original entity (via update): +```yaml +- type: + name: "" + action: update + content: "> [!info] Content recategorized to [[]]\n> This entity was recategorized by /bedrock:compress. See [[]] for the current version." + relations: + : [""] + source: "compress" +``` + +#### Capability 4 fixes (create missing entities) + +For each missing entity: +```yaml +- type: + name: "" + action: create + content: "" + relations: + : ["", "", ...] + source: "compress" +``` + +Plus, for each mentioning entity (to establish backlinks): +```yaml +- type: + name: "" + action: update + content: "" + relations: + : [""] + source: "compress" +``` + +#### Capability 5 fixes (misnamed entities) + +**For alias additions:** +```yaml +- type: + name: "" + action: update + content: "" + relations: {} + source: "compress" + metadata: + aliases: ["", ""] +``` + +For each file where the variant was found (to add the wikilink): +```yaml +- type: + name: "" + action: update + content: "" + relations: + : [""] + source: "compress" +``` + +**For entity merges** (two files for the same real-world entity): +```yaml +- type: + name: "" + action: update + content: "" + relations: + : [...] + source: "compress" + metadata: + aliases: [""] +``` + +Add a consolidation callout in the secondary entity: +```yaml +- type: + name: "" + action: update + content: "> [!info] Content consolidated in [[]]\n> This entity has been consolidated by /bedrock:compress. See [[]] for the merged version." + relations: + : [""] + source: "compress" +``` + +### 4.2 Invoke /bedrock:preserve + +Use the Skill tool to invoke `/bedrock:preserve --vault ` passing the compiled structured entity list as argument. +The `--vault ` flag ensures preserve writes to the same vault. + +Include `source: "compress"` for all entities so `/bedrock:preserve` records provenance. + +If running in cron mode (autonomous capabilities): +- Add to the invocation prompt: "Autonomous mode — do not ask for confirmation, process directly." + +### 4.3 Await result + +`/bedrock:preserve` returns: +- List of created/updated entities +- Commit hash (if there was a commit) +- Any errors or warnings + +Record the result for use in the final report (Phase 5). + +--- + +## Phase 5 — Final Report + +Present to the user: + +```markdown +## /bedrock:compress — Report + +### Mode: interactive / cron + +### Alignment fixes applied +| # | Capability | Findings | Fixed | Queued | +|---|---|---|---|---| +| 1 | Broken backlinks | N | M | — | +| 2 | Concept match | N | M | P (cron) | +| 3 | Entity misalignment | N | M | P (cron) | +| 4 | Duplicated entities | N | M | — | +| 5 | Misnamed entities | N | M | P (cron) | + +**Total:** N findings, M fixed, P queued + +### Entities processed (via /bedrock:preserve) +| Type | Name | Action | +|---|---|---| +| | | create / update | +| ... | ... | ... | + +### Queued proposals (cron mode only) +- Created fleeting note: [[-compress-proposals]] +- Contains N proposals for capabilities 2, 3, 5 +- Review and run `/bedrock:compress` in interactive mode to execute + +### Git +- Commit: +- Push: success / failed (reason) + +### Suggestions +- Run `/bedrock:healthcheck` for a full vault health report +- [additional suggestions based on findings] +``` + +If no fixes were applied (user refused all, or no findings): +Present only the summary table with zero counts. + +--- + +## Error Handling + +| Situation | Action | +|---|---| +| Empty vault (no entities) | Report "No entities found in the vault." and end | +| No findings across all 5 capabilities | Report "Vault is aligned." and end | +| User refuses all findings (interactive) | Report "No changes made." and end | +| Error reading entity | Skip entity, warn in the report | +| `/bedrock:preserve` fails | Report the error, list what was NOT processed | +| Entity without frontmatter | Skip entity, warn in the report | +| `--mode` argument not recognized | Default to `interactive`, warn the user | + +--- + +## Critical Rules + +| Rule | Detail | +|---|---| +| All writes via /bedrock:preserve | NEVER use Write or Edit on entity files. Invoke `/bedrock:preserve` via the Skill tool. | +| Mechanical vs. semantic split | Capabilities 1, 4 = mechanical (autonomous in cron). Capabilities 2, 3, 5 = semantic (queued in cron, confirmed in interactive). | +| User confirmation in interactive | ALWAYS wait for explicit confirmation before delegating to /bedrock:preserve in interactive mode. | +| Append-only for people/teams/topics | When fixing entities of these types, NEVER delete existing content. Add callouts and new links only. | +| Actors allow free merge | Actor bodies can be modified freely. Frontmatter is merge-only (never delete fields). | +| Never remove wikilinks | When fixing backlinks or renaming, ADD new links. Never remove existing ones. | +| Entity definitions are authoritative | Capabilities 2 and 3 MUST read entity definitions from the plugin directory. Do not hardcode classification heuristics. | +| 3+ threshold | Capabilities 2 and 4 require a term/name to appear in 3+ different entities before flagging. | +| Provenance | All entities delegated to /bedrock:preserve use `source: "compress"`. | +| MCP in main context | Do NOT use subagents for MCP calls — permissions are not inherited. | +| Sensitive data | NEVER include credentials, tokens, passwords, PANs, CVVs. | +| Vault resolution first | Resolve `VAULT_PATH` before any file operation or git command — never assume CWD is the vault | +| All git commands use `git -C ` | Never assume CWD is the vault | +| All entity paths use `/` prefix | `/actors/`, not `actors/` | +| Pass --vault to /preserve | ALWAYS include `--vault ` when delegating to `/bedrock:preserve` | diff --git a/plugins/bedrock/skills/confluence-to-markdown/SKILL.md b/plugins/bedrock/skills/confluence-to-markdown/SKILL.md new file mode 100644 index 0000000..ae80870 --- /dev/null +++ b/plugins/bedrock/skills/confluence-to-markdown/SKILL.md @@ -0,0 +1,303 @@ +--- +name: confluence-to-markdown +description: > + Internal fetcher module for Confluence pages. Fetches content via Atlassian MCP (preferred), + REST API with Basic Auth (fallback), or browser DOM extraction via Claude in Chrome (last resort) + and returns Markdown. Used by /bedrock:teach and /bedrock:sync — not intended for direct user invocation. +user_invocable: false +allowed-tools: Bash, Read, Write, WebFetch, ToolSearch, mcp__plugin_atlassian_atlassian__*, mcp__claude-in-chrome__* +--- + +# Confluence Fetcher + +Internal module — invoked by `/bedrock:teach` Phase 1 and `/bedrock:sync` Phase 2, not user-invocable. + +Fetches a Confluence page and returns its content as Markdown. Three layers in fallback order: +**MCP (preferred) → REST API → Browser DOM extraction.** + +**Dependency:** Browser fallback (Layer 3) requires `scripts/extract.js` (relative to this skill directory). + +--- + +## Step 1 — Parse URL + +Parse the Confluence URL. Accept these formats: +- `https://.atlassian.net/wiki/spaces//pages//` +- `https://<domain>.atlassian.net/wiki/spaces/<spaceKey>/pages/<pageId>` +- `https://<domain>.atlassian.net/wiki/x/<shortlink>` +- `https://<domain>.atlassian.net/wiki/pages/viewpage.action?pageId=<pageId>` + +Extract: +- **Base URL**: `https://<domain>.atlassian.net` (everything before `/wiki/...`) +- **Page ID**: the numeric ID from the URL path (segment after `/pages/`) or `pageId` query parameter +- **Full URL**: the original URL as provided (needed for browser fallback) + +--- + +## Step 2 — Layer 1: MCP (Atlassian) + +The preferred layer. Uses the `plugin:atlassian:atlassian` MCP server if installed and authenticated. + +### 2.1 Check MCP availability + +Use ToolSearch to check if Atlassian MCP tools are available: + +``` +ToolSearch(query: "atlassian confluence page", max_results: 5) +``` + +Evaluate the result: + +- **MCP tools found and functional** (tools other than `authenticate` and `complete_authentication` are available) → proceed to **2.2 Fetch via MCP** +- **Only `authenticate` / `complete_authentication` tools found** (MCP installed but not authenticated) → proceed to **2.3 Guide authentication** +- **No Atlassian MCP tools found** → log and fall through: + +> **MCP not available:** No Atlassian MCP server installed. +> Install the Atlassian MCP plugin for Claude Code to enable direct Confluence access. +> Falling back to API (Layer 2). + +### 2.2 Fetch via MCP + +Use the Atlassian MCP tools to fetch the page content. The specific tool depends on what the MCP exposes after authentication (typically a page read or content retrieval tool). + +Call the MCP tool passing the page ID or URL. The MCP returns the page content directly. + +- **Success** → convert content to Markdown if not already, proceed to **Output Contract** +- **Error** → log the error and fall through to Layer 2: + +> **MCP fetch failed:** {error message}. +> Falling back to API (Layer 2). + +### 2.3 Guide authentication + +If the MCP is installed but not authenticated, guide the user: + +> **MCP not authenticated:** The Atlassian MCP server is installed but requires authentication. +> Run `mcp__plugin_atlassian_atlassian__authenticate` to start the OAuth flow, then complete it in your browser. +> After authentication, Confluence pages can be fetched directly via MCP. + +Ask the user: "Would you like to authenticate the Atlassian MCP now, or skip to API fallback (Layer 2)?" + +- **User wants to authenticate** → invoke `mcp__plugin_atlassian_atlassian__authenticate`, wait for the user to complete the OAuth flow, then retry **2.2 Fetch via MCP** +- **User declines** → log "User declined MCP authentication, falling to Layer 2" → continue to Step 3 + +--- + +## Step 3 — Layer 2: API (REST) + +Uses the Confluence REST API with Basic Auth (API token + email). + +### 3.1 Check credentials + +```bash +echo "CONFLUENCE_API_TOKEN: ${CONFLUENCE_API_TOKEN:+set}" && echo "CONFLUENCE_USER_EMAIL: ${CONFLUENCE_USER_EMAIL:+set}" +``` + +- **Both set** → proceed to **3.2 Compute auth header** +- **Either missing** → guide and fall through: + +> **API not available:** `CONFLUENCE_API_TOKEN` or `CONFLUENCE_USER_EMAIL` environment variable is not set. +> Generate an API token at https://id.atlassian.com/manage-profile/security/api-tokens and export both variables: +> `export CONFLUENCE_API_TOKEN="your-token"` and `export CONFLUENCE_USER_EMAIL="your-email"`. +> Falling back to Browser extraction (Layer 3). + +### 3.2 Compute Basic Auth header + +```bash +echo -n "${CONFLUENCE_USER_EMAIL}:${CONFLUENCE_API_TOKEN}" | base64 +``` + +### 3.3 Fetch the page + +Use `WebFetch`: +``` +WebFetch( + url: "{baseUrl}/wiki/api/v2/pages/{pageId}?body-format=storage", + headers: { + "Authorization": "Basic {base64_value}", + "Accept": "application/json" + } +) +``` + +If WebFetch cannot send the Authorization header, fall back to `curl` via Bash: +```bash +curl -sL -H "Authorization: Basic {base64_value}" -H "Accept: application/json" \ + "{baseUrl}/wiki/api/v2/pages/{pageId}?body-format=storage" +``` + +### 3.4 Extract content from response + +The API returns JSON with: +- `title` — page title +- `body.storage.value` — XHTML content (Confluence storage format) + +### 3.5 Convert XHTML to Markdown + +Convert the storage format XHTML to Markdown using these rules: + +| XHTML element | Markdown output | +|---|---| +| `<h1>` through `<h6>` | `#` through `######` | +| `<p>` | Paragraph with blank line separation | +| `<strong>`, `<b>` | `**text**` | +| `<em>`, `<i>` | `*text*` | +| `<s>`, `<del>` | `~~text~~` | +| `<a href="...">` | `[text](url)` | +| `<ul>` / `<ol>` / `<li>` | Markdown lists (respect nesting) | +| `<table>` | Markdown table with `\|` separators and header row | +| `<ac:structured-macro ac:name="code">` | Fenced code block with language from `<ac:parameter ac:name="language">` | +| `<pre>` | Fenced code block | +| `<code>` (inline) | `` `code` `` | +| `<blockquote>` | `> text` | +| `<hr>` | `---` | +| Confluence macros (`<ac:*>`) with text | Extract text content | +| Confluence macros with no text (images, drawio, attachments) | Skip silently | + +### 3.6 Error handling + +| HTTP status | Action | +|---|---| +| 200 OK | Proceed to **Output Contract** | +| 401 Unauthorized | Log and fall through to Layer 3: | + +> **API authentication failed:** API returned 401. The token may be expired or invalid. +> Regenerate your token at https://id.atlassian.com/manage-profile/security/api-tokens. +> Falling back to Browser extraction (Layer 3). + +| HTTP status | Action | +|---|---| +| 403 Forbidden | Abort (no fallback can bypass permissions): | + +> **API access denied:** API returned 403. The user does not have access to this page. +> Verify page permissions in Confluence. + +| HTTP status | Action | +|---|---| +| 404 Not Found | Abort: | + +> **Page not found:** API returned 404. The page ID may be incorrect. +> Verify the URL: `{original_url}`. + +--- + +## Step 4 — Layer 3: Browser (Claude in Chrome) + +Last resort. Opens the page in Chrome and extracts content via DOM scraping. + +### 4.1 Load Chrome tools + +Via ToolSearch: +``` +select:mcp__claude-in-chrome__tabs_context_mcp,mcp__claude-in-chrome__tabs_create_mcp +select:mcp__claude-in-chrome__navigate +select:mcp__claude-in-chrome__javascript_tool +``` + +If Chrome MCP tools are not available, abort: + +> **Browser not available:** Claude in Chrome MCP is not installed or not running. +> Install the Claude in Chrome extension and ensure it is connected. +> No further fallback layers available — cannot fetch this Confluence page. + +### 4.2 Get browser context + +``` +mcp__claude-in-chrome__tabs_context_mcp(createIfEmpty: true) +``` + +### 4.3 Navigate to the page + +``` +mcp__claude-in-chrome__tabs_create_mcp() +mcp__claude-in-chrome__navigate(url: "<full confluence URL>", tabId: <id>) +``` + +### 4.4 Execute extraction script + +Read `scripts/extract.js` from this skill's directory using the Read tool. Then execute it: + +``` +mcp__claude-in-chrome__javascript_tool( + action: "javascript_exec", + text: <contents of extract.js>, + tabId: <id> +) +``` + +The script returns JSON: +```json +{ + "status": "ready", + "totalLength": 52969, + "totalChunks": 6, + "chunkSize": 10000, + "title": "Page Title", + "instructions": "Run window.__confluence.chunk(0), window.__confluence.chunk(1), etc." +} +``` + +If the script returns an `error` field: handle accordingly (login page, empty content, wrong page). + +### 4.5 Read chunks + +For each chunk from `0` to `totalChunks - 1`: +``` +mcp__claude-in-chrome__javascript_tool( + action: "javascript_exec", + text: "window.__confluence.chunk(N)", + tabId: <id> +) +``` + +Concatenate all chunks into a single Markdown string. + +### 4.6 Validate + +Check that the result is not empty and not a login page. If validation fails: + +> **Browser extraction failed:** Could not extract content from the page. +> Ensure you are logged into Confluence in Chrome and the page has loaded. +> No further fallback layers available — cannot fetch this Confluence page. + +--- + +## Output Contract + +Return to the caller (`/bedrock:teach` or `/bedrock:sync`): +- **Markdown content**: the full page content as Markdown +- **Page title**: extracted from MCP response, API response (`title` field), or browser extraction (`title` in JSON) +- **Layer used**: MCP, API, or Browser + +The caller is responsible for saving the content to its target location. + +--- + +## Hard Rules + +| Rule | Detail | +|---|---| +| Read-only | Never write back to Confluence. | +| No OAuth interactive flows | Use only existing MCP auth, API tokens, or browser sessions. | +| Validate before returning | Do not return empty content, HTML error pages, or login pages. | +| Layer order is sacred | Always try MCP → API → Browser, in that order. Never skip ahead unless a layer is unavailable or user declines. | +| Guide before falling through | If a layer exists but is misconfigured, guide the user before moving to the next layer. | +| Skip rich media silently | Images, diagrams, drawio, and attachment macros are omitted without error. | +| Best-effort | If a layer fails, try the next. If all fail, report and abort — do not retry indefinitely. | +| 403 is terminal | Permission denied cannot be resolved by falling to another layer — abort immediately. | + +--- + +## Troubleshooting + +| Problem | Solution | +|---|---| +| MCP not authenticated | Run `mcp__plugin_atlassian_atlassian__authenticate` to start OAuth flow | +| MCP tools not found | Atlassian MCP plugin not installed — use API or browser fallback | +| API returns 401 | Token expired — regenerate at https://id.atlassian.com/manage-profile/security/api-tokens | +| API returns 403 | User lacks page access — check Confluence permissions | +| API returns 404 | Wrong page ID — verify URL | +| Chrome extension disconnected | Refresh extension, call `tabs_context_mcp(createIfEmpty: true)` | +| Browser redirects to login | User not authenticated — log into Confluence in Chrome, retry | +| `extract.js` returns empty | Page may not have loaded — wait and retry, or check if page is empty | +| Shortlink URL (`/wiki/x/...`) with API | Navigate in browser first to resolve full URL with page ID | diff --git a/plugins/bedrock/skills/confluence-to-markdown/scripts/extract.js b/plugins/bedrock/skills/confluence-to-markdown/scripts/extract.js new file mode 100644 index 0000000..93636f1 --- /dev/null +++ b/plugins/bedrock/skills/confluence-to-markdown/scripts/extract.js @@ -0,0 +1,312 @@ +// Confluence Page → Markdown Extractor +// Usage: Read this file, then execute via javascript_tool in a tab loaded with a Confluence page +// +// Step 1 (init): Run the full script to extract and store content in window.__confluence +// Step 2 (read): Run window.__confluence.chunk(N) to read chunk N (0-indexed) +// +// The script extracts HTML from the Confluence content container, converts it to Markdown, +// and stores it in a global variable so it can be read in chunks without re-parsing the DOM. + +(function () { + var CHUNK_SIZE = 10000; + + // Fallback chain of content container selectors + var selectors = [ + '[data-testid="renderer-container"]', + '#content .wiki-content', + '#main-content', + 'article', + '#content', + 'document.body' + ]; + + var container = null; + for (var i = 0; i < selectors.length; i++) { + if (selectors[i] === 'document.body') { + container = document.body; + } else { + container = document.querySelector(selectors[i]); + } + if (container) break; + } + + if (!container) { + return JSON.stringify({ error: 'Could not find content container on the page.' }); + } + + // Check for login redirect + var bodyText = document.body.innerText || ''; + if (bodyText.includes('Log in') && bodyText.includes('Atlassian') && bodyText.length < 2000) { + return JSON.stringify({ error: 'Atlassian login page detected. User must log into Confluence in Chrome first.' }); + } + + // HTML-to-Markdown conversion + function htmlToMarkdown(el) { + var result = ''; + var children = el.childNodes; + + for (var i = 0; i < children.length; i++) { + var node = children[i]; + + if (node.nodeType === 3) { + // Text node + result += node.textContent; + continue; + } + + if (node.nodeType !== 1) continue; + + var tag = node.tagName.toLowerCase(); + + // Headings + if (/^h[1-6]$/.test(tag)) { + var level = parseInt(tag.charAt(1)); + var prefix = ''; + for (var h = 0; h < level; h++) prefix += '#'; + result += '\n\n' + prefix + ' ' + node.textContent.trim() + '\n\n'; + continue; + } + + // Paragraphs + if (tag === 'p') { + var inner = htmlToMarkdown(node).trim(); + if (inner) result += '\n\n' + inner + '\n\n'; + continue; + } + + // Line breaks + if (tag === 'br') { + result += '\n'; + continue; + } + + // Bold + if (tag === 'strong' || tag === 'b') { + var boldContent = htmlToMarkdown(node).trim(); + if (boldContent) result += '**' + boldContent + '**'; + continue; + } + + // Italic + if (tag === 'em' || tag === 'i') { + var italicContent = htmlToMarkdown(node).trim(); + if (italicContent) result += '*' + italicContent + '*'; + continue; + } + + // Strikethrough + if (tag === 's' || tag === 'del') { + var strikeContent = htmlToMarkdown(node).trim(); + if (strikeContent) result += '~~' + strikeContent + '~~'; + continue; + } + + // Links + if (tag === 'a') { + var href = node.getAttribute('href') || ''; + var linkText = htmlToMarkdown(node).trim(); + if (linkText && href) { + result += '[' + linkText + '](' + href + ')'; + } else if (linkText) { + result += linkText; + } + continue; + } + + // Inline code + if (tag === 'code' && node.parentElement && node.parentElement.tagName.toLowerCase() !== 'pre') { + result += '`' + node.textContent + '`'; + continue; + } + + // Code blocks + if (tag === 'pre') { + var codeEl = node.querySelector('code'); + var codeText = codeEl ? codeEl.textContent : node.textContent; + var lang = ''; + if (codeEl) { + var cls = codeEl.getAttribute('class') || ''; + var langMatch = cls.match(/language-(\w+)/); + if (langMatch) lang = langMatch[1]; + } + // Also check Confluence data attribute for language + var dataLang = node.getAttribute('data-syntaxhighlighter-params'); + if (!lang && dataLang) { + var brushMatch = dataLang.match(/brush:\s*(\w+)/); + if (brushMatch) lang = brushMatch[1]; + } + result += '\n\n```' + lang + '\n' + codeText.trim() + '\n```\n\n'; + continue; + } + + // Unordered lists + if (tag === 'ul') { + result += '\n' + convertList(node, '- ', 0) + '\n'; + continue; + } + + // Ordered lists + if (tag === 'ol') { + result += '\n' + convertList(node, '1. ', 0) + '\n'; + continue; + } + + // Tables + if (tag === 'table') { + result += '\n\n' + convertTable(node) + '\n\n'; + continue; + } + + // Blockquotes + if (tag === 'blockquote') { + var bqContent = htmlToMarkdown(node).trim(); + if (bqContent) { + var bqLines = bqContent.split('\n'); + result += '\n\n'; + for (var bq = 0; bq < bqLines.length; bq++) { + result += '> ' + bqLines[bq] + '\n'; + } + result += '\n'; + } + continue; + } + + // Horizontal rules + if (tag === 'hr') { + result += '\n\n---\n\n'; + continue; + } + + // Divs and other containers — recurse + if (tag === 'div' || tag === 'span' || tag === 'section' || tag === 'td' || tag === 'th' || tag === 'li') { + result += htmlToMarkdown(node); + continue; + } + + // Confluence macros and panels — extract text content + if (node.classList && (node.classList.contains('confluence-information-macro') || + node.classList.contains('panel') || + node.classList.contains('expand-container'))) { + result += '\n\n' + htmlToMarkdown(node).trim() + '\n\n'; + continue; + } + + // Fallback: recurse into unknown elements + result += htmlToMarkdown(node); + } + + return result; + } + + function convertList(listEl, marker, depth) { + var items = listEl.children; + var result = ''; + var indent = ''; + for (var d = 0; d < depth; d++) indent += ' '; + var counter = 1; + + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (item.tagName.toLowerCase() !== 'li') continue; + + // Check for nested lists + var nestedUl = item.querySelector(':scope > ul'); + var nestedOl = item.querySelector(':scope > ol'); + + // Get direct text (excluding nested lists) + var clone = item.cloneNode(true); + var nestedLists = clone.querySelectorAll('ul, ol'); + for (var n = 0; n < nestedLists.length; n++) { + nestedLists[n].parentNode.removeChild(nestedLists[n]); + } + var text = htmlToMarkdown(clone).trim(); + + var actualMarker = marker === '1. ' ? (counter + '. ') : marker; + result += indent + actualMarker + text + '\n'; + counter++; + + if (nestedUl) { + result += convertList(nestedUl, '- ', depth + 1); + } + if (nestedOl) { + result += convertList(nestedOl, '1. ', depth + 1); + } + } + + return result; + } + + function convertTable(tableEl) { + var rows = tableEl.querySelectorAll('tr'); + if (rows.length === 0) return ''; + + var result = ''; + var colCount = 0; + + for (var r = 0; r < rows.length; r++) { + var cells = rows[r].querySelectorAll('td, th'); + if (cells.length > colCount) colCount = cells.length; + + result += '|'; + for (var c = 0; c < cells.length; c++) { + var cellText = htmlToMarkdown(cells[c]).trim().replace(/\n+/g, ' '); + result += ' ' + cellText + ' |'; + } + result += '\n'; + + // Add separator after first row (header) + if (r === 0) { + result += '|'; + for (var s = 0; s < cells.length; s++) { + result += ' --- |'; + } + result += '\n'; + } + } + + return result; + } + + // Extract title + var title = ''; + var titleEl = document.querySelector('[data-testid="title-text"]') || + document.querySelector('#title-text') || + document.querySelector('h1'); + if (titleEl) title = titleEl.textContent.trim(); + + // Convert content + var markdown = htmlToMarkdown(container); + + // Clean up excessive whitespace + markdown = markdown.replace(/\n{3,}/g, '\n\n').trim(); + + if (!markdown || markdown.length < 10) { + return JSON.stringify({ error: 'Page content is empty or too short. The page may not have loaded yet.' }); + } + + var totalLength = markdown.length; + var totalChunks = Math.ceil(totalLength / CHUNK_SIZE); + + // Store in global for chunk reads + window.__confluence = { + text: markdown, + title: title, + totalLength: totalLength, + totalChunks: totalChunks, + chunkSize: CHUNK_SIZE, + chunk: function (n) { + var start = n * CHUNK_SIZE; + if (start >= this.totalLength) return JSON.stringify({ error: 'Chunk index out of range', max: this.totalChunks - 1 }); + return this.text.substring(start, start + CHUNK_SIZE); + } + }; + + return JSON.stringify({ + status: 'ready', + totalLength: totalLength, + totalChunks: totalChunks, + chunkSize: CHUNK_SIZE, + title: title, + instructions: 'Run window.__confluence.chunk(0), window.__confluence.chunk(1), etc. to read each chunk.' + }); +})(); diff --git a/plugins/bedrock/skills/gdoc-to-markdown/SKILL.md b/plugins/bedrock/skills/gdoc-to-markdown/SKILL.md new file mode 100644 index 0000000..e8eb5a1 --- /dev/null +++ b/plugins/bedrock/skills/gdoc-to-markdown/SKILL.md @@ -0,0 +1,421 @@ +--- +name: gdoc-to-markdown +description: > + Internal fetcher module for Google Docs and Sheets. Fetches content via MCP (preferred, when available), + Google API with bearer token or public URL export (fallback), or browser DOM extraction via Claude in + Chrome (last resort) and returns Markdown. + Used by /bedrock:teach and /bedrock:sync — not intended for direct user invocation. +user_invocable: false +allowed-tools: Bash, Read, Write, WebFetch, ToolSearch, mcp__claude-in-chrome__* +--- + +# Google Docs & Sheets Fetcher + +Internal module — invoked by `/bedrock:teach` Phase 1 and `/bedrock:sync` Phase 2, not user-invocable. + +Fetches a Google Docs document or Google Sheets spreadsheet and converts it to a local Markdown file. +Supports both document types with automatic detection. Three layers in fallback order: +**MCP (preferred) → API / Public Export → Browser DOM extraction.** + +**Dependency:** Browser fallback (Layer 3) requires `scripts/extract.js` (relative to this skill directory). + +--- + +## Step 1 — Parse URL and Detect Type + +Parse the URL. Accept these formats: + +**Google Docs:** +- `https://docs.google.com/document/d/{docId}/edit` +- `https://docs.google.com/document/d/{docId}/edit#heading=...` +- `https://docs.google.com/document/d/{docId}/edit?tab=t.0` +- `https://docs.google.com/document/d/{docId}` +- Raw document ID (no URL) — treat as Doc by default + +**Google Sheets:** +- `https://docs.google.com/spreadsheets/d/{docId}/edit` +- `https://docs.google.com/spreadsheets/d/{docId}/edit#gid=0` +- `https://docs.google.com/spreadsheets/d/{docId}` +- Raw spreadsheet ID — only if the user explicitly mentions "sheet" or "spreadsheet" + +**Detect type:** +- URL contains `/spreadsheets/d/` → **Sheet** +- URL contains `/document/d/` → **Doc** +- Raw ID with no URL → default to **Doc** unless user context indicates Sheet + +Extract the `{docId}` — the string between `/d/` and the next `/` or end of path. + +--- + +## Step 2 — Layer 1: MCP (Google Docs) + +The preferred layer. Checks if a Google Docs/Drive MCP server is installed and authenticated. + +### 2.1 Check MCP availability + +Use ToolSearch to check if any Google Docs or Google Drive MCP tools are available: + +``` +ToolSearch(query: "google docs drive document", max_results: 5) +``` + +Evaluate the result: + +- **Google Docs/Drive MCP tools found and functional** → proceed to **2.2 Fetch via MCP** +- **MCP tools found but not authenticated** → proceed to **2.3 Guide authentication** +- **No Google Docs MCP tools found** (this is the expected case today) → log and fall through: + +> **MCP not available:** No Google Docs/Drive MCP server installed. +> When a Google Docs MCP becomes available, install it for direct document access. +> Falling back to API (Layer 2). + +### 2.2 Fetch via MCP + +Use the Google Docs MCP tools to fetch the document content. The specific tool depends on what the MCP exposes. + +- **Success** → convert content to Markdown if not already, proceed to **Output Contract** +- **Error** → log the error and fall through to Layer 2: + +> **MCP fetch failed:** {error message}. +> Falling back to API (Layer 2). + +### 2.3 Guide authentication + +If an MCP is installed but not authenticated, guide the user: + +> **MCP not authenticated:** A Google Docs MCP server is installed but requires authentication. +> Complete the authentication flow as prompted by the MCP server. +> After authentication, Google documents can be fetched directly via MCP. + +Ask the user: "Would you like to authenticate the Google Docs MCP now, or skip to API fallback (Layer 2)?" + +- **User wants to authenticate** → invoke the MCP authentication tool, wait for the user to complete the flow, then retry **2.2 Fetch via MCP** +- **User declines** → log "User declined MCP authentication, falling to Layer 2" → continue to Step 3 + +--- + +## Step 3 — Layer 2: API + +Uses the Google Drive/Sheets API with a bearer token or public URL export. + +### Strategy selection + +- **If `GOOGLE_ACCESS_TOKEN` env var exists** → use **Strategy A (API with token)** +- **If `GOOGLE_ACCESS_TOKEN` is NOT set** → use **Strategy B (Public Export)** +- **If Strategy B fails (private document)** → guide and fall through to Layer 3: + +> **API not available:** This document requires authentication and no `GOOGLE_ACCESS_TOKEN` is set. +> Generate a token at https://developers.google.com/oauthplayground/ with the `https://www.googleapis.com/auth/drive.readonly` scope. +> Export: `export GOOGLE_ACCESS_TOKEN="your-token"`. +> Falling back to Browser extraction (Layer 3). + +Inform the caller which strategy is being used and whether the document is a **Doc** or **Sheet**. + +--- + +### Google Docs — Strategy A (API with token) + +#### A.1 Fetch as Markdown + +Use `WebFetch`: +``` +WebFetch( + url: "https://www.googleapis.com/drive/v3/files/{docId}/export?mimeType=text/markdown", + headers: { "Authorization": "Bearer {GOOGLE_ACCESS_TOKEN}" }, + prompt: "Return the COMPLETE raw content exactly as-is. Do not summarize or truncate." +) +``` + +If WebFetch cannot send the Authorization header, fall back to Bash: +```bash +curl -sL -H "Authorization: Bearer ${GOOGLE_ACCESS_TOKEN}" \ + "https://www.googleapis.com/drive/v3/files/{docId}/export?mimeType=text/markdown" +``` + +#### A.2 Validate + +- Valid Markdown content → proceed to **Output Contract** +- 401 → guide and fall through to Layer 3: + +> **API authentication failed:** Google API returned 401. The token may be expired or invalid. +> Refresh your token at https://developers.google.com/oauthplayground/. +> Falling back to Browser extraction (Layer 3). + +- 403 → abort (permissions issue, no fallback can bypass): + +> **API access denied:** Google API returned 403. You do not have access to this document. +> Verify document sharing permissions in Google Docs. + +- 404 → abort: + +> **Document not found:** Google API returned 404. The document ID may be incorrect. +> Verify the URL or document ID. + +- Empty response → guide and fall through to Layer 3: + +> **API returned empty:** The document appears to be empty or the export failed. +> Falling back to Browser extraction (Layer 3). + +**Do not post-process** the Markdown — return Google's native output as-is. + +--- + +### Google Docs — Strategy B (Public Export) + +#### B.1 Fetch via public endpoint + +```bash +curl -sL "https://docs.google.com/document/d/{docId}/export?format=md" +``` + +The `-L` flag follows the 307 redirect to `*.googleusercontent.com`. + +#### B.2 Validate + +- Valid Markdown content → proceed to **Output Contract** +- HTML error page or Google login page → document is private, fall through to Layer 3: + +> **Public export not available:** Document is private and requires authentication. +> Set `GOOGLE_ACCESS_TOKEN` for API access, or ensure you are logged into Google in Chrome. +> Falling back to Browser extraction (Layer 3). + +- Empty response → fall through to Layer 3: + +> **Public export returned empty:** The document appears to be empty or inaccessible. +> Falling back to Browser extraction (Layer 3). + +--- + +### Google Sheets — Strategy A (API with token) + +#### A.1 List all sheet tabs + +```bash +curl -sL -H "Authorization: Bearer ${GOOGLE_ACCESS_TOKEN}" \ + "https://sheets.googleapis.com/v4/spreadsheets/{docId}?fields=sheets.properties" +``` + +Returns JSON with `sheets[].properties.title` (sheet name) and `sheets[].properties.sheetId` (gid). + +#### A.2 Export each tab as CSV + +For each tab: +```bash +curl -sL -H "Authorization: Bearer ${GOOGLE_ACCESS_TOKEN}" \ + "https://docs.google.com/spreadsheets/d/{docId}/export?format=csv&gid={sheetGid}" +``` + +If the export endpoint fails for a specific tab, fall back to the Sheets API values endpoint: +```bash +curl -sL -H "Authorization: Bearer ${GOOGLE_ACCESS_TOKEN}" \ + "https://sheets.googleapis.com/v4/spreadsheets/{docId}/values/{sheetName}!A:ZZ" +``` + +Convert the JSON `values` array rows to comma-separated values to produce CSV. + +#### A.3 Convert CSV to Markdown tables + +For each tab's CSV: +1. Parse CSV correctly — respect quoted fields (fields containing commas, newlines, or double quotes wrapped in `"..."` are a single field) +2. First row = header → `| col1 | col2 | ... |` +3. Separator row → `| --- | --- | ... |` +4. Data rows → `| val1 | val2 | ... |` +5. Escape pipe characters `|` within cell values as `\|` + +#### A.4 Concatenate all tabs + +For each tab, prepend: `## {sheet_name}` followed by a blank line, then the Markdown table, then a blank line. Tabs appear in the same order as returned by the Sheets API metadata. + +#### A.5 Validate + +- At least one tab produced valid content → proceed to **Output Contract** +- 401 → guide and fall through to Layer 3: + +> **API authentication failed:** Sheets API returned 401. The token may be expired or invalid. +> Refresh your token at https://developers.google.com/oauthplayground/. +> Falling back to Browser extraction (Layer 3). + +- 403 → abort: + +> **API access denied:** Sheets API returned 403. You do not have access to this spreadsheet. +> Verify spreadsheet sharing permissions or ensure token has `drive.readonly` scope. + +- 404 → abort: + +> **Spreadsheet not found:** Sheets API returned 404. The spreadsheet ID may be incorrect. +> Verify the URL. + +- All tabs empty → fall through to Layer 3: + +> **API returned empty:** The spreadsheet appears to be empty. +> Falling back to Browser extraction (Layer 3). + +--- + +### Google Sheets — Strategy B (Public Export) + +#### B.1 Export first tab + +```bash +curl -sL "https://docs.google.com/spreadsheets/d/{docId}/gviz/tq?tqx=out:csv&gid=0" +``` + +#### B.2 Validate and convert + +- Valid CSV → convert to Markdown table (same rules as Strategy A step A.3), proceed to **Output Contract** +- HTML error page or Google login page → spreadsheet is private, fall through to Layer 3: + +> **Public export not available:** Spreadsheet is private and requires authentication. +> Set `GOOGLE_ACCESS_TOKEN` for API access, or ensure you are logged into Google in Chrome. +> Falling back to Browser extraction (Layer 3). + +- Empty response → fall through to Layer 3: + +> **Public export returned empty:** The spreadsheet appears to be empty or inaccessible. +> Falling back to Browser extraction (Layer 3). + +#### B.3 Multi-sheet limitation + +Inform the caller: "Public export can only retrieve the first sheet tab. To export all tabs, set `GOOGLE_ACCESS_TOKEN`." + +Format the single tab with heading `## Sheet1` followed by the Markdown table. + +--- + +## Step 4 — Layer 3: Browser (Claude in Chrome) + +Last resort. Opens the document in Chrome and extracts content via DOM scraping. + +### 4.1 Load Chrome tools + +Via ToolSearch: +``` +select:mcp__claude-in-chrome__tabs_context_mcp,mcp__claude-in-chrome__tabs_create_mcp +select:mcp__claude-in-chrome__navigate +select:mcp__claude-in-chrome__javascript_tool +``` + +If Chrome MCP tools are not available, abort: + +> **Browser not available:** Claude in Chrome MCP is not installed or not running. +> Install the Claude in Chrome extension and ensure it is connected. +> No further fallback layers available — cannot fetch this document. + +### 4.2 Get browser context + +``` +mcp__claude-in-chrome__tabs_context_mcp(createIfEmpty: true) +``` + +### 4.3 Navigate to the document + +``` +mcp__claude-in-chrome__tabs_create_mcp() +mcp__claude-in-chrome__navigate(url: "<full document URL>", tabId: <id>) +``` + +### 4.4 Execute extraction script + +Read `scripts/extract.js` from this skill's directory using the Read tool. Then execute it: + +``` +mcp__claude-in-chrome__javascript_tool( + action: "javascript_exec", + text: <contents of extract.js>, + tabId: <id> +) +``` + +The script returns JSON: +```json +{ + "status": "ready", + "totalLength": 31450, + "totalChunks": 4, + "chunkSize": 10000, + "title": "Document Title", + "docType": "doc", + "instructions": "Run window.__gdoc.chunk(0), window.__gdoc.chunk(1), etc." +} +``` + +If the script returns an `error` field: handle accordingly (login page, empty content, wrong page). + +### 4.5 Read chunks + +For each chunk from `0` to `totalChunks - 1`: +``` +mcp__claude-in-chrome__javascript_tool( + action: "javascript_exec", + text: "window.__gdoc.chunk(N)", + tabId: <id> +) +``` + +Concatenate all chunks into a single Markdown string. + +### 4.6 Validate + +Check that the result is not empty and not a login page. If validation fails: + +> **Browser extraction failed:** Could not extract content from the document. +> Ensure you are logged into Google in Chrome and the document has loaded. +> No further fallback layers available — cannot fetch this document. + +--- + +## Output Contract + +### Save the file + +- **Doc** → save to `/tmp/gdoc_{docId}.md` +- **Sheet** → save to `/tmp/gsheet_{docId}.md` + +Write the Markdown content using the Write tool. Verify the file was written by reading the first few lines. + +### Return to caller + +Return to `/bedrock:teach` or `/bedrock:sync`: +- **Output file path**: `/tmp/gdoc_{docId}.md` or `/tmp/gsheet_{docId}.md` +- **Document type**: Doc or Sheet +- **Layer used**: MCP, API, Public Export, or Browser +- **Tabs exported** (Sheets only): number of tabs + +The caller copies the file to `$TEACH_TMP/<slug>.md`. + +--- + +## Hard Rules + +| Rule | Detail | +|---|---| +| Read-only | Never write back to Google Docs or Sheets. | +| No OAuth interactive flows | Use only existing MCP auth, static token from `GOOGLE_ACCESS_TOKEN`, or browser session. | +| Validate before saving | Do not save empty files, HTML error pages, or Google login pages. | +| Layer order is sacred | Always try MCP → API → Browser, in that order. Never skip ahead unless a layer is unavailable or user declines. | +| Guide before falling through | If a layer exists but is misconfigured, guide the user before moving to the next layer. | +| No Markdown post-processing for Docs | Return Google's native Markdown export as-is (API layer). | +| Export all sheet tabs (API) | Do not skip tabs or allow selective export. | +| Respect CSV quoting rules | Quoted fields are single fields, even with commas or newlines inside. | +| Best-effort | If a layer fails, try the next. If all fail, report and abort — do not retry indefinitely. | +| 403 is terminal | Permission denied cannot be resolved by falling to another layer — abort immediately. | + +--- + +## Troubleshooting + +| Problem | Solution | +|---|---| +| No Google Docs MCP available | Expected today — skip to API layer automatically | +| API returns 401 | Token expired — refresh at https://developers.google.com/oauthplayground/ | +| API returns 403 | User lacks access to the document/spreadsheet | +| API returns 404 | Document ID is wrong — verify URL | +| Public export returns HTML login page | Document is private — set `GOOGLE_ACCESS_TOKEN` or use browser | +| Content is truncated | Google Drive API limits exports to 10 MB — document may be too large | +| WebFetch fails to send Authorization header | Fall back to `curl -H "Authorization: Bearer {token}" -sL "<url>"` via Bash | +| Sheets 403 for metadata | Token may lack `drive.readonly` or `spreadsheets.readonly` scope | +| Sheets CSV export returns HTML | Export endpoint blocked — fall back to Sheets API values endpoint | +| Public Sheets export returns only first tab | Expected limitation — multi-sheet export requires `GOOGLE_ACCESS_TOKEN` | +| Chrome extension disconnected | Refresh extension, call `tabs_context_mcp(createIfEmpty: true)` | +| Browser redirects to Google login | User not authenticated — log into Google in Chrome, retry | +| `extract.js` returns empty | Document may not have loaded — wait and retry, or check if document is empty | diff --git a/plugins/bedrock/skills/gdoc-to-markdown/scripts/extract.js b/plugins/bedrock/skills/gdoc-to-markdown/scripts/extract.js new file mode 100644 index 0000000..aa6f3f2 --- /dev/null +++ b/plugins/bedrock/skills/gdoc-to-markdown/scripts/extract.js @@ -0,0 +1,373 @@ +// Google Docs / Sheets → Markdown Extractor +// Usage: Read this file, then execute via javascript_tool in a tab loaded with a Google Doc or Sheet +// +// Step 1 (init): Run the full script to extract and store content in window.__gdoc +// Step 2 (read): Run window.__gdoc.chunk(N) to read chunk N (0-indexed) +// +// The script detects whether the page is a Google Doc or Google Sheet, +// extracts content from the appropriate container, converts it to Markdown, +// and stores it in a global variable so it can be read in chunks. + +(function () { + var CHUNK_SIZE = 10000; + + // Detect document type + var isSheet = window.location.hostname === 'docs.google.com' && + window.location.pathname.indexOf('/spreadsheets/') !== -1; + var isDoc = !isSheet; + var docType = isSheet ? 'sheet' : 'doc'; + + // Check for login redirect + var bodyText = document.body.innerText || ''; + if ((bodyText.includes('Sign in') || bodyText.includes('Log in')) && + bodyText.includes('Google') && bodyText.length < 2000) { + return JSON.stringify({ error: 'Google login page detected. User must log into Google in Chrome first.' }); + } + + // --- Google Docs extraction --- + if (isDoc) { + // Fallback chain of content container selectors for Google Docs + var docSelectors = [ + '.kix-appview-editor', + '.kix-page', + '[data-page-id]', + '.docs-editor', + '#docs-editor', + '.doc-content', + 'article', + 'document.body' + ]; + + var container = null; + for (var i = 0; i < docSelectors.length; i++) { + if (docSelectors[i] === 'document.body') { + container = document.body; + } else { + container = document.querySelector(docSelectors[i]); + } + if (container) break; + } + + if (!container) { + return JSON.stringify({ error: 'Could not find content container on the Google Docs page.' }); + } + + var markdown = docHtmlToMarkdown(container); + markdown = markdown.replace(/\n{3,}/g, '\n\n').trim(); + + if (!markdown || markdown.length < 10) { + return JSON.stringify({ error: 'Document content is empty or too short. The page may not have loaded yet.' }); + } + + return finalize(markdown, docType); + } + + // --- Google Sheets extraction --- + if (isSheet) { + // Try to extract from the visible grid + var sheetContainer = document.querySelector('.waffle') || + document.querySelector('table.waffle') || + document.querySelector('.grid-container table') || + document.querySelector('table'); + + if (!sheetContainer) { + return JSON.stringify({ error: 'Could not find spreadsheet content. The page may not have loaded yet.' }); + } + + var markdown = sheetTableToMarkdown(sheetContainer); + markdown = '## Sheet1\n\n' + markdown; + markdown = markdown.trim(); + + if (!markdown || markdown.length < 10) { + return JSON.stringify({ error: 'Spreadsheet content is empty or too short. The page may not have loaded yet.' }); + } + + return finalize(markdown, docType); + } + + // --- Shared functions --- + + function docHtmlToMarkdown(el) { + var result = ''; + var children = el.childNodes; + + for (var i = 0; i < children.length; i++) { + var node = children[i]; + + if (node.nodeType === 3) { + result += node.textContent; + continue; + } + + if (node.nodeType !== 1) continue; + + var tag = node.tagName.toLowerCase(); + + // Headings + if (/^h[1-6]$/.test(tag)) { + var level = parseInt(tag.charAt(1)); + var prefix = ''; + for (var h = 0; h < level; h++) prefix += '#'; + result += '\n\n' + prefix + ' ' + node.textContent.trim() + '\n\n'; + continue; + } + + // Paragraphs + if (tag === 'p') { + var inner = docHtmlToMarkdown(node).trim(); + if (inner) result += '\n\n' + inner + '\n\n'; + continue; + } + + // Line breaks + if (tag === 'br') { + result += '\n'; + continue; + } + + // Bold + if (tag === 'strong' || tag === 'b') { + var boldContent = docHtmlToMarkdown(node).trim(); + if (boldContent) result += '**' + boldContent + '**'; + continue; + } + + // Italic + if (tag === 'em' || tag === 'i') { + var italicContent = docHtmlToMarkdown(node).trim(); + if (italicContent) result += '*' + italicContent + '*'; + continue; + } + + // Strikethrough + if (tag === 's' || tag === 'del') { + var strikeContent = docHtmlToMarkdown(node).trim(); + if (strikeContent) result += '~~' + strikeContent + '~~'; + continue; + } + + // Links + if (tag === 'a') { + var href = node.getAttribute('href') || ''; + var linkText = docHtmlToMarkdown(node).trim(); + if (linkText && href) { + result += '[' + linkText + '](' + href + ')'; + } else if (linkText) { + result += linkText; + } + continue; + } + + // Inline code + if (tag === 'code' && node.parentElement && node.parentElement.tagName.toLowerCase() !== 'pre') { + result += '`' + node.textContent + '`'; + continue; + } + + // Code blocks + if (tag === 'pre') { + var codeEl = node.querySelector('code'); + var codeText = codeEl ? codeEl.textContent : node.textContent; + var lang = ''; + if (codeEl) { + var cls = codeEl.getAttribute('class') || ''; + var langMatch = cls.match(/language-(\w+)/); + if (langMatch) lang = langMatch[1]; + } + result += '\n\n```' + lang + '\n' + codeText.trim() + '\n```\n\n'; + continue; + } + + // Unordered lists + if (tag === 'ul') { + result += '\n' + convertList(node, '- ', 0) + '\n'; + continue; + } + + // Ordered lists + if (tag === 'ol') { + result += '\n' + convertList(node, '1. ', 0) + '\n'; + continue; + } + + // Tables + if (tag === 'table') { + result += '\n\n' + sheetTableToMarkdown(node) + '\n\n'; + continue; + } + + // Blockquotes + if (tag === 'blockquote') { + var bqContent = docHtmlToMarkdown(node).trim(); + if (bqContent) { + var bqLines = bqContent.split('\n'); + result += '\n\n'; + for (var bq = 0; bq < bqLines.length; bq++) { + result += '> ' + bqLines[bq] + '\n'; + } + result += '\n'; + } + continue; + } + + // Horizontal rules + if (tag === 'hr') { + result += '\n\n---\n\n'; + continue; + } + + // Google Docs specific: kix-lineview contains a line of text + if (node.classList && node.classList.contains('kix-lineview')) { + var lineText = node.textContent; + if (lineText) result += lineText + '\n'; + continue; + } + + // Google Docs specific: kix-paragraphrenderer contains a paragraph + if (node.classList && node.classList.contains('kix-paragraphrenderer')) { + var paraText = docHtmlToMarkdown(node).trim(); + if (paraText) result += '\n' + paraText + '\n'; + continue; + } + + // Divs, spans, and other containers — recurse + if (tag === 'div' || tag === 'span' || tag === 'section' || + tag === 'td' || tag === 'th' || tag === 'li') { + result += docHtmlToMarkdown(node); + continue; + } + + // Fallback: recurse into unknown elements + result += docHtmlToMarkdown(node); + } + + return result; + } + + function convertList(listEl, marker, depth) { + var items = listEl.children; + var result = ''; + var indent = ''; + for (var d = 0; d < depth; d++) indent += ' '; + var counter = 1; + + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (item.tagName.toLowerCase() !== 'li') continue; + + var nestedUl = item.querySelector(':scope > ul'); + var nestedOl = item.querySelector(':scope > ol'); + + var clone = item.cloneNode(true); + var nestedLists = clone.querySelectorAll('ul, ol'); + for (var n = 0; n < nestedLists.length; n++) { + nestedLists[n].parentNode.removeChild(nestedLists[n]); + } + var text = docHtmlToMarkdown(clone).trim(); + + var actualMarker = marker === '1. ' ? (counter + '. ') : marker; + result += indent + actualMarker + text + '\n'; + counter++; + + if (nestedUl) { + result += convertList(nestedUl, '- ', depth + 1); + } + if (nestedOl) { + result += convertList(nestedOl, '1. ', depth + 1); + } + } + + return result; + } + + function sheetTableToMarkdown(tableEl) { + var rows = tableEl.querySelectorAll('tr'); + if (rows.length === 0) return ''; + + var result = ''; + var colCount = 0; + + // Filter out completely empty rows + var nonEmptyRows = []; + for (var r = 0; r < rows.length; r++) { + var cells = rows[r].querySelectorAll('td, th'); + var hasContent = false; + for (var c = 0; c < cells.length; c++) { + if (cells[c].textContent.trim()) { + hasContent = true; + break; + } + } + if (hasContent) nonEmptyRows.push(rows[r]); + } + + if (nonEmptyRows.length === 0) return ''; + + for (var r = 0; r < nonEmptyRows.length; r++) { + var cells = nonEmptyRows[r].querySelectorAll('td, th'); + if (cells.length > colCount) colCount = cells.length; + + result += '|'; + for (var c = 0; c < cells.length; c++) { + var cellText = cells[c].textContent.trim().replace(/\n+/g, ' ').replace(/\|/g, '\\|'); + result += ' ' + cellText + ' |'; + } + result += '\n'; + + // Add separator after first row (header) + if (r === 0) { + result += '|'; + for (var s = 0; s < cells.length; s++) { + result += ' --- |'; + } + result += '\n'; + } + } + + return result; + } + + function finalize(markdown, type) { + // Extract title + var title = ''; + var titleEl = document.querySelector('[data-document-title]') || + document.querySelector('.docs-title-input') || + document.querySelector('.doc-title') || + document.querySelector('title'); + if (titleEl) { + title = titleEl.getAttribute('data-document-title') || + titleEl.value || + titleEl.textContent || ''; + title = title.trim(); + // Clean up " - Google Docs" or " - Google Sheets" suffix + title = title.replace(/\s*-\s*Google (Docs|Sheets|Spreadsheets)$/, ''); + } + + var totalLength = markdown.length; + var totalChunks = Math.ceil(totalLength / CHUNK_SIZE); + + window.__gdoc = { + text: markdown, + title: title, + docType: type, + totalLength: totalLength, + totalChunks: totalChunks, + chunkSize: CHUNK_SIZE, + chunk: function (n) { + var start = n * CHUNK_SIZE; + if (start >= this.totalLength) return JSON.stringify({ error: 'Chunk index out of range', max: this.totalChunks - 1 }); + return this.text.substring(start, start + CHUNK_SIZE); + } + }; + + return JSON.stringify({ + status: 'ready', + totalLength: totalLength, + totalChunks: totalChunks, + chunkSize: CHUNK_SIZE, + title: title, + docType: type, + instructions: 'Run window.__gdoc.chunk(0), window.__gdoc.chunk(1), etc. to read each chunk.' + }); + } +})(); diff --git a/plugins/bedrock/skills/healthcheck/SKILL.md b/plugins/bedrock/skills/healthcheck/SKILL.md new file mode 100644 index 0000000..0511706 --- /dev/null +++ b/plugins/bedrock/skills/healthcheck/SKILL.md @@ -0,0 +1,380 @@ +--- +name: healthcheck +description: > + Read-only vault health diagnostic. Generates a report without modifying any files. + Checks: graphify-out integrity, setup verification, orphan entities, dangling content, + old content (>15 days). Safe to run at any frequency. + Use when: "bedrock healthcheck", "bedrock-healthcheck", "vault health", "check vault", + "vault status", "/bedrock:healthcheck". +user_invocable: true +allowed-tools: Bash, Read, Glob, Grep +--- + +# /bedrock:healthcheck — Vault Health Report + +## Plugin Paths + +Entity definitions and templates are in the plugin directory, not 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 diagnose. 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. + +**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` 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. +- Graphify output: `<VAULT_PATH>/graphify-out/` + +--- + +## Overview + +This skill produces a diagnostic report of vault health without modifying any files. +It scans the vault once, runs 5 checks against the scan data, and prints the results. + +**You are a read-only agent.** You do NOT write files, commit, push, invoke other skills, +or spawn subagents. Your only output is the diagnostic report printed to the terminal. + +### Five health checks + +| # | Check | What it verifies | +|---|---|---| +| 1 | graphify-out | Graph.json exists, is valid, is fresh | +| 2 | Setup | Directories, templates, entity definitions, plugin manifest | +| 3 | Orphan entities | Entities with zero inbound wikilinks | +| 4 | Dangling content | Entities fully disconnected (no inbound, no outbound, no relations) | +| 5 | Old content | Entities with `updated_at` older than 15 days | + +### Status values + +Each check reports one of: +- **OK** — no issues found +- **WARN** — issues found (with details) +- **MISSING** — prerequisite not met (e.g., graphify-out absent) + +--- + +## Phase 1 — Scan the Vault + +Read all entity files once and store the data for use across all 5 checks. + +### 1.1 Enumerate entity files + +For each entity directory (`<VAULT_PATH>/actors/`, `<VAULT_PATH>/people/`, `<VAULT_PATH>/teams/`, `<VAULT_PATH>/concepts/`, `<VAULT_PATH>/topics/`, `<VAULT_PATH>/discussions/`, `<VAULT_PATH>/projects/`, `<VAULT_PATH>/fleeting/`): + +1. List all `.md` files using Glob, **excluding `_template.md` and `_template_node.md`** + - For actors: include both `<VAULT_PATH>/actors/*.md` (flat) and `<VAULT_PATH>/actors/*/*.md` (folder) + - Include code entities: `<VAULT_PATH>/actors/*/nodes/*.md` +2. Record the directory and filename for each entity + +### 1.2 Read entity data + +For each entity file found in 1.1: + +1. Read the file +2. Extract from frontmatter: + - `type` + - `name` (or derive from filename) + - `aliases` (array) + - `updated_at` (date string) + - All frontmatter array fields that contain wikilinks (e.g., `actors`, `people`, `teams`, `related_to`, etc.) +3. Extract from body: + - All wikilinks `[[target]]` (regex: `\[\[([^\]]+)\]\]`) +4. Compute: + - `outbound_wikilinks`: union of all wikilinks from body + frontmatter arrays + - `has_frontmatter_relations`: true if any frontmatter array field contains at least one wikilink value + - `entity_slug`: the filename without extension (used as the canonical identifier) + +**Output:** `vault_entities` map: `entity_slug → {type, name, aliases[], updated_at, outbound_wikilinks[], has_frontmatter_relations, file_path}` + +Also collect: `all_entity_slugs` — set of all entity slugs in the vault (for resolving wikilinks). + +--- + +## Phase 2 — Run Checks + +### 2.1 Check 1 — graphify-out + +1. Check if directory `<VAULT_PATH>/graphify-out/` exists: + ```bash + ls -d <VAULT_PATH>/graphify-out/ 2>/dev/null && echo "EXISTS" || echo "MISSING" + ``` + +2. If MISSING: + - Status: **MISSING** + - Details: "graphify-out/ directory not found. Run `/bedrock:teach` on an actor repository to generate." + - Skip remaining sub-checks. + +3. If EXISTS, check `<VAULT_PATH>/graphify-out/graph.json`: + ```bash + test -f <VAULT_PATH>/graphify-out/graph.json && echo "EXISTS" || echo "MISSING" + ``` + +4. If graph.json MISSING: + - Status: **MISSING** + - Details: "graph.json not found in graphify-out/. Run `/bedrock:teach` to generate." + - Skip remaining sub-checks. + +5. If graph.json EXISTS, validate and extract stats: + ```bash + python3 -c " + import json, os, time + from pathlib import Path + + g = json.loads(Path('<VAULT_PATH>/graphify-out/graph.json').read_text()) + nodes = g.get('nodes', []) + code_nodes = [n for n in nodes if n.get('file_type') == 'code'] + mtime = os.path.getmtime('<VAULT_PATH>/graphify-out/graph.json') + mod_date = time.strftime('%Y-%m-%d', time.localtime(mtime)) + days_old = (time.time() - mtime) / 86400 + + print(f'total_nodes={len(nodes)}') + print(f'code_nodes={len(code_nodes)}') + print(f'mod_date={mod_date}') + print(f'days_old={int(days_old)}') + print(f'stale={\"yes\" if days_old > 30 else \"no\"}') + " 2>/dev/null || echo "INVALID_JSON" + ``` + +6. If INVALID_JSON: + - Status: **WARN** + - Details: "graph.json exists but is not valid JSON." + +7. If valid: + - If stale (>30 days): Status: **WARN**, Details: "Graph.json is stale (>30 days). Last updated: {mod_date}. Run `/bedrock:teach` or `/bedrock:sync` to update." + - If fresh: Status: **OK**, Details: "{total_nodes} nodes ({code_nodes} code). Last updated: {mod_date}." + +**Store:** `graphify_status`, `graphify_details`, `graphify_node_count` + +### 2.2 Check 2 — Setup + +Verify the vault structure and plugin dependencies. + +#### 2.2.1 Entity directories + +Check that each expected directory exists: +- `<VAULT_PATH>/actors/`, `<VAULT_PATH>/people/`, `<VAULT_PATH>/teams/`, `<VAULT_PATH>/concepts/`, `<VAULT_PATH>/topics/`, `<VAULT_PATH>/discussions/`, `<VAULT_PATH>/projects/`, `<VAULT_PATH>/fleeting/` + +For each directory, check if `_template.md` exists inside it. + +Record: list of missing directories, list of directories missing templates. + +#### 2.2.2 Entity definitions + +Check that entity definitions exist in the plugin directory: +``` +<base_dir>/../../entities/ +``` + +Expected files: `actor.md`, `person.md`, `team.md`, `concept.md`, `topic.md`, `discussion.md`, `project.md`, `fleeting.md` + +Record: list of missing entity definitions. + +#### 2.2.3 Plugin manifest + +Check that `.claude-plugin/plugin.json` exists and is valid JSON: +```bash +python3 -c "import json; json.loads(open('.claude-plugin/plugin.json').read()); print('VALID')" 2>/dev/null || echo "INVALID" +``` + +Record: manifest status. + +#### 2.2.4 Result + +- If everything present: Status: **OK**, Details: "All directories, templates, definitions, and manifest verified." +- If issues found: Status: **WARN**, Details: list of missing items. + +**Store:** `setup_status`, `setup_details`, `setup_issue_count` + +### 2.3 Check 3 — Orphan entities + +For each entity in `vault_entities`: + +1. Count **inbound wikilinks**: how many OTHER entities reference this entity via `[[entity_slug]]` + - Search: count how many entities in `vault_entities` have this entity's slug in their `outbound_wikilinks` + - Also check for alias matches: if entity has `aliases`, check if any alias (converted to slug format) appears in other entities' outbound wikilinks +2. If inbound count = 0: mark as **orphan** + +Exclude from orphan check: +- Templates (`_template.md`) +- The entity itself (self-links don't count) + +**Store:** `orphan_entities[]` — list of `{entity_slug, type}` +**Aggregate:** `orphan_count_by_type` — map `type → count` + +Result: +- If 0 orphans: Status: **OK** +- If orphans found: Status: **WARN**, Details: count per type + entity names + +### 2.4 Check 4 — Dangling content + +From the orphan list (Check 3), further filter for entities that are **fully disconnected**: + +An entity is dangling if ALL three conditions are true: +1. **No inbound wikilinks** (already orphan from Check 3) +2. **No outbound wikilinks** in the body (entity's `outbound_wikilinks` is empty) +3. **No frontmatter relations** (entity's `has_frontmatter_relations` is false) + +**Store:** `dangling_entities[]` — list of `{entity_slug, type}` + +Result: +- If 0 dangling: Status: **OK** +- If dangling found: Status: **WARN**, Details: entity names + +### 2.5 Check 5 — Old content + +For each entity in `vault_entities`: + +1. Read `updated_at` from the entity data +2. If `updated_at` is missing or cannot be parsed: skip (do not flag — missing metadata is a setup issue, not a staleness issue) +3. Calculate age in days: `current_date - updated_at` +4. If age > 15 days: mark as **old** + +**Store:** `old_entities[]` — list of `{entity_slug, type, updated_at, age_days}`, sorted by `age_days` descending (oldest first) +**Aggregate:** `old_count_by_type` — map `type → count` + +Result: +- If 0 old: Status: **OK** +- If old found: Status: **WARN**, Details: count per type, oldest entity with age + +--- + +## Phase 3 — Generate Report + +Print the full report to the terminal. + +```markdown +## /bedrock:healthcheck — Report + +| Check | Status | Count | Details | +|---|---|---|---| +| graphify-out | {graphify_status} | {node_count} nodes | {graphify_details} | +| Setup | {setup_status} | {setup_issue_count} issues | {setup_details} | +| Orphan entities | {orphan_status} | {orphan_count} orphans | {orphan_details} | +| Dangling content | {dangling_status} | {dangling_count} dangling | {dangling_details} | +| Old content (>15d) | {old_status} | {old_count} stale | {old_details} | +``` + +### Orphan details (if WARN) + +```markdown +### Orphan Entities + +| # | Entity | Type | +|---|---|---| +| 1 | [[entity-slug]] | actor | +| 2 | [[entity-slug]] | person | +| ... | ... | ... | + +**By type:** actors: N, people: N, teams: N, ... +``` + +### Dangling details (if WARN) + +```markdown +### Dangling Content (fully disconnected) + +| # | Entity | Type | +|---|---|---| +| 1 | [[entity-slug]] | fleeting | +| ... | ... | ... | +``` + +### Old content details (if WARN) + +```markdown +### Old Content (>15 days without update) + +| # | Entity | Type | Last updated | Age | +|---|---|---|---|---| +| 1 | [[entity-slug]] | actor | 2026-03-01 | 45 days | +| 2 | [[entity-slug]] | topic | 2026-03-15 | 31 days | +| ... | ... | ... | ... | ... | + +**By type:** actors: N, topics: N, ... +``` + +### Suggestions + +Based on findings, append actionable suggestions: + +- If orphan or dangling count > 0: "Run `/bedrock:compress` to detect and fix alignment issues (broken backlinks, missing entities)." +- If graphify-out is MISSING or stale: "Run `/bedrock:teach` on an actor repository to generate or update the graph." +- If old count > 0: "Review {N} stale entities for relevance. Consider updating or archiving." +- If setup has issues: "Run `/bedrock:setup` to initialize missing directories or templates." +- If all checks are OK: "Vault is healthy. No action needed." + +--- + +## Error Handling + +| Situation | Action | +|---|---| +| Empty vault (no entity files) | Report all checks as OK with 0 counts. "Vault is empty — no entities found." | +| Entity file cannot be read | Skip entity, do not fail the check. Note in report: "N entities skipped due to read errors." | +| Frontmatter cannot be parsed | Skip entity for frontmatter-dependent checks (updated_at, relations). Count as readable for wikilink checks. | +| graphify-out/graph.json is not valid JSON | Report as WARN for Check 1. Continue with other checks. | +| Plugin directory not accessible | Report as WARN for Check 2. Continue with other checks. | +| No `updated_at` field in entity | Skip for Check 5 (old content). Do not flag as old. | + +--- + +## Critical Rules + +| Rule | Detail | +|---|---| +| Read-only | NEVER use Write, Edit, Skill, or Agent tools. This skill only reads and reports. | +| No git operations | NEVER run git add, commit, push, pull, or any mutating git command. | +| No skill invocations | NEVER invoke /bedrock:compress, /bedrock:teach, /bedrock:preserve, or any other skill. Suggest them in text only. | +| Terminal output only | The report is printed to the terminal. No files are created or modified. | +| Single-pass scan | Phase 1 scans once. All 5 checks in Phase 2 use the same scan data. | +| Exclude templates | Always exclude `_template.md` and `_template_node.md` from entity counts and checks. | +| 15-day threshold | Old content is defined as `updated_at` > 15 days from current date. Hardcoded. | +| 30-day threshold | Stale graph is defined as graph.json modification date > 30 days. Hardcoded. | +| Bare wikilinks | Parse wikilinks as `[[name]]` only. Never path-qualified (`[[dir/name]]`). | +| Sensitive data | NEVER include credentials, tokens, passwords, PANs, CVVs in the report. | +| Vault resolution first | Resolve `VAULT_PATH` before any file operation — never assume CWD is the vault | +| All entity paths use `<VAULT_PATH>/` prefix | `<VAULT_PATH>/actors/`, not `actors/` | diff --git a/plugins/bedrock/skills/preserve/SKILL.md b/plugins/bedrock/skills/preserve/SKILL.md new file mode 100644 index 0000000..f9fe9aa --- /dev/null +++ b/plugins/bedrock/skills/preserve/SKILL.md @@ -0,0 +1,966 @@ +--- +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). + +**Step 7 — Record merge stats for the Phase 7 report.** Capture `nodes_added`, `nodes_merged`, `edges_added` (from Step 3's Python stdout) and `stale_flag_set` (from Step 6). These values are threaded through to Phase 7's report block under a new **"Graphify merge"** section and returned in the skill's result payload to the caller (e.g., `/bedrock:teach`). + +**Step 8 — Point subsequent phases at the merged location.** After the merge succeeds, set `graphify_output_path := <VAULT_PATH>/graphify-out/` for all downstream phases. Phase 1.3 (graphify-output parsing), Phase 2 (matching), and the rest of the flow read from the merged vault location — not from the original temp input. + +--- + +## Phase 1 — Parse Input + +`/bedrock:preserve` accepts three input modes. Determine which to apply: + +### 1.1 Structured input + +When called by another skill (e.g., `/bedrock:teach`) or when the user provides an explicit list. +The format is a list of entities, each with: + +```yaml +- type: actor | person | team | concept | topic | discussion | project | fleeting | code + name: "canonical entity name" + action: create | update + content: "content to include in the entity body" + relations: + actors: ["actor-slug-1", "actor-slug-2"] + people: ["person-slug-1"] + teams: ["team-slug-1"] + concepts: ["concept-slug-1"] + topics: ["topic-slug-1"] + discussions: ["discussion-slug-1"] + projects: ["project-slug-1"] + code: ["node-slug-1"] + source: "github | confluence | jira | session | manual | gdoc | csv | graphify" + metadata: {} # additional frontmatter fields specific to the type +``` + +If the input follows this format (or something close): parse directly and go to Phase 2. + +### 1.2 Free-form input + +When the user provides natural text, meeting notes, session context, or any +unstructured content. Analyze the text and extract: + +1. **Mentioned entities** — identify by name, alias, or reference: + - People: names in "First Last" format + - Actors: service names, APIs, repositories + - Teams: squad names + - Concepts: patterns, principles, techniques, protocols, abstractions + - Topics: discussion themes, bugs, RFCs, features + - Discussions: meetings, decisions, debates + - Projects: initiatives, migrations, cross-team features + +2. **Inferred action** — for each entity: + - If the entity already exists in the vault: `update` + - If the entity does not exist: `create` + +3. **Content** — what was said about each entity in the input + +4. **Relations** — infer which entities relate to each other based on context + +5. **Source** — infer: `session` (conversation), `meeting-notes` (minutes), `manual` (typed text) + +To classify new content, consult the plugin's entity definitions (see "Plugin Paths" section) (loaded in Phase 2.0): +- "When to create" section → positive criteria for creating a new entity +- "When NOT to create" section → exclusion criteria +- "How to distinguish from other types" section → disambiguation + +Convert the result to the structured format from section 1.1 and proceed. + +### 1.3 Graphify output input + +When called by `/bedrock:teach` (or any skill) with a graphify output reference, +OR when the user invokes `/bedrock:preserve` directly pointing at a `graphify-out/` directory: + +**Input format:** +- `graphify_output_path`: path to `graphify-out/` directory +- `source_url`: original external source URL/path (optional — may not be present for manual invocation) +- `source_type`: type of external source (optional) + +**Detection:** If the input contains a path ending in `graphify-out/` or `graphify-out`, +or references `graph.json`, treat as graphify output input. + +**Processing:** + +1. **Read graph.json** from `graphify_output_path/graph.json`: + - Parse NetworkX node-link format + - Extract all nodes with: `id`, `label`, `file_type`, `source_file`, `source_location` + - Extract all edges with: `source`, `target`, `relation`, `confidence`, `confidence_score` + - If `graph.json` is missing or empty: abort with error "No graph.json found in graphify output. Run /graphify first." + +2. **Read obsidian files** from `graphify_output_path/obsidian/*.md`: + - For each markdown file, read frontmatter and body content + - Correlate with graph.json by matching filename stem to node `id` (kebab-cased) + - If obsidian file doesn't exist for a node: fall back to graph.json metadata alone + +3. **Read analysis** from `graphify_output_path/.graphify_analysis.json` (if exists): + - Extract community assignments, god nodes, community labels + - Use community labels to inform `domain/*` tags when creating entities + +4. **Classify graphify nodes into vault entity types** — /preserve owns this classification: + - Read ALL entity definitions from plugin (see "Plugin Paths") + - For each graphify node, classify: + - `file_type: code` → `code` (actor inferred from `source_file` path or repo name in the path) + - `file_type: document` or `file_type: paper` → check for concept first: if the node describes a pattern, principle, technique, protocol, or abstraction AND is self-contained AND is not specific to a single actor → `concept` + - `file_type: document` (non-concept) → classify using entity definitions ("When to create" / "When NOT to create" / "How to distinguish") + - `file_type: paper` (non-concept) → `topic` or `fleeting` depending on completeness criteria + - God nodes (high degree in `.graphify_analysis.json`) → consider as `actor`, `concept`, or `topic` + - Apply Zettelkasten classification (section 1.4): if content doesn't meet completeness criteria → `fleeting` + +5. **Filter relevant nodes:** + - For code entities: select top ~50 by relevance (degree > average, or label contains "Service", "Controller", "Client", "Factory", "Handler", "Mapper", "Gateway", "Provider"). Exclude test nodes (labels with "Test", "Tests", "Builder", "Mock", "Fake") and trivial nodes (getters, setters, simple DTOs). + - For document/paper nodes: include all. + +6. **Match against existing vault** — Use existing textual matching logic from Phase 2 (filename, name, aliases, `graphify_node_id`). Mark matched nodes as `update`, unmatched as `create`. + +7. **Build internal structured format** for each classified + filtered entity: + - `type`: from classification (step 4) + - `name`: from graphify node `label` (kebab-cased for filename) + - `action`: `create` or `update` (from step 6 matching) + - `content`: from obsidian markdown file body (or generate from graph.json metadata if no obsidian file) + - `relations`: from graph.json edges (convert node ids to entity slugs via kebab-case) + - `source`: from input `source_type` (or `"graphify"` if not provided) + - `source_url`: from input `source_url` (if provided) + - `source_type`: from input `source_type` (if provided) + - `metadata`: for code entities, include: + - `graphify_node_id`: node `id` from graph.json + - `actor`: wikilink of the parent actor (inferred from `source_file` path) + - `node_type`: infer from context (`function`, `class`, `module`, `interface`, `endpoint`) + - `source_file`: relative path from graph.json + - `confidence`: from the strongest edge connected to the node (`EXTRACTED` > `INFERRED` > `AMBIGUOUS`) + - `metadata`: for concepts (from graphify), include: + - `graphify_node_id`: node `id` from graph.json + - `confidence`: from the strongest edge connected to the node (`EXTRACTED` > `INFERRED` > `AMBIGUOUS`) + +8. **Proceed to Phase 3** (Change Proposal) — present the classified entity list for user confirmation, then execute writes as normal (Phases 4-7). + +> **Note:** When invoked directly by the user (not via /teach), the user confirmation in Phase 3 +> is the only gate before writes. When invoked via /teach, /teach has already shown the user +> the graphify report (god nodes, communities) providing context for the confirmation. + +### 1.4 Zettelkasten Classification + +Before converting to structured format, classify each entity by Zettelkasten role. +Consult the plugin's entity definitions ("Completeness Criteria" section) to determine the correct type: + +**Classification rule:** +- If the content meets the completeness criteria of a permanent type (actor, person, team) → classify as permanent +- If the content has `graphify_node_id` and `actor` defined → classify as `code` (permanent extension, sub-entity of actor) +- If the content defines a pattern, principle, technique, protocol, or abstraction that is self-contained and actor-independent → classify as `concept` (permanent) +- If the content meets the completeness criteria of a bridge type (topic, discussion) → classify as bridge +- If the content meets the completeness criteria of an index type (project) → classify as index +- **If the content does NOT meet the completeness criteria of any type** → classify as `fleeting` + +**Heuristics for fleeting:** +- Vague mention without concrete data (no repo name, no full person name, no date, no decision) +- Idea or hypothesis without confirmation ("it seems like...", "maybe...", "someone mentioned...") +- Fragment of information without sufficient context to be self-contained +- Generic TODO without assignee or deadline + +**When in doubt, err on the side of fleeting** — it is safer to capture as fleeting and promote later than to create an incomplete permanent entity. + +If the input came from another skill (e.g., `/bedrock:teach`) and already includes a classification suggestion (`type: fleeting`), respect the suggestion but validate against the criteria above. + +If no input was provided: ask the user "What would you like to preserve in the vault? Provide text, meeting notes, or a list of entities." + +--- + +## Phase 2 — Matching with Existing Entities + +**Objective:** Correlate entities from the input with the existing vault. + +### 2.0 Read entity definitions + +Read ALL entity definition files from the plugin (see "Plugin Paths" section): +`<base_dir>/../../entities/*.md` +These files define what each entity type is, when to create, when NOT to create, and how +to distinguish between types. Internalize these definitions — you will use them to classify new +content (especially in free-form mode, Phase 1.2). + +### 2.1 Collect vault entities + +List all files in each entity directory (exclude `_template.md` and `_template_node.md`): + +``` +<VAULT_PATH>/actors/*.md and <VAULT_PATH>/actors/*/*.md (actors can be folders) +<VAULT_PATH>/actors/*/nodes/*.md (code entities within actors) +<VAULT_PATH>/people/*.md +<VAULT_PATH>/teams/*.md +<VAULT_PATH>/topics/*.md +<VAULT_PATH>/discussions/*.md (if exists) +<VAULT_PATH>/projects/*.md (if exists) +<VAULT_PATH>/fleeting/*.md (if exists) +``` + +For each file found, extract: +- `filename` (without extension) — canonical identifier +- `name` (or `title`) from frontmatter — human-readable name +- `aliases` from frontmatter — alternative names +- `graphify_node_id` from frontmatter — for code entities (if present) + +### 2.2 Textual matching + +For each entity from the input, check if it already exists in the vault: + +**Match rules (in priority order):** + +1. **Exact match by filename** (case-insensitive): `billing-api` == `billing-api` +2. **Match by name/title field** (case-insensitive): `"Billing API"` finds `billing-api.md` +3. **Match by aliases** (case-insensitive): `"BillingAPI"` finds `billing-api.md` if alias contains "BillingAPI" +4. **Match by filename without hyphens** (case-insensitive): `billing-api` → `billingapi` finds "BillingAPI" +5. **Match by graphify_node_id** (for code entities): exact match by `graphify_node_id` in frontmatter. This is the most reliable match for code entities and takes priority over the others when present. + +**Safety rules:** +- DO NOT match by substrings of 3 characters or fewer (e.g., "api" should not match everything) +- Maximum 20 correlations per entity type +- In case of ambiguity: record all candidates and resolve in Phase 3 (proposal) + +### 2.3 Classify actions + +For each entity from the input: +- If match found in vault: mark as `update` (update existing entity) +- If no match: mark as `create` (new entity) +- If the input already specified the action: respect the input's action + +### 2.4 Enrich via external sources (best-effort) + +For entities of type `actor` that have a `repository` field in frontmatter: + +**GitHub MCP** (call directly, NOT via subagent): +- `mcp__plugin_github_github__list_pull_requests` → recent PRs (5, state=all, sort=updated) +- `mcp__plugin_github_github__list_commits` → recent commits (5) + +**Atlassian MCP**: +- Search for Jira issues from the relevant squad +- Search for related Confluence pages + +> **IMPORTANT:** Enrichment is best-effort. If MCP is not available or fails, continue without it. Record which sources failed in the final report. + +> **IMPORTANT:** DO NOT use subagents for MCP calls. Permissions are not inherited by subagents. + +--- + +## Phase 3 — Change Proposal + +**Objective:** Present to the user EVERYTHING that will be done, BEFORE executing. + +### 3.1 Build proposal + +For each entity, present: + +``` +## Change Proposal — /bedrock:preserve + +### Entities to create +| # | Type | Name | File | Relations | +|---|---|---|---|---| +| 1 | actor | billing-new-api | actors/billing-new-api.md | [[squad-payments]], [[alice-smith]] | + +### Entities to update +| # | Type | Name | File | Changes | +|---|---|---|---|---| +| 1 | actor | billing-api | actors/billing-api.md | Add "Recent Activity" section | + +### Bidirectional links +| Source entity | Target entity | Section added | +|---|---|---| +| [[billing-new-api]] | [[squad-payments]] | "Related Actors" in squad-payments | +| [[squad-payments]] | [[billing-new-api]] | "team" in billing-new-api | + +### Sources consulted +- ✅ Local vault +- ✅ / ❌ GitHub MCP +- ✅ / ❌ Atlassian MCP + +Total: N entities to create, M to update, P bidirectional links. +``` + +### 3.2 Await confirmation + +Ask: "Confirm execution? (yes/no/adjust)" + +- **yes**: proceed to Phase 4 +- **no**: abort and inform +- **adjust**: ask what to adjust, modify proposal, re-present + +**DO NOT proceed without explicit user confirmation.** + +--- + +## Phase 4 — Execute Changes + +**Objective:** Create and update entities as per the approved proposal. + +### 4.1 Create new entities + +For each entity marked as `create`: + +1. Read the plugin template: `<base_dir>/../../templates/<directory>/_template.md` +2. Fill frontmatter with data from input + matching: + - `type`: entity type + - `name` (or `title` for topics): extracted name + - `aliases`: generate at least 1 alias following the convention per type (see conventions.md) + - `tags`: use hierarchical tags: `[type/<type>, status/<status>, domain/<domain>]` + - `updated_at`: today's date (YYYY-MM-DD) + - `updated_by`: "preserve@agent" + - Relation fields: wikilinks to correlated entities + - Remaining fields: fill with data from input or leave empty +3. Fill body following the template structure +4. Add mandatory callouts when applicable: + - Actors with `status: deprecated` → `> [!warning] Deprecated` + - Actors with `pci: true` → `> [!danger] PCI Scope` +5. Save to `<directory>/<filename>.md` + +**Rules per entity type:** + +| Type | Directory | Filename pattern | Name frontmatter key | +|---|---|---|---| +| actor | actors/ or actors/\<name\>/ | `repo-name.md` | `name` | +| code | actors/\<actor\>/nodes/ | `node-slug.md` | `name` | +| person | people/ | `first-last.md` | `name` | +| team | teams/ | `squad-name.md` | `name` | +| concept | concepts/ | `slug.md` | `name` | +| topic | topics/ | `YYYY-MM-category-slug.md` | `title` | +| discussion | discussions/ | `YYYY-MM-DD-slug.md` | `title` | +| project | projects/ | `project-slug.md` | `name` | +| fleeting | fleeting/ | `YYYY-MM-DD-slug.md` | `title` | + +### 4.1.2 Code entity specific rules + +When creating a code entity: + +1. **Resolve the parent actor:** the `actor` field in the input (wikilink or slug) indicates the actor. Verify that the actor exists in `actors/`. +2. **Ensure folder structure:** if the actor is still a flat file (`actors/<name>.md`): + - Create folder `actors/<name>/` + - Move `actors/<name>.md` → `actors/<name>/<name>.md` (use `git mv`) + - Create subfolder `actors/<name>/nodes/` + - Add "Knowledge Nodes" section to the actor body (before the "Infrastructure" section or at the end) +3. **Create code entity:** use template `actors/_template_node.md` + - Save to `actors/<actor>/nodes/<node-slug>.md` + - Filename: kebab-case of the node's `name` (e.g., `ProcessTransaction` → `process-transaction.md`) + - Fill `graphify_node_id`, `actor`, `node_type`, `source_file`, `confidence` from the input + - Inherit `domain/*` tags from the parent actor + - Generate at least 1 alias (human-readable name + camelCase if applicable) +4. **Bidirectional backlink:** + - In the code entity: `actor: "[[actor-name]]"` in frontmatter + - In the actor: add `- [[node-slug]] — brief description` in the "Knowledge Nodes" section + +### 4.1.1 Linking rules by Zettelkasten role + +When filling the entity body, apply semantic linking rules by role: + +- **Permanent notes** (actors, people, teams, concepts): wikilinks in the body must have textual context. + E.g., "receives authorizations from [[payment-gateway]] via gRPC" — not just "[[payment-gateway]]" +- **Bridge notes** (topics, discussions): wikilinks in the body explain *why* permanents relate. + E.g., "the deprecation of [[legacy-gateway]] is blocked because clients depend on [[billing-api]]" +- **Index notes** (projects): wikilinks in the body point to where the knowledge is. + E.g., "progress documented in [[2026-06-deprecation-legacy-gateway]]" +- **Fleeting notes**: exploratory wikilinks allowed without full textual context. + +### 4.2 Update existing entities + +For each entity marked as `update`: + +1. Read the existing file +2. **Frontmatter:** merge — update fields with new data. NEVER delete existing fields. + - ALWAYS update `updated_at` and `updated_by` + - Add new wikilinks to existing arrays (do not duplicate) + - Add new aliases if discovered +3. **Body:** + - **Actors:** can be modified/merged — new information replaces outdated information + - **People, Teams, Concepts, Topics:** append-only — add information, NEVER delete existing content + - **Discussions, Projects:** append-only for the general body; structured fields (action_items, conclusions) can be updated + - **"Recent Activity" section** (actors): REPLACE content (temporal data) +4. **Wikilinks:** add new ones, NEVER remove existing ones + +### 4.3 Populate `sources` field (when applicable) + +If the input contains `source_url` and `source_type` (provided by `/bedrock:teach` or another caller): + +**When creating an entity:** +- Add to frontmatter: + ```yaml + sources: + - url: "<source_url>" + type: "<source_type>" + synced_at: "<today's date>" + ``` + +**When updating an entity:** +1. Read the existing `sources` field from frontmatter +2. If the URL already exists in the list: update `synced_at` with today's date +3. If the URL does not exist: append new entry `{url, type, synced_at}` +4. Sort by `synced_at` descending (most recent first) +5. NEVER remove existing entries (append-only) + +**If the input does NOT contain `source_url`:** do not modify the `sources` field — keep the existing value (or `[]` if new entity). + +--- + +## Phase 5 — Bidirectional Linking + +**Objective:** Ensure that every relation is reciprocal. + +### 5.1 Linking rules + +When creating/updating entity X with a reference to entity Y: +- Check if Y already references X +- If NOT: add reference from Y → X + +**Bidirectional linking graph:** + +``` +Team ──members──→ Person ──team──→ Team +Team ──actors──→ Actor ──team──→ Team +Topic ──people──→ Person +Topic ──actors──→ Actor +Person ──focal_points──→ Actor +Project ──focal_points──→ Person ──projects──→ Project +Project ──related_actors──→ Actor +Project ──related_topics──→ Topic +Project ──related_teams──→ Team +Discussion ──related_actors──→ Actor +Discussion ──related_people──→ Person +Discussion ──related_projects──→ Project +Discussion ──related_topics──→ Topic +Code ──actor──→ Actor ──"Knowledge Nodes" section──→ Code +Code ──relations──→ Code (bidirectional via relations[]) +``` + +### 5.2 Implementation + +For each pair (X → Y) in the approved proposal: + +1. Read entity Y +2. Identify the corresponding field/section in the reverse link (Y → X) +3. **In frontmatter:** if the array field exists, add wikilink `[[X]]` if not already present +4. **In body:** if there is a corresponding section (e.g., `## Discussions`, `## Related Projects`): + - If section exists: add `- [[X]] — brief context` at the end of the list + - If section does NOT exist: create the section in the appropriate location (before "Expected Bidirectional Links" or before the last `---`) +5. Update `updated_at` and `updated_by` of Y +6. Save + +**Idempotency:** if the wikilink `[[X]]` already exists in Y's field/section, DO NOT add it again. + +### 5.3 Linking sections by target entity type + +| Target entity (Y) | Body section | Frontmatter field | +|---|---|---| +| Actor receiving link from Discussion | `## Discussions` | — | +| Actor receiving link from Project | `## Related Projects` | — | +| Person receiving link from Discussion | `## Discussions` | — | +| Person receiving link from Project | `## Projects` | `projects` (if exists) | +| Topic receiving link from Project | `## Related Projects` | — | + +For frontmatter-based links (team↔actor, team↔person, person↔team, etc.): use only the YAML field, do not create a body section. + +--- + +## Phase 6 — Publish + +### 6.1 Prepare commit + +Determine the commit message following the convention: + +**Single entity:** +``` +vault(<type>): <verb> <name> [source: <source>] +``` + +Types: `actor`, `person`, `team`, `concept`, `topic`, `discussion`, `project`, `source` +Verbs: `creates`, `updates`, `links` +Sources: `memory`, `github`, `jira`, `confluence`, `gdoc`, `csv`, `manual`, `session`, `preserve` + +**Multiple entities:** +``` +vault: preserves N entities [source: <sources>] +``` + +Or, if called by `/bedrock:teach`: +``` +vault: teaches <source-name>, creates N updates M entities [source: <type>] +``` + +### 6.2 Execute git workflow + +#### 6.2.1 Stage and commit + +```bash +# Stage touched entities (includes actor subfolders: actors/*/nodes/) +git -C <VAULT_PATH> add actors/ people/ teams/ topics/ discussions/ projects/ fleeting/ + +# Check if there is anything to commit +git -C <VAULT_PATH> diff --cached --quiet && echo "Nothing to commit" && exit 0 +``` + +#### 6.2.2 Read git strategy + +Read the vault's git strategy from `.bedrock/config.json`: + +```bash +cat <VAULT_PATH>/.bedrock/config.json 2>/dev/null +``` + +Extract the `git.strategy` field. If the file does not exist or has no `git` key, default to `"commit-push"`. + +Valid values: `"commit-push"`, `"commit-push-pr"`, `"commit-only"`. + +#### 6.2.3 Dispatch by strategy + +**Strategy: `commit-push`** (default) + +```bash +git -C <VAULT_PATH> commit -m "<message per convention>" +git -C <VAULT_PATH> push origin main +``` + +If push fails (conflict): +```bash +git -C <VAULT_PATH> pull --rebase origin main +git -C <VAULT_PATH> push origin main +``` + +If it fails 2x: STOP and inform the user. +If there is no remote: commit locally and warn. + +--- + +**Strategy: `commit-push-pr`** + +First, check that `gh` is available: + +```bash +which gh 2>/dev/null +``` + +If `gh` is not found: warn the user and **fall back to `commit-push`** strategy (above). + +If `gh` is available: + +1. **Create a branch.** Derive the branch name from the commit message: + + - Single entity: `vault/<YYYY-MM-DD>-<entity-name>` (e.g., `vault/2026-04-15-billing-api`) + - Multiple entities: `vault/<YYYY-MM-DD>-batch-<N>-entities` (e.g., `vault/2026-04-15-batch-7-entities`) + + Check for collisions: + ```bash + git -C <VAULT_PATH> branch --list "vault/<YYYY-MM-DD>-<slug>*" + ``` + If the branch already exists, append a counter: `vault/2026-04-15-billing-api-2`. + + ```bash + git -C <VAULT_PATH> checkout -b <branch-name> + ``` + +2. **Commit and push the branch:** + ```bash + git -C <VAULT_PATH> commit -m "<message per convention>" + git -C <VAULT_PATH> push origin <branch-name> + ``` + +3. **Open a pull request:** + ```bash + cd <VAULT_PATH> && gh pr create --title "<commit message>" --body "Automated by /bedrock:preserve" --base main + ``` + +4. **Return to main:** + ```bash + git -C <VAULT_PATH> checkout main + ``` + +--- + +**Strategy: `commit-only`** + +```bash +git -C <VAULT_PATH> commit -m "<message per convention>" +``` + +Do not push. Output: +``` +Git strategy: commit-only — changes committed locally. Use `git push` manually when ready. +``` + +--- + +## Phase 7 — Report + +Present to the user: + +``` +## Preserve — Report + +### Entities created +| Type | Name | File | Source | +|---|---|---|---| +| actor | payment-new-api | actors/payment-new-api.md | github | + +### Entities updated +| Type | Name | File | Changes | +|---|---|---|---| +| actor | billing-api | actors/billing-api.md | Recent Activity, wikilinks | + +### Bidirectional links applied +| Source | Target | Type | +|---|---|---| +| [[billing-new-api]] | [[squad-payments]] | frontmatter: actors[] | +| [[squad-payments]] | [[billing-new-api]] | frontmatter: team | + +### Graphify merge (only when Phase 0.2 ran) +| Metric | Value | +|---|---| +| Nodes added | N | +| Nodes merged | M | +| Edges added | P | +| Analysis marked stale | true / false | + +Omit this section entirely when Phase 0.2 was skipped (no `graphify_output_path`, or backward-compat path match). + +The same four fields are included in the skill's return payload (e.g., consumed by `/bedrock:teach`): + +```yaml +graphify_merge: + nodes_added: N + nodes_merged: M + edges_added: P + stale_flag_set: true | false +``` + +### Sources consulted +- ✅ Local vault +- ✅ / ❌ GitHub MCP +- ✅ / ❌ Atlassian MCP + +### Git +- Commit: `vault: preserves 2 entities [source: github]` +- Push: ✅ success / ❌ failed (reason) + +### Warnings +- [orphan wikilinks, ambiguous entities, MCP unavailable, etc.] +``` + +--- + +## Critical Rules + +| # | Rule | +|---|---| +| 1 | **NEVER delete content** written by another agent or human (except the "Recent Activity" section in actors, which is temporal) | +| 2 | **NEVER overwrite frontmatter** — only merge new fields. NEVER delete existing fields. | +| 3 | **NEVER commit sensitive data** (credentials, tokens, PANs, CVVs) | +| 4 | **ALWAYS update** `updated_at` and `updated_by` on every touched entity | +| 5 | **ALWAYS use kebab-case** without accents for filenames | +| 6 | **ALWAYS follow the templates** from `_template.md` when creating new pages | +| 7 | **ALWAYS confirm** proposal with user before executing writes | +| 8 | **Maximum 2 push attempts** — after that, abort and inform | +| 9 | **Best-effort for external sources** — never block due to unavailable MCP | +| 10 | **Idempotency in wikilinks** — do not add a link that already exists | +| 11 | **Frontmatter keys in English**, values in the vault's configured language | +| 12 | **Bare wikilinks** — `[[name]]`, never `[[dir/name]]` | +| 13 | **Hierarchical tags** — `[type/actor]`, never `[actor]` | +| 14 | **Mandatory aliases** — at least 1 alias per new entity | +| 15 | **Mandatory callouts** — `[!warning] Deprecated` for deprecated, `[!danger] PCI Scope` for PCI | +| 16 | **Vault resolution first** — resolve `VAULT_PATH` before any file operation or git command | +| 17 | **All git commands use `git -C <VAULT_PATH>`** — never assume CWD is the vault | +| 18 | **All entity paths use `<VAULT_PATH>/` prefix** — `<VAULT_PATH>/actors/`, not `actors/` | +| 19 | **Graphify merge is append-only** — Phase 0.2 never deletes nodes, edges, obsidian content, or GRAPH_REPORT sections. On node-id collision: union `sources` by URL, take most-recent `updated_at`, union labels/tags. On edge collision by `(source, target, type)`: drop the incoming duplicate. | +| 20 | **Graphify merge backward-compat** — if `graphify_output_path` resolves to the same absolute path as `<VAULT_PATH>/graphify-out/`, Phase 0.2 is a no-op. Legacy callers and `/bedrock:sync` continue to work unchanged. | +| 21 | **Graphify merge is atomic** — `graph.json` is merged into a `.staging` file and atomically renamed. If validation or merge fails, the vault's `graph.json` is untouched. | +| 22 | **`.graphify_analysis.json` is marked stale, never recomputed** — Phase 0.2 sets `stale: true` on merge. `/bedrock:compress` owns recomputation. | diff --git a/plugins/bedrock/skills/setup/SKILL.md b/plugins/bedrock/skills/setup/SKILL.md new file mode 100644 index 0000000..8333276 --- /dev/null +++ b/plugins/bedrock/skills/setup/SKILL.md @@ -0,0 +1,1301 @@ +--- +name: setup +description: > + Initialize any folder as a Bedrock-powered Obsidian vault. Creates entity directories, + copies templates, configures language and domain taxonomy, scaffolds connected example + entities, and checks dependencies. + Use when: "bedrock setup", "bedrock-setup", "/bedrock:setup", "initialize vault", + "setup vault", "create vault", "bootstrap vault", or when a user wants to start + a new Second Brain with Bedrock. +user_invocable: true +allowed-tools: Bash, Read, Write, Glob, Grep +--- + +# /bedrock:setup — Vault Initialization + +## Plugin Paths + +Templates and entity definitions 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` (auto-injected into context) + +Where `<base_dir>` is the path shown in "Base directory for this skill". + +--- + +## Overview + +This skill bootstraps any folder into a fully functional Bedrock-powered Obsidian vault +through an interactive guided flow. It creates directories, copies templates, configures +the vault, scaffolds example entities with bidirectional wikilinks, checks dependencies, +and guides the user through next steps. + +**You are a setup agent.** Follow the phases below in order. Do not skip steps. + +--- + +## Phase 0 — Idempotency Check + +Check if the vault is already initialized: + +```bash +ls .bedrock/config.json 2>/dev/null +``` + +**If `.bedrock/config.json` exists:** + +1. Read and display the current configuration: + ``` + This vault is already initialized: + - Language: <language> + - Preset: <preset> + - Domains: <domains> + - Git strategy: <git.strategy or "commit-push" if absent> + - Initialized at: <date> + ``` + +2. Check if this vault is registered in the global vault registry: + ```bash + cat <base_dir>/../../vaults.json 2>/dev/null + ``` + If the registry exists, check if any entry has a `path` matching the current working directory. + - **If registered:** display "Registered as vault `<name>`" alongside the config above. + - **If NOT registered:** display "This vault is not yet registered in the global vault registry." + +3. Ask the user: + > "This vault is already initialized. What would you like to do?" + > 1. **Reconfigure** — Update language, domains, git strategy, and regenerate vault CLAUDE.md (directories and entities are NOT touched) + > 2. **Register only** — Register this vault in the global registry (if not already registered) without changing configuration + > 3. **Skip** — Exit with no changes + + - **Reconfigure**: proceed to Phase 1, but set `RECONFIGURE_MODE = true`. In Phase 3, skip directory creation (3.1), template copying (3.2), Obsidian configuration (3.5), and example entity generation (3.6). Phase 3.7 (vault registration) still runs. + - **Register only**: skip directly to Phase 3.7 (vault registration). If already registered, display "This vault is already registered as `<name>`. No changes made." and exit. + - **Skip**: exit with "No changes made. Vault is already initialized." + +**If `.bedrock/config.json` does NOT exist:** proceed to Phase 1 with `RECONFIGURE_MODE = false`. + +--- + +## Phase 1 — Language and Dependencies + +### 1.1 Language Selection + +Ask the user: + +> "What language should vault content be written in?" +> 1. **English (en-US)** *(default)* +> 2. **Portuguese (pt-BR)** +> 3. **Spanish (es)** +> 4. **Other** — specify a locale code (e.g., `fr-FR`, `de-DE`, `ja-JP`) +> +> Press Enter for default (en-US). + +Store the selected language as `VAULT_LANGUAGE`. This determines: +- The language of example entity content +- The language directive in the vault CLAUDE.md +- The language instruction for all future skill output in this vault + +### 1.2 Dependency Check + +Check for external tools, environment variables, and MCP servers that enhance the Bedrock experience. +**Never block initialization.** + +**Dependencies to check:** + +| Dependency | Check method | What it unlocks | +|---|---|---| +| graphify | Glob: `~/.claude/skills/graphify/SKILL.md` | **Required.** Extraction engine for all `/bedrock:teach` ingestion. Without it, /teach cannot function. | +| docling | Bash: `command -v docling >/dev/null 2>&1` | **Required.** Universal file → markdown converter used by `/bedrock:teach` to ingest DOCX, PPTX, XLSX, HTML, EPUB, PDF, images, and other non-markdown formats. Without it, /teach can only ingest text-native formats. | +| CONFLUENCE_API_TOKEN + CONFLUENCE_USER_EMAIL | Bash: `test -n "$CONFLUENCE_API_TOKEN" && test -n "$CONFLUENCE_USER_EMAIL"` | Confluence page ingestion via `/bedrock:teach` (API strategy). | +| GOOGLE_ACCESS_TOKEN | Bash: `test -n "$GOOGLE_ACCESS_TOKEN"` | Google Docs and Sheets ingestion via `/bedrock:teach` (API strategy). | +| claude-in-chrome MCP | ToolSearch: `select:mcp__claude-in-chrome__tabs_context_mcp` (succeeds = available) | **Optional.** Browser fallback for Confluence pages when API credentials are unavailable. | + +### 1.2.1 Auto-install graphify if missing + +If the graphify probe in the table above returns no file, attempt to install graphify silently before generating the dependency report. Execute this fallback chain in order, stopping at the first successful re-probe. + +**Step 1 — pipx (preferred, isolated):** + +```bash +command -v pipx >/dev/null 2>&1 && pipx install graphifyy && graphify install +``` + +Re-probe: `Glob: ~/.claude/skills/graphify/SKILL.md`. If the file now exists, stop — graphify is installed. + +**Step 2 — pip (if pipx unavailable or Step 1 failed):** + +Only if Step 1's re-probe still finds nothing, and Python 3.10+ is available: + +```bash +{ command -v pip3 >/dev/null 2>&1 || command -v pip >/dev/null 2>&1; } && \ + python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3, 10) else 1)' 2>/dev/null && \ + { pip3 install graphifyy 2>/dev/null || pip install graphifyy; } && graphify install +``` + +Re-probe. If found, stop. + +**Step 3 — curl (Python 3.10+ not available):** + +If Steps 1 and 2 were both unrunnable because `pipx`, `pip`, and Python 3.10+ are all missing, **warn the user explicitly before falling back:** + +> ⚠️ Python 3.10+ is not available on this system. Falling back to manual skill install via `curl`. To receive graphify updates through the official installer, install Python 3.10+ and re-run `/bedrock:setup`. + +Then: + +```bash +mkdir -p ~/.claude/skills/graphify && \ + curl -fsSL https://raw.githubusercontent.com/safishamsi/graphify/v1/skills/graphify/skill.md \ + > ~/.claude/skills/graphify/SKILL.md +``` + +Re-probe. If found, stop. + +**Step 4 — Manual instructions (last resort):** + +If all prior steps failed (no network, upstream unavailable, or all tooling missing), print the graphify warning shown in Section 1.2.2 below. Do not abort — setup continues regardless. + +**Note on package name:** The PyPI package is currently published as `graphifyy` — temporary while the upstream project reclaims the `graphify` name. When that flip happens, update Steps 1 and 2 to `pip install graphify && graphify install`. + +**After the chain completes**, run one final `Glob: ~/.claude/skills/graphify/SKILL.md`. The graphify row in the dependency-report table (Section 1.2.2 below) MUST reflect this post-install status — `installed` if the file now exists, `NOT FOUND` otherwise. Proceed to Section 1.2.2 regardless of outcome. **Never block initialization.** + +### 1.2.1.1 Auto-install docling if missing + +If the docling probe (`command -v docling`) returns nothing, attempt a silent install using the same fallback chain as graphify. Emit a one-line status message before starting — no interactive prompt. + +> docling not found — installing silently (one-time setup; first run may take several minutes to download ML models). + +**Step 1 — pipx (preferred, isolated):** + +```bash +command -v pipx >/dev/null 2>&1 && pipx install docling +``` + +Re-probe: `command -v docling`. If found, stop. + +**Step 2 — pip (if pipx unavailable or Step 1 failed):** + +```bash +{ command -v pip3 >/dev/null 2>&1 || command -v pip >/dev/null 2>&1; } && \ + { pip3 install --user docling 2>/dev/null || pip install --user docling; } +``` + +Re-probe. If found, stop. + +**Step 3 — Manual instructions (last resort):** + +If both steps failed (no `pipx`/`pip`, no network, or a permissions error), print the docling warning shown in Section 1.2.2 below. Do not abort — setup continues regardless. + +**After the chain completes**, run one final `command -v docling` probe. The docling row in the dependency-report table (Section 1.2.2 below) MUST reflect this post-install status — `installed` if the command is now on PATH, `NOT FOUND` otherwise. Proceed to Section 1.2.2 regardless of outcome. **Never block initialization.** + +### 1.2.2 Report status + +**Report format:** + +``` +## Dependency Check + +| Dependency | Status | What it unlocks | +|---|---|---| +| graphify | installed / NOT FOUND | Extraction engine for /teach | +| docling | installed / NOT FOUND | Universal file → markdown converter for /teach | +| Confluence API credentials | configured / NOT SET | Confluence page ingestion (API) | +| Google API token | configured / NOT SET | Google Docs/Sheets ingestion (API) | +| claude-in-chrome MCP | available / NOT FOUND | Browser fallback for Confluence | + +### Source availability summary +| Source type | Status | Requirements | +|---|---|---| +| Confluence | ready / partial / unavailable | API credentials or Chrome extension | +| Google Docs | ready / limited / unavailable | API token or public documents only | +| Google Sheets | ready / limited / unavailable | API token (all tabs) or public (first tab only) | +| GitHub | ready | git CLI | +| Remote URL | ready | WebFetch or curl | +| Local files | ready | filesystem access | +| Non-markdown files (DOCX, PPTX, XLSX, PDF, HTML, EPUB, images) | ready / unavailable | docling installed | +``` + +For **graphify** specifically (required): + +``` +> graphify is not installed. This is REQUIRED for /bedrock:teach to work. +> To install, check https://github.com/safishamsi/graphify for instructions. +> +> Your vault will initialize, but /bedrock:teach will not function until graphify is installed. +``` + +For **docling** specifically (required for non-markdown ingestion): + +``` +> docling is not installed. This is REQUIRED for /bedrock:teach to ingest non-markdown files +> (DOCX, PPTX, XLSX, PDF, HTML, EPUB, images, etc.). +> To install manually: pipx install docling (or: pip install --user docling) +> More info: https://github.com/docling-project/docling +> +> Your vault will initialize, but /bedrock:teach will only handle markdown/text inputs until +> docling is installed. /teach also attempts a silent auto-install on first invocation if the +> dependency is still missing. +``` + +For missing environment variables (optional): + +``` +> CONFLUENCE_API_TOKEN and CONFLUENCE_USER_EMAIL are not set. +> To ingest Confluence pages, generate an API token at: +> https://id.atlassian.com/manage-profile/security/api-tokens +> Then set: CONFLUENCE_API_TOKEN=<token> and CONFLUENCE_USER_EMAIL=<your-email> +> +> Alternative: If you have the Claude in Chrome extension with Confluence logged in, browser extraction will work as a fallback. +> This is optional — your vault will work without Confluence ingestion. +``` + +``` +> GOOGLE_ACCESS_TOKEN is not set. +> To ingest Google Docs/Sheets, generate an access token at: +> https://developers.google.com/oauthplayground/ +> Select scope: https://www.googleapis.com/auth/drive.readonly +> Then set: GOOGLE_ACCESS_TOKEN=<token> +> +> Public Google Docs/Sheets can still be ingested without a token (limited). +> This is optional — your vault will work without Google ingestion. +``` + +**Proceed regardless of results.** Never block initialization for missing dependencies. + +--- + +## Phase 2 — Vault Objective + +### 2.1 Present Presets + +Ask the user: + +> "What is the primary purpose of this vault?" +> +> 1. **Engineering team** — Track services, APIs, teams, and technical decisions +> 2. **Product management** — Track features, research, projects, and analytics +> 3. **Company wiki** — Centralized knowledge base across departments +> 4. **Personal second brain** — Personal knowledge management and learning +> 5. **Open source project** — Track contributors, issues, architecture, and community +> 6. **Custom** — Define your own domains and focus + +### 2.2 Resolve Preset + +Based on the user's selection, resolve the preset configuration from this lookup table: + +```yaml +presets: + engineering: + label: "Engineering team" + domains: [backend, frontend, infra, data, platform, security] + description: "Engineering team knowledge base for tracking services, APIs, technical decisions, and team operations" + team_name: "platform-team" + team_aliases: ["Platform", "Platform Team"] + team_scope: "Core platform services and infrastructure" + team_purpose: "Maintain and evolve the platform layer" + people: + - slug: "alice-chen" + name: "Alice Chen" + aliases: ["Alice Chen", "Alice"] + role: "Tech Lead" + email: "alice.chen@company.com" + focal_points: ["billing-api"] + - slug: "bob-santos" + name: "Bob Santos" + aliases: ["Bob Santos", "Bob"] + role: "Backend Engineer" + email: "bob.santos@company.com" + focal_points: [] + actor_slug: "billing-api" + actor_name: "billing-api" + actor_aliases: ["Billing API", "Billing Service"] + actor_category: "api" + actor_description: "REST API for billing operations — invoices, payments, and subscriptions" + actor_stack: "Go · Gin · PostgreSQL · Kafka" + actor_status: "active" + actor_criticality: "high" + topic_slug: "2026-04-feature-api-migration" + topic_title: "API v2 Migration" + topic_aliases: ["API Migration", "v2 Migration"] + topic_category: "feature" + topic_objective: "Migrate billing API from v1 to v2 with improved performance and new endpoints" + project_slug: "platform-modernization" + project_name: "Platform Modernization" + project_aliases: ["Platform Modernization", "PlatMod"] + project_description: "Modernize the platform layer with new APIs, improved observability, and reduced technical debt" + + product: + label: "Product management" + domains: [product, design, research, analytics, growth] + description: "Product management knowledge base for tracking features, user research, projects, and product analytics" + team_name: "product-team" + team_aliases: ["Product", "Product Team"] + team_scope: "Product strategy, discovery, and delivery" + team_purpose: "Drive product roadmap and user experience" + people: + - slug: "carol-kim" + name: "Carol Kim" + aliases: ["Carol Kim", "Carol"] + role: "Product Manager" + email: "carol.kim@company.com" + focal_points: ["analytics-dashboard"] + - slug: "david-mueller" + name: "David Mueller" + aliases: ["David Mueller", "David"] + role: "UX Researcher" + email: "david.mueller@company.com" + focal_points: [] + actor_slug: "analytics-dashboard" + actor_name: "analytics-dashboard" + actor_aliases: ["Analytics Dashboard", "Dashboard"] + actor_category: "api" + actor_description: "Web dashboard for product analytics — funnels, cohorts, and feature adoption tracking" + actor_stack: "TypeScript · Next.js · PostgreSQL · ClickHouse" + actor_status: "active" + actor_criticality: "medium" + topic_slug: "2026-04-feature-user-research-q1" + topic_title: "Q1 User Research Findings" + topic_aliases: ["User Research Q1", "Q1 Research"] + topic_category: "feature" + topic_objective: "Synthesize Q1 user research findings into actionable product decisions" + project_slug: "product-launch-v2" + project_name: "Product Launch v2" + project_aliases: ["Product Launch v2", "PLv2"] + project_description: "Launch the redesigned product experience with improved onboarding and analytics" + + company-wiki: + label: "Company wiki" + domains: [engineering, product, operations, finance, hr, legal] + description: "Company-wide knowledge base for cross-department collaboration and institutional memory" + team_name: "operations-team" + team_aliases: ["Operations", "Operations Team"] + team_scope: "Cross-functional operations and internal tooling" + team_purpose: "Ensure smooth operations and knowledge sharing across departments" + people: + - slug: "emma-silva" + name: "Emma Silva" + aliases: ["Emma Silva", "Emma"] + role: "Operations Lead" + email: "emma.silva@company.com" + focal_points: ["internal-portal"] + - slug: "frank-weber" + name: "Frank Weber" + aliases: ["Frank Weber", "Frank"] + role: "Knowledge Manager" + email: "frank.weber@company.com" + focal_points: [] + actor_slug: "internal-portal" + actor_name: "internal-portal" + actor_aliases: ["Internal Portal", "Company Portal"] + actor_category: "monolith" + actor_description: "Internal web portal for employee self-service — HR, IT requests, and knowledge base access" + actor_stack: "Python · Django · PostgreSQL · Redis" + actor_status: "active" + actor_criticality: "medium" + topic_slug: "2026-04-feature-onboarding-process" + topic_title: "New Employee Onboarding Process" + topic_aliases: ["Onboarding Process", "New Hire Onboarding"] + topic_category: "feature" + topic_objective: "Standardize the onboarding process for new employees across all departments" + project_slug: "knowledge-base-rollout" + project_name: "Knowledge Base Rollout" + project_aliases: ["KB Rollout", "Knowledge Base Rollout"] + project_description: "Roll out the structured knowledge base across all departments with Bedrock automation" + + personal: + label: "Personal second brain" + domains: [learning, career, projects, ideas, health, finance] + description: "Personal knowledge management vault for learning, projects, ideas, and life organization" + team_name: null # No team for personal vault + people: + - slug: "me" + name: "Me" + aliases: ["Me"] + role: "Owner" + email: "" + focal_points: ["reading-tracker"] + actor_slug: "reading-tracker" + actor_name: "reading-tracker" + actor_aliases: ["Reading Tracker", "Book Tracker"] + actor_category: "monolith" + actor_description: "Personal tool for tracking books, articles, and learning resources" + actor_stack: "Markdown · Obsidian · Dataview" + actor_status: "active" + actor_criticality: "low" + topic_slug: "2026-04-feature-learning-rust" + topic_title: "Learning Rust" + topic_aliases: ["Learning Rust", "Rust Journey"] + topic_category: "feature" + topic_objective: "Track progress and notes while learning the Rust programming language" + project_slug: "side-project-alpha" + project_name: "Side Project Alpha" + project_aliases: ["Side Project Alpha", "SPA"] + project_description: "Build a personal side project to apply new skills and explore interesting technology" + + open-source: + label: "Open source project" + domains: [core, docs, community, ci-cd, integrations] + description: "Open source project knowledge base for tracking architecture, contributors, issues, and community" + team_name: "core-maintainers" + team_aliases: ["Core Maintainers", "Maintainers"] + team_scope: "Core library development and release management" + team_purpose: "Maintain the core library and coordinate community contributions" + people: + - slug: "alice-chen" + name: "Alice Chen" + aliases: ["Alice Chen", "Alice"] + role: "Lead Maintainer" + email: "alice.chen@project.org" + focal_points: ["my-oss-lib"] + - slug: "bob-santos" + name: "Bob Santos" + aliases: ["Bob Santos", "Bob"] + role: "Core Contributor" + email: "bob.santos@project.org" + focal_points: [] + actor_slug: "my-oss-lib" + actor_name: "my-oss-lib" + actor_aliases: ["My OSS Lib", "The Library"] + actor_category: "monolith" + actor_description: "Core open source library — the main project repository" + actor_stack: "TypeScript · Node.js · Jest · GitHub Actions" + actor_status: "active" + actor_criticality: "very-high" + topic_slug: "2026-04-feature-v2-migration" + topic_title: "v2 Migration Guide" + topic_aliases: ["v2 Migration", "Migration Guide"] + topic_category: "feature" + topic_objective: "Plan and document the migration path from v1 to v2 for all users" + project_slug: "v2-roadmap" + project_name: "v2 Roadmap" + project_aliases: ["v2 Roadmap", "Version 2"] + project_description: "Roadmap for the v2 release — breaking changes, new features, and migration tooling" +``` + +### 2.3 Custom Preset + +If the user selects **Custom**: + +1. Ask: "What is the purpose of this vault? (1-2 sentences)" + - Store as `description` + +2. Ask: "List 3-6 domain tags for your vault (comma-separated). These will be used as `domain/*` tags." + - Example: "backend, frontend, mobile, data, devops" + - Store as `domains` + +3. Ask: "Would you like me to generate example entities, or skip them?" + - If generate: ask for a team name, 2 people names, an actor name (or use generic defaults: `example-team`, `alice-example`, `bob-example`, `example-service`, `example-topic`, `example-project`) + - If skip: set `SKIP_EXAMPLES = true` + +Build a custom preset object following the same structure as the named presets. +For fields not provided by the user, use sensible generic defaults. + +### 2.4 Git Strategy Selection + +Ask the user: + +> "How should Bedrock handle git commits and pushes?" +> +> 1. **commit-push** *(default)* — Commit and push directly to `main` (trunk-based) +> 2. **commit-push-pr** — Commit to a branch, push, and open a pull request targeting `main` +> 3. **commit-only** — Commit locally without pushing (for offline or local-only vaults) +> +> Press Enter for default (commit-push). + +Store the selected strategy as `GIT_STRATEGY`. + +**If the user selects `commit-push-pr`:** + +Check if the `gh` CLI is available: + +```bash +which gh 2>/dev/null +``` + +If `gh` is not found, warn: + +``` +> ⚠️ The `gh` CLI is not installed. The `commit-push-pr` strategy requires it to create pull requests. +> Install it from https://cli.github.com/ before using /bedrock:preserve, /bedrock:compress, or /bedrock:sync. +> +> You can still select this strategy — skills will fall back to `commit-push` if `gh` is not available at runtime. +``` + +Proceed regardless — never block initialization for missing tools. + +--- + +## Phase 3 — Scaffold + +### 3.1 Create Entity Directories + +> **Skip if `RECONFIGURE_MODE = true`.** + +Create all 7 entity directories: + +```bash +mkdir -p actors people teams topics discussions projects fleeting +``` + +If any directory already exists, this is a no-op (safe). + +### 3.2 Copy Templates + +> **Skip if `RECONFIGURE_MODE = true`.** + +For each entity type, read the template from the plugin and write it to the vault: + +| Source (plugin) | Destination (vault) | +|---|---| +| `<base_dir>/../../templates/actors/_template.md` | `actors/_template.md` | +| `<base_dir>/../../templates/people/_template.md` | `people/_template.md` | +| `<base_dir>/../../templates/teams/_template.md` | `teams/_template.md` | +| `<base_dir>/../../templates/topics/_template.md` | `topics/_template.md` | +| `<base_dir>/../../templates/discussions/_template.md` | `discussions/_template.md` | +| `<base_dir>/../../templates/projects/_template.md` | `projects/_template.md` | +| `<base_dir>/../../templates/fleeting/_template.md` | `fleeting/_template.md` | + +For each template: +1. Use Read to read the source file from the plugin directory +2. Use Write to write it to the vault directory + +**Copy templates verbatim.** Do not translate or modify them. + +If a `_template.md` already exists in the destination, **overwrite it** — templates should +always match the latest plugin version. + +> **Fallback:** If a template file cannot be read (path resolution fails), report: +> "Could not copy template for `<type>`. You can manually copy it from the plugin's +> `templates/<type>/_template.md` directory." + +### 3.3 Create `.bedrock/config.json` + +Create the `.bedrock/` directory and write the configuration: + +```bash +mkdir -p .bedrock +``` + +Write `.bedrock/config.json` with this schema: + +```json +{ + "version": "1.0.0", + "language": "<VAULT_LANGUAGE>", + "preset": "<selected preset name>", + "domains": ["<domain1>", "<domain2>", "..."], + "git": { + "strategy": "<GIT_STRATEGY>" + }, + "initialized_at": "<today's date YYYY-MM-DD>", + "initialized_by": "init@agent" +} +``` + +**Field definitions:** +- `version`: Always `"1.0.0"` — schema version for future migrations +- `language`: The language code from Phase 1 (e.g., `"en-US"`, `"pt-BR"`) +- `preset`: The selected preset name (e.g., `"engineering"`, `"personal"`, `"custom"`) +- `domains`: Array of domain strings resolved from the preset +- `git.strategy`: The git strategy from Phase 2.3. Valid values: `"commit-push"`, `"commit-push-pr"`, `"commit-only"`. If omitted, defaults to `"commit-push"`. +- `initialized_at`: Today's date in `YYYY-MM-DD` format +- `initialized_by`: Always `"init@agent"` + +### 3.4 Generate Vault CLAUDE.md + +Write a `CLAUDE.md` file at the vault root with content tailored to the selected preset and language. + +**IMPORTANT:** This file describes THIS SPECIFIC VAULT — its purpose, language, and conventions. +It does NOT duplicate the plugin's CLAUDE.md (which covers writing rules, entity types, tags, git workflow, and zettelkasten principles — all auto-loaded by Claude Code when the plugin is active). + +**Template for vault CLAUDE.md:** + +```markdown +# <Vault Name> — CLAUDE.md + +> This vault is powered by the [Bedrock plugin](https://github.com/iurykrieger/claude-bedrock). +> Plugin-level instructions (entity types, writing rules, tags, git workflow) are loaded automatically. +> This file describes what is specific to THIS vault. + +## Purpose + +<vault description from preset> + +## Language + +All content in this vault is written in **<language name> (<locale code>)**. +When creating or updating entities, use <language name> for all text content. +Frontmatter keys remain in English. Technical terms in English are acceptable. + +## Domains + +This vault uses the following domain tags: + +<list of domain/* tags> + +When creating entities, use `domain/<name>` tags from this list. +New domains can be added as the vault grows. + +## Quick Reference + +| Action | Skill | +|---|---| +| Search and query the vault | `/bedrock:ask` | +| Ingest external sources (Confluence, Google Docs, GitHub repositories, remote URLs, and any docling-supported file format — DOCX, PPTX, XLSX, PDF, HTML, EPUB, images, and more) | `/bedrock:teach` | +| Create or update entities manually | `/bedrock:preserve` | +| Deduplicate and check vault health | `/bedrock:compress` | +| Re-sync entities with external sources | `/bedrock:sync` | +``` + +**Adaptation rules:** +- `<Vault Name>`: Derive from the preset label or use the folder name. For custom presets, use the user's stated purpose. +- `<vault description>`: Use the preset's `description` field. +- `<language name>`: Full language name (e.g., "English", "Portuguese", "Spanish"). +- `<locale code>`: The `VAULT_LANGUAGE` value (e.g., `en-US`, `pt-BR`). +- `<list of domain/* tags>`: Format as a markdown list: `- domain/backend`, `- domain/frontend`, etc. + +**Write all CLAUDE.md content in the selected `VAULT_LANGUAGE`.** +If the language is pt-BR, write sections headers and descriptions in Portuguese. +If en-US, write in English. Etc. + +### 3.5 Create Obsidian Configuration + +> **Skip if `RECONFIGURE_MODE = true`.** + +Create a `.obsidian/` directory with default configuration files so the vault is +ready to use in Obsidian immediately — with wikilinks, a color-coded graph view, +and a minimal plugin setup. + +**Step 1:** Create the `.obsidian/` directory: + +```bash +mkdir -p .obsidian +``` + +**Step 2:** For each config file below, check if it already exists. If it does, +skip it and log `"Skipped .obsidian/<file> — already exists"`. If it does not exist, +create it with the content specified. + +#### `.obsidian/app.json` + +```json +{ + "useMarkdownLinks": false, + "newLinkFormat": "shortest", + "strictLineBreaks": false, + "showFrontmatter": true +} +``` + +- `useMarkdownLinks: false` — Obsidian uses wikilinks (matches Bedrock's `[[name]]` convention) +- `newLinkFormat: "shortest"` — generates bare `[[name]]` links without path prefix +- `showFrontmatter: true` — frontmatter is central to Bedrock entities; visible by default + +#### `.obsidian/appearance.json` + +```json +{ + "baseFontSize": 16, + "theme": "obsidian" +} +``` + +- `theme: "obsidian"` — Obsidian's built-in dark theme. Clean, high-contrast. + +#### `.obsidian/graph.json` + +```json +{ + "collapse-filter": true, + "search": "", + "showTags": false, + "showAttachments": false, + "hideUnresolved": false, + "showOrphans": true, + "collapse-color-groups": false, + "colorGroups": [ + { + "query": "tag:#type/actor", + "color": { "a": 1, "rgb": 4886745 } + }, + { + "query": "tag:#type/person", + "color": { "a": 1, "rgb": 5294200 } + }, + { + "query": "tag:#type/team", + "color": { "a": 1, "rgb": 15241530 } + }, + { + "query": "tag:#type/topic", + "color": { "a": 1, "rgb": 10181046 } + }, + { + "query": "tag:#type/discussion", + "color": { "a": 1, "rgb": 15844367 } + }, + { + "query": "tag:#type/project", + "color": { "a": 1, "rgb": 15158332 } + }, + { + "query": "tag:#type/fleeting", + "color": { "a": 1, "rgb": 9807270 } + } + ], + "collapse-display": true, + "showArrow": false, + "textFadeMultiplier": 0, + "nodeSizeMultiplier": 1, + "lineSizeMultiplier": 1, + "collapse-forces": true, + "centerStrength": 0.5, + "repelStrength": 10, + "linkStrength": 1, + "linkDistance": 250, + "scale": 1, + "close": false +} +``` + +**Color palette (7 entity types):** + +| Entity | Tag query | Color | RGB int | +|---|---|---|---| +| actor | `tag:#type/actor` | Blue (#4A90D9) | `4886745` | +| person | `tag:#type/person` | Green (#50C878) | `5294200` | +| team | `tag:#type/team` | Orange (#E8913A) | `15241530` | +| topic | `tag:#type/topic` | Purple (#9B59B6) | `10181046` | +| discussion | `tag:#type/discussion` | Gold (#F1C40F) | `15844367` | +| project | `tag:#type/project` | Red (#E74C3C) | `15158332` | +| fleeting | `tag:#type/fleeting` | Grey (#95A5A6) | `9807270` | + +Key graph settings: +- `collapse-color-groups: false` — color groups panel starts expanded so users see the mapping +- `showTags: false` — tag nodes hidden to keep graph focused on entities +- `showOrphans: true` — orphan entities visible for vault health + +#### `.obsidian/core-plugins.json` + +```json +["graph"] +``` + +- Only `graph` enabled — the minimum required for the color groups to work. + All other core plugins are disabled for a clean experience. + +**Step 3:** Track the results for the setup summary (Phase 4). For each file, +record whether it was `Created` or `Skipped (already exists)`. + +### 3.6 Create Example Entities + +> **Skip if `RECONFIGURE_MODE = true` or `SKIP_EXAMPLES = true`.** + +Using the resolved preset data from Phase 2, create connected example entities. +**Write all entity content in the selected `VAULT_LANGUAGE`.** + +The entities form a mini-graph where every wikilink has a matching backlink. + +#### 3.6.1 Determine Entity Set + +**For all presets except `personal`:** Create 6 entities: +1. Team: `teams/<team_name>.md` +2. Person 1: `people/<person1_slug>.md` +3. Person 2: `people/<person2_slug>.md` +4. Actor: `actors/<actor_slug>.md` +5. Topic: `topics/<topic_slug>.md` +6. Project: `projects/<project_slug>.md` + +**For `personal` preset:** Create 4 entities (no team): +1. Person: `people/<person_slug>.md` +2. Actor: `actors/<actor_slug>.md` +3. Topic: `topics/<topic_slug>.md` +4. Project: `projects/<project_slug>.md` + +#### 3.6.2 Create Team Entity + +> **Skip for `personal` preset.** + +Read the team template from `<base_dir>/../../templates/teams/_template.md` as structural reference. + +Write `teams/<team_name>.md`: + +```markdown +--- +type: team +name: "<team_name>" +aliases: <team_aliases as YAML array> +scope: "<team_scope>" +purpose: "<team_purpose>" +members: ["[[<person1_slug>]]", "[[<person2_slug>]]"] +actors: ["[[<actor_slug>]]"] +jira_board: "" +confluence_space: "" +sources: [] +updated_at: <today YYYY-MM-DD> +updated_by: "init@agent" +tags: [type/team, domain/<first_domain>] +--- + +# <team display name> + +> <team_scope>. <team_purpose>. + +## Members + +| Person | Role | +|---|---| +| [[<person1_slug>]] | <person1_role> | +| [[<person2_slug>]] | <person2_role> | + +## Actors under Ownership + +| Actor | Category | Status | +|---|---|---| +| [[<actor_slug>]] | <actor_category> | <actor_status> | + +## Responsibilities + +- <responsibility derived from team_scope> +- <responsibility derived from team_purpose> +``` + +#### 3.6.3 Create Person Entities + +Read the person template from `<base_dir>/../../templates/people/_template.md` as structural reference. + +**For each person in the preset's `people` array**, write `people/<person_slug>.md`: + +```markdown +--- +type: person +name: "<person_name>" +aliases: <person_aliases as YAML array> +role: "<person_role>" +team: "[[<team_name>]]" +focal_points: <person_focal_points as wikilink array, e.g. ["[[billing-api]]"]> +email: "<person_email>" +github: "" +slack: "" +jira: "" +sources: [] +updated_at: <today YYYY-MM-DD> +updated_by: "init@agent" +tags: [type/person, domain/<first_domain>] +--- + +# <person_name> + +> <person_role> on [[<team_name>]]. <brief context about their focus>. + +## Team + +Member of [[<team_name>]]. + +## Focal Points + +<for each focal_point:> +- [[<focal_point>]] — <brief involvement context> + +## Active Topics + +- [[<topic_slug>]] — <brief description> + +## Projects + +- [[<project_slug>]] — <brief description> +``` + +**For `personal` preset:** Omit the `team` field (set to `""`), omit the `Team` section, and write only 1 person entity. + +#### 3.6.4 Create Actor Entity + +Read the actor template from `<base_dir>/../../templates/actors/_template.md` as structural reference. + +Write `actors/<actor_slug>.md`: + +```markdown +--- +type: actor +name: "<actor_name>" +aliases: <actor_aliases as YAML array> +category: "<actor_category>" +description: "<actor_description>" +repository: "" +stack: "<actor_stack>" +status: "<actor_status>" +team: "[[<team_name>]]" +criticality: "<actor_criticality>" +pci: false +known_issues: [] +sources: [] +last_synced_at: "" +last_synced_sha: "" +updated_at: <today YYYY-MM-DD> +updated_by: "init@agent" +tags: [type/actor, status/<actor_status>, domain/<first_domain>] +--- + +# <actor display name> + +> <actor_description>. + +## Details + +| Field | Value | +|---|---| +| Repository | — | +| Stack | <actor_stack> | +| Status | <actor_status> | +| Criticality | <actor_criticality> | +| PCI | no | +| Team | [[<team_name>]] | + +## Dependencies + +- No dependencies documented yet. + +## Related Topics + +- [[<topic_slug>]] — <brief description> + +## Related Projects + +- [[<project_slug>]] — <brief description> +``` + +**For `personal` preset:** Set `team` to `""` and omit the Team row from the Details table. + +#### 3.6.5 Create Topic Entity + +Read the topic template from `<base_dir>/../../templates/topics/_template.md` as structural reference. + +Write `topics/<topic_slug>.md`: + +```markdown +--- +type: topic +title: "<topic_title>" +aliases: <topic_aliases as YAML array> +category: "<topic_category>" +status: "open" +people: ["[[<person1_slug>]]"] +actors: ["[[<actor_slug>]]"] +objective: "<topic_objective>" +created_at: <today YYYY-MM-DD> +sources: [] +updated_at: <today YYYY-MM-DD> +updated_by: "init@agent" +tags: [type/topic, status/open, category/<topic_category>, domain/<first_domain>] +--- + +# <topic_title> + +> <topic_objective>. + +## Context + +This topic was created as an example during vault initialization. Replace this content +with real context about the topic's background and motivation. + +## People Involved + +| Person | Role | +|---|---| +| [[<person1_slug>]] | focal point | + +## Actors Involved + +| Actor | Relation | +|---|---| +| [[<actor_slug>]] | affected system | + +## History + +| Date | Event | +|---|---| +| <today YYYY-MM-DD> | Topic created during vault initialization | + +## Decisions + +- No decisions recorded yet. + +## Next Steps + +- [ ] Replace this example content with real information +- [ ] Link to related topics and discussions + +## Related Projects + +- [[<project_slug>]] — <brief description> +``` + +#### 3.6.6 Create Project Entity + +Read the project template from `<base_dir>/../../templates/projects/_template.md` as structural reference. + +Write `projects/<project_slug>.md`: + +```markdown +--- +type: project +name: "<project_name>" +aliases: <project_aliases as YAML array> +description: "<project_description>" +status: "planning" +deadline: "" +progress: "Vault initialized — project tracking setup complete" +blockers: [] +action_items: + - description: "Replace example content with real project data" + status: "todo" + deadline: "" + owner: "[[<person1_slug>]]" +focal_points: ["[[<person1_slug>]]"] +related_topics: ["[[<topic_slug>]]"] +related_actors: ["[[<actor_slug>]]"] +related_teams: ["[[<team_name>]]"] +sources: [] +updated_at: <today YYYY-MM-DD> +updated_by: "init@agent" +tags: [type/project, status/planning, domain/<first_domain>] +--- + +# <project_name> + +> <project_description>. + +## Overview + +This project was created as an example during vault initialization. Replace this content +with the real project description, motivation, and expected outcomes. + +## Status + +| Field | Value | +|---|---| +| Status | planning | +| Deadline | — | +| Progress | Vault initialized — project tracking setup complete | + +## Action Items + +| Item | Status | Deadline | Owner | +|---|---|---|---| +| Replace example content with real project data | todo | — | [[<person1_slug>]] | + +## Focal Points + +| Person | Role | +|---|---| +| [[<person1_slug>]] | lead | + +## Related Topics + +| Topic | Relation | +|---|---| +| [[<topic_slug>]] | related topic | + +## Related Actors + +| Actor | Relation | +|---|---| +| [[<actor_slug>]] | affected system | + +## Related Teams + +| Team | Relation | +|---|---| +| [[<team_name>]] | owning team | +``` + +**For `personal` preset:** Remove the `related_teams` field and the "Related Teams" section. + +#### 3.6.7 Verify Bidirectional Links + +After creating all entities, verify the wikilink graph is fully bidirectional. + +**Expected links (non-personal presets):** + +``` +Team → Person 1: members[] ✓ | Person 1 → Team: team field ✓ +Team → Person 2: members[] ✓ | Person 2 → Team: team field ✓ +Team → Actor: actors[] ✓ | Actor → Team: team field ✓ +Topic → Person 1: people[] ✓ | Person 1 → Topic: "Active Topics" ✓ +Topic → Actor: actors[] ✓ | Actor → Topic: "Related Topics" ✓ +Project → Person 1: focal_points[] ✓ | Person 1 → Project: "Projects" ✓ +Project → Actor: related_actors[] ✓ | Actor → Project: "Related Projects" ✓ +Project → Team: related_teams[] ✓ +Project → Topic: related_topics[] ✓ | Topic → Project: "Related Projects" ✓ +``` + +Use Grep to spot-check that each entity file contains the expected wikilinks +to its connected entities. If any link is missing, fix it before proceeding. + +### 3.7 Register Vault in Global Registry + +> **This phase runs in ALL modes** — fresh setup, reconfigure, and register-only. + +Register this vault in the plugin's global vault registry so it can be targeted +by name from any directory using `--vault <name>`. + +**Step 1:** Resolve the registry path: + +``` +REGISTRY_PATH = <base_dir>/../../vaults.json +``` + +**Step 2:** Read the existing registry (if it exists): + +```bash +cat <REGISTRY_PATH> 2>/dev/null +``` + +If the file does not exist or is empty, initialize an empty registry: +```json +{ + "vaults": [] +} +``` + +**Step 3:** Check if this vault is already registered (match by absolute path of CWD): + +- **If already registered:** display "This vault is already registered as `<name>`." and skip to Phase 4. +- **If not registered:** continue to Step 4. + +**Step 4:** Prompt the user for a vault name: + +> "Choose a name for this vault. This name is used with `--vault <name>` to target this vault from any directory." +> Default: `<basename of CWD>` + +Validate the name: +- Must be kebab-case: lowercase, no spaces, only letters, numbers, and hyphens +- Must be unique across all registered vaults +- If invalid or duplicate, ask the user to choose another name + +**Step 5:** Determine default status: + +- If the registry has no other vaults (empty `vaults` array), mark this vault as default (`"default": true`) +- If other vaults exist, ask the user: + > "Set this vault as the default? (current default: `<current_default_name>`) [y/N]" + - If yes: set `"default": true` on this vault and `"default": false` on all others + - If no: set `"default": false` + +**Step 6:** Append the vault entry and write the registry: + +```json +{ + "name": "<vault_name>", + "path": "<absolute path of CWD>", + "default": true | false +} +``` + +Write the updated registry to `REGISTRY_PATH` using the Write tool. Format with 2-space indentation. + +**Step 7:** Confirm: + +``` +Vault registered as `<vault_name>` (<path>). +<if default:> This is your default vault. +<if not default:> Default vault is `<current_default_name>`. Use `/bedrock:vaults --set-default <vault_name>` to change. +``` + +--- + +## Phase 4 — Next Steps Guide + +After all files are created, present the user with a summary and next steps. + +**Summary format:** + +``` +## Vault Initialized + +**Language:** <language> +**Preset:** <preset label> +**Domains:** <comma-separated domains> +**Vault name:** <vault_name> <if default: "(default)"> + +### Files Created + +| Type | Path | +|---|---| +| Config | .bedrock/config.json | +| CLAUDE.md | CLAUDE.md | +| Obsidian | .obsidian/app.json | +| Obsidian | .obsidian/appearance.json | +| Obsidian | .obsidian/graph.json | +| Obsidian | .obsidian/core-plugins.json | +| Template | actors/_template.md | +| Template | people/_template.md | +| Template | teams/_template.md | +| Template | topics/_template.md | +| Template | discussions/_template.md | +| Template | projects/_template.md | +| Template | fleeting/_template.md | +| Example | teams/<team>.md | +| Example | people/<person1>.md | +| Example | people/<person2>.md | +| Example | actors/<actor>.md | +| Example | topics/<topic>.md | +| Example | projects/<project>.md | + +### What's Next? + +1. **Open the vault in Obsidian** — Open this folder as an Obsidian vault. You'll see the example entities + and their connections in the graph view immediately. + +2. **Ingest your first source** — Run `/bedrock:teach <url>` to import content from: + - A GitHub repository URL + - A Confluence page URL + - A Google Docs URL + - A local markdown or CSV file path + +3. **Ask your vault** — Run `/bedrock:ask <question>` to search across all entities. + Example: `/bedrock:ask what do we know about <actor>?` + +4. **Create entities manually** — Run `/bedrock:preserve` with free-form text or structured input + to add new entities to the vault. + +5. **Maintain vault health** — Periodically run `/bedrock:compress` to detect duplicates, + orphan entities, and stale content. + +6. **Replace example content** — The example entities are there to show you how Bedrock works. + Edit or delete them as you start adding real content. + +7. **Manage vaults** — Run `/bedrock:vaults` to list all registered vaults, set a default, + or remove a vault. Use `--vault <name>` on any skill to target a specific vault from + any directory. + +> **Tip:** The graph view is preconfigured with 7 colors — one for each entity type. +> Open Graph View (Ctrl/Cmd+G) to see your entities color-coded by type. +> Customize colors in Graph View → Groups if you prefer different colors. + +> **Tip:** The example entities are fully connected with bidirectional wikilinks. +> Open the Obsidian graph view to see how they relate to each other — this is the +> pattern all future entities will follow. +``` + +Adapt the language of this guide to `VAULT_LANGUAGE`. + +--- + +## Critical Rules + +| # | Rule | +|---|---| +| 1 | **NEVER block initialization** for missing dependencies — always warn and continue | +| 2 | **NEVER modify existing skills** — init only creates new files in the vault | +| 3 | **NEVER auto-install** dependencies — only provide instructions | +| 4 | **NEVER run `git init`** — the user handles their own git setup | +| 5 | **ALWAYS copy templates verbatim** — no translation or modification | +| 6 | **ALWAYS create bidirectional wikilinks** in example entities | +| 7 | **ALWAYS use hierarchical tags** — `type/actor`, never `actor` | +| 8 | **ALWAYS use bare wikilinks** — `[[name]]`, never `[[dir/name]]` | +| 9 | **ALWAYS write entity content in VAULT_LANGUAGE** — adapt example text to the chosen language | +| 10 | **Idempotency** — respect `.bedrock/config.json` existence and offer reconfigure/register/skip | +| 11 | **ALWAYS register vault** in the global registry during setup — prompt for name, validate kebab-case and uniqueness | +| 12 | **First vault is auto-default** — if the registry is empty, mark the new vault as default without asking | diff --git a/plugins/bedrock/skills/sync/SKILL.md b/plugins/bedrock/skills/sync/SKILL.md new file mode 100644 index 0000000..e862832 --- /dev/null +++ b/plugins/bedrock/skills/sync/SKILL.md @@ -0,0 +1,1279 @@ +--- +name: sync +description: > + Re-synchronizes the vault with external sources. In default mode, scans the `sources` + field of all entities, deduplicates URLs, fetches updated content from each source + (Confluence, GDocs, GitHub, Markdown), performs incremental diff and delegates writing to + /bedrock:preserve. With --people, scans actor repositories via GitHub API and + identifies active contributors. With --github, detects relevant activity in + repositories and correlates PRs with topics/projects via LLM semantic matching. + Use when: "bedrock sync", "bedrock-sync", "/bedrock:sync", "synchronize", + "update sources", "sync people", "sync github". +user_invocable: true +allowed-tools: Bash, Read, Write, Edit, Glob, Grep, Skill, Agent, mcp__plugin_github_github__*, mcp__plugin_atlassian_atlassian__* +--- + +# /bedrock:sync — Vault Synchronization + +## 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 sync. 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 parsing `--people` or `--github`. + +**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. Store the resolved vault name as `VAULT_NAME`. + +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`. Store its name as `VAULT_NAME`. + +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`. Store its name as `VAULT_NAME`. + +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. +- Git operations: `git -C <VAULT_PATH> <command>` +- When delegating to `/bedrock:preserve`, pass `--vault <VAULT_NAME>` + +--- + +## Overview + +This skill synchronizes the vault with external sources. It operates in three modes: + +| Mode | Flag | Description | +|---|---|---| +| **Sources (default)** | _(none)_ | Re-synchronizes entities with a populated `sources` field | +| **People** | `--people` | Scans actor repositories and identifies active contributors | +| **GitHub** | `--github` | Detects activity in repos and correlates PRs with topics/projects | + +--- + +## Routing + +Analyze the argument passed by the user: + +1. If argument contains `--people` → go to **Mode: Sync People** (below) +2. If argument contains `--github` → go to **Mode: Sync GitHub** (below) +3. Otherwise → go to **Mode: Sync Sources (default)** (below) + +> **Note:** If no argument is passed, or the argument does not contain recognized flags, +> execute the default mode (Sync Sources). + +--- +--- + + +# Mode: Sync Sources (default) + + + + +## Overview + +This skill scans the `sources` field of all vault entities, deduplicates by URL, +fetches updated content from each external source, compares with existing entities +in the vault (incremental diff), and delegates all changes to `/bedrock:preserve` for centralized writing. + +`/bedrock:sync` **does NOT write entities directly** — all entity writing goes through `/bedrock:preserve`. +After re-sync, `/bedrock:preserve` updates `synced_at` in the `sources` field of affected entities. + +`/bedrock:sync` **does NOT ingest new sources** — for that, use `/bedrock:teach`. + +**You are an execution agent.** Follow the phases below in order, without skipping steps. + +--- + +## Phase 0 — Synchronize the Vault + +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. + +--- + +## Phase 1 — Collect Syncable Sources + +Provenance is recorded in the `sources` field of each entity's frontmatter. +Scan all entities to collect unique URLs. + +1. Use Grep to find entities with a non-empty `sources` field: + ``` + Grep pattern "^sources:" in directories: actors/, people/, teams/, topics/, discussions/, projects/, fleeting/ + ``` +2. For each file found, use Read to extract the `sources` field from the YAML frontmatter. + Each entry has: `{url, type, synced_at}` +3. **Build URL → entities map:** + Deduplicate by URL. For each unique URL, record all entities that reference it: + ``` + { + "https://mycompany.atlassian.net/...": { + type: "confluence", + synced_at: "2026-04-09", + entities: ["actors/billing-api.md", "topics/2026-04-feature-x.md"] + }, + "https://github.com/acme-corp/billing-api": { + type: "github-repo", + synced_at: "2026-04-10", + entities: ["actors/billing-api.md"] + } + } + ``` +4. **Filter syncable sources:** + - Keep only URLs with `type` in (`confluence`, `gdoc`, `github-repo`, `markdown`) + - Ignore URLs with `type` = `csv` or `manual` (log: "URL X ignored — non-syncable type") +5. Store the list of syncable URLs with their entity maps + +Report: "Phase 1: N entities with sources, M unique URLs found, K syncable, J ignored (non-syncable type)." + +--- + +## Phase 2 — Re-read Sources + +For each syncable source, fetch updated content: + +### 2.1 Confluence + +For sources with `source_type: confluence`: + +1. Read the internal fetcher at `<base_dir>/../confluence-to-markdown/SKILL.md` +2. Follow its instructions to parse the URL, choose layer (MCP → API → browser), and extract content +3. The fetcher returns Markdown content and page title + +### 2.2 Google Docs + +For sources with `source_type: gdoc`: + +1. Read the internal fetcher at `<base_dir>/../gdoc-to-markdown/SKILL.md` +2. Follow its instructions to parse the URL, detect document type, choose layer (MCP → API/public export → browser), and extract content +3. The fetcher returns Markdown content and document metadata + +### 2.3 GitHub Repository + +For sources with `source_type: github-repo`: + +1. Extract `owner/repo` from the URL (path segments after `github.com/`) +2. Use GitHub MCP directly (NOT via subagent — MCP permissions are not inherited): + - `mcp__plugin_github_github__get_file_contents` → read the repo's README.md + - `mcp__plugin_github_github__list_commits` → last 10 commits + - `mcp__plugin_github_github__list_pull_requests` → last 5 PRs (state=all, sort=updated) +3. Compile everything into a single markdown text + +> **Best-effort:** If any MCP call fails, continue with what was obtained. Do NOT block the sync. + +### 2.4 Local Markdown + +For sources with `source_type: markdown`: + +1. Extract the path from the `url` field +2. Use Read to read the file directly +3. If the file does not exist: log and skip + +### 2.5 Error handling + +- If reading a source fails (MCP unavailable, broken URL, missing file): + - Log the error: "Source X failed — reason" + - Continue with remaining sources + - Do NOT abort the entire execution for one source + +Report: "Phase 2: N sources read successfully, M failed (list)." + +--- + +## Phase 3 — Incremental Diff + Entity Extraction + +### 3.1 Load entity definitions + +Use Read to read ALL entity definition files from the plugin (see "Plugin Paths" section): +`<base_dir>/../../entities/*.md` +These files define what each entity type is, when to create, and how to distinguish them. +Internalize these definitions — you will use them to classify content. + +### 3.2 Catalog existing entities + +Use Glob to list all files in each entity directory (excluding `_template.md`): +- `<VAULT_PATH>/actors/*.md` +- `<VAULT_PATH>/people/*.md` +- `<VAULT_PATH>/teams/*.md` +- `<VAULT_PATH>/topics/*.md` +- `<VAULT_PATH>/discussions/*.md` +- `<VAULT_PATH>/projects/*.md` +- `<VAULT_PATH>/fleeting/*.md` + +For each file found: +- Extract the filename without extension (e.g.: `billing-api`) +- Use Read to extract the `name` (or `title`) and `aliases` fields from the YAML frontmatter +- Store: `{filename, name, aliases, type}` for matching + +### 3.3 Analyze content and detect changes + +For each source successfully read in Phase 2: + +1. **Identify entities mentioned in the updated content:** + - For each entity cataloged in Phase 3.2, check if the filename, name, or alias appears in the content + - Match rules: + - Normalize for comparison: lowercase, no accents, no hyphens + - Partial match acceptable for compound names (e.g.: "billing api" matches "billing-api") + - Do NOT match substrings of 3 letters or fewer (e.g.: "api" does NOT match "billing-api") + - Do NOT match generic words (e.g.: "company", "service", "system") + +2. **Compare with the source's `entities_generated`:** + - Entity in content AND already in vault → candidate for `update` (if there is new info in the content) + - Entity in content but NOT in vault → candidate for `create` + - Entity in `entities_generated` but NOT in updated content → **keep** (do not delete) + +3. **Classify new entities:** + - For `create` candidates, consult the entity definitions: + - "When to create" section → positive criteria + - "When NOT to create" section → exclusion criteria + - "How to distinguish from other types" section → disambiguation + + > **Projects:** `project` is a valid type in extraction. When classifying new entities, + > pay special attention to signals of initiatives with closed scope (deadline, deliverables, + > focal points). Consult `entities/project.md` for creation criteria. An excerpt that + > mentions migration with a deadline and responsible person is probably a project, not a topic. + +4. **Record** for each detected entity: + - Type (actor, person, team, topic, discussion, project) + - Canonical name (filename or suggested slug) + - Action: `create` or `update` + - Extracted info: excerpt of the content where it appears + - Source of origin: source slug + +Report: "Phase 3: N entities detected (P creates, Q updates) across M sources." + +--- + +## Phase 4 — Consolidated Confirmation + +**REQUIRED:** Before creating/updating any entity, present a SINGLE list +with all changes from ALL sources: + +``` +## Sync — Proposed Changes + +| # | Source | Type | Name | Action | Info | +|---|---|---|---|---|---| +| 1 | roadmap-26q1 | topic | 2026-04-feature-x | create | New topic mentioned | +| 2 | eventos-cobranca | actor | webhook-receiver | update | Description updated | +| ... | ... | ... | ... | ... | ... | + +Total: N creates, M updates across P sources. +Confirm? (yes/no/adjust) +``` + +- **yes**: proceed to Phase 5 +- **no**: abort with "Sync cancelled. No entities modified." +- **adjust**: ask what to adjust, modify list, re-present + +**If no changes detected in any source:** +- Report: "No changes detected in any source. Vault is already up to date." +- Skip to Phase 6 (update `last_synced` anyway) + +**Do NOT proceed without explicit user confirmation.** + +--- + +## Phase 5 — Delegate to /bedrock:preserve + +### 5.1 Compile structured list + +Build the entity list in the format accepted by `/bedrock:preserve`: + +```yaml +entities: + - type: topic + name: "2026-04-feature-x" + action: create + content: "relevant excerpt from content extracted in Phase 3..." + relations: + actors: ["actor-slug-1"] + people: ["person-slug-1"] + source: "confluence" + - type: actor + name: "webhook-receiver" + action: update + content: "new context extracted in Phase 3..." + source: "github-repo" +``` + +**Compilation rules:** +- `type` and `name`: extracted from Phase 3 +- `action`: `create` or `update` as identified +- `content`: excerpt of the source content that justifies the entity +- `relations`: infer relationships between entities in the list (if A mentions B, include B in A's relations) +- `source`: use the `source_type` of the originating source + +### 5.2 Invoke /bedrock:preserve + +Use the Skill tool to invoke `/bedrock:preserve --vault <VAULT_NAME>` passing the structured list as argument. +The `--vault <VAULT_NAME>` flag ensures preserve writes to the same vault. + +`/bedrock:preserve` handles: +- Textual matching with existing entities +- Creation of new entities following templates +- Updating existing entities (merge/append-only) +- Bidirectional linking (wikilinks) +- Git commit of entities + +### 5.3 Await result + +`/bedrock:preserve` returns: +- List of created/updated entities +- Commit hash (if there was a commit) +- Any errors or warnings + +Record the result for use in the final report (Phase 7). + +--- + +## Phase 6 — Update synced_at in Entities + +After re-sync of each URL, `/bedrock:preserve` has already updated the entities with new content. +Additionally, for each URL processed successfully, pass `source_url` and `source_type` +to `/bedrock:preserve` so it updates `synced_at` in the `sources` field of each mapped entity. + +The URL → entities map (built in Phase 1) indicates which entities need +`synced_at` updated for each re-synced URL. + +> **Note:** `/bedrock:preserve` already handles the entity commit. `/bedrock:sync` does NOT make a separate commit. + +--- + +## Phase 7 — Report + +Present to the user: + +``` +## Report + +| Metric | Value | +|---|---| +| Sources found | N | +| Sources synchronized | N | +| Sources ignored (type) | N | +| Sources with error | N | +| Entities created | N | +| Entities updated | N | + +### Per source +| Source | Type | Entities | Status | +|---|---|---|---| +| roadmap-26q1 | confluence | 3 creates, 2 updates | ✅ | +| acme-corp-billing-api | github-repo | 0 creates, 1 update | ✅ | +| manual-notes | manual | — | ⏭️ ignored | +| broken-source | confluence | — | ❌ error (reason) | + +### Git +- Commit (entities): <hash from /bedrock:preserve or "no entities"> +- Commit (sources): vault: syncs N sources [source: sync] +- Push: ✅ success / ❌ failed (reason) + +### Suggestions +- [sources with errors that can be fixed] +- [entities mentioned in content but not created, if any] +``` + +--- + +## Critical Rules + +| # | Rule | +|---|---| +| 1 | **NEVER write entities directly** — all entity writing goes through `/bedrock:preserve` | +| 2 | **NEVER create sources** — `/bedrock:sync` only processes URLs already registered in entities' `sources` field | +| 3 | **NEVER delete entities** — entities absent from updated content are kept | +| 4 | **ALWAYS confirm** consolidated proposal with user before executing (Phase 4) | +| 5 | **Best-effort for external sources** — never block due to unavailable MCP or broken URL | +| 6 | **MCP in main context** — do NOT use subagents for GitHub/Atlassian MCP calls | +| 7 | **csv and manual sources are ignored** — static types with no URL to re-fetch | +| 8 | **Maximum 2 push attempts** — after that, abort and inform | +| 9 | **Sensitive data** — NEVER include credentials, tokens, passwords, PANs, CVVs | +| 10 | **Frontmatter keys in English**, values in the vault's configured language | +| 11 | **Bare wikilinks** — `[[name]]`, never `[[dir/name]]` | + +--- +--- + +# Mode: Sync People (--people) + + + + +Skill that populates `people/` from recent commits in repositories listed in `actors/`. + +**You are an execution agent.** Follow the phases below in order, without skipping steps. +Do not make git commit/push. Do not update `topics/` or `actors/`. Do not read CLAUDE.md from repositories. + +--- + +## Phase 1 — Actor collection + +1. Use Glob to list all files `<VAULT_PATH>/actors/*.md` +2. Exclude `<VAULT_PATH>/actors/_template.md` +3. For each file, use Read to extract from the YAML frontmatter: + - `repository` — GitHub URL (e.g.: `https://github.com/acme-corp/billing-api/`) + - `team` — squad wikilink (e.g.: `[[squad-payments]]`) + - `name` — canonical name of the actor (e.g.: `billing-api`) +4. Parse `owner/repo` from the URL: extract the two path segments after `github.com/` +5. **Skip** actors without a `repository` field, with an empty URL, or with a URL that does not contain `github.com` +6. Store the list of valid actors: `{name, owner, repo, team_wikilink, team_slug}` + - `team_slug`: extracted from the wikilink, e.g.: `[[squad-payments]]` → `squad-payments` + +At the end of this phase, report: "Phase 1: N actors found, M with valid repository, K skipped." + +--- + +## Phase 2 — Commit collection + +For each actor in the list (in parallel when possible): + +1. Calculate the date 30 days ago in ISO 8601 format (e.g.: `2026-03-04T00:00:00Z`) +2. Execute via Bash: + ``` + gh api "repos/{owner}/{repo}/commits?since={date_30_days}&per_page=100" 2>/dev/null + ``` +3. If the command fails (404, 403, network error): **log and skip** — do not fail the execution +4. For each commit in the JSON result, extract: + - `author.login` — GitHub login (may be `null` if commit via email without linked account) + - `commit.author.name` — author's display name +5. **Filter bots:** ignore commits where: + - `author.login` is `null` + - `author.login` contains `[bot]` + - `author.login` (case-insensitive) is exactly: `dependabot`, `renovate`, `github-actions`, `snyk-bot`, `codecov`, `sonarcloud`, `renovate-bot`, `depfu` +6. Store the valid commits associated with the actor + +At the end of this phase, report: "Phase 2: N repositories accessed, M with commits, K inaccessible (list). Total of L commits from P unique contributors." + +--- + +## Phase 3 — Aggregation + +1. Group all commits by `author.login` (lowercase) +2. For each unique person, build: + - `github`: login in lowercase + - `name`: `commit.author.name` from the most recent commit (fallback: login if name is empty) + - `focal_points`: list of canonical actor names where the person has commits (no duplicates) + - `team_counts`: commit count by squad (e.g.: `{squad-payments: 15, squad-notifications: 3}`) + - `team`: squad with most commits; in case of tie, first alphabetically + - `filename`: derived from `name` → lowercase, no accents (normalize NFD and remove combining marks), spaces→hyphens, special characters removed, kebab-case + - E.g.: `Alice Smith` → `alice-smith.md` + - E.g.: `José María` → `jose-maria.md` + - Fallback: if name not available, use login as filename + +3. **Duplicate detection by filename:** + - If two contributors (different logins) generate the same filename: append `-2`, `-3`, etc. to the second + - If a file `people/{filename}` already exists with a different `github`: treat as different person, append suffix + +At the end of this phase, report: "Phase 3: N unique contributors identified. Distribution by squad: [list]." + +--- + +## Phase 4 — Write people + +For each person: + +### If `people/{filename}` does NOT exist — CREATE: + +Use Write to create the file with this exact content (replace the placeholders): + +```markdown +--- +type: person +name: "{display_name}" +role: "" +team: "[[{team_slug}]]" +focal_points: [{focal_points_yaml}] +github: "{github_login}" +jira: "" +updated_at: {today_date_YYYY-MM-DD} +updated_by: "sync-people" +tags: [type/person] +--- + +# {Display Name} + +> Active contributor identified via commits in the last 30 days. + +## Team + +Member of [[{team_slug}]]. + +## Focal Points + +{focal_points_list} + +## Active Topics + +_No topics linked yet._ +``` + +Where: +- `{focal_points_yaml}` = YAML array of wikilinks, e.g.: `["[[billing-api]]", "[[notification-service]]"]` +- `{focal_points_list}` = markdown list, e.g.: + ``` + - [[billing-api]] — recent commits + - [[notification-service]] — recent commits + ``` +- `{today_date_YYYY-MM-DD}` = today's date in `YYYY-MM-DD` format + +### If `people/{filename}` ALREADY exists — UPDATE: + +1. Use Read to read the existing file +2. **Merge focal_points:** add new actors to the existing YAML array, without removing those already there +3. **Update team:** overwrite with the new calculation (squad with most commits) +4. **Update updated_at:** today's date +5. **Update updated_by:** `"sync-people"` +6. Update the "Focal Points" section in the markdown body to reflect the merged list +7. Use Edit to apply the changes (do not rewrite the entire file — preserve manual content) + +**Identification by login:** Before creating a new file, use Grep to search for `github: "{login}"` in `<VAULT_PATH>/people/*.md`. If found, update that file instead of creating a new one (even if the filename does not match). + +At the end of this phase, report: "Phase 4: N people created, M updated." + +--- + +## Phase 5 — Update teams + +For each squad that received new people: + +1. Use Read to read `teams/{team_slug}.md` +2. Extract the `members` array from the YAML frontmatter +3. For each person in the squad: add `"[[{person_filename_without_ext}]]"` to the array if it does not exist +4. Update `updated_at` and `updated_by: "sync-people"` in the frontmatter +5. Use Edit to apply the changes to the frontmatter + +**Do not modify** any other section of the team file. + +At the end of this phase, report: "Phase 5: N teams updated. [list of squads → number of members added]." + +--- + +## Phase 6 — Final report + +Print a consolidated summary: + +``` +## Sync People — Report + +| Metric | Value | +|---|---| +| Actors scanned | N | +| Repositories accessed | N | +| Inaccessible repositories | N | +| Commits analyzed | N | +| Contributors found | N | +| People created | N | +| People updated | N | +| Teams updated | N | + +### People by squad + +| Squad | People | +|---|---| +| squad-payments | alice, bob | +| squad-notifications | carol | +| ... | ... | + +### Inaccessible repositories + +- owner/repo — error (if any) +``` + +--- + +## General rules + +- **Language:** Use the vault's configured language for content, technical terms in English +- **Filenames:** kebab-case, no accents, lowercase +- **Wikilinks:** no path — `[[name]]`, never `[[people/name]]` +- **Frontmatter:** valid YAML, double quotes for strings with special characters +- **Idempotency:** identify people by `github` login, not by filename +- **Errors:** log and continue — never fail the entire execution for one repo or person +- **No git:** do not commit, push, or perform any git operations +- **No topics:** do not create/update files in `topics/` +- **No actors:** do not modify files in `actors/` + +--- +--- + +# Mode: Sync GitHub (--github) + + + + +## Overview + +This is an **autonomous agent** designed to run in background without human interaction. +It traverses all actors with `status: active` and a populated `repository` field, +fetches recent PRs via GitHub MCP, filters noise, uses LLM semantic matching to correlate +PRs with existing topics/projects in the vault, and delegates updates to `/bedrock:preserve`. + +**Operating mode: autonomous.** +- Does NOT ask for confirmation — processes and writes automatically +- Safety guaranteed by: (1) only HIGH confidence correlations generate updates, + (2) topics/projects receive informational notes (append), never status overwrite, + (3) medium confidence correlations are recorded in the report for human review +- Generates a report in `fleeting/` at the end of each execution + +**For recurring execution:** +- Via `/loop`: `/loop 6h /bedrock:sync --github` +- Via `/schedule`: configure cron with this skill + +`/bedrock:sync --github` **does NOT write entities directly** — all entity writing goes through `/bedrock:preserve`. +Exception: watermark fields (`last_synced_at`, `last_synced_sha`) in actor frontmatter are written +directly via Edit (they are not new entities, they are sync metadata). + +`/bedrock:sync --github` **does NOT create new topics or projects** — it only updates existing ones. + +**You are an autonomous execution agent.** Follow the phases below in order, without skipping steps. +Do NOT ask for user confirmation in any phase. + +--- + +## Phase 0 — Synchronize the Vault + +Execute: +```bash +git -C <VAULT_PATH> pull --rebase origin main +``` + +If the pull fails: +- No remote configured: log "No remote configured. Working locally." and proceed. +- Pull conflict: `git -C <VAULT_PATH> rebase --abort`, log the error and **ABORT** the entire execution. + Record in the report: "Aborted — git conflict on initial pull." +- Otherwise: proceed. + +--- + +## Phase 1 — Collect Syncable Actors + +1. Use Glob to list all files `<VAULT_PATH>/actors/*.md` +2. Exclude `<VAULT_PATH>/actors/_template.md` +3. For each file, use Read to extract from the YAML frontmatter: + - `status` — actor status + - `repository` — GitHub repository URL + - `name` — actor name + - `last_synced_at` — date of last GitHub sync (may not exist) + - `last_synced_sha` — SHA of last sync (may not exist) +4. **Filter syncable actors:** + - Keep only actors with `status: active` (or `in-development`) + - Keep only actors with a populated `repository` field containing `github.com` + - Ignore actors with `status: deprecated` (log: "Actor X ignored — deprecated") + - Ignore actors without `repository` or with an invalid URL (log: "Actor X ignored — no GitHub repository") +5. For each syncable actor, extract `owner/repo` from the URL: + - Parse the URL: `https://github.com/<owner>/<repo>/` → `owner`, `repo` + - Remove trailing slashes and `.git` suffix if present +6. Store the list of syncable actors with: `{filename, name, owner, repo, last_synced_at, last_synced_sha}` + +Log: "Phase 1: N actors found, M syncable, K ignored (deprecated/no repo)." + +--- + +## Phase 2 — Fetch PRs via GitHub MCP + +For each syncable actor, fetch recent PRs: + +1. Use GitHub MCP directly (NOT via Agent tool — MCP permissions are not inherited by subagents): + - `mcp__plugin_github_github__list_pull_requests` with parameters: + - `owner`: repo owner + - `repo`: repo name + - `state`: "all" (open, merged, closed) + - `sort`: "updated" + - `per_page`: 20 +2. **Filter by watermark:** + - If the actor has `last_synced_at`: keep only PRs with `updated_at` >= `last_synced_at` + - If the actor does NOT have `last_synced_at`: keep only PRs from the last 30 days +3. Record for each PR: + - `number`, `title`, `body` (description), `state` (open/closed), `merged` (bool) + - `user.login` (author) + - `updated_at`, `created_at` + - `head.sha` (latest commit SHA) + +> **Best-effort:** If the MCP call fails for an actor (rate limit, private repo, invalid URL): +> - Log the error: "Actor X failed — reason" +> - Continue with the remaining actors +> - Do NOT abort the entire execution for one actor + +> **Quick skip:** If no PRs were returned or all PRs are older than the watermark, +> log "Actor X — no relevant activity" and skip to the next actor. + +Log: "Phase 2: N actors queried, M with relevant PRs, K without activity, J with errors." + +--- + +## Phase 3 — Filter Noise + +For each PR collected in Phase 2, apply noise filters: + +### 3.1 Filter by author +Remove PRs from bots and automated tools: +- Author contains `[bot]` or `bot` in login (e.g.: `dependabot[bot]`, `renovate[bot]`, `github-actions[bot]`) +- Author is `dependabot`, `renovate`, `snyk-bot`, `greenkeeper` + +### 3.2 Filter by title +Remove PRs with titles indicating automatic or irrelevant changes: +- Title starts with: `Bump `, `chore(deps)`, `build(deps)`, `Update dependency` +- Title contains: `version bump`, `dependency update`, `auto-merge` +- Title is just a version number (e.g.: `v1.2.3`, `1.2.3`) + +### 3.3 Record result +For each actor, maintain a list of relevant PRs (post-filter). +If all PRs from an actor were filtered: log "Actor X — all PRs filtered (noise)" and skip. + +Log: "Phase 3: N total PRs, M relevant after filter, K filtered as noise." + +--- + +## Phase 4 — LLM Semantic Matching + +### 4.1 Load topics and projects catalog + +Use Glob + Read to collect: + +**Topics (`<VAULT_PATH>/topics/*.md`, excluding `_template.md`):** +- `filename` (without extension) +- `title` +- `aliases` +- `status` (open, in-progress, completed, cancelled) +- `actors` (list of wikilinks) +- `objective` +- `category` + +**Projects (`projects/*.md`, excluding `_template.md`):** +- `filename` (without extension) +- `name` +- `aliases` +- `status` (planning, active, blocked, completed) +- `related_actors` (list of wikilinks) +- `blockers` +- `action_items` (list with description, status) +- `progress` + +Store as catalog for matching. + +### 4.2 Prepare matching batch + +For each actor with relevant PRs, build a block: + +``` +Actor: <actor-name> (<owner>/<repo>) +Relevant PRs: +- PR #<number>: "<title>" (state: <open|closed|merged>, author: <login>, date: <date>) + Description: <first 200 chars of body> +- PR #<number>: ... +``` + +### 4.3 Execute semantic matching + +With the topics/projects catalog and the PR batch, analyze semantically: + +For each relevant PR, determine: + +1. **Correlation with topic/project:** Does the PR relate to an existing topic or project? + - Consider: PR title, description, originating actor, aliases of topics/projects + - Match by: theme (deprecation, feature, bugfix), mentioned system, aligned objective + - Do NOT match generically — require clear semantic relationship + +2. **Status implication:** If there is a correlation, does the PR imply a status change? + - Merged PR in an actor listed in an "open" topic → suggests status "in-progress" or "completed" + - Merged PR that resolves a project blocker → suggests blocker removal + - Open PR for a feature in a "blocked" topic → suggests status "in-progress" + - Closed PR without merge → no status implication + +3. **Change classification:** + - `status_hint` — suggested status (e.g.: "in-progress", "completed") + - `evidence` — evidence description (e.g.: "PR #42 merged implements feature X of topic Y") + - `confidence` — high, medium, low + - `entity_type` — "topic" or "project" + - `entity_name` — topic/project filename + +**Matching rules:** +- Prioritize high confidence matches (PR title explicitly mentions the topic/project) +- Discard low confidence matches (vague correlation, only by domain) +- If no PRs from an actor have correlation: record "no correlation" and proceed +- Do NOT create new topics/projects — only correlate with existing ones + +### 4.4 Classify results by action level + +Separate correlations into two groups: + +**HIGH confidence → automatic action:** +- Will be processed automatically in Phase 5 (without human confirmation) +- Criteria: PR title explicitly mentions the topic/project, OR the PR is in an actor + listed in the `actors`/`related_actors` frontmatter of the topic/project AND the PR theme + aligns with the topic's `objective` or the project's `progress`/`action_items` + +**MEDIUM confidence → report only:** +- Will NOT be processed automatically +- Will be recorded in the final report (Phase 7) for human review +- Criteria: plausible semantic correlation but without explicit evidence + +Log: "Phase 4: N correlations (P high confidence → action, Q medium confidence → report). R actors with activity without correlation." + +--- + +## Phase 5 — Delegate to /bedrock:preserve and Update Actors + +### 5.1 Compile list for /bedrock:preserve + +Build the entity list in the format accepted by `/bedrock:preserve`. +**Include ONLY HIGH confidence correlations + actor activity.** + +**For topics with HIGH correlation:** +```yaml +- type: topic + name: "topic-filename" + action: update + content: | + ## GitHub Activity (sync-github YYYY-MM-DD) + + | PR | Repo | Status | Evidence | + |---|---|---|---| + | #42 | billing-api | merged | Implements feature X | + + > [!info] Suggested status: in-progress + > Based on PR #42 merged in billing-api that implements feature X. + > Automatically detected by sync-github@agent. + source: "github" +``` + +**For projects with HIGH correlation:** +```yaml +- type: project + name: "project-filename" + action: update + content: | + ## GitHub Activity (sync-github YYYY-MM-DD) + + | PR | Repo | Status | Evidence | + |---|---|---|---| + | #15 | orders-api | merged | Resolves blocker Y | + + > [!info] Suggested status: active + > Based on PR #15 merged in orders-api that resolves blocker Y. + > Project status reflects a management decision — review this suggestion. + > Automatically detected by sync-github@agent. + source: "github" +``` + +**For actors with relevant activity (all, not just those with correlation):** +```yaml +- type: actor + name: "actor-name" + action: update + content: | + ## Recent Activity (sync-github YYYY-MM-DD) + + | PR | Title | Status | Author | Date | + |---|---|---|---|---| + | #42 | Feature X | merged | alice | 2026-04-10 | + | #34 | Refactoring Y | open | bob | 2026-04-09 | + source: "github" +``` + +**Compilation rules:** +- Content for topics/projects: append-only. Add "GitHub Activity" section with `[!info]` callout suggesting status. NEVER overwrite the `status` field directly. +- Content for actors: merge-ok. The "Recent Activity" section replaces the previous version (if it exists). +- `source: "github"` for all entities +- Bare wikilinks: `[[actor-name]]`, never `[[actors/actor-name]]` + +### 5.2 Invoke /bedrock:preserve + +Use the Skill tool to invoke `/bedrock:preserve --vault <VAULT_NAME>` passing the structured list as argument. +The `--vault <VAULT_NAME>` flag ensures preserve writes to the same vault. + +> **IMPORTANT for background execution:** When invoking `/bedrock:preserve`, include in the +> instruction that `/bedrock:preserve` must also operate without human confirmation. +> Add to the prompt: "Autonomous mode — do not ask for confirmation, process directly." + +`/bedrock:preserve` handles: +- Textual matching with existing entities +- Updating existing entities (merge/append-only) +- Bidirectional linking (wikilinks) +- Git commit of entities + +### 5.3 Update actor watermarks + +After `/bedrock:preserve` completes, update the frontmatter of EACH processed actor (with or without correlation): + +1. Use Read to read the actor file +2. Use Edit to update in the frontmatter: + - `last_synced_at`: today's date (YYYY-MM-DD) + - `last_synced_sha`: SHA of the most recent commit from the most recent PR (or keep previous if no PRs) + - `updated_at`: today's date + - `updated_by`: `"sync-github@agent"` +3. If the fields `last_synced_at` and `last_synced_sha` do not exist: add them to the frontmatter (before `updated_at`) + +### 5.4 Git commit of watermarks + +```bash +git -C <VAULT_PATH> add actors/ +git -C <VAULT_PATH> diff --cached --quiet && echo "Nothing to commit" && exit 0 +``` + +#### Read git strategy + +Read the vault's git strategy from `.bedrock/config.json`: + +```bash +cat <VAULT_PATH>/.bedrock/config.json 2>/dev/null +``` + +Extract the `git.strategy` field. If the file does not exist or has no `git` key, default to `"commit-push"`. + +Valid values: `"commit-push"`, `"commit-push-pr"`, `"commit-only"`. + +Prepare the commit message following the convention: +``` +vault(source): syncs github activity for N actors [source: github] +``` + +#### Dispatch by strategy + +**Strategy: `commit-push`** (default) + +```bash +git -C <VAULT_PATH> commit -m "<message per convention>" +git -C <VAULT_PATH> push origin main +``` + +If push fails (conflict): +```bash +git -C <VAULT_PATH> pull --rebase origin main +git -C <VAULT_PATH> push origin main +``` + +If it fails 2x: log the error and continue to the report. +If there is no remote: commit locally and log. + +--- + +**Strategy: `commit-push-pr`** + +First, check that `gh` is available: + +```bash +which gh 2>/dev/null +``` + +If `gh` is not found: warn the user and **fall back to `commit-push`** strategy (above). + +If `gh` is available: + +1. **Create a branch.** Derive the branch name from the commit message: + + `vault/<YYYY-MM-DD>-sync-github-<N>-actors` (e.g., `vault/2026-04-15-sync-github-5-actors`) + + Check for collisions: + ```bash + git -C <VAULT_PATH> branch --list "vault/<YYYY-MM-DD>-sync-github*" + ``` + If the branch already exists, append a counter: `vault/2026-04-15-sync-github-5-actors-2`. + + ```bash + git -C <VAULT_PATH> checkout -b <branch-name> + ``` + +2. **Commit and push the branch:** + ```bash + git -C <VAULT_PATH> commit -m "<message per convention>" + git -C <VAULT_PATH> push origin <branch-name> + ``` + +3. **Open a pull request:** + ```bash + cd <VAULT_PATH> && gh pr create --title "<commit message>" --body "Automated by /bedrock:sync" --base main + ``` + +4. **Return to main:** + ```bash + git -C <VAULT_PATH> checkout main + ``` + +--- + +**Strategy: `commit-only`** + +```bash +git -C <VAULT_PATH> commit -m "<message per convention>" +``` + +Do not push. Output: +``` +Git strategy: commit-only — changes committed locally. Use `git push` manually when ready. +``` + +--- + +## Phase 6 — Generate Report + +Generate a complete execution report and save as a fleeting note. + +### 6.1 Build report content + +```markdown +--- +type: fleeting +name: "sync-github YYYY-MM-DD" +aliases: ["Sync GitHub YYYY-MM-DD"] +status: "raw" +updated_at: YYYY-MM-DD +updated_by: "sync-github@agent" +tags: [type/fleeting, status/raw] +--- + +# Sync GitHub — YYYY-MM-DD + +> Automatic report generated by sync-github@agent. + +## Summary + +| Metric | Value | +|---|---| +| Actors found | N | +| Actors synchronized | N | +| Actors ignored (deprecated/no repo) | N | +| Actors with error (MCP) | N | +| PRs collected | N | +| Relevant PRs (post-filter) | N | +| PRs filtered (noise) | N | +| High confidence correlations (processed) | N | +| Medium confidence correlations (for review) | N | +| Actors with activity (no correlation) | N | + +## Processed Correlations (High Confidence) + +| Actor | PR | Entity | Type | Suggested Status | Evidence | +|---|---|---|---|---|---| +| [[billing-api]] | #42 | [[2026-04-feature-x]] | topic | in-progress | PR implements feature X | +| ... | ... | ... | ... | ... | ... | + +## Correlations for Human Review (Medium Confidence) + +> [!todo] Review correlations below +> These correlations were detected with medium confidence. Review and apply manually if correct. + +| Actor | PR | Entity | Type | Suggested Status | Evidence | +|---|---|---|---|---|---| +| [[notification-service]] | #33 | [[2026-04-bugfix-timeout-notifications]] | topic | in-progress | PR title mentions timeout | +| ... | ... | ... | ... | ... | ... | + +## Activity by Actor + +| Actor | Relevant PRs | Summary | +|---|---|---| +| [[billing-api]] | #42 merged, #43 open | Feature X completed, refactoring Y in progress | +| [[notification-service]] | #33 merged | Timeout fix | +| ... | ... | ... | + +## Updated Actors (Watermark) + +| Actor | last_synced_at | last_synced_sha | +|---|---|---| +| [[billing-api]] | YYYY-MM-DD | abc1234 | +| ... | ... | ... | + +## Errors + +| Actor | Error | +|---|---| +| actor-x | MCP timeout | +| ... | ... | + +## Git + +- Commit (entities): <hash from /bedrock:preserve or "no entities"> +- Commit (watermarks): vault(source): syncs github activity [source: github] +- Push: success / failed (reason) +``` + +### 6.2 Save report + +Save the report to `<VAULT_PATH>/fleeting/YYYY-MM-DD-sync-github.md`. + +If the file already exists (duplicate execution on the same day): overwrite with most recent data. + +### 6.3 Git commit of report + +```bash +git -C <VAULT_PATH> add fleeting/ +git -C <VAULT_PATH> diff --cached --quiet && echo "Nothing to commit" && exit 0 +``` + +#### Read git strategy + +Read the vault's git strategy from `.bedrock/config.json`: + +```bash +cat <VAULT_PATH>/.bedrock/config.json 2>/dev/null +``` + +Extract the `git.strategy` field. If the file does not exist or has no `git` key, default to `"commit-push"`. + +Valid values: `"commit-push"`, `"commit-push-pr"`, `"commit-only"`. + +Prepare the commit message: +``` +vault(note): creates sync-github-report YYYY-MM-DD [source: github] +``` + +#### Dispatch by strategy + +**Strategy: `commit-push`** (default) + +```bash +git -C <VAULT_PATH> commit -m "<message per convention>" +git -C <VAULT_PATH> push origin main +``` + +If push fails (conflict): +```bash +git -C <VAULT_PATH> pull --rebase origin main +git -C <VAULT_PATH> push origin main +``` + +If it fails 2x: log the error and continue. +If there is no remote: commit locally and log. + +--- + +**Strategy: `commit-push-pr`** + +First, check that `gh` is available: + +```bash +which gh 2>/dev/null +``` + +If `gh` is not found: warn the user and **fall back to `commit-push`** strategy (above). + +If `gh` is available: + +1. **Create a branch.** Derive the branch name: + + `vault/<YYYY-MM-DD>-sync-github-report` (e.g., `vault/2026-04-15-sync-github-report`) + + Check for collisions: + ```bash + git -C <VAULT_PATH> branch --list "vault/<YYYY-MM-DD>-sync-github-report*" + ``` + If the branch already exists, append a counter. + + ```bash + git -C <VAULT_PATH> checkout -b <branch-name> + ``` + +2. **Commit and push the branch:** + ```bash + git -C <VAULT_PATH> commit -m "<message per convention>" + git -C <VAULT_PATH> push origin <branch-name> + ``` + +3. **Open a pull request:** + ```bash + cd <VAULT_PATH> && gh pr create --title "<commit message>" --body "Automated by /bedrock:sync" --base main + ``` + +4. **Return to main:** + ```bash + git -C <VAULT_PATH> checkout main + ``` + +--- + +**Strategy: `commit-only`** + +```bash +git -C <VAULT_PATH> commit -m "<message per convention>" +``` + +Do not push. Output: +``` +Git strategy: commit-only — changes committed locally. Use `git push` manually when ready. +``` + +--- + +## Phase 7 — Finalize + +Log final message: + +``` +sync-github@agent completed. +- Actors processed: N +- Correlations processed (high): N +- Correlations for review (medium): N +- Report: fleeting/YYYY-MM-DD-sync-github.md +``` + +**Execution ends here.** The agent does not wait for user response. + +--- + +## Critical Rules + +| # | Rule | +|---|---| +| 1 | **AUTONOMOUS MODE** — do NOT ask for user confirmation in any phase | +| 2 | **NEVER write entities directly** — all writing of topics/projects/actors goes through `/bedrock:preserve` | +| 3 | **NEVER create new topics or projects** — only update existing ones | +| 4 | **NEVER overwrite status** of topics/projects — only add a note with suggestion via `[!info]` callout | +| 5 | **Only HIGH confidence generates action** — medium confidence correlations go only to the report | +| 6 | **Best-effort for GitHub MCP** — never block due to rate limit or inaccessible repo | +| 7 | **MCP in main context** — do NOT use Agent tool for GitHub MCP calls | +| 8 | **Filter noise before matching** — dependabot, version bumps, bots | +| 9 | **Conservative semantic matching** — discard low confidence correlations | +| 10 | **Maximum 2 push attempts** — after that, log and continue | +| 11 | **Sensitive data** — NEVER include credentials, tokens, passwords, PANs, CVVs | +| 12 | **Frontmatter keys in English**, values in the vault's configured language | +| 13 | **Bare wikilinks** — `[[name]]`, never `[[dir/name]]` | +| 14 | **Append-only for topics** — add information, never delete existing content | +| 15 | **Report always generated** — even if no correlations, generate report in `<VAULT_PATH>/fleeting/` | +| 16 | **Vault resolution first** — resolve `VAULT_PATH` before any file operation or git command — never assume CWD is the vault | +| 17 | **All git commands use `git -C <VAULT_PATH>`** — never assume CWD is the vault | +| 18 | **All entity paths use `<VAULT_PATH>/` prefix** — `<VAULT_PATH>/actors/`, not `actors/` | +| 19 | **Pass --vault to /preserve** — ALWAYS include `--vault <VAULT_NAME>` when delegating to `/bedrock:preserve` | diff --git a/plugins/bedrock/skills/teach/SKILL.md b/plugins/bedrock/skills/teach/SKILL.md new file mode 100644 index 0000000..54f5261 --- /dev/null +++ b/plugins/bedrock/skills/teach/SKILL.md @@ -0,0 +1,525 @@ +--- +name: teach +description: > + Teaches the Second Brain to recognize a new external data source. Fetches content from + Confluence, Google Docs, GitHub repositories, remote URLs, or any local file format + supported by docling (DOCX, PPTX, XLSX, PDF, HTML, EPUB, images, Markdown, CSV, and more), + converts non-markdown formats to markdown via docling, runs the /graphify extraction pipeline, + and delegates entity persistence (including the graphify-output merge) to /bedrock:preserve. + Use when: "bedrock teach", "bedrock-teach", "teach", "ingest source", "import document", "/bedrock:teach", + or when the user provides a Confluence, Google Docs, or GitHub URL, a remote file URL, or + a local file path to incorporate into the vault. +user_invocable: true +allowed-tools: Bash, Read, Write, Edit, Glob, Grep, Skill, Agent, WebFetch, mcp__plugin_github_github__*, mcp__plugin_atlassian_atlassian__* +--- + +# /bedrock:teach — External Source Ingestion into the Second Brain + +## Plugin Paths + +Entity definitions and templates are in the plugin directory, not at 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 teach. 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 (the remaining text is the source URL/path). + +**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. Store the resolved vault name as `VAULT_NAME`. + +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`. Store its name as `VAULT_NAME`. + +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`. Store its name as `VAULT_NAME`. + +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` and other relevant fields for use in later phases. + +**From this point forward, ALL vault file operations use `<VAULT_PATH>` as the root.** +- Graphify output: `<VAULT_PATH>/graphify-out/` +- When delegating to `/bedrock:preserve`, pass `--vault <VAULT_NAME>` + +--- + +## Overview + +This skill receives an external source (URL or local path), fetches its content to a temporary +directory, converts non-markdown files to markdown via docling, runs the `/graphify` extraction +pipeline on the tmp content, and delegates entity persistence (plus graphify-output merge) to +`/bedrock:preserve`. + +**You are a fetcher and orchestrator agent.** Your job is to: +1. Ensure docling is installed (auto-install if missing) +2. Classify the input and fetch content to `/tmp` +3. Convert fetched files to markdown via docling (when applicable) +4. Invoke `/graphify` to extract a knowledge graph into a per-run temp directory +5. Delegate graph merge and entity writes to `/bedrock:preserve` +6. Clean up temporary files + +You do NOT classify entities, create vault files, write to the vault directly, or merge graph state. +All extraction is done by `/graphify`. All writes (including the graphify-output merge into the +vault's cumulative `graphify-out/`) are done by `/bedrock:preserve`. + +Follow the phases below in order, without skipping steps. + +--- + +## Phase 0 — Ensure docling is installed + +Before any fetch or conversion, verify that the `docling` CLI is available. If missing, install +it silently using the same fallback chain `/bedrock:setup` uses for graphify, emitting a single +status line before proceeding. + +```bash +if command -v docling >/dev/null 2>&1; then + echo "Phase 0: docling already installed — proceeding." +else + echo "Phase 0: docling not found — installing silently (one-time setup, may take a few minutes for model download)." + # Step 1 — pipx (preferred, isolated) + if command -v pipx >/dev/null 2>&1; then + pipx install docling >/dev/null 2>&1 || true + fi + # Step 2 — pip (fallback if pipx unavailable or failed) + if ! command -v docling >/dev/null 2>&1; then + if command -v pip3 >/dev/null 2>&1; then + pip3 install --user docling >/dev/null 2>&1 || true + elif command -v pip >/dev/null 2>&1; then + pip install --user docling >/dev/null 2>&1 || true + fi + fi + # Final re-probe + if ! command -v docling >/dev/null 2>&1; then + echo "ERROR: docling install failed. Run /bedrock:setup to install it, or install manually: pipx install docling" + exit 1 + fi + echo "Phase 0: docling installed." +fi +``` + +**Failure mode:** If install fails (no `pipx`/`pip`, network outage, permission denied), abort +the skill with the error above. Do NOT fetch or mutate anything. Direct the user to `/bedrock:setup`. + +**No user prompt:** this step is silent — one status line on success, one error line on failure. + +--- + +## Phase 1 — Fetch + +### 1.1 Classify the input + +The user provides an argument. Classify it in the following priority order. URL-type routing +is unchanged; local files no longer have an extension allowlist — any existing file is accepted, +and Phase 1.5 decides whether to run docling on it. + +| Input | Detected type | Fetch method | +|---|---|---| +| URL containing `confluence` or `atlassian.net` | confluence | Read `skills/confluence-to-markdown/SKILL.md`, follow instructions, save output to tmp | +| URL containing `docs.google.com` | gdoc | Read `skills/gdoc-to-markdown/SKILL.md`, follow instructions, save output to tmp | +| URL containing `github.com` | github-repo | `git clone --depth 1` to tmp + GitHub MCP enrichment (docling never runs on GitHub repos) | +| URL starting with `http://` or `https://` (any other) | remote-binary | Download raw bytes to tmp via `curl`/WebFetch; Phase 1.5 decides conversion | +| Local file path (any existing file) | local-file | Copy to tmp; Phase 1.5 decides conversion | +| Local directory path | local-dir | Copy directory to tmp | +| No match above | manual | Ask the user: "Could not identify the source type. Paste the content or provide a valid URL/path." | + +If no argument was provided: ask the user "What source do you want to ingest? Provide a URL (Confluence, Google Docs, GitHub, or any HTTP(S) URL) or a local file path (any file type — docling will convert it to markdown if supported)." + +### 1.2 Create temporary directory + +All content is fetched to a temporary directory. This is the single input path for `/graphify`. + +```bash +TEACH_TMP="/tmp/bedrock-teach-$(date +%s)" +mkdir -p "$TEACH_TMP" +echo "Temporary directory: $TEACH_TMP" +``` + +Store the path for use in subsequent phases. + +### 1.3 Fetch content + +Execute the fetch strategy for the detected type. All content lands in `$TEACH_TMP/`. + +#### 1.3.1 GitHub repository + +For GitHub URLs (e.g.: `https://github.com/acme-corp/billing-api`): + +1. Extract `owner/repo` and `repo-name` from the URL +2. Clone the repository (shallow): + ```bash + git clone --depth 1 <url> "$TEACH_TMP/<repo-name>" + ``` +3. GitHub MCP enrichment — call directly in main context (NOT via subagent — MCP permissions are not inherited): + - `mcp__plugin_github_github__get_file_contents` → read the repo's README.md + - `mcp__plugin_github_github__list_commits` → last 10 commits + - `mcp__plugin_github_github__list_pull_requests` → last 5 PRs (state=all, sort=updated) +4. Compile MCP results into a single markdown file and save as `$TEACH_TMP/<repo-name>/_github_metadata.md` + +> **Best-effort:** If any MCP call fails, continue with what was obtained. Do NOT block ingestion. + +#### 1.3.2 Confluence + +For Confluence URLs: +1. Read the internal skill at `<base_dir>/../confluence-to-markdown/SKILL.md` +2. Follow its instructions to parse the URL, choose layer (MCP → API → browser), and extract content +3. Save the returned Markdown content to `$TEACH_TMP/<slug>.md` + - `<slug>` is derived from the page title or URL path (kebab-case, lowercase) + +If all three layers (MCP, API, browser) are unavailable: warn the user with the guidance message from the fetcher module and abort this source type. + +#### 1.3.3 Google Docs / Sheets + +For Google Docs or Sheets URLs: +1. Read the internal skill at `<base_dir>/../gdoc-to-markdown/SKILL.md` +2. Follow its instructions to parse the URL, detect document type (Doc vs Sheet), choose layer (MCP → API/public export → browser), and extract content +3. The fetcher saves output to `/tmp/gdoc_{docId}.md` or `/tmp/gsheet_{docId}.md` +4. Copy the output file to `$TEACH_TMP/<slug>.md` + - `<slug>` is derived from the document title or URL path (kebab-case, lowercase) + +If all three layers (MCP, API/public export, browser) are unavailable: warn the user with the guidance message from the fetcher module and abort this source type. + +#### 1.3.4 Remote URL (generic) + +For any other HTTP/HTTPS URL, download the raw bytes so docling can operate on binary formats +(PDF, DOCX, PPTX, XLSX, images, etc.) that WebFetch cannot return faithfully as text: + +1. Try `curl` first for true binary fidelity: + ```bash + curl -fsSL -o "$TEACH_TMP/<filename-derived-from-url>" "<url>" + ``` + - `<filename-derived-from-url>` preserves the URL's basename (including extension) when + available; fall back to `<slug>.bin` if no extension is present. +2. If `curl` is unavailable or the URL returns an HTML page (by Content-Type), fall back to + WebFetch and save the response text as `$TEACH_TMP/<slug>.md`. + +If both attempts fail: warn "Could not fetch URL. Check if the URL is accessible." and abort. + +Phase 1.5 decides whether the downloaded file goes through docling, based on the file extension. + +#### 1.3.5 Local file (any format) + +For local files: +1. Verify the file exists using Read (or `test -f`). +2. Copy to tmp preserving the filename: + ```bash + cp "<local-path>" "$TEACH_TMP/" + ``` + +No extension-based filtering — any existing file is accepted. Phase 1.5 decides conversion. + +#### 1.3.6 Local directory + +For local directories: +1. Verify the directory exists +2. Copy to tmp (excluding heavy directories): + ```bash + rsync -a --exclude='.git' --exclude='node_modules' --exclude='bin' --exclude='obj' \ + --exclude='.vs' --exclude='TestResults' --exclude='packages' \ + "<local-dir>/" "$TEACH_TMP/$(basename <local-dir>)/" + ``` + +### 1.4 Phase 1 result + +At the end of this phase, you should have: +- **`$TEACH_TMP`**: directory with all fetched content (local path for graphify) +- **`source_url`**: original URL or file path provided by the user +- **`source_type`**: `confluence`, `gdoc`, `github-repo`, `remote-binary`, `local-file`, `local-dir`, or `manual` + +Report: "Phase 1 complete: Content fetched to `$TEACH_TMP`. Source type: `<source_type>`." + +--- + +## Phase 1.5 — Docling Conversion + +For every fetched file in `$TEACH_TMP` that is not a GitHub repo and is not already markdown +output from Confluence/GDoc fetchers, check whether docling supports the file type and, if so, +convert it to markdown in place. GitHub repos (`source_type == "github-repo"`) skip this phase +entirely and flow straight to graphify. + +### 1.5.1 Docling-supported extensions + +Docling supports conversion for the following file types (as of the version installed by +Phase 0 / `/bedrock:setup`). Compare by lowercase file extension: + +``` +.pdf .docx .pptx .xlsx +.html .htm +.md .adoc +.png .jpg .jpeg .tiff .bmp +.epub +``` + +- `.md` is listed here because docling passes markdown through largely unchanged. In practice, + running docling on `.md` is a no-op we skip to save time — treat `.md` as already-markdown. +- `.txt` and `.csv` are NOT in docling's supported list (they are plain-text already); skip + docling and pass through raw. + +### 1.5.2 Routing and failure rules + +For each file under `$TEACH_TMP` (excluding files inside `<repo-name>/` subdirectories of a +`github-repo` source — skip those entirely): + +1. **Skip by type — already markdown or plain text:** if extension is `.md`, `.txt`, or `.csv`, + leave the file untouched and record status `passed-through` for the report. Graphify handles + these natively. + +2. **Skip by routing — not docling-supported:** if the extension is not in the supported list + above AND is not `.md`/`.txt`/`.csv`, leave the file untouched and record status + `passed-through` with a note `(type not supported by docling)`. Graphify decides what to do + with the raw file. + +3. **Run docling:** otherwise, invoke docling and replace the source file with the converted + markdown. Docling writes to the working directory by default; use `--to md` and `--output` + to target a predictable path: + + ```bash + cd "$TEACH_TMP" + docling --from <auto> --to md --output "$TEACH_TMP" "<relative-file-path>" + ``` + + Docling produces `<stem>.md` alongside the source. After a successful run: + - Remove the original binary: `rm "<relative-file-path>"`. + - Record status `converted` for the report with the new markdown filename. + +4. **Failure fallback:** if docling exits non-zero for a file: + - If the source file's extension is `.md`, `.txt`, or `.csv` (already handled by rule 1, + so this branch is defensive): leave the original file in place, record status + `failed-fallback (raw passthrough)`, and continue with other files. + - Otherwise (binary format like `.docx`, `.pdf`, etc.): **abort the entire skill**. Clean + up `$TEACH_TMP` (`rm -rf "$TEACH_TMP"`) and emit a clear error: + `ERROR: docling failed to convert <file>. Aborting ingestion. Temp directory cleaned up.` + Do NOT proceed to graphify or preserve. + +### 1.5.3 Phase 1.5 result + +At the end of Phase 1.5: +- `$TEACH_TMP` contains markdown files (either originals or docling-converted). +- You have a per-file status map to surface in Phase 4's report: + - `converted`: ran docling successfully + - `passed-through`: skipped docling (markdown/plain text or unsupported type) + - `failed-fallback`: docling failed but file was text-native; continued with raw file + +Report: "Phase 1.5 complete: N converted, M passed-through, P failed-fallback." + +--- + +## Phase 2 — Extract + +### 2.1 Invoke /graphify into a per-run temp directory + +Use the Skill tool to invoke `/graphify`, directing its output to a per-run temp directory +(**not** the vault). The vault's cumulative `graphify-out/` is updated by `/bedrock:preserve`'s +Phase 0 merge step, not by this skill. + +``` +/graphify $TEACH_TMP --mode deep --obsidian --obsidian-dir $TEACH_TMP +``` + +The convention used here: passing `--obsidian-dir $TEACH_TMP` makes graphify write its +`graphify-out/` tree under `$TEACH_TMP/graphify-out/`. Store that path as: + +```bash +GRAPHIFY_OUT_NEW="$TEACH_TMP/graphify-out" +``` + +**IMPORTANT:** +- Invoke via the Skill tool — never call graphify Python API directly. +- `/graphify` runs its full pipeline: detect → extract (AST + semantic) → build → cluster → analyze → obsidian export. +- Output lands in `$GRAPHIFY_OUT_NEW`, which is inside the temp directory. The vault's + `<VAULT_PATH>/graphify-out/` is NOT touched by this skill — `/bedrock:preserve` owns that write. + +### 2.2 Verify output + +After `/graphify` completes, verify the output in the temp location: + +```bash +if [ -f "$GRAPHIFY_OUT_NEW/graph.json" ] && [ -s "$GRAPHIFY_OUT_NEW/graph.json" ]; then + echo "graphify output verified: graph.json exists and is non-empty" +else + echo "ERROR: $GRAPHIFY_OUT_NEW/graph.json is missing or empty" +fi +``` + +**If graph.json is missing or empty:** +- Warn the user: "graphify extraction failed — no graph produced. Check the content and try again." +- Clean up tmp: `rm -rf "$TEACH_TMP"` +- Abort gracefully + +### 2.3 Phase 2 result + +The following files should exist in `$GRAPHIFY_OUT_NEW`: +- `graph.json` — knowledge graph (nodes, edges, communities) +- `GRAPH_REPORT.md` — audit report with god nodes, surprising connections +- `obsidian/*.md` — one markdown file per node +- `.graphify_analysis.json` — communities, cohesion scores, god nodes + +Report: "Phase 2 complete: graphify extraction finished in `$GRAPHIFY_OUT_NEW`. Graph: N nodes, M edges. Will be merged into the vault by /bedrock:preserve." + +--- + +## Phase 3 — Delegate to /bedrock:preserve + +### 3.1 Compile input for /preserve + +Pass the **temp** graphify output path and provenance metadata to `/bedrock:preserve`. The +skill's Phase 0.2 merges this temp output into the vault's cumulative `graphify-out/`: + +``` +graphify_output_path: $GRAPHIFY_OUT_NEW # = $TEACH_TMP/graphify-out/ +source_url: <source_url from Phase 1> +source_type: <source_type from Phase 1> +``` + +**IMPORTANT:** +- `/teach` does NOT classify graphify nodes into entity types. Entity classification, filtering, + matching, and user confirmation are all `/bedrock:preserve`'s responsibility (Phase 1.3). +- `/teach` does NOT merge the graph into the vault. That is `/bedrock:preserve`'s responsibility + (Phase 0.2). We pass the per-run temp path; preserve merges and then reads from the merged + `<VAULT_PATH>/graphify-out/`. + +### 3.2 Invoke /preserve + +Use the Skill tool to invoke `/bedrock:preserve --vault <VAULT_NAME>` passing the graphify +output reference (pointing at `$GRAPHIFY_OUT_NEW`) and provenance metadata as the argument. +The `--vault <VAULT_NAME>` flag ensures preserve writes to the same vault. + +### 3.3 Receive result + +`/bedrock:preserve` returns: +- List of entities created/updated +- Commit hash (if there was a commit) +- **`graphify_merge` block:** `{nodes_added, nodes_merged, edges_added, stale_flag_set}` from + preserve's Phase 0.2 merge +- Any errors or warnings + +Record the result for use in the report (Phase 4). + +--- + +## Phase 4 — Cleanup and Report + +### 4.1 Cleanup temporary directory + +After `/bedrock:preserve` confirms completion, remove the temporary directory: + +```bash +rm -rf "$TEACH_TMP" +echo "Temporary directory cleaned up: $TEACH_TMP" +``` + +**IMPORTANT:** Clean up AFTER /preserve confirms, not after graphify finishes. +The graphify output in `graphify-out/` is NOT cleaned up — it lives in the vault +and is used by `/bedrock:ask` for graph traversal. + +### 4.2 Report + +Present to the user: + +``` +## /bedrock:teach — Report + +### Ingested source +- **Type:** <source_type> +- **URL/Path:** <source_url> + +### Docling conversion (Phase 1.5) +| File | Status | Notes | +|---|---|---| +| report.docx | converted | output: report.md | +| notes.txt | passed-through | text-native | +| diagram.svg | passed-through | type not supported by docling | + +Summary: N converted, M passed-through, P failed-fallback. +(Omit this block entirely for `source_type == "github-repo"` where docling is bypassed.) + +### Extraction (via /graphify) +- **Graph:** N nodes, M edges, P communities (fresh run into $TEACH_TMP) +- **Report:** $GRAPHIFY_OUT_NEW/GRAPH_REPORT.md (before merge) + +### Graphify merge (via /bedrock:preserve Phase 0.2) +| Metric | Value | +|---|---| +| Nodes added | N | +| Nodes merged | M | +| Edges added | P | +| Analysis marked stale | true / false | + +(Pulled verbatim from `/bedrock:preserve`'s `graphify_merge` return block.) + +### Entities processed (via /bedrock:preserve) +| Type | Name | Action | +|---|---|---| +| actor | billing-api | update | +| topic | 2026-04-migration-payments | create | +| code | process-transaction | create | + +### Provenance +Each entity above received in the `sources` frontmatter field: +- url: <source_url> +- type: <source_type> +- synced_at: <today's date> + +### Git +- Commit: <hash from /bedrock:preserve or "no entities"> +- Push: success / failed (reason) + +### Suggestions +- [list of entities mentioned in the content but not created, if any] +- [recommendations for future re-ingestion, if applicable] +``` + +--- + +## Critical Rules + +| Rule | Detail | +|---|---| +| Invoke /graphify via Skill tool | NEVER call graphify Python API directly (`graphify.detect`, `graphify.build`, `graphify.extract`, etc.). Always invoke via the Skill tool. | +| All remote content fetched to /tmp | Every input type is fetched to `/tmp/bedrock-teach-<ts>/` before invoking graphify. graphify receives only a local path. | +| /teach does NOT classify entities | Entity classification, filtering, matching, and user confirmation are `/bedrock:preserve`'s responsibility. /teach passes the graphify output path and provenance metadata. | +| Delegate to /bedrock:preserve | ALL entities are persisted via `/bedrock:preserve` — teach does NOT create, update, or write vault entities. | +| /teach does NOT merge graphify output into the vault | Graphify is invoked into `$TEACH_TMP/graphify-out/` (per-run temp dir); `/bedrock:preserve`'s Phase 0.2 merges that into `<VAULT_PATH>/graphify-out/`. /teach never writes directly to the vault's `graphify-out/`. | +| Docling auto-install is silent | Phase 0 auto-installs docling if missing with a single status line — no user prompt. Fail the skill if install fails; direct the user to `/bedrock:setup`. | +| Docling skipped for GitHub repos | `source_type == "github-repo"` skips Phase 1.5 entirely — cloned repos flow straight to graphify. | +| Docling routing rule | Run docling on files with docling-supported extensions (see Phase 1.5.1). Pass-through for `.md`/`.txt`/`.csv` and for extensions not in docling's supported list. | +| Docling failure fallback | On docling non-zero exit: if file is `.md`/`.txt`/`.csv`, continue with raw file. For any other extension, abort the entire skill and clean up `$TEACH_TMP`. | +| Cleanup /tmp after /preserve confirms | Remove `/tmp/bedrock-teach-<ts>/` only after /preserve confirms completion, not after graphify finishes. | +| Provenance via source_url | ALWAYS include `source_url` and `source_type` when delegating to /bedrock:preserve. | +| Internal fetcher skills | Read internal skills from `<base_dir>/../confluence-to-markdown/SKILL.md` and `<base_dir>/../gdoc-to-markdown/SKILL.md` for content fetching. Never invoke external skills. | +| Best-effort for external sources | If MCP or fetch fails, warn and continue with what was obtained. Never block ingestion. | +| MCP in main context | Do NOT use subagents for GitHub/Atlassian MCP calls — permissions are not inherited. | +| Maximum 2 push attempts | After that, abort and inform (handled by /preserve). | +| Sensitive data | NEVER include credentials, tokens, passwords, PANs, CVVs. | +| Vault resolution first | Resolve `VAULT_PATH` before any file operation — never assume CWD is the vault | +| Pass --vault to /preserve | ALWAYS include `--vault <VAULT_NAME>` when delegating to `/bedrock:preserve` | diff --git a/plugins/bedrock/skills/vaults/SKILL.md b/plugins/bedrock/skills/vaults/SKILL.md new file mode 100644 index 0000000..6e79f66 --- /dev/null +++ b/plugins/bedrock/skills/vaults/SKILL.md @@ -0,0 +1,170 @@ +--- +name: vaults +description: > + Manage registered Bedrock vaults. List all vaults, set a default vault, + or remove a vault from the registry. The registry lives in the plugin + directory and maps vault names to filesystem paths. + Use when: "bedrock vaults", "bedrock-vaults", "/bedrock:vaults", "list vaults", + "set default vault", "remove vault", "show vaults", "which vault", "my vaults", + or when a user wants to manage their registered vaults. +user_invocable: true +allowed-tools: Bash, Read, Write, Edit, Glob, Grep +--- + +# /bedrock:vaults — Vault Management + +## Plugin Paths + +The vault registry lives in the plugin root directory, not in any vault. +Use the "Base directory for this skill" provided at invocation to resolve paths: + +- Vault registry: `<base_dir>/../../vaults.json` +- Plugin CLAUDE.md: `<base_dir>/../../CLAUDE.md` (auto-injected into context) + +Where `<base_dir>` is the path shown in "Base directory for this skill". + +--- + +## Overview + +This skill manages the global vault registry — the file that maps vault names to +filesystem paths and tracks which vault is the default. + +**You are a management agent.** This skill is read-only with respect to vaults themselves — +it never reads or modifies vault entities, never runs git operations inside vaults, and +never touches `.bedrock/config.json`. It only reads and writes the registry file (`vaults.json`). + +--- + +## Phase 0 — Parse Input + +Parse the user's input to determine the command mode: + +| Input pattern | Mode | Variables | +|---|---|---| +| No flags / empty / `list` | **list** | — | +| `--set-default <name>` | **set-default** | `TARGET_NAME = <name>` | +| `--remove <name>` | **remove** | `TARGET_NAME = <name>` | + +If the input doesn't match any pattern, default to **list** mode. + +--- + +## Phase 1 — Read Registry + +Resolve the registry path: + +``` +REGISTRY_PATH = <base_dir>/../../vaults.json +``` + +Read the registry file: + +```bash +cat <REGISTRY_PATH> 2>/dev/null +``` + +**If the file does not exist or is empty:** +- For **list** mode: display "No vaults registered. Run `/bedrock:setup` in a vault directory to register your first vault." +- For **set-default** and **remove** modes: display "No vaults registered. Nothing to modify." and exit. + +**If the file exists:** parse the JSON. Expected schema: + +```json +{ + "vaults": [ + { + "name": "<string>", + "path": "<absolute-path>", + "default": true | false + } + ] +} +``` + +Store the parsed vaults array as `VAULTS`. + +--- + +## Phase 2 — Execute Command + +### 2.1 List Mode + +For each vault in `VAULTS`, check if the path still exists on disk: + +```bash +test -d "<vault_path>" && echo "exists" || echo "missing" +``` + +Present a table: + +``` +## Registered Vaults + +| Name | Path | Default | Status | +|---|---|---|---| +| my-vault | /Users/me/vaults/my-vault | * | ok | +| team-vault | /Users/me/vaults/team-vault | | ok | +| old-vault | /Users/me/vaults/old-vault | | missing | +``` + +- The `Default` column shows `*` for the default vault +- The `Status` column shows `ok` if the directory exists, `missing` if it does not + +If any vault has status `missing`, add a note: + +``` +> Vaults marked as "missing" have paths that no longer exist on disk. +> Run `/bedrock:vaults --remove <name>` to clean up, or re-create the vault at the registered path. +``` + +### 2.2 Set-Default Mode + +1. Find the vault with `name == TARGET_NAME` in `VAULTS` +2. If not found: display "Vault `<TARGET_NAME>` is not registered. Available vaults:" followed by a list of names. Exit. +3. If found: + - Set `"default": false` on all vaults + - Set `"default": true` on the matching vault + - Write the updated registry back to `REGISTRY_PATH` + - Display: "Default vault set to `<TARGET_NAME>` (<path>)." + +### 2.3 Remove Mode + +1. Find the vault with `name == TARGET_NAME` in `VAULTS` +2. If not found: display "Vault `<TARGET_NAME>` is not registered. Available vaults:" followed by a list of names. Exit. +3. If found: + - Remove the entry from `VAULTS` + - If the removed vault was the default AND other vaults remain, mark the first remaining vault as default and inform the user + - Write the updated registry back to `REGISTRY_PATH` + - Display: "Vault `<TARGET_NAME>` removed from registry. Files on disk were NOT deleted (<path>)." + +--- + +## Phase 3 — Write Registry + +When writing the registry (set-default or remove modes), use the Write tool to overwrite `REGISTRY_PATH` with the updated JSON: + +```json +{ + "vaults": [ + { "name": "...", "path": "...", "default": true }, + { "name": "...", "path": "...", "default": false } + ] +} +``` + +Format the JSON with 2-space indentation for readability. + +--- + +## Critical Rules + +| # | Rule | +|---|---| +| 1 | **NEVER modify vault files** — this skill only touches `vaults.json` | +| 2 | **NEVER run git operations** — no git pull, commit, push, or any git command | +| 3 | **NEVER delete files on disk** — `--remove` only removes the registry entry | +| 4 | **ALWAYS validate vault name exists** before set-default or remove | +| 5 | **ALWAYS check path existence** when listing vaults — flag missing paths | +| 6 | **ALWAYS maintain exactly one default** — if the default is removed, auto-assign the first remaining vault | +| 7 | **Vault names are kebab-case** — lowercase, no spaces, no special characters beyond hyphens | diff --git a/plugins/bedrock/templates/actors/_template.md b/plugins/bedrock/templates/actors/_template.md new file mode 100644 index 0000000..0b6cae8 --- /dev/null +++ b/plugins/bedrock/templates/actors/_template.md @@ -0,0 +1,84 @@ +--- +type: actor +name: "" +aliases: [] # ["Display Name", "SIGLA"] — min 1 alias +category: "" # api | worker | consumer | producer | cronjob | lambda | monolith +description: "" +repository: "" +stack: "" +status: "" # active | deprecated | in-development +team: "[[squad-name]]" +criticality: "" # very-high | high | medium | low +pci: false +known_issues: [] +sources: [] # [{url: "https://...", type: "confluence|gdoc|github-repo|csv|markdown|manual", synced_at: YYYY-MM-DD}] +last_synced_at: "" # YYYY-MM-DD — last sync via /sync-github (optional) +last_synced_sha: "" # SHA of last synced commit (optional) +updated_at: YYYY-MM-DD +updated_by: "" +tags: [type/actor] # + status/{active,deprecated,in-development} + domain/{payments,finance,notifications,checkout,orders,integrations,compliance,core,data,infra,marketplace,internal-tools,platform,security} + scope/{pci,sox,lgpd,hipaa,gdpr,soc2} +--- + +<!-- Zettelkasten role: permanent note --> +<!-- Links in the body must have context: "receives authorizations from [[payment-gateway]] via gRPC" --> + +# Actor Name + +> Brief description of the system's function. + +<!-- Mandatory callouts — uncomment when applicable: --> +<!-- > [!warning] Deprecated --> +<!-- > This system is being deprecated. Replacement: [[replacement]]. --> + +<!-- > [!danger] PCI Scope --> +<!-- > This system is in PCI DSS scope. Never log card data (PAN, CVV, tracks, EMV). --> + +## Details + +| Field | Value | +|---|---| +| Repository | [repo-name](https://github.com/org/repo-name) | +| Stack | Language · Framework · Database · Messaging | +| Status | active / deprecated | +| Criticality | very-high / high / medium / low | +| PCI | yes / no | +| Team | [[squad-name]] | + +## Dependencies + +- Depends on: [[repo-name]] (flow description) +- Depended by: [[repo-name]] (flow description) + +## Flows + +- `actor-name` ← ACTION from [[repo-name]] +- `actor-name` → ACTION to [[repo-name]] + +## Dev Commands + +```bash +# Development commands +``` + +## Known Issues + +- Issue 1 — description and impact + +## Related Topics + +- [[YYYY-MM-type-slug]] — brief description + +--- + +## Expected Bidirectional Links + +> This section is a reference for agents and can be removed in real pages. + +| From | To | Field | +|---|---|---| +| Actor → Team | `[[squad-name]]` | `team` in frontmatter | +| Actor → Actor | `[[repo-name]]` | "Dependencies" and "Flows" sections | +| Actor → Topic | `[[YYYY-MM-type-slug]]` | "Related Topics" section | +| Team → Actor | `[[repo-name]]` | `actors` in Team frontmatter | +| Topic → Actor | `[[repo-name]]` | `actors` in Topic frontmatter | +| Person → Actor | `[[repo-name]]` | "Focal Points" in Person | diff --git a/plugins/bedrock/templates/concepts/_template.md b/plugins/bedrock/templates/concepts/_template.md new file mode 100644 index 0000000..1bdb992 --- /dev/null +++ b/plugins/bedrock/templates/concepts/_template.md @@ -0,0 +1,51 @@ +--- +type: concept +name: "" +aliases: [] # ["Readable Name", "Acronym"] — min 1 alias +description: "" +related_to: ["[[entity-name]]"] # wikilinks to any related entity type +sources: [] # [{url: "https://...", type: "confluence|gdoc|github-repo|csv|markdown|manual", synced_at: YYYY-MM-DD}] +updated_at: YYYY-MM-DD +updated_by: "" +tags: [type/concept] # + domain/{payments,finance,notifications,checkout,orders,integrations,compliance,core,data,infra,marketplace,internal-tools,platform,security} +--- + +<!-- Zettelkasten role: permanent note --> +<!-- Links in the body must have context: "commonly used by [[billing-api]] and [[notification-service]] for resilient HTTP calls" --> + +# Concept Name + +> One-line definition of what this concept IS. + +## Description + +Detailed explanation of the concept — what it is, how it works, and why it matters. +Self-contained: a reader should understand the concept without needing to read other entities. + +## Key Characteristics + +- Characteristic 1 +- Characteristic 2 +- Characteristic 3 + +## Where it Applies + +- [[entity-name]] — how the concept applies to this entity + +## Related Concepts + +- [[concept-name]] — relationship description + +--- + +## Expected Bidirectional Links + +> This section is a reference for agents and can be removed in real pages. + +| From | To | Field | +|---|---|---| +| Concept → Actor | `[[repo-name]]` | "Where it Applies" section | +| Concept → Topic | `[[YYYY-MM-type-slug]]` | "Where it Applies" section | +| Concept → Concept | `[[concept-name]]` | "Related Concepts" section | +| Actor → Concept | `[[concept-name]]` | "Related Topics" or body reference | +| Topic → Concept | `[[concept-name]]` | body reference | diff --git a/plugins/bedrock/templates/discussions/_template.md b/plugins/bedrock/templates/discussions/_template.md new file mode 100644 index 0000000..bafdea8 --- /dev/null +++ b/plugins/bedrock/templates/discussions/_template.md @@ -0,0 +1,77 @@ +--- +type: discussion +title: "" +aliases: [] # ["Short Title"] — min 1 alias if title is long +date: YYYY-MM-DD +summary: "" +conclusions: [] +action_items: [] +related_topics: ["[[YYYY-MM-type-slug]]"] +related_actors: ["[[repo-name]]"] +related_people: ["[[first-last]]"] +related_projects: ["[[project-slug]]"] +related_teams: ["[[squad-name]]"] +source: "" # session | meeting-notes | jira | confluence | manual +sources: [] # [{url: "https://...", type: "confluence|gdoc|github-repo|csv|markdown|manual", synced_at: YYYY-MM-DD}] +updated_at: YYYY-MM-DD +updated_by: "" +tags: [type/discussion] # + domain/* optional +--- + +<!-- Zettelkasten role: bridge note --> +<!-- Links in the body contextualize participation: "[[bob-jones]] presented the migration proposal for [[legacy-gateway]]" --> + +# Discussion Title + +> Brief summary of the discussion in 1-2 sentences. + +## Context + +Description of the context and motivation for this discussion. + +## Participants + +| Person | Role | +|---|---| +| [[first-last]] | participant | + +## Discussed Actors + +| Actor | Context | +|---|---| +| [[repo-name]] | context of the mention | + +## Conclusions + +- Conclusion 1 +- Conclusion 2 + +## Action Items + +- [ ] Action 1 — owner: [[first-last]] +- [ ] Action 2 — owner: [[first-last]] + +## Related Projects + +- [[project-slug]] + +## Related Topics + +- [[YYYY-MM-type-slug]] + +--- + +## Expected Bidirectional Links + +> This section is a reference for agents and can be removed in real pages. + +| From | To | Field | +|---|---|---| +| Discussion -> Actor | `[[repo-name]]` | `related_actors` in frontmatter | +| Discussion -> Person | `[[first-last]]` | `related_people` in frontmatter | +| Discussion -> Topic | `[[YYYY-MM-type-slug]]` | `related_topics` in frontmatter | +| Discussion -> Project | `[[project-slug]]` | `related_projects` in frontmatter | +| Discussion -> Team | `[[squad-name]]` | `related_teams` in frontmatter | +| Actor -> Discussion | `[[YYYY-MM-DD-slug]]` | "Discussions" section in Actor | +| Person -> Discussion | `[[YYYY-MM-DD-slug]]` | "Discussions" section in Person | +| Project -> Discussion | `[[YYYY-MM-DD-slug]]` | "Discussions" section in Project | diff --git a/plugins/bedrock/templates/fleeting/_template.md b/plugins/bedrock/templates/fleeting/_template.md new file mode 100644 index 0000000..d42fea5 --- /dev/null +++ b/plugins/bedrock/templates/fleeting/_template.md @@ -0,0 +1,43 @@ +--- +type: fleeting +title: "" +aliases: [] # ["Short Title"] — min 1 alias if title is long +source: "" # session | teach | manual +captured_at: YYYY-MM-DD +status: "raw" # raw | reviewing | promoted | archived +promoted_to: "" # "[[target-note]]" when promoted +sources: [] # [{url: "https://...", type: "confluence|gdoc|github-repo|csv|markdown|manual", synced_at: YYYY-MM-DD}] +updated_at: YYYY-MM-DD +updated_by: "" +tags: [type/fleeting, status/raw] # + domain/* optional +--- + +<!-- Zettelkasten role: fleeting note --> +<!-- Content in maturation — exploratory links allowed without full textual context --> + +# Fleeting Title + +> Raw information capture. Source: `source`. + +## Content + +Captured information — ideas, fragments, mentions. Does not need to be complete or well structured. + +## Possible Connections + +Exploratory wikilinks to entities that seem related: +- [[entity-name]] — reason for the possible connection + +## Capture Context + +Where it came from, when, and any additional context that helps with future promotion. + +--- + +## Expected Bidirectional Links + +> This section is a reference for agents and can be removed in real pages. + +| From | To | Field | +|---|---|---| +| Fleeting -> Permanent/Bridge | `[[entity-name]]` | `promoted_to` in frontmatter (when promoted) | diff --git a/plugins/bedrock/templates/people/_template.md b/plugins/bedrock/templates/people/_template.md new file mode 100644 index 0000000..d55dca8 --- /dev/null +++ b/plugins/bedrock/templates/people/_template.md @@ -0,0 +1,54 @@ +--- +type: person +name: "" +aliases: [] # ["Full Name Capitalized", "Nickname"] — min 1 alias +role: "" +team: "[[squad-name]]" +focal_points: [] +email: "" # full corporate email (e.g.: alice.smith@company.com) +github: "" # optional — GitHub login, when applicable +slack: "" # optional — Slack handle (e.g.: @alice.smith) +jira: "" +sources: [] # [{url: "https://...", type: "confluence|gdoc|github-repo|csv|markdown|manual", synced_at: YYYY-MM-DD}] +updated_at: YYYY-MM-DD +updated_by: "" +tags: [type/person] # + domain/* optional +--- + +<!-- Zettelkasten role: permanent note --> +<!-- Links in the body must have context: "leads the migration of [[legacy-gateway]] to [[billing-api]]" --> + +<!-- Filename convention: corporate email prefix, dots → hyphens. + E.g.: alice.smith@company.com → alice-smith.md + When email is unknown: first-last.md based on full name. --> + +# First Last + +> Brief description (2-3 lines) about the person's current role in the organization — position, area of expertise, and relevant context. + +## Team + +Member of [[squad-name]]. + +## Focal Points + +- [[repo-name]] — context of involvement +- [[repo-name]] — context of involvement + +## Active Topics + +- [[YYYY-MM-type-slug]] — brief description + +--- + +## Expected Bidirectional Links + +> This section is a reference for agents and can be removed in real pages. + +| From | To | Field | +|---|---|---| +| Person → Team | `[[squad-name]]` | `team` in frontmatter | +| Person → Actor | `[[repo-name]]` | "Focal Points" section | +| Person → Topic | `[[YYYY-MM-type-slug]]` | "Active Topics" section | +| Team → Person | `[[first-last]]` | `members` in Team frontmatter | +| Topic → Person | `[[first-last]]` | `people` in Topic frontmatter | diff --git a/plugins/bedrock/templates/projects/_template.md b/plugins/bedrock/templates/projects/_template.md new file mode 100644 index 0000000..4f53278 --- /dev/null +++ b/plugins/bedrock/templates/projects/_template.md @@ -0,0 +1,112 @@ +--- +type: project +name: "" +aliases: [] # ["Acronym", "Short Name"] — min 1 alias (e.g., ["V2 Migration"]) +description: "" +status: "" # planning | active | blocked | completed +deadline: "" +progress: "" +blockers: [] +action_items: + - description: "Action item description" + status: "todo" # todo | in_progress | done | blocked + deadline: "YYYY-MM-DD" + owner: "[[first-last]]" +focal_points: ["[[first-last]]"] +related_topics: ["[[YYYY-MM-type-slug]]"] +related_actors: ["[[repo-name]]"] +related_teams: ["[[squad-name]]"] +sources: [] # [{url: "https://...", type: "confluence|gdoc|github-repo|csv|markdown|manual", synced_at: YYYY-MM-DD}] +updated_at: YYYY-MM-DD +updated_by: "" +tags: [type/project] # + status/{planning,active,blocked,completed} + domain/* optional +--- + +<!-- Zettelkasten role: index note --> +<!-- Links in the body point to where the knowledge lives: "progress documented in [[2026-06-deprecation-legacy-gateway]]" — curation, not repetition --> + +# Project Name + +> Brief description of the project's objective and scope. + +## Overview + +Description of the project, its motivation, and expected outcomes. + +## Status + +| Field | Value | +|---|---| +| Status | planning / active / blocked / completed | +| Deadline | YYYY-MM-DD | +| Progress | description of current progress | + +> **Convention:** The project status reflects a management decision. The action items below help infer the actual state, but do not derive the status automatically. Examples: if all items are `done`, the project is likely `completed`; if any item is `blocked`, consider updating the project status to `blocked`. + +## Action Items + +| Item | Status | Deadline | Owner | +|---|---|---|---| +| Item description | todo | YYYY-MM-DD | [[first-last]] | + +> Action items are defined in the frontmatter (`action_items` field) to enable Dataview queries. The table above is a visualization for readability. + +**Dataview query — pending items for this project:** + +```dataview +TABLE WITHOUT ID + item.description AS "Item", + item.status AS "Status", + item.deadline AS "Deadline", + item.owner AS "Owner" +FROM "projects" +WHERE file.name = this.file.name +FLATTEN action_items AS item +WHERE item.status != "done" +SORT item.deadline ASC +``` + +## Blockers + +- Blocker 1 — description and impact + +## Focal Points + +| Person | Role | +|---|---| +| [[first-last]] | lead | +| [[first-last]] | contributor | + +## Related Topics + +| Topic | Relation | +|---|---| +| [[YYYY-MM-type-slug]] | related topic | + +## Related Actors + +| Actor | Relation | +|---|---| +| [[repo-name]] | affected system | + +## Related Teams + +| Team | Relation | +|---|---| +| [[squad-name]] | owning team | + +--- + +## Expected Bidirectional Links + +> This section is a reference for agents and can be removed in real pages. + +| From | To | Field | +|---|---|---| +| Project → Topic | `[[YYYY-MM-type-slug]]` | `related_topics` in frontmatter | +| Project → Actor | `[[repo-name]]` | `related_actors` in frontmatter | +| Project → Person | `[[first-last]]` | `focal_points` in frontmatter | +| Project → Team | `[[squad-name]]` | `related_teams` in frontmatter | +| Actor → Project | `[[project-slug]]` | "Related Projects" section in Actor | +| Topic → Project | `[[project-slug]]` | "Related Projects" section in Topic | +| Person → Project | `[[project-slug]]` | "Projects" section in Person | diff --git a/plugins/bedrock/templates/teams/_template.md b/plugins/bedrock/templates/teams/_template.md new file mode 100644 index 0000000..0eaf919 --- /dev/null +++ b/plugins/bedrock/templates/teams/_template.md @@ -0,0 +1,59 @@ +--- +type: team +name: "" +aliases: [] # ["Short Name", "Full Name"] — min 1 alias (e.g., ["Payments", "Squad Payments"]) +scope: "" +purpose: "" +members: ["[[first-last]]"] +actors: ["[[repo-name]]"] +jira_board: "" +confluence_space: "" +sources: [] # [{url: "https://...", type: "confluence|gdoc|github-repo|csv|markdown|manual", synced_at: YYYY-MM-DD}] +updated_at: YYYY-MM-DD +updated_by: "" +tags: [type/team] # + domain/{payments,finance,notifications,checkout,orders,integrations,compliance,core,data,infra,marketplace,internal-tools,platform,security} +--- + +<!-- Zettelkasten role: permanent note --> +<!-- Links in the body must have context: "responsible for operating [[billing-api]]" --> + +# Team Name + +> Brief description of scope and purpose. + +## Members + +| Person | Role | +|---|---| +| [[first-last]] | Role | +| [[first-last]] | Role | + +## Actors under Ownership + +| Actor | Category | Status | +|---|---|---| +| [[repo-name]] | api | active | +| [[repo-name]] | worker | deprecated | + +## Responsibilities + +- Responsibility 1 +- Responsibility 2 + +## Useful Links + +- Jira Board: [link]() +- Confluence Space: [link]() + +--- + +## Expected Bidirectional Links + +> This section is a reference for agents and can be removed in real pages. + +| From | To | Field | +|---|---|---| +| Team → Person | `[[first-last]]` | `members` in frontmatter | +| Team → Actor | `[[repo-name]]` | `actors` in frontmatter | +| Person → Team | `[[squad-name]]` | `team` in Person frontmatter | +| Actor → Team | `[[squad-name]]` | `team` in Actor frontmatter | diff --git a/plugins/bedrock/templates/topics/_template.md b/plugins/bedrock/templates/topics/_template.md new file mode 100644 index 0000000..0a76932 --- /dev/null +++ b/plugins/bedrock/templates/topics/_template.md @@ -0,0 +1,72 @@ +--- +type: topic +title: "" +aliases: [] # ["Short Title"] — min 1 alias (e.g., ["Deprecation Probe"]) +category: "" # bugfix | troubleshooting | rfc | incident | feature | deprecation | compliance +status: "" # open | in-progress | completed | cancelled +people: ["[[first-last]]"] +actors: ["[[repo-name]]"] +objective: "" +created_at: YYYY-MM-DD +sources: [] # [{url: "https://...", type: "confluence|gdoc|github-repo|csv|markdown|manual", synced_at: YYYY-MM-DD}] +updated_at: YYYY-MM-DD +updated_by: "" +tags: [type/topic] # + status/{open,in-progress,completed,cancelled} + category/{deprecation,bugfix,...} + domain/* optional +--- + +<!-- Zettelkasten role: bridge note --> +<!-- Links in the body explain WHY permanents relate: "the deprecation of [[legacy-gateway]] is blocked because clients of the legacy system depend on the tokenization of [[billing-api]]" --> + +# Topic Title + +> Brief description of the topic's objective. + +<!-- Mandatory callout for deprecation topics: --> +<!-- > [!warning] Deprecated --> +<!-- > Description of the deprecation plan and affected systems. --> + +## Context + +Description of context and motivation. + +## People Involved + +| Person | Role | +|---|---| +| [[first-last]] | focal point | +| [[first-last]] | contributor | + +## Actors Involved + +| Actor | Relation | +|---|---| +| [[repo-name]] | affected system | +| [[repo-name]] | replacement system | + +## History + +| Date | Event | +|---|---| +| YYYY-MM-DD | Event description | + +## Decisions + +- Decision 1 — justification + +## Next Steps + +- [ ] Action 1 +- [ ] Action 2 + +--- + +## Expected Bidirectional Links + +> This section is a reference for agents and can be removed in real pages. + +| From | To | Field | +|---|---|---| +| Topic → Person | `[[first-last]]` | `people` in frontmatter | +| Topic → Actor | `[[repo-name]]` | `actors` in frontmatter | +| Person → Topic | `[[YYYY-MM-type-slug]]` | "Active Topics" in Person | +| Actor → Topic | `[[YYYY-MM-type-slug]]` | "Related Topics" in Actor |