From a2b667f497552271e759e5747028a1a948545bb6 Mon Sep 17 00:00:00 2001 From: "david.delagneau" Date: Fri, 17 Apr 2026 08:05:23 -0600 Subject: [PATCH] feat: Obsidian integration via cli scripts --- .opencode/agents/fidelity.md | 5 + .opencode/agents/workspace.md | 3 + .opencode/commands/fidelity-context.md | 1 + .opencode/commands/index.md | 2 + .opencode/commands/memory-create.md | 43 +++ .opencode/commands/memory-health.md | 27 ++ .opencode/commands/workspace-context.md | 1 + AGENTS.md | 5 + README.md | 21 +- core/README.md | 2 + core/integrations/memory-vault-model.md | 92 ++++++ core/integrations/obsidian-model.md | 22 +- core/profiles/create-project-profile.md | 7 + opencode.json | 1 + profiles/fidelity/profile.md | 4 + scripts/README.md | 15 + scripts/memory/README.md | 42 +++ scripts/memory/memory.sh | 335 ++++++++++++++++++++ scripts/obsidian/cli.sh | 20 ++ scripts/obsidian/daily.sh | 2 +- scripts/obsidian/open.sh | 2 +- scripts/obsidian/search.sh | 2 +- vault/.obsidian/graph.json | 4 +- vault/00-start/glossary.md | 1 + vault/00-start/obsidian-usage.md | 25 ++ vault/00-start/start-here.md | 2 + vault/00-start/workspace-architecture.md | 13 + vault/03-context/process/workspace-model.md | 2 + vault/07-maps/tooling.md | 10 + vault/09-templates/decision.md | 7 +- vault/09-templates/meeting-note.md | 7 +- vault/09-templates/person.md | 5 +- vault/09-templates/system.md | 5 +- vault/09-templates/work-item.md | 9 +- vault/09-templates/workstream.md | 5 +- 35 files changed, 715 insertions(+), 34 deletions(-) create mode 100644 .opencode/commands/memory-create.md create mode 100644 .opencode/commands/memory-health.md create mode 100644 core/integrations/memory-vault-model.md create mode 100644 scripts/memory/README.md create mode 100755 scripts/memory/memory.sh create mode 100755 scripts/obsidian/cli.sh diff --git a/.opencode/agents/fidelity.md b/.opencode/agents/fidelity.md index 6f4c1c9..df029c1 100644 --- a/.opencode/agents/fidelity.md +++ b/.opencode/agents/fidelity.md @@ -13,10 +13,14 @@ Behavior rules: - Treat `core/` as the reusable project-independent operating model. - Treat `profiles/fidelity/profile.md` as the active Fidelity project profile. - Treat `vault/` as the canonical clean knowledge base for humans and AI. +- Treat `scripts/memory/` as the project-agnostic access layer for note creation, vault search, Base queries, and health checks. +- Treat `scripts/obsidian/` as the current Obsidian adapter. Do not couple durable memory rules to Obsidian-specific behavior. - Treat `ai/inbox/` and generated connector files as raw evidence only, not promoted memory. - Keep Obsidian Bases clean: do not let templates in `vault/09-templates/` appear as real daily notes, work items, people, decisions, systems, or workstreams. - Role mapping notes such as `vault/04-people/manager.md` are `type: role-map`; actual people profiles are `type: person`. - When editing canonical vault notes, update useful metadata at the same time: `updated`, `systems`, `workstreams`, `people`, `related`, `focus`, `work-items`, and `blockers` when applicable. +- When creating a new typed note, prefer `bash scripts/memory/memory.sh create [title]`, then inspect and refine the generated Markdown. +- When checking vault quality, use `bash scripts/memory/memory.sh health` and direct file inspection. - Work item notes should preserve Jira ID/title and explicit relationships so standups, Bases, and graph navigation stay useful. - Daily notes should include `focus`, `work-items`, and `blockers` when those values are clear. - Before answering a prompt that depends on current state, verify the latest relevant files instead of relying only on conversation history. @@ -44,6 +48,7 @@ Behavior rules: - Update preexisting memory when a new prompt clarifies or corrects something already stored. - Do not wait for a dedicated sync command if the correct memory update is already obvious. - Do not leave behavior-only corrections only in daily logs. If a correction should affect future output, update the tool or instruction that produces that output. +- If the memory interface or Obsidian adapter fails, continue with direct Markdown operations when safe and do not promote the failure as project memory. - Do not over-promote uncertain information. Keep uncertain items in the daily log. - When drafting communication, preserve technical meaning and improve clarity in natural US English. - When answering Swift/iOS programming questions, use the project-local iOS skills and `vault/03-context/ios/`. diff --git a/.opencode/agents/workspace.md b/.opencode/agents/workspace.md index 3985c4d..571eec8 100644 --- a/.opencode/agents/workspace.md +++ b/.opencode/agents/workspace.md @@ -13,12 +13,15 @@ Behavior rules: - Load `core/` first for project-independent operating rules. - Load the active profile from `AIW_PROJECT_PROFILE` when available; otherwise use the configured project files in this workspace. - Treat `vault/` as the canonical clean knowledge base. +- Treat `scripts/memory/` as the stable memory access layer. +- Treat tool-specific integrations such as Obsidian as replaceable adapters. - Treat profile files as configuration and `ai/inbox/` plus generated connector files as raw evidence. - Keep Obsidian Bases clean by excluding templates and typing role maps separately from people. - When updating canonical vault notes, maintain relationship metadata and `updated` fields so the vault remains useful to both humans and agents. - Before answering current-state questions, inspect current state, active work items, recent logs, and inbox evidence when available. - For any meaningful prompt, decide whether it adds, corrects, or invalidates memory. - Update the smallest correct canonical file when memory should change. +- Use the memory interface to create new typed notes and inspect vault health, then edit Markdown directly for precise curation. - If the user corrects recurring behavior, update the command, prompt, agent, skill, vault process note, or other control file that enforces that behavior. - Keep imported evidence separate from promoted memory. - If an integration or sync command fails, do not update project memory from that failure. diff --git a/.opencode/commands/fidelity-context.md b/.opencode/commands/fidelity-context.md index 3c39445..11b117a 100644 --- a/.opencode/commands/fidelity-context.md +++ b/.opencode/commands/fidelity-context.md @@ -9,6 +9,7 @@ Use these files as the baseline context: @README.md @core/README.md @core/memory/operational-memory.md +@core/integrations/memory-vault-model.md @core/integrations/communication-model.md @profiles/fidelity/profile.md @vault/00-start/start-here.md diff --git a/.opencode/commands/index.md b/.opencode/commands/index.md index bb1c004..28b1d99 100644 --- a/.opencode/commands/index.md +++ b/.opencode/commands/index.md @@ -7,6 +7,8 @@ Slash commands available in this workspace. ## Generic Commands - [Workspace Context](workspace-context.md) +- [Memory Health](memory-health.md) +- [Memory Create](memory-create.md) - [Communication Sync](communication-sync.md) - [Archive Import](archive-import.md) - [AI Prompt](ai-prompt.md) diff --git a/.opencode/commands/memory-create.md b/.opencode/commands/memory-create.md new file mode 100644 index 0000000..2a6f3f4 --- /dev/null +++ b/.opencode/commands/memory-create.md @@ -0,0 +1,43 @@ +--- +description: Create a canonical memory note using the project-agnostic memory interface +--- + +Create a new canonical memory note from a known type. + +Supported types: + +- `daily` +- `work-item` +- `person` +- `decision` +- `system` +- `workstream` +- `meeting-note` + +User request: + +$ARGUMENTS + +Read: + +@core/integrations/memory-vault-model.md +@vault/00-start/workspace-architecture.md +@vault/09-templates/work-item.md +@vault/09-templates/person.md +@vault/09-templates/decision.md +@vault/09-templates/system.md +@vault/09-templates/workstream.md + +Instructions: + +- Parse the requested type, slug, and title from `$ARGUMENTS`. +- Use `bash scripts/memory/memory.sh create [title]` when type and slug are clear. +- If type or slug is not clear, do not guess. Explain the expected format. +- After creation, inspect the generated file and update metadata/content if the user provided enough context. +- Keep the note in the canonical folder selected by the memory interface. + +Return: + +1. Created file +2. Any metadata/content refined +3. Any missing details the user may want to add diff --git a/.opencode/commands/memory-health.md b/.opencode/commands/memory-health.md new file mode 100644 index 0000000..cd8f02a --- /dev/null +++ b/.opencode/commands/memory-health.md @@ -0,0 +1,27 @@ +--- +description: Check canonical memory health and Obsidian adapter status +--- + +Run a lightweight health check for the canonical memory vault. + +Read: + +@core/integrations/memory-vault-model.md +@core/integrations/obsidian-model.md +@vault/00-start/workspace-architecture.md + +Run: + +!`bash scripts/memory/memory.sh health` + +Structured views: + +!`for base in work-items people decisions daily systems workstreams; do echo "\n## $base"; bash scripts/memory/memory.sh base-query "$base" paths 2>/dev/null || bash scripts/memory/memory.sh base-query "$base" 2>/dev/null || true; done` + +Respond with: + +1. Health status +2. Any issues that should be fixed now +3. Any optional Obsidian-only improvements + +Do not modify project memory unless the user explicitly asks for cleanup. diff --git a/.opencode/commands/workspace-context.md b/.opencode/commands/workspace-context.md index 8317246..4b0e8bb 100644 --- a/.opencode/commands/workspace-context.md +++ b/.opencode/commands/workspace-context.md @@ -8,6 +8,7 @@ Read core: @core/README.md @core/memory/operational-memory.md +@core/integrations/memory-vault-model.md @core/integrations/communication-model.md @core/profiles/create-project-profile.md diff --git a/AGENTS.md b/AGENTS.md index 9cdc6f3..473a4ff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,6 +27,7 @@ The detailed operating rules live in: - `vault/03-context/process/ai-to-ai-prompting.md` - `vault/03-context/process/workspace-model.md` - `vault/03-context/process/memory-promotion-rules.md` +- `core/integrations/memory-vault-model.md` - `vault/04-people/manager.md` - `vault/04-people/index.md` - `vault/02-work-items/index.md` @@ -37,6 +38,8 @@ These are also loaded through `opencode.json`. - Assume the workspace may contain stale context until checked. - Treat `vault/` as the canonical clean memory for humans and AI. Treat `ai/inbox/` as raw evidence only. +- Treat `scripts/memory/` as the project-agnostic interface for creating notes, searching memory, querying Bases, and running vault health checks. +- Treat `scripts/obsidian/` as the current Obsidian adapter, not as the core memory abstraction. - Keep Obsidian Bases clean: templates in `vault/09-templates/` must not be treated as real notes, and role mapping files such as `vault/04-people/manager.md` must not be typed as people. - Maintain useful vault properties when editing canonical notes, especially work-item relationships (`systems`, `workstreams`, `people`, `related`) and daily note fields (`focus`, `work-items`, `blockers`). - Before answering questions that depend on current work state, inspect `vault/01-current/current-work.md` and the latest relevant daily note under `vault/06-daily/`. @@ -58,6 +61,8 @@ These are also loaded through `opencode.json`. - If a new prompt corrects prior understanding, update the canonical file directly instead of keeping both versions alive. - Do not ask what should be saved when the correct destination is already clear. - If the user provides durable new facts, update the appropriate context files instead of leaving the new information only in chat history. +- When creating a new canonical note from a known type, prefer `scripts/memory/memory.sh create [title]` so type-to-folder routing stays centralized. +- If the Obsidian CLI adapter fails, fall back to direct Markdown operations and treat the failure as tooling status, not project context. - If a previous context file is now stale or inaccurate, update that file directly. - Prefer correcting canonical context over appending contradictory notes. - Keep changes concise and auditable. diff --git a/README.md b/README.md index 8c91e2d..7c1e6ee 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,12 @@ Repeatable working guides for: ### /scripts -Helpers for future automation around context generation and communication drafting. +Helpers for automation around memory access, context generation, communication drafting, and imports. + +- `scripts/memory/` -> project-agnostic interface for canonical memory +- `scripts/obsidian/` -> current Obsidian adapter and URI helpers +- `scripts/mattermost/` -> live communication connector +- `scripts/slack/` -> historical archive importer ### /vault/.obsidian @@ -129,6 +134,8 @@ Open `vault/` as the Obsidian vault. Do not open the repository root as the vaul Portable vault configuration can be versioned, while local layout and plugin runtime files are ignored. +Obsidian is the current visual interface over canonical memory. The reusable memory access layer is `scripts/memory/`, so the workspace can later swap Obsidian for another Markdown knowledge tool without changing the memory model. + --- ## Daily Usage @@ -218,6 +225,8 @@ This keeps AI output tied to the latest workspace state instead of relying on ch ## Generic Commands - `/workspace-context` -> load core plus active profile +- `/memory-health` -> check canonical memory and adapter health +- `/memory-create` -> create a typed canonical note through the memory interface - `/communication-sync` -> sync live communication evidence and promote high-confidence memory - `/archive-import` -> import historical archive evidence - `/ai-prompt` -> generate a self-contained prompt for another AI @@ -293,8 +302,16 @@ Runtime/evidence stays outside the vault: Ignored Obsidian runtime files include workspace layout, plugin cache, snippets, and local plugin installs. -Obsidian URI helpers live under `scripts/obsidian/`: +Project-agnostic memory helpers live under `scripts/memory/`: +- `scripts/memory/memory.sh create [title]` +- `scripts/memory/memory.sh search [folder]` +- `scripts/memory/memory.sh base-query [format]` +- `scripts/memory/memory.sh health` + +Obsidian adapter helpers live under `scripts/obsidian/`: + +- `scripts/obsidian/cli.sh ` - `scripts/obsidian/open.sh ` - `scripts/obsidian/daily.sh` - `scripts/obsidian/search.sh ` diff --git a/core/README.md b/core/README.md index 6475687..2efbfb9 100644 --- a/core/README.md +++ b/core/README.md @@ -64,3 +64,5 @@ See `core/integrations/communication-model.md` for the reusable connector contra Optional navigation layers such as Obsidian should read the same Markdown files instead of copying memory into a second store. See `core/integrations/obsidian-model.md` for the recommended vault model. + +The project-agnostic memory interface lives in `scripts/memory/` and is documented in `core/integrations/memory-vault-model.md`. Agents should use that interface for note creation, vault search, Base queries, and health checks, while still editing Markdown directly for precise curation. diff --git a/core/integrations/memory-vault-model.md b/core/integrations/memory-vault-model.md new file mode 100644 index 0000000..691ddfb --- /dev/null +++ b/core/integrations/memory-vault-model.md @@ -0,0 +1,92 @@ +# Memory Vault Integration Model + +## Purpose + +Define a project-agnostic interface for reading, creating, searching, and validating the canonical Markdown memory. + +Obsidian is the current human-facing implementation, but the workspace should not depend on Obsidian-specific behavior for core memory maintenance. + +--- + +## Layers + +### Canonical Memory + +The source of truth is plain Markdown under `vault/`. + +Agents should edit canonical notes directly when precision matters because direct edits produce auditable diffs. + +### Memory Interface + +Use `scripts/memory/memory.sh` for vault-level operations: + +- `root` prints the configured memory root +- `read ` reads a vault-relative note +- `search [folder]` searches memory +- `create [title]` creates a note in the canonical folder from the matching template +- `base-query [format]` queries a structured view when supported +- `health` checks required files and optional navigation health + +### Tool Adapter + +The current adapter is Obsidian CLI through `scripts/obsidian/cli.sh`. + +If Obsidian CLI is unavailable or fails, `scripts/memory/memory.sh` falls back to direct Markdown operations unless `AIW_MEMORY_BACKEND=obsidian` is explicitly set. + +--- + +## Configuration + +- `AIW_MEMORY_VAULT_DIR`: canonical memory directory. Defaults to `/vault`. +- `AIW_MEMORY_BACKEND`: `auto`, `files`, or `obsidian`. Defaults to `auto`. +- `AIW_OBSIDIAN_VAULT_DIR`: Obsidian-specific vault override, still supported by the adapter. +- `AIW_OBSIDIAN_VAULT_NAME`: Obsidian URI vault name override for URI wrappers. + +Backend meanings: + +- `auto`: use Obsidian CLI when useful and available; otherwise use direct Markdown. +- `files`: force direct Markdown behavior. +- `obsidian`: require Obsidian CLI for supported operations and fail if unavailable. + +--- + +## Note Type Routing + +The memory interface owns type-to-folder routing: + +- `daily` -> `vault/06-daily/` +- `work-item` -> `vault/02-work-items/` +- `person` -> `vault/04-people/` +- `decision` -> `vault/05-decisions/` +- `system` -> `vault/03-context/systems/` +- `workstream` -> `vault/03-context/workstreams/` +- `meeting-note` -> `vault/06-daily/` + +Templates live in `vault/09-templates/`. + +This gives agents an automatic destination for new notes without coupling the rule to Obsidian. + +--- + +## Agent Rules + +- Use the memory interface when creating new canonical notes from a known type. +- Use the memory interface for broad search, Base queries, and vault health checks. +- Edit Markdown directly for precise memory curation, corrections, and content updates. +- Do not treat Obsidian CLI failure as project context. +- Do not require Obsidian to be running for core workspace operation. +- Keep raw evidence outside `vault/`; promote only curated memory. + +--- + +## Useful Obsidian CLI Operations + +When available, Obsidian CLI improves human-facing memory workflows: + +- `search:context` gives Obsidian-aware search context. +- `create path=... template=...` creates notes from templates. +- `base:query` reads Bases as structured views. +- `properties` and `property:*` inspect or update metadata. +- `unresolved`, `orphans`, `links`, `backlinks`, and `tags` support vault health checks. + +These are optional enhancements. The canonical memory remains Markdown. diff --git a/core/integrations/obsidian-model.md b/core/integrations/obsidian-model.md index 5930384..ab7ec83 100644 --- a/core/integrations/obsidian-model.md +++ b/core/integrations/obsidian-model.md @@ -126,11 +126,23 @@ Do not use Bases for raw inboxes, generated evidence, scripts, or runtime logs. ## CLI Wrappers -Use `scripts/obsidian/` for non-interactive Obsidian URI helpers: +Use `scripts/memory/` as the project-agnostic memory interface. +Use `scripts/obsidian/` only as the current Obsidian adapter: + +- `cli.sh` runs the official Obsidian CLI from the configured vault directory. - `uri.sh` generates encoded Obsidian URIs. -- `open.sh ` opens a note. -- `daily.sh` opens the configured daily note. -- `search.sh ` opens Obsidian search. +- `open.sh ` opens a note through Obsidian URI. +- `daily.sh` opens the configured daily note through Obsidian URI. +- `search.sh ` opens Obsidian search through Obsidian URI. -The wrappers default to `vault/` and can be overridden with `AIW_OBSIDIAN_VAULT_DIR` and `AIW_OBSIDIAN_VAULT_NAME`. +The memory interface can use Obsidian CLI when available and fall back to direct Markdown operations when it is not. + +The agent should depend on `scripts/memory/memory.sh` for portable operations: + +- `create [title]` +- `search [folder]` +- `base-query [format]` +- `health` + +This keeps Obsidian replaceable while still making the current vault easier to use. diff --git a/core/profiles/create-project-profile.md b/core/profiles/create-project-profile.md index 582f0ae..e5e91cb 100644 --- a/core/profiles/create-project-profile.md +++ b/core/profiles/create-project-profile.md @@ -58,6 +58,8 @@ Use generic variables first: ```text AIW_PROJECT_PROFILE= +AIW_MEMORY_VAULT_DIR= +AIW_MEMORY_BACKEND=auto AIW_CHANNEL_PREFIX= AIW_MATTERMOST_SYNC_CMD= AIW_SLACK_EXPORT_PATH= @@ -72,6 +74,8 @@ Connector secrets belong in ignored `.env` files, not in profile files. Start with generic commands: - `/workspace-context` +- `/memory-health` +- `/memory-create` - `/communication-sync` - `/archive-import` - `/standup` @@ -92,6 +96,7 @@ Before using the workspace for real work: - run a communication sync with test channels or a dry sample - generate one standup from sample context - verify that imported evidence and promoted memory stay separate +- run `bash scripts/memory/memory.sh health` --- @@ -99,6 +104,8 @@ Before using the workspace for real work: Open `vault/` as the Obsidian vault. +Use `scripts/memory/` as the project-agnostic memory interface. Obsidian is the default visual and CLI-backed adapter, but profile logic should not depend on Obsidian. + Recommended rules: - keep `vault/` as the clean canonical human/AI memory diff --git a/opencode.json b/opencode.json index ac17fde..35207f6 100644 --- a/opencode.json +++ b/opencode.json @@ -43,6 +43,7 @@ "./README.md", "./core/README.md", "./core/memory/operational-memory.md", + "./core/integrations/memory-vault-model.md", "./core/integrations/communication-model.md", "./core/integrations/obsidian-model.md", "./core/profiles/create-project-profile.md", diff --git a/profiles/fidelity/profile.md b/profiles/fidelity/profile.md index 393c0a5..c76a9f9 100644 --- a/profiles/fidelity/profile.md +++ b/profiles/fidelity/profile.md @@ -70,6 +70,8 @@ Core Fidelity context remains in: Raw communication evidence remains outside the vault under `ai/inbox/` and generated connector folders. +Memory access should go through the project-agnostic `scripts/memory/` interface for note creation, search, Base queries, and health checks. Obsidian is the current visual and CLI adapter, not a Fidelity-specific dependency. + Important domain themes: - Fid4 consumer validation @@ -87,6 +89,8 @@ Important domain themes: Generic commands: - `/workspace-context` +- `/memory-health` +- `/memory-create` - `/communication-sync` - `/archive-import` - `/ai-prompt` diff --git a/scripts/README.md b/scripts/README.md index dddbbfa..fa1ea47 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -3,11 +3,26 @@ This directory contains helpers that automate: - context aggregation +- canonical memory access - standup generation - manager update drafting - Mattermost-ready message formatting - historical Slack import +The project-agnostic memory interface lives in: + +- `scripts/memory/` + +Recommended commands: + +```bash +bash scripts/memory/memory.sh health +bash scripts/memory/memory.sh search "PDIAP-15765" +bash scripts/memory/memory.sh create work-item pdiap-15999 "Example title" +``` + +This interface defaults to Markdown files under `vault/`, uses Obsidian CLI when useful and available, and falls back to direct file operations. + The default workspace Mattermost extractor now lives in: - `scripts/mattermost/` diff --git a/scripts/memory/README.md b/scripts/memory/README.md new file mode 100644 index 0000000..7c7662d --- /dev/null +++ b/scripts/memory/README.md @@ -0,0 +1,42 @@ +# Memory Scripts + +This directory exposes a project-agnostic interface for the canonical memory vault. + +The current implementation uses Markdown files under `vault/` and can optionally delegate to the Obsidian CLI when it is available. The agent should depend on this memory interface, not on Obsidian-specific behavior, so the backing tool can be replaced later. + +## Backend Model + +- `AIW_MEMORY_VAULT_DIR` points to the canonical Markdown memory directory. +- `AIW_MEMORY_BACKEND` defaults to `auto`. +- `auto` uses Obsidian CLI when it is useful and available, then falls back to direct Markdown operations. +- `files` forces direct Markdown operations. +- `obsidian` requires the Obsidian CLI for supported operations. + +## Commands + +```bash +bash scripts/memory/memory.sh root +bash scripts/memory/memory.sh read 01-current/current-work.md +bash scripts/memory/memory.sh search "PDIAP-15765" +bash scripts/memory/memory.sh create work-item pdiap-15999 "Example title" +bash scripts/memory/memory.sh base-query work-items +bash scripts/memory/memory.sh health +``` + +## Note Creation + +`create` maps note types to canonical folders and templates: + +- `daily` -> `06-daily/` +- `work-item` -> `02-work-items/` +- `person` -> `04-people/` +- `decision` -> `05-decisions/` +- `system` -> `03-context/systems/` +- `workstream` -> `03-context/workstreams/` +- `meeting-note` -> `06-daily/` + +The template is resolved from `09-templates/.md`. When Obsidian CLI is available, the script uses `obsidian create path=... template=...`. Otherwise it creates the file directly from the template and resolves the basic `{{title}}`, `{{date}}`, and `{{time}}` variables. + +## Agent Rule + +Use these scripts for vault-level operations such as creating notes, querying Bases, validating navigation health, and searching memory. For precise content edits, agents should still edit Markdown files directly so diffs remain auditable. diff --git a/scripts/memory/memory.sh b/scripts/memory/memory.sh new file mode 100755 index 0000000..8faa961 --- /dev/null +++ b/scripts/memory/memory.sh @@ -0,0 +1,335 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +VAULT_DIR="${AIW_MEMORY_VAULT_DIR:-${AIW_OBSIDIAN_VAULT_DIR:-$WORKSPACE_ROOT/vault}}" +BACKEND="${AIW_MEMORY_BACKEND:-auto}" +OBSIDIAN_CLI="$WORKSPACE_ROOT/scripts/obsidian/cli.sh" + +usage() { + cat >&2 <<'EOF' +usage: memory.sh [args] + +commands: + root + read + search [folder] + create [title] + base-query [format] + health +EOF +} + +die() { + echo "$*" >&2 + exit 1 +} + +ensure_vault() { + [[ -d "$VAULT_DIR" ]] || die "vault directory not found: $VAULT_DIR" +} + +has_obsidian() { + [[ "$BACKEND" != "files" ]] && command -v obsidian >/dev/null 2>&1 && [[ -x "$OBSIDIAN_CLI" ]] +} + +require_obsidian_if_forced() { + if [[ "$BACKEND" == "obsidian" ]] && ! has_obsidian; then + die "AIW_MEMORY_BACKEND=obsidian but Obsidian CLI is unavailable" + fi +} + +safe_path() { + local rel="$1" + [[ "$rel" != /* ]] || die "path must be vault-relative: $rel" + [[ "$rel" != *".."* ]] || die "path must not contain '..': $rel" + printf '%s/%s\n' "$VAULT_DIR" "$rel" +} + +template_name_for_type() { + case "$1" in + daily) echo "daily" ;; + work-item) echo "work-item" ;; + person) echo "person" ;; + decision) echo "decision" ;; + system) echo "system" ;; + workstream) echo "workstream" ;; + meeting-note) echo "meeting-note" ;; + *) die "unsupported note type: $1" ;; + esac +} + +folder_for_type() { + case "$1" in + daily) echo "06-daily" ;; + work-item) echo "02-work-items" ;; + person) echo "04-people" ;; + decision) echo "05-decisions" ;; + system) echo "03-context/systems" ;; + workstream) echo "03-context/workstreams" ;; + meeting-note) echo "06-daily" ;; + *) die "unsupported note type: $1" ;; + esac +} + +note_path_for_type() { + local type="$1" + local slug="$2" + local folder + folder="$(folder_for_type "$type")" + case "$slug" in + *.md) echo "$folder/$slug" ;; + *) echo "$folder/$slug.md" ;; + esac +} + +render_template() { + local template="$1" + local title="$2" + local slug="$3" + python3 - "$template" "$title" "$slug" <<'PY' +import datetime +import pathlib +import re +import sys + +template = pathlib.Path(sys.argv[1]) +title = sys.argv[2] +slug = sys.argv[3] +now = datetime.datetime.now() +ticket = slug.removesuffix(".md").upper() +ticket = re.sub(r"^([A-Z]+)-?(\d+)$", r"\1-\2", ticket) +content = template.read_text() +content = content.replace("{{title}}", title) +content = content.replace("{{slug}}", slug.removesuffix(".md")) +content = content.replace("{{ticket}}", ticket) +content = content.replace("{{date}}", now.strftime("%Y-%m-%d")) +content = content.replace("{{date:YYYY-MM-DD}}", now.strftime("%Y-%m-%d")) +content = content.replace("{{time}}", now.strftime("%H:%M")) +print(content, end="") +PY +} + +postprocess_note() { + local rel="$1" + local title="$2" + local slug="$3" + local target + target="$(safe_path "$rel")" + [[ -f "$target" ]] || return 0 + python3 - "$target" "$title" "$slug" <<'PY' +import datetime +import pathlib +import re +import sys + +path = pathlib.Path(sys.argv[1]) +title = sys.argv[2] +slug = sys.argv[3].removesuffix(".md") +ticket = re.sub(r"^([A-Z]+)-?(\d+)$", r"\1-\2", slug.upper()) +today = datetime.datetime.now().strftime("%Y-%m-%d") +now = datetime.datetime.now().strftime("%H:%M") + +content = path.read_text() +content = content.replace("{{title}}", title) +content = content.replace("{{slug}}", slug) +content = content.replace("{{ticket}}", ticket) +content = content.replace("{{date}}", today) +content = content.replace("{{date:YYYY-MM-DD}}", today) +content = content.replace("{{time}}", now) + +lines = content.splitlines() +if lines[:1] == ["---"]: + try: + end = lines.index("---", 1) + except ValueError: + end = -1 + if end > 0: + frontmatter = lines[1:end] + body = lines[end + 1 :] + for i, line in enumerate(frontmatter): + if line.startswith("title:"): + frontmatter[i] = f"title: {title}" + elif line.startswith("ticket:"): + frontmatter[i] = f"ticket: {ticket}" + elif line.startswith("date:"): + frontmatter[i] = f"date: {today}" + elif line.startswith("updated:"): + frontmatter[i] = f"updated: {today}" + lines = ["---", *frontmatter, "---", *body] + content = "\n".join(lines) + "\n" + +path.write_text(content) +PY +} + +create_direct() { + local type="$1" + local slug="$2" + local title="${3:-$slug}" + local rel target template_name template + rel="$(note_path_for_type "$type" "$slug")" + target="$(safe_path "$rel")" + template_name="$(template_name_for_type "$type")" + template="$VAULT_DIR/09-templates/$template_name.md" + + [[ -e "$target" ]] && die "note already exists: $rel" + [[ -f "$template" ]] || die "template not found: 09-templates/$template_name.md" + mkdir -p "$(dirname "$target")" + render_template "$template" "$title" "$slug" > "$target" + postprocess_note "$rel" "$title" "$slug" + echo "$rel" +} + +create_note() { + local type="$1" + local slug="$2" + local title="${3:-$slug}" + local rel template_name + rel="$(note_path_for_type "$type" "$slug")" + template_name="$(template_name_for_type "$type")" + + require_obsidian_if_forced + if has_obsidian; then + if "$OBSIDIAN_CLI" create "path=$rel" "template=$template_name" >/dev/null 2>&1; then + postprocess_note "$rel" "$title" "$slug" + echo "$rel" + return 0 + fi + [[ "$BACKEND" == "obsidian" ]] && die "obsidian create failed for $rel" + fi + + create_direct "$type" "$slug" "$title" +} + +search_memory() { + local query="$1" + local folder="${2:-}" + + require_obsidian_if_forced + if has_obsidian; then + if [[ -n "$folder" ]]; then + if "$OBSIDIAN_CLI" search:context "query=$query" "path=$folder" "limit=50"; then + return 0 + fi + else + if "$OBSIDIAN_CLI" search:context "query=$query" "limit=50"; then + return 0 + fi + fi + [[ "$BACKEND" == "obsidian" ]] && die "obsidian search failed" + fi + + if [[ -n "$folder" ]]; then + rg -n -- "$query" "$VAULT_DIR/$folder" + else + rg -n -- "$query" "$VAULT_DIR" + fi +} + +base_query() { + local base="$1" + local format="${2:-md}" + local base_path="08-bases/${base%.base}.base" + local type_name="${base%.base}" + + require_obsidian_if_forced + if has_obsidian; then + if "$OBSIDIAN_CLI" base:query "path=$base_path" "format=$format"; then + return 0 + fi + [[ "$BACKEND" == "obsidian" ]] && die "obsidian base query failed for $base_path" + fi + + case "$type_name" in + work-items) rg -l '^type: work-item$' "$VAULT_DIR/02-work-items" ;; + people) rg -l '^type: person$' "$VAULT_DIR/04-people" ;; + decisions) rg -l '^type: decision$' "$VAULT_DIR/05-decisions" ;; + daily) rg -l '^type: daily$' "$VAULT_DIR/06-daily" ;; + systems) rg -l '^type: system$' "$VAULT_DIR/03-context/systems" ;; + workstreams) rg -l '^type: workstream$' "$VAULT_DIR/03-context/workstreams" ;; + *) die "unsupported base fallback: $base" ;; + esac +} + +health_check() { + local failed=0 + local required=( + "00-start/start-here.md" + "01-current/current-work.md" + "01-current/work-items.md" + "02-work-items/index.md" + "03-context/project.md" + "04-people/index.md" + "07-maps/index.md" + "08-bases/work-items.base" + "09-templates/work-item.md" + ) + + ensure_vault + + for rel in "${required[@]}"; do + if [[ ! -e "$VAULT_DIR/$rel" ]]; then + echo "missing: $rel" + failed=1 + fi + done + + if [[ -d "$WORKSPACE_ROOT/.obsidian" ]]; then + echo "unexpected root Obsidian config: .obsidian" + failed=1 + fi + + if has_obsidian; then + "$OBSIDIAN_CLI" unresolved total 2>/dev/null | sed 's/^/unresolved-links: /' || true + "$OBSIDIAN_CLI" orphans total 2>/dev/null | sed 's/^/orphans: /' || true + "$OBSIDIAN_CLI" tags total 2>/dev/null | sed 's/^/tags: /' || true + else + echo "obsidian-cli: unavailable; file checks only" + fi + + if [[ "$failed" -eq 0 ]]; then + echo "memory health ok" + fi + exit "$failed" +} + +main() { + ensure_vault + local command="${1:-}" + [[ -n "$command" ]] || { usage; exit 1; } + shift || true + + case "$command" in + root) + echo "$VAULT_DIR" + ;; + read) + [[ $# -eq 1 ]] || die "usage: memory.sh read " + cat "$(safe_path "$1")" + ;; + search) + [[ $# -ge 1 ]] || die "usage: memory.sh search [folder]" + search_memory "$1" "${2:-}" + ;; + create) + [[ $# -ge 2 ]] || die "usage: memory.sh create [title]" + create_note "$1" "$2" "${3:-$2}" + ;; + base-query) + [[ $# -ge 1 ]] || die "usage: memory.sh base-query [format]" + base_query "$1" "${2:-md}" + ;; + health) + health_check + ;; + *) + usage + exit 1 + ;; + esac +} + +main "$@" diff --git a/scripts/obsidian/cli.sh b/scripts/obsidian/cli.sh new file mode 100755 index 0000000..4e1c5f5 --- /dev/null +++ b/scripts/obsidian/cli.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +VAULT_DIR="${AIW_MEMORY_VAULT_DIR:-${AIW_OBSIDIAN_VAULT_DIR:-$WORKSPACE_ROOT/vault}}" + +if ! command -v obsidian >/dev/null 2>&1; then + echo "obsidian CLI is not installed or not in PATH" >&2 + exit 127 +fi + +if [[ ! -d "$VAULT_DIR" ]]; then + echo "vault directory not found: $VAULT_DIR" >&2 + exit 1 +fi + +cd "$VAULT_DIR" +exec obsidian "$@" diff --git a/scripts/obsidian/daily.sh b/scripts/obsidian/daily.sh index 6ca4d72..f076d25 100755 --- a/scripts/obsidian/daily.sh +++ b/scripts/obsidian/daily.sh @@ -4,7 +4,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -VAULT_DIR="${AIW_OBSIDIAN_VAULT_DIR:-$WORKSPACE_ROOT/vault}" +VAULT_DIR="${AIW_OBSIDIAN_VAULT_DIR:-${AIW_MEMORY_VAULT_DIR:-$WORKSPACE_ROOT/vault}}" VAULT_NAME="${AIW_OBSIDIAN_VAULT_NAME:-$(basename "$VAULT_DIR")}" URI="$("$SCRIPT_DIR/uri.sh" daily "vault=$VAULT_NAME")" diff --git a/scripts/obsidian/open.sh b/scripts/obsidian/open.sh index f29281d..6e478e6 100755 --- a/scripts/obsidian/open.sh +++ b/scripts/obsidian/open.sh @@ -4,7 +4,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -VAULT_DIR="${AIW_OBSIDIAN_VAULT_DIR:-$WORKSPACE_ROOT/vault}" +VAULT_DIR="${AIW_OBSIDIAN_VAULT_DIR:-${AIW_MEMORY_VAULT_DIR:-$WORKSPACE_ROOT/vault}}" VAULT_NAME="${AIW_OBSIDIAN_VAULT_NAME:-$(basename "$VAULT_DIR")}" if [[ $# -lt 1 ]]; then diff --git a/scripts/obsidian/search.sh b/scripts/obsidian/search.sh index 4fc7822..6bcc4d8 100755 --- a/scripts/obsidian/search.sh +++ b/scripts/obsidian/search.sh @@ -4,7 +4,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -VAULT_DIR="${AIW_OBSIDIAN_VAULT_DIR:-$WORKSPACE_ROOT/vault}" +VAULT_DIR="${AIW_OBSIDIAN_VAULT_DIR:-${AIW_MEMORY_VAULT_DIR:-$WORKSPACE_ROOT/vault}}" VAULT_NAME="${AIW_OBSIDIAN_VAULT_NAME:-$(basename "$VAULT_DIR")}" if [[ $# -lt 1 ]]; then diff --git a/vault/.obsidian/graph.json b/vault/.obsidian/graph.json index f6d8918..14ded2d 100644 --- a/vault/.obsidian/graph.json +++ b/vault/.obsidian/graph.json @@ -60,6 +60,6 @@ "repelStrength": 10, "linkStrength": 1, "linkDistance": 250, - "scale": 1.0087453683557874, + "scale": 1.3297103454416062, "close": true -} +} \ No newline at end of file diff --git a/vault/00-start/glossary.md b/vault/00-start/glossary.md index 36ea28c..be40253 100644 --- a/vault/00-start/glossary.md +++ b/vault/00-start/glossary.md @@ -24,6 +24,7 @@ Common terms used in this workspace. - `inbox`: raw or lightly processed communication evidence under `ai/inbox/`. - `promotion`: moving high-confidence evidence into canonical memory. - `tooling behavior`: reusable behavior encoded in commands, prompts, skills, agents, or knowledge rules. +- `memory interface`: project-agnostic scripts under `scripts/memory/` that create, search, query, and validate canonical memory without coupling the workspace to Obsidian. --- diff --git a/vault/00-start/obsidian-usage.md b/vault/00-start/obsidian-usage.md index 06adc14..bfdae83 100644 --- a/vault/00-start/obsidian-usage.md +++ b/vault/00-start/obsidian-usage.md @@ -10,6 +10,8 @@ tags: Use Obsidian to navigate and review workspace memory. Do not use it as a separate memory database. +Obsidian is the current interface over canonical Markdown memory. The workspace-level abstraction is `scripts/memory/`, so agents should use Obsidian-specific commands only through that adapter when possible. + --- ## Recommended Start @@ -126,3 +128,26 @@ For normal onboarding, keep them hidden and navigate through the named maps. - Templates may contain the final note `type` so newly created notes are useful immediately, but Bases must exclude the template folder so template files do not appear as data rows. - Role mapping files such as `04-people/manager.md` should use `type: role-map`, not `type: person`. - Bookmarks should keep `00-start/start-here.md`, `00-start/onboarding.md`, current work, work items, people, and high-value Bases easy to open. + +--- + +## CLI And Templates + +The official Obsidian CLI can create notes from templates, query Bases, inspect properties, and search the vault. + +In this workspace, prefer the platform-agnostic memory wrapper: + +```bash +bash scripts/memory/memory.sh create work-item pdiap-15999 "Example title" +bash scripts/memory/memory.sh search "PDIAP-15765" +bash scripts/memory/memory.sh base-query work-items +bash scripts/memory/memory.sh health +``` + +The wrapper owns note type routing, so a `work-item` goes to `02-work-items/`, a `person` goes to `04-people/`, a `decision` goes to `05-decisions/`, and so on. + +Use Obsidian CLI directly only when debugging Obsidian-specific behavior: + +```bash +bash scripts/obsidian/cli.sh help +``` diff --git a/vault/00-start/start-here.md b/vault/00-start/start-here.md index d198e9f..be21b76 100644 --- a/vault/00-start/start-here.md +++ b/vault/00-start/start-here.md @@ -75,6 +75,8 @@ If you are new to this project, read: - [Communication Rules](../03-context/process/communication-rules.md) - [Context Maintenance](../03-context/process/context-maintenance.md) - [Workspace Architecture](workspace-architecture.md) +- Memory interface: `scripts/memory/` +- Obsidian adapter: `scripts/obsidian/` --- diff --git a/vault/00-start/workspace-architecture.md b/vault/00-start/workspace-architecture.md index d72f305..ddac6c6 100644 --- a/vault/00-start/workspace-architecture.md +++ b/vault/00-start/workspace-architecture.md @@ -50,6 +50,19 @@ Runtime and generated files can be used as evidence, but durable project facts s --- +## Memory Access Layer + +Use `scripts/memory/` as the platform-agnostic interface to canonical memory. + +Obsidian is the current visual and CLI-backed adapter, but the source of truth remains Markdown under `vault/`. + +- Agents use `scripts/memory/memory.sh create` when a new typed note is needed. +- Agents use `scripts/memory/memory.sh search` or direct Markdown reads for context lookup. +- Agents use `scripts/memory/memory.sh base-query` and `health` for structured review. +- Precise memory edits should still be made directly to Markdown so changes stay auditable. + +--- + ## Memory Rule Promoted memory lives in `vault/`. diff --git a/vault/03-context/process/workspace-model.md b/vault/03-context/process/workspace-model.md index a1ebdfa..ac5bf3a 100644 --- a/vault/03-context/process/workspace-model.md +++ b/vault/03-context/process/workspace-model.md @@ -48,6 +48,8 @@ When the user corrects a recurring behavior, the workspace should update the fil - `core/` for reusable project-independent behavior - `profiles//` for project-specific assumptions - `vault/.obsidian/` only for portable vault configuration, not project memory +- `scripts/memory/` for project-agnostic memory access, creation, search, Base queries, and health checks +- `scripts/obsidian/` for the current Obsidian adapter, not for core memory semantics - `.opencode/commands/` for slash commands - `prompts/` for reusable drafting templates - `.opencode/agents/` and `AGENTS.md` for default agent behavior diff --git a/vault/07-maps/tooling.md b/vault/07-maps/tooling.md index 2219a65..1cfc32f 100644 --- a/vault/07-maps/tooling.md +++ b/vault/07-maps/tooling.md @@ -24,6 +24,8 @@ Commands, prompts, skills, workflows, and automation surfaces that make the work - Commands index: `.opencode/commands/index.md` - Workspace context: `.opencode/commands/workspace-context.md` +- Memory health: `.opencode/commands/memory-health.md` +- Memory create: `.opencode/commands/memory-create.md` - Communication sync: `.opencode/commands/communication-sync.md` - Archive import: `.opencode/commands/archive-import.md` - AI prompt: `.opencode/commands/ai-prompt.md` @@ -51,3 +53,11 @@ Commands, prompts, skills, workflows, and automation surfaces that make the work - Story draft prompt: `prompts/story-draft.md` - OpenCode entry workflow: `workflows/opencode-entry.md` - Daily context sync: `workflows/daily-context-sync.md` + +--- + +## Memory Interface + +- Project-agnostic memory scripts: `scripts/memory/` +- Current Obsidian adapter: `scripts/obsidian/` +- Canonical vault: `vault/` diff --git a/vault/09-templates/decision.md b/vault/09-templates/decision.md index 0524d0c..168fb37 100644 --- a/vault/09-templates/decision.md +++ b/vault/09-templates/decision.md @@ -3,7 +3,7 @@ type: decision project: fidelity status: proposed date: -title: +title: "{{title}}" systems: [] workstreams: [] people: [] @@ -11,10 +11,10 @@ related: [] tags: - decision - fidelity -updated: +updated: "{{date:YYYY-MM-DD}}" --- -# Decision Title +# {{title}} ## Decision @@ -37,4 +37,3 @@ updated: ## Follow-up - - diff --git a/vault/09-templates/meeting-note.md b/vault/09-templates/meeting-note.md index b29f977..c04470e 100644 --- a/vault/09-templates/meeting-note.md +++ b/vault/09-templates/meeting-note.md @@ -1,17 +1,17 @@ --- type: meeting-note project: fidelity -date: +date: "{{date:YYYY-MM-DD}}" people: [] work-items: [] systems: [] tags: - meeting - fidelity -updated: +updated: "{{date:YYYY-MM-DD}}" --- -# Meeting Note +# {{title}} ## Context @@ -34,4 +34,3 @@ updated: ## Memory Updates - - diff --git a/vault/09-templates/person.md b/vault/09-templates/person.md index 309e86b..1ef06a5 100644 --- a/vault/09-templates/person.md +++ b/vault/09-templates/person.md @@ -9,10 +9,10 @@ related: [] tags: - person - fidelity -updated: +updated: "{{date:YYYY-MM-DD}}" --- -# Name +# {{title}} ## Role @@ -35,4 +35,3 @@ updated: ## Related Context - - diff --git a/vault/09-templates/system.md b/vault/09-templates/system.md index 0c5a7ba..b02bb43 100644 --- a/vault/09-templates/system.md +++ b/vault/09-templates/system.md @@ -9,10 +9,10 @@ related: [] tags: - system - fidelity -updated: +updated: "{{date:YYYY-MM-DD}}" --- -# System Name +# {{title}} ## Role @@ -35,4 +35,3 @@ updated: ## Related Context - - diff --git a/vault/09-templates/work-item.md b/vault/09-templates/work-item.md index b52f7d9..f5f1f26 100644 --- a/vault/09-templates/work-item.md +++ b/vault/09-templates/work-item.md @@ -2,8 +2,8 @@ type: work-item project: fidelity status: active -ticket: -title: +ticket: "{{ticket}}" +title: "{{title}}" systems: [] workstreams: [] people: [] @@ -11,10 +11,10 @@ related: [] tags: - work-item - fidelity -updated: +updated: "{{date:YYYY-MM-DD}}" --- -# Ticket - Title +# {{ticket}} - {{title}} ## Status @@ -43,4 +43,3 @@ updated: ## Next Step - - diff --git a/vault/09-templates/workstream.md b/vault/09-templates/workstream.md index 8e3b14c..292d658 100644 --- a/vault/09-templates/workstream.md +++ b/vault/09-templates/workstream.md @@ -9,10 +9,10 @@ related: [] tags: - workstream - fidelity -updated: +updated: "{{date:YYYY-MM-DD}}" --- -# Workstream Name +# {{title}} ## Goal @@ -35,4 +35,3 @@ updated: ## Related Work - -