mcp: project-plan — herbylab Project Management
This commit is contained in:
parent
cb688cedd4
commit
b168f1cc07
174
Sources/Homelab/herbylab-project-management.md
Normal file
174
Sources/Homelab/herbylab-project-management.md
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
---
|
||||||
|
created: '2026-05-11'
|
||||||
|
path: Sources/Homelab
|
||||||
|
project: herbylab-project-management
|
||||||
|
status: active
|
||||||
|
tags:
|
||||||
|
- mcp
|
||||||
|
- automation
|
||||||
|
- n8n
|
||||||
|
- obsidian
|
||||||
|
type: project-plan
|
||||||
|
updated: '2026-05-11'
|
||||||
|
---
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Extend `herbylab` MCP to manage projects and notes as first-class artifacts backed by Postgres, with Trello as the visual projection. Vault remains the immutable content store in Phase 1; Postgres becomes sole source in a later phase.
|
||||||
|
|
||||||
|
Solves the pain of status hygiene and project visibility — 44+ vault entries, almost all flagged `active`, no aggregate view, no real query surface, frontmatter status edits feel wrong.
|
||||||
|
|
||||||
|
## Locked Decisions
|
||||||
|
|
||||||
|
**Three-layer model.** Mirrors Trellis:
|
||||||
|
- Vault md files — content, narrative, immutable. Feeds wiki-vault.
|
||||||
|
- Postgres (`herbylab` schema) — state, metadata, queryable index.
|
||||||
|
- Trello — visual projection of project state. Read-mostly.
|
||||||
|
|
||||||
|
**Single MCP, not split.** Project tools live alongside vault tools in `herbylab`. One connector, one tool surface.
|
||||||
|
|
||||||
|
**Polymorphic tool shape.** `create_artifact(kind, ...)` and `update_artifact(kind, slug, ...)` dispatch on `kind`. Adds future artifact types without growing the tool surface.
|
||||||
|
|
||||||
|
**Postgres co-located with Trellis.** Same instance, separate schema (`herbylab.*`). Trellis's schema-namespace pattern leaves room for this by design.
|
||||||
|
|
||||||
|
**Phase 1: dual-write, immutable entries.**
|
||||||
|
- `create_artifact` writes Postgres row + vault file atomically.
|
||||||
|
- No content updates via MCP — entries immutable once created.
|
||||||
|
- `update_artifact` only mutates metadata (status, tags, title, summary, domain).
|
||||||
|
- Vault stays the read surface for Obsidian and wiki-vault.
|
||||||
|
- New phases/addenda get new files, matching existing pattern (`ob1-deployment` + `ob1-main-deployment`).
|
||||||
|
|
||||||
|
**Phase N: vault retires, Postgres becomes sole source.**
|
||||||
|
- Viewer TBD — wiki-vault adaptation or new web UI served from Postgres.
|
||||||
|
- Out of scope for this plan.
|
||||||
|
|
||||||
|
**Source of truth for status: Postgres.** Status changes mid-conversation via `update_artifact` — never a file edit.
|
||||||
|
|
||||||
|
**Status propagates via NOTIFY → n8n → Trello.** Postgres trigger fires `pg_notify('project_changed', ...)` on insert/update. n8n LISTENs, projects to Trello.
|
||||||
|
|
||||||
|
**Frontmatter scope simplifies.** Vault frontmatter holds identity (`project`, `type`, `path`, `tags`, `created`). Status and `updated` leave frontmatter — Postgres concerns.
|
||||||
|
|
||||||
|
## Schema (Phase 1)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE SCHEMA herbylab;
|
||||||
|
|
||||||
|
CREATE TYPE herbylab.artifact_kind AS ENUM ('note', 'project');
|
||||||
|
CREATE TYPE herbylab.project_status AS ENUM ('active', 'paused', 'blocked', 'completed', 'archived');
|
||||||
|
CREATE TYPE herbylab.project_domain AS ENUM ('homelab', 'dev', 'venture', 'reference');
|
||||||
|
|
||||||
|
CREATE TABLE herbylab.artifacts (
|
||||||
|
slug TEXT NOT NULL,
|
||||||
|
kind herbylab.artifact_kind NOT NULL,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
domain herbylab.project_domain,
|
||||||
|
tags TEXT[] DEFAULT '{}',
|
||||||
|
vault_path TEXT NOT NULL,
|
||||||
|
body TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
PRIMARY KEY (kind, slug)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE herbylab.projects (
|
||||||
|
slug TEXT PRIMARY KEY,
|
||||||
|
status herbylab.project_status NOT NULL DEFAULT 'active',
|
||||||
|
summary TEXT,
|
||||||
|
trello_card_id TEXT,
|
||||||
|
FOREIGN KEY (kind, slug) REFERENCES herbylab.artifacts (kind, slug)
|
||||||
|
DEFERRABLE INITIALLY DEFERRED
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_artifacts_kind ON herbylab.artifacts(kind);
|
||||||
|
CREATE INDEX idx_artifacts_tags ON herbylab.artifacts USING GIN(tags);
|
||||||
|
CREATE INDEX idx_projects_status ON herbylab.projects(status);
|
||||||
|
```
|
||||||
|
|
||||||
|
`body` stores the markdown blob. Vault file holds the same content. Dual-write keeps them in lockstep; immutability eliminates drift risk.
|
||||||
|
|
||||||
|
## NOTIFY trigger
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE OR REPLACE FUNCTION herbylab.notify_project_change() RETURNS TRIGGER AS $$
|
||||||
|
DECLARE payload JSON;
|
||||||
|
BEGIN
|
||||||
|
payload := json_build_object(
|
||||||
|
'op', TG_OP,
|
||||||
|
'slug', COALESCE(NEW.slug, OLD.slug),
|
||||||
|
'status', COALESCE(NEW.status, OLD.status)
|
||||||
|
);
|
||||||
|
PERFORM pg_notify('project_changed', payload::text);
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE TRIGGER projects_notify
|
||||||
|
AFTER INSERT OR UPDATE OR DELETE ON herbylab.projects
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION herbylab.notify_project_change();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tool surface
|
||||||
|
|
||||||
|
**Vault tools (existing, unchanged):**
|
||||||
|
- `save_vault_note`
|
||||||
|
- `get_vault_project`
|
||||||
|
- `check_vault_file`
|
||||||
|
- `list_vault_projects`
|
||||||
|
- `get_vault_schema`
|
||||||
|
|
||||||
|
**Artifact tools (new):**
|
||||||
|
- `create_artifact(kind, slug, title, vault_path, content, domain?, tags?, summary?)` — writes Postgres row + vault file. For `kind="project"`, also inserts `projects` row with default `status='active'`.
|
||||||
|
- `update_artifact(kind, slug, **fields)` — mutates metadata only (status, title, tags, summary, domain). Never touches body or vault file.
|
||||||
|
- `get_artifact(kind, slug)` — returns row + linked file path.
|
||||||
|
- `list_artifacts(kind?, status?, domain?, tag?)` — filtered query.
|
||||||
|
|
||||||
|
`list_vault_projects` deprecates in favor of `list_artifacts` once cutover completes.
|
||||||
|
|
||||||
|
## Open Items
|
||||||
|
|
||||||
|
- [ ] Postgres deployment shape — depends on Trellis decision (LXC vs container)
|
||||||
|
- [ ] Migration tooling — match whatever Trellis lands on
|
||||||
|
- [ ] Trello board layout — single board with status lists vs board per domain
|
||||||
|
- [ ] Duplicate cleanup before backfill: `ob1-deployment` + `ob1-main-deployment`, `editor-stack-upgrade` (Homelab + Venture), `content-hub-phase5-architecture` + `-planning`
|
||||||
|
- [ ] Naming pin-down: memory has both `herbylab` and `herby-dev` — settle on one
|
||||||
|
|
||||||
|
## Phases
|
||||||
|
|
||||||
|
### Phase 1 — Infrastructure
|
||||||
|
- [ ] Postgres instance available with `herbylab` schema on the Trellis-shared instance
|
||||||
|
- [ ] Migration tooling chosen (match Trellis decision)
|
||||||
|
- [ ] Apply schema, types, indexes, NOTIFY trigger
|
||||||
|
|
||||||
|
### Phase 2 — MCP tools
|
||||||
|
- [ ] `create_artifact` (dual-write, transactional)
|
||||||
|
- [ ] `update_artifact` (metadata only)
|
||||||
|
- [ ] `get_artifact`, `list_artifacts` (read paths)
|
||||||
|
- [ ] Tag normalization mirroring Trellis pattern
|
||||||
|
|
||||||
|
### Phase 3 — Trello projection
|
||||||
|
- [ ] Trello board structure: lists for `active`, `paused`, `blocked`, `completed`. Labels for domain.
|
||||||
|
- [ ] n8n workflow: Postgres LISTEN node on `project_changed`
|
||||||
|
- [ ] Branch on op: INSERT → create card, UPDATE → move/edit card, DELETE → archive
|
||||||
|
- [ ] Store `trello_card_id` back to `projects` row on creation
|
||||||
|
|
||||||
|
### Phase 4 — Backfill and cutover
|
||||||
|
- [ ] Script walks existing vault project-plan files, populates `artifacts` + `projects` rows
|
||||||
|
- [ ] Status defaults to current frontmatter value (42 of 44 are `active`)
|
||||||
|
- [ ] Manual triage pass in Trello — drag to honest columns
|
||||||
|
- [ ] Strip `status` / `updated` from vault frontmatter going forward
|
||||||
|
- [ ] Backfill of existing frontmatter optional
|
||||||
|
|
||||||
|
### Phase N — Vault retirement
|
||||||
|
- Deferred. Revisit once Phase 1–4 are operational and read patterns are clearer.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
**Why dual-write in Phase 1.** Keeps two readers (Obsidian, wiki-vault) functional during transition. Immutability of entries kills the main drift risk — entries land once, neither side updates content. Update tool only touches metadata, which lives only in Postgres.
|
||||||
|
|
||||||
|
**Why immutable entries.** Matches existing behavior — new phases get new files (`-phase2`, `-addendum`) rather than edits. Codifying this as a constraint removes a class of bugs and makes Phase N (Postgres-only) trivially achievable later.
|
||||||
|
|
||||||
|
**Why Postgres co-located with Trellis.** Trellis's plan explicitly chose schema-namespacing to leave room for consolidation. Same instance, separate schemas, no new infrastructure. Backup/recovery story is shared.
|
||||||
|
|
||||||
|
**Why not Trellis itself for project tracking.** Trellis is for conversation threads — coarse session-level checkpoints, append-only events, paused/resumed work. Projects are different: durable, named, status-bearing, visualized externally. Different lifecycle, different reader, different surface.
|
||||||
|
|
||||||
|
**Why the polymorphic tool shape.** Avoids tool sprawl. Adding a future artifact type (task, decision log, etc.) is a `kind` value, not a new MCP tool. Tradeoff is a fatter schema in the tool description.
|
||||||
Loading…
Reference in New Issue
Block a user