pal-e-docs

pal-e-docs forgejo

Notes

Phase 77
  • Phase 6: Vector Search (pgvector) phase-postgres-6-vector-search

    Goal: Add semantic/vector search to pal-e-docs using pgvector. Enable AI agents to find conceptually related notes, not just keyword matches.

    Owner: Dev agent

    Repo: pal-e-platform, pal-e-docs, pal-e-docs-sdk, pal-e-docs-mcp

    Depends on: Phase 5 (full-text search) — COMPLETED, Phase 7 (block content model) — COMPLETED, Phase 7f (clean data) — COMPLETED

    Why

    Full-text search finds keyword matches. Vector search finds conceptual matches. Example: searching "how do we handle credentials" should return the secrets management SOP even if it never uses the word "credentials."

    Key Decisions (see decision-phase6-vector-search-architecture)

    Decision Choice
    Embedding model Qwen3-Embedding-4B via Ollama (GPU-accelerated on GTX 1070)
    Dimensions 768 (configurable, can increase later)
    What to embed Per-block (paragraph, list, heading, table, code. Skip mermaid.)
    Pipeline Async via PostgreSQL LISTEN/NOTIFY (no Redis/Celery, event-driven, sub-30s)
    Ollama Platform service (own namespace, like CNPG)
    Worker Separate k8s Deployment (owns GPU resource request, independent failure domain)
    Instruction prefixes Asymmetric — different prefixes for document indexing vs query embedding

    Sub-Phase Status

    # Sub-Phase Repo Status
    6a Deploy Ollama as platform service pal-e-platform COMPLETED
    6b pgvector extension + schema migration pal-e-docs COMPLETED
    6b-1 Fix extension ownership (migration-only) pal-e-docs COMPLETED (PR #140, Issue #126)
    6c Async embedding pipeline + backfill pal-e-docs COMPLETED (PR #130, Issue #129)
    6c-1 Enforce Closes #N + strengthen post-merge reminder claude-custom COMPLETED (PR #74, Issue #73)
    6c-2 QA nits — dead code, N+1 query, dep hygiene, k8s hardening pal-e-docs COMPLETED (PR #136, Issue #135)
    6d Semantic search API + SDK + MCP tool pal-e-docs, sdk, mcp COMPLETED (API #138, SDK #20, MCP #30)
    6d-1 SDK integration tests pal-e-docs-sdk COMPLETED (PR #23, Issue #22)
    6e Hybrid ranking (tsvector + vector) pal-e-docs COMPLETED (PR #141, Issue #139)

    Sub-Phase Details

    6a: Deploy Ollama as platform service

    Repo: pal-e-platform (Terraform + Helm)

    Status: COMPLETED — PR #25 (initial deploy), PR #27 (runtimeClassName fix). Issue #24, #26.

    • Create ollama namespace
    • Deploy Ollama via Helm chart with GPU resource request (nvidia.com/gpu: 1)
    • Pull qwen3-embedding:4b model on startup
    • ClusterIP service on port 11434 for internal access
    • Verify GPU acceleration is active (ollama ps shows GPU layers)

    Verification (2026-03-08): Node reports nvidia.com/gpu: 1 capacity. Ollama pod Running. Model qwen3-embedding:4b (4.0B, Q4_K_M) fully loaded in VRAM (3.5GB). Embedding API returns 768-dim vectors.

    6b: pgvector extension + schema migration

    Repo: pal-e-docs

    Status: COMPLETED — PR #122. Issue #121.

    • Enable pgvector extension via CREATE EXTENSION IF NOT EXISTS vector
    • Alembic migration l2g3h4i5j6k7: add embedding vector(768) column to blocks table
    • Add embedding_status varchar(20) column (default 'pending')
    • Create HNSW index on embedding column (vector_cosine_ops)
    • Postgres trigger: on block INSERT or UPDATE of content or block_type, sets embedding_status = 'pending' and NOTIFY embedding_queue. Skips mermaid blocks (sets 'skipped').
    • Added pgvector>=0.3 dependency

    6b-1: Fix extension ownership (platform-provides pattern)

    Repo: pal-e-docs (migration fix), deployments (CNPG CRD)

    Status: NOT STARTED — Issue #126.

    Problem: The 6b migration includes CREATE EXTENSION IF NOT EXISTS vector, which requires superuser. The Alembic migration runs as the paledocs app user (not superuser), causing CrashLoopBackOff on deploy. Fixed manually via kubectl exec as postgres superuser, but this breaks fresh deployments.

    Fix: Follow the platform-provides/app-consumes pattern:

    • Remove CREATE EXTENSION from Alembic migration, replace with existence check + informative error
    • Add extension provisioning to CNPG Cluster CRD in deployments repo (bootstrap.initdb.postInitSQL)
    • Remove DROP EXTENSION from downgrade

    6c: Async embedding pipeline + backfill

    Repo: pal-e-docs (new module + k8s Deployment). Same Docker image, different entrypoint.

    • Embedding worker process: src/pal_e_docs/embedding_worker.py — standalone Python process (not FastAPI). LISTEN embedding_queue as primary trigger, periodic poll fallback (every 60s) for missed notifications during restarts.
    • Block text extraction: block_type-aware content JSON → plain text. Paragraph: strip HTML. List: join items. Heading: "{note_title} > {heading_text}" (parent context join). Table: flatten headers + rows. Code: raw text. Mermaid: already skipped by trigger.
    • Ollama integration: POST http://ollama.ollama.svc.cluster.local:11434/api/embed with model qwen3-embedding:4b. Document prefix: "Represent this platform knowledge base section for retrieval: {block_text}". Store 768-dim vector in blocks.embedding.
    • State machine: embedding_status transitions: pending → processing → completed | error. The processing state prevents duplicate work on pod restart. Existing skipped state (mermaid) unchanged.
    • Reliability: retry with exponential backoff on Ollama transient errors. Batch processing (10 blocks/cycle live, higher for backfill). Graceful SIGTERM handling — finish current batch, don't leave blocks in processing state.
    • Observability: Prometheus metrics — embedding_total, embedding_errors_total, embedding_duration_seconds, embedding_queue_depth. Health endpoint (/healthz) for k8s liveness/readiness probes. Structured logging.
    • k8s Deployment: k8s/embedding-worker.yaml — same image as API pod, entrypoint python -m pal_e_docs.embedding_worker. No GPU resource request (worker calls Ollama over HTTP). Minimal resources (10m CPU, 64Mi request, 256Mi limit). Add to kustomization.yaml.
    • Config: add ollama_url to Settings (PALDOCS_OLLAMA_URL, default: in-cluster service URL).
    • Dependencies: add httpx to main deps (Ollama HTTP client). Use raw psycopg2 connection for LISTEN (SQLAlchemy doesn't expose it).
    • Backfill: --backfill flag — one-time run to embed all ~5K pending blocks. Rate-limited batches, progress logging. Can run as kubectl exec into the worker pod.

    6d: Semantic search API + SDK + MCP tool

    Repos: pal-e-docs (API), pal-e-docs-sdk (client), pal-e-docs-mcp (tool)

    • GET /notes/semantic-search?q=...&limit=10 — query prefix applied, cosine similarity search
    • Returns: matching blocks with note context, similarity score, block content snippet
    • SDK: client.semantic_search(query, limit)
    • MCP: semantic_search tool wrapping SDK

    6e: Hybrid ranking (tsvector + vector)

    Repo: pal-e-docs

    • Combine full-text search score (tsvector ts_rank) with vector similarity (cosine distance)
    • Weighted ranking: configurable alpha between keyword relevance and semantic similarity
    • GET /notes/search?q=...&mode=hybrid — unified search endpoint
    • MCP: update search_notes tool to support mode parameter

    Dependency Chain

    6a (Ollama) → 6b (pgvector schema) → 6b-1 (extension fix) → 6c (pipeline + backfill) → 6d (API + MCP) → 6e (hybrid)
    

    6a and 6b can run in parallel (different repos). 6b-1 doesn't block 6c (extension already installed). 6c depends on both 6a and 6b. 6d depends on 6c. 6e depends on 6d.

    • decision-phase6-vector-search-architecture — full decision record with model research and hardware analysis
    • phase-postgres-7-block-content — prerequisite phase (COMPLETED)
    • phase-postgres-7f-doc-cleanup-sop — prerequisite phase (COMPLETED, clean data)
    • concept-phase5-self-hosted-rag — RAG architecture vision
    • concept-phase5-database-side-intelligence — database-side intelligence pattern
    • benchmark-phase5-knowledge-baseline — baseline measurements
  • Goal: Zero frontend tests → Playwright E2E smoke tests covering all major features. Backend has 513 tests; frontend needs parity.

    Owner: Dev-Frontend agent

    Repo: forgejo_admin/pal-e-app

    Depends on: None — can start immediately. Auth tests (F5) can be added later.

    Scope

    • Install Playwright + @playwright/test as dev dependencies
    • Create playwright.config.ts with dark theme, Chromium, base URL pointing to local dev server
    • Add test and test:e2e scripts to package.json
    • Smoke tests covering all shipped features:
      • T1: Home page loads with nav (Dashboard, Search, Notes, Projects, Boards, Tags, Repos)
      • T2: Search — navigate to /search, type query, see results with type badges
      • T3: Dashboard — /dashboard loads with board rollup, per-project cards
      • T4: Board filtering — type pills filter items, hide-done toggle works
      • T5: Quick-jot — FAB opens modal, fill title, create note, verify toast
      • T6: Note detail — navigate to /notes/{slug}, see blocks rendered, TOC sidebar
      • T7: Board drag-drop — move item between columns (desktop)
    • Add Playwright test step to .woodpecker.yaml (after build, before push)
    • CI runs tests against built app (not dev server) for production fidelity

    Deliverables

    • TBD — filled after completion
    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-ci-infra — Phase 7 (CI hardening)
  • Phase F5: Keycloak OIDC Auth for pal-e-app phase-pal-e-docs-frontend-auth

    Goal: Add Keycloak OIDC authentication to pal-e-app. Write operations (Quick-Jot) require login. Read operations remain public. Reuse the proven westside-app Auth.js pattern.

    Owner: Dev-Frontend agent

    Repo: forgejo_admin/pal-e-app

    Depends on: None — Keycloak is deployed, OIDC flow proven in westside-app (PR #9).

    Scope

    • Add Auth.js (SvelteKit) with Keycloak OIDC provider — reuse pattern from westside-app/src/auth.ts
    • Create pal-e-app client in Keycloak realm (or reuse existing client)
    • SOPS-encrypt client secret, deploy to pal-e-app namespace k8s secret
    • Protect write routes: POST /api/notes requires authenticated session
    • Quick-Jot FAB: show only when authenticated, prompt login when not
    • Add sign-in/sign-out routes (/signin, /signout)
    • Nav bar: show user name + sign out when authenticated, sign in button when not
    • Read routes (/notes, /boards, /search, /dashboard) remain public — no auth required
    • Session cookie: HttpOnly, Secure, SameSite=Lax

    Deliverables

    • TBD — filled after completion
    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-quick-jot — Phase F3 (write capability that needs auth)
    • phase-pal-e-docs-private-notes — Phase F6 (private notes depend on auth)
  • Phase F4: DORA Dashboard phase-pal-e-docs-dora-dashboard

    Goal: The board IS the DORA dashboard — this phase makes the metrics visible. Cross-project rollup showing what needs attention, deployment frequency, and lead time estimates.

    Owner: Dev-Frontend agent

    Repo: forgejo_admin/pal-e-app

    Depends on: phase-pal-e-docs-board-filtering — reuses board filtering concepts and summary card patterns.

    Scope

    • New route: /dashboard (or enhance / landing page)
    • Cross-project board rollup: items in each column across ALL boards
    • Items needing attention: needs_approval items, stuck in_progress items
    • Deployment frequency: done items per day/week (from board item timestamps)
    • Lead time estimates: time from next_updone (from board item created_at/updated_at)
    • Per-project cards with board summary + link to full board
    • Links to relevant boards for drill-down

    Deliverables

    • TBD — filled after completion
    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-board-filtering — Phase F2 (reuses summary card patterns)
    • project-dora-thesis — DORA = Observability + Kanban
  • Phase F3: Quick-Jot Note Creation phase-pal-e-docs-quick-jot

    Goal: First write capability in the frontend — capture thoughts fast via a quick-jot modal, triage later. The inbox concept.

    Owner: Dev-Frontend agent

    Repo: forgejo_admin/pal-e-app

    Depends on: None — backend POST /notes already built and tested.

    Scope

    • Floating action button (bottom-right) or keyboard shortcut to open quick-jot modal
    • Modal: title (required), optional body (single paragraph), optional project dropdown, optional note_type (defaults to todo)
    • Creates note via SvelteKit API proxy route → POST /notes on backend
    • Auto-generates slug from title (lowercase, hyphenated)
    • Lands on project board as backlog item if project has a board (via board sync)
    • Success feedback: toast/notification with link to created note
    • New API proxy route: POST /api/notes in SvelteKit
    • Minimal — no rich editor, just title + optional body text

    Deliverables

    • TBD — filled after completion
    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-frontend-search — Phase F1 (search helps find created notes)
    • phase-pal-e-docs-board-filtering — Phase F2 (board filtering helps triage quick-jots)
  • Phase F2: Board Filtering + Status View phase-pal-e-docs-board-filtering

    Goal: Add filtering and status overview to board pages so Lucas can filter 10-20+ items by type/label and see status at a glance.

    Owner: Dev-Frontend agent

    Repo: forgejo_admin/pal-e-app

    Depends on: None — backend already supports item_type and column filtering on GET /boards/{slug}/items.

    Scope

    • Filter pills on board page: by item_type (phase, issue, todo, plan) using typeColor() for pill colors
    • Active filter = filled pill, inactive = outline pill. Multiple filters AND together
    • Column item counts in headers (enhance existing count badges)
    • Collapsible columns option: click header to toggle, collapsed shows header + count only
    • "Hide done" toggle — most common filter action
    • Board status summary card at top: items by column, items by type, horizontal bar distribution
    • Project page enhancement: show board mini-view on project detail page
    • Client-side filtering (all items already loaded for drag-drop)
    • URL-persistent filter state (?type=phase&hide_done=true)

    Deliverables

    • TBD — filled after completion
    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-sprint-board-component — Phase 3 that built the kanban board
    • phase-pal-e-docs-frontend-search — Phase F1 (parallel frontend work)
  • Phase F1: Backend-Powered Search in Frontend phase-pal-e-docs-frontend-search

    Goal: Expose the backend's full-text, semantic, and hybrid search through the pal-e-app frontend — the #1 feature that transforms browsing into operating.

    Owner: Dev-Frontend agent

    Repo: forgejo_admin/pal-e-app

    Depends on: None — backend search APIs already built and tested.

    Scope

    • New route: /search with +page.server.ts calling GET /notes/search?q=...&mode=keyword|semantic|hybrid
    • Display ranked results with headline snippets, note_type badges (using existing typeColor()), project tags
    • URL-persistent query params (/search?q=deploy+recovery&mode=hybrid)
    • Toggle: keyword vs semantic vs hybrid search mode
    • Search input in nav bar (+layout.svelte) that navigates to /search on submit
    • Keyboard shortcut: / or Cmd+K to focus search
    • Add searchNotes() function to src/lib/api.ts
    • Loading state for semantic search (Ollama embedding latency)
    • Empty state with search tips

    Deliverables

    • TBD — filled after completion
    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-activate-semantic-search — Phase 5a that built the backend search
    • phase-pal-e-docs-board-filtering — Phase F2 (parallel frontend work)
  • Goal: Address 5 actionable QA nits from PR #130 review to bring the embedding worker to production quality.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs

    Depends on: Phase 6c (PR #130 must merge first)

    Problem

    QA approved PR #130 with 7 nits. 2 are no-ops (hardcoded image tag is inherited pattern; health server binding 0.0.0.0 is standard k8s). 5 are actionable:

    Fix

    • Nit 1: Dead code. EMBEDDABLE_TYPES constant defined at line 52 but never referenced. Either use it in the block filtering logic (replacing inline checks) or remove it. Prefer using it — it makes the filtering explicit and testable.
    • Nit 2: Redundant set_isolation_level. The reconnection path (line 523) calls conn.set_isolation_level() but _connect() already sets it. Remove the duplicate call in the reconnect path.
    • Nit 3: N+1 query for note titles. _fetch_pending_blocks does a per-block query to get the parent note title for heading context. At 5K blocks during backfill, this is 5K extra queries. Fix: batch the title lookup with a JOIN or a single query that fetches all needed note titles upfront.
    • Nit 4: Duplicate httpx dependency. httpx appears in both main deps (line 25) and dev deps (line 32) in pyproject.toml. Remove from dev deps — it's now a production dependency.
    • Nit 6: No terminationGracePeriodSeconds. The worker handles SIGTERM gracefully but k8s defaults to 30s before SIGKILL. Add explicit terminationGracePeriodSeconds: 60 to give the worker time to finish a batch and reset processing blocks to pending.
    • phase-postgres-6-vector-search — parent phase
    • phase-postgres-6c1-autoclose-enforcement — sibling subphase (also discovered during 6c)
  • Phase F11: Design System Overhaul + UX Redesign phase-pal-e-docs-design-overhaul

    Goal: Replace the AI-generated aesthetic with a distinctive, professional design system that prioritizes the "what are my latest projects / what changed recently" user story. Kill the AI slop.

    Owner: Dev agent (code), Betty Sue (design direction + QA coordination)

    Repo: forgejo_admin/pal-e-app

    Depends on: F5 (auth), F6 (private notes), F8 (editing), F9 (board CRUD) — all COMPLETED

    Context

    The current pal-e-app aesthetic was built fast by dev agents and hits every AI slop anti-pattern: cyan-on-dark, pink neon accents on near-black, identical card grids, system fonts, centered everything, no design token system. The color palette (#e94560 accent, #0a0a14 background, #55efc4/#00cec9 type badges) is the textbook AI color palette warned against by the frontend-design guidelines.

    The home page shows all 10 projects alphabetically — it doesn't answer the primary user question: "What are my latest projects? What changed? What needs attention?"

    Current technical state: Tailwind 4.1, zero CSS custom properties, all colors hardcoded as arbitrary Tailwind values (bg-[#0e0e18]), no font imports, no type scale.

    Design Direction

    Aesthetic: Editorial / Command Center. Not a generic SaaS dashboard. Not a dark-mode dev tool. A well-typeset intelligence briefing — clean, scannable, information-dense but not cluttered. Every pixel earns its place.

    • Light mode primary (dark mode secondary) — dark mode is lazy; light mode forces real design decisions
    • Real typography — distinctive heading font + clean body font. Not Inter, not Roboto, not system defaults.
    • Warm neutrals — stone/slate/warm grays, not blue-tinted near-black
    • One accent color — not a rainbow of type badges. Typography-driven hierarchy.
    • Activity-first home page — "Recently updated," "In progress," "Needs attention" — not alphabetical dumps
    • Left-aligned, asymmetric layout — break the centered-everything pattern
    • Progressive disclosure — the home page is a briefing, not a directory

    Scope

    F11a: Design Token System + Color Palette (COMPLETED — PR #35)

    • Create CSS custom properties in app.css for all design tokens (colors, spacing, typography, radii)
    • Replace ALL hardcoded hex values (bg-[#0e0e18]) with token references
    • Choose a professional color palette — warm neutrals, one strong accent
    • Support light mode (primary) and dark mode (secondary) via prefers-color-scheme or toggle
    • Tint neutrals toward brand hue for cohesion
    • Note type colors: simplify from 16 distinct colors to a restrained system (3-4 hues max with shade variations)

    F11b: Typography + Font Loading (COMPLETED — PR #35)

    • Select and import distinctive fonts: display/heading + body pair
    • Implement modular type scale with clamp() for fluid sizing
    • Font loading strategy: font-display: swap, preload critical fonts
    • Vary weights and sizes to create clear visual hierarchy without relying on color

    F11c: Home Page Redesign — "What Are My Latest Projects?" (COMPLETED — PR #37, Issue #36)

    • Activity-first layout: recently updated notes/projects at top
    • In-progress board items section — what's actively being worked on across all projects
    • Project cards with last-updated timestamps and activity indicators
    • Board summary with progress bars (backlog/in-progress/done ratios)
    • Kill the alphabetical project dump and full tag wall
    • Prototype approved in html-playground/6-pal-e-home before port

    F11d: Navigation Rethink

    • Streamline nav: Primary (Home/Activity, Projects, Search) vs Secondary (Notes, Tags, Repos)
    • Consider sidebar navigation vs top bar
    • Mobile-responsive hamburger menu
    • Keyboard shortcuts (existing / for search is good — extend)
    • Breadcrumb improvements for note navigation
    • Gamification elements: progress indicators on project links, notification badges

    F11e: Search + Discovery UX

    • URL-driven search (?q= param should work on notes page)
    • Search results with better context (snippet, project, type, date)
    • Filter persistence across navigation
    • Consider semantic search toggle (backend already supports it)

    F11f: Private Notes + Auth UX Polish

    • Lock icon visibility audit — current h-3 w-3 text-gray-600 is too subtle on dark backgrounds
    • Signin page design — not Auth.js default
    • Signout confirmation or redirect behavior
    • Role display in nav (not just username)

    F11g: Component Library Polish

    • Cards: break the identical grid pattern. Vary sizes by importance/type.
    • Badges: typography-driven, not rainbow colored
    • Buttons: clear primary/secondary/ghost hierarchy
    • Tables: readable in light mode
    • Code blocks: proper syntax highlighting theme
    • Empty states: teach the interface, not just "nothing here"

    Investigation Required

    • Does the pal-e-docs API support ?sort=updated_at or similar for activity-first queries? — YES, default sort is updated_at DESC, limit/offset params added in PR #181
    • Can we add updated_at to the notes list response for "recently updated" sorting? — YES, already in NoteSummary schema
    • What data does the DORA dashboard already expose that could feed gamification?
    • Playground-first approach: should design experiments happen in html-playground before pal-e-app? — YES, SOP created: sop-frontend-experiment

    Acceptance Criteria

    • Zero hardcoded hex values in component code — all via CSS custom properties
    • Home page answers "what changed recently?" within 2 seconds of loading
    • Passes the AI Slop Test: if someone saw it, they wouldn't immediately say "AI made this"
    • Lock icons visible and tested for private notes
    • Search works via URL params
    • Light mode looks professional; dark mode looks intentional (not the default)
    • Lighthouse accessibility score >= 90

    QA Nits (deferred from PR #37)

    • Negative relativeTime handling for future dates
    • Implicit boardProgress return type — add explicit TS type
    • Asymmetric in-progress test assertion
    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-validation-hardening — F10 (predecessor — design audit started there)
    • phase-pal-e-docs-frontend-auth — F5 (auth foundation)
    • phase-pal-e-docs-private-notes — F6 (private notes foundation)
    • convention-frontend-css — CSS custom property rules
    • sop-frontend-experiment — prototype-first workflow
  • Phase 4: Knowledge Tiering — list_notes Default Exclusion

    Goal: Make list_notes stop returning completed/historical notes by default. Cold exclusion via status-based filtering.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs, forgejo_admin/pal-e-docs-sdk, forgejo_admin/pal-e-docs-mcp

    Depends on: Phase 2 (milestone note type — completed)

    Scope

    Tiering Rules (status-based, first pass)

    Tier Rule Default in list_notes
    Hot/Warm Notes with status NOT in cold set, or NULL status Included
    Cold Status in {completed, done, deprecated, deferred, archived} Excluded (include_cold=true to opt in)

    Edge Cases

    • Explicit status=completed param bypasses cold exclusion (caller clearly wants cold notes)
    • NULL status → not cold (included by default)
    • Search endpoints (semantic_search, search_notes) unaffected

    Deliverables

    Backend (COMPLETED)

    • PR #191 on pal-e-docs — merged 2026-03-17
    • COLD_STATUSES frozenset in routes/notes.py
    • include_cold Query param on GET /notes (default false)
    • Smart override: explicit status= bypasses cold filter
    • NULL status handled via or_ clause
    • 6 new tests, 638 total passing
    • Forgejo issue #190 (closed)

    SDK (COMPLETED)

    • PR #33 on pal-e-docs-sdk — merged 2026-03-17
    • include_cold: bool = False param on list_notes()
    • 3 new tests
    • Forgejo issue #32 (closed)

    MCP (COMPLETED)

    • PR #44 on pal-e-docs-mcp — merged 2026-03-17
    • include_cold Field param on list_notes tool with descriptive help text
    • 4 new tests
    • Forgejo issue #43 (closed)

    Deferred Scope

    • Parent-chain CTE (notes under completed milestones) — stretch goal for subphase
    • include_frozen param — not needed yet (archived notes are rare)
    • Session injection hook changes — default behavior already correct

    QA Nits (non-blocking)

    • PR #191: Missing note_type + cold composition test; inaccurate _seed_notes docstring; resolved status not in COLD_STATUSES
    • PR #33: Bool omission pattern differs from None-based convention (functionally correct)
    • PR #44: Zero nits
    • plan-2026-03-16-knowledge-architecture — parent plan
    • phase-2026-03-16-2-milestone-note-type — prerequisite (completed)
    • convention-block-first-access — complementary access optimization
  • Phase 3: Gapped Integer Positions phase-2026-03-16-3-gapped-positions

    Phase 3: Gapped Integer Positions

    Goal: Eliminate cascading position shifts on block inserts by using gapped integers (spacing of 1000) with periodic rebalance.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs

    Depends on: None (independent of phases 1-2)

    Scope

    • Block positions: New blocks get positions spaced by 1000 (0, 1000, 2000, ...). Insert between 19000 and 20000 → position 19500. No other rows touched.
    • Rebalance endpoint: POST /notes/{slug}/blocks/rebalance renumbers all blocks to gapped positions preserving order.
    • Collision handling: Insert at occupied position shifts only the collider (+1). Gap exhaustion triggers automatic rebalance before insert.
    • Delete preserves gaps: No more sequential reindexing on delete.

    Deliverables

    • PR #189 — merged 2026-03-17
    • POSITION_GAP = 1000 constant in blocks/parser.py, exported from blocks/__init__.py
    • Parser produces gapped positions (0, 1000, 2000...)
    • Collision-only shift in create_block (was: shift all blocks at position >= target)
    • Gap-preserving delete (was: reindex to sequential after every delete)
    • POST /notes/{slug}/blocks/rebalance endpoint with RebalanceOut schema
    • 24 new tests in test_gapped_positions.py, 4 existing test files updated
    • 631 total tests passing

    Deferred Scope

    • Note child positions — not needed (already gap-tolerant)
    • BoardItem positions — not needed (already tolerate gaps/collisions)
    • Data migration — not needed (old sequential positions still work)

    QA Nits (non-blocking)

    • Stale docstring in parser.py lines 33/38 — still references "0-based ordering" and "paragraph-3" examples
    • _seed_note_with_blocks in test_blocks_api.py could use a comment clarifying intentional use of legacy sequential positions
    • plan-2026-03-16-knowledge-architecture — parent plan
    • Forgejo issue #188 (closed)
  • Phase 5: Retroactive Milestone Parenting (pal-e-docs dogfood) phase-2026-03-16-5-retroactive-milestones

    Phase 5: Retroactive Milestone Parenting (pal-e-docs dogfood)

    Goal: Upgrade pal-e-docs existing milestone notes to proper note_type=milestone, parent completed plans under them, and validate the full hierarchy works before rolling out to other projects.

    Owner: Betty Sue / Dottie (data migration, all pal-e-docs operations)

    Repo: n/a (data migration via MCP tools, no code changes)

    Depends on: Phase 2 (milestone note_type must exist in the enum)

    Scope

    • Upgrade 5 existing pal-e-docs milestone notes from note_type=doc to note_type=milestone
    • Upgrade milestone-2026-03-16-knowledge-architecture (this plan's parent) to note_type=milestone
    • Parent the 12 completed pal-e-docs plans under their respective milestones via update_note(parent_slug=...)
    • Update project-pal-e-docs project page: milestones as primary structure
    • Validate: list_notes(parent_slug="milestone-...") correctly returns plan hierarchy
    • Document findings — what worked, what was awkward, what needs convention adjustment

    Deliverables

    • 5 milestone notes upgraded from note_type=doc to note_type=milestone, all set to status=completed
    • milestone-2026-03-16-knowledge-architecture upgraded from doc to milestone (already done in Phase 2 verification)
    • 11 completed plans parented under their respective milestones via parent_slug: 3 under project-genesis, 6 under knowledge-engine, 2 under board-system-frontend
    • plan-pal-e-docs left unparented — per decision, in-flight work not reparented. Spans milestones 4+5.
    • Validated: list_notes(note_type="milestone") returns 6 milestones (5 completed, 1 active). list_notes(parent_slug="milestone-2026-03-01-knowledge-engine") returns 6 child plans.
    • plan-2026-03-16-knowledge-architecture — parent plan
    • phase-2026-03-16-2-milestone-note-type — prerequisite
    • Existing milestones: milestone-2026-02-24-project-genesis, milestone-2026-03-01-knowledge-engine, milestone-2026-03-13-board-system-frontend, milestone-2026-03-14-frontend-workbench, milestone-2026-03-15-knowledge-loop
  • Phase 2: Milestone Note Type + Convention phase-2026-03-16-2-milestone-note-type

    Phase 2: Milestone Note Type + Convention

    Goal: Make milestone a first-class note_type with lifecycle statuses, create the template, and establish the "one milestone per project, one plan per milestone" convention.

    Owner: Dev agent (schema change) + Betty Sue/Dottie (convention/template docs)

    Repo: forgejo_admin/pal-e-docs, forgejo_admin/pal-e-docs-sdk, forgejo_admin/pal-e-docs-mcp

    Depends on: None

    Scope

    Backend (pal-e-docs)

    • Add "milestone" to NoteType literal in routes/notes.py
    • Add VALID_STATUSES entry: "milestone": ["not-started", "active", "completed"]

    SDK + MCP

    • Update SDK note_type docstrings to include "milestone"
    • Update MCP tool descriptions to include "milestone"

    Conventions (pal-e-agency docs)

    • Create template-milestone note
    • Update template-plan — add guidance that plans should have a milestone parent
    • Update template-project-page — milestones as primary organizer
    • Create or update convention: "One active milestone per project, one plan per milestone"

    Deliverables

    • Backend: PR #187 merged — added "milestone" to NoteType + VALID_STATUSES (schemas.py, routes/notes.py). 577 tests pass.
    • Convention: template-milestone created in pal-e-agency.
    • Remaining: SDK + MCP docstring updates, template-plan and template-project-page convention updates still pending.
    • plan-2026-03-16-knowledge-architecture — parent plan
    • milestone-2026-03-16-knowledge-architecture — this milestone is the first dogfood (created as doc type, will be upgraded to milestone type)
    • template-plan — will be updated
    • template-project-page — will be updated
  • Goal: Eliminate CSS duplication across pal-e-app, establish a single CSS methodology, and complete the design token system beyond colors.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-app

    Depends on: phase-pal-e-docs-design-overhaul (F11, in-progress — F11a/b/c completed)

    Forgejo Issue: pal-e-app #40

    Why

    CSS audit (2026-03-16) found two competing methodologies, incomplete design tokens, and repeated patterns across components.

    Scope

    Global utility classes, complete design tokens, refactor duplicated scoped styles. See issue #40 for full spec.

    Deliverables

    • PR #41 (pal-e-app) — 19 files, +184/-345 lines (net -161). QA approved after fix loop (blocker: missing .card transition, resolved).
    • Design tokens added: --radius-sm/md/lg/pill, --shadow-sm/md/lg, --z-overlay/modal/toast, --transition-fast/normal
    • Global classes added: .btn-primary, .form-input, .card-grid, .card
    • Eliminated: 6 duplicate .btn-primary definitions, 3 duplicate .form-input, 5 duplicate .card-grid + .card, 40+ hardcoded border-radius values, 20+ hardcoded transition values
    • Removed: dead .btn-secondary class, empty style block in repos page
    • Methodology established: Tailwind for layout, CSS vars for theming, global classes for repeated component patterns
    • phase-pal-e-docs-design-overhaul — parent phase (F11)
    • plan-pal-e-docs — parent plan
    • convention-frontend-css — convention to update with methodology decision
  • Goal: pal-e-docs and pal-e-app are safe for public internet traffic — anonymous visitors see only public content, private projects/notes/boards are invisible.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs + forgejo_admin/pal-e-app

    Depends on: phase-pal-e-docs-private-notes (F6, completed)

    Forgejo Issues: pal-e-docs #184 (API filtering) + pal-e-app #38 (frontend SSR fix)

    Why

    Phase F6 implemented is_public filtering on note endpoints and added X-PaleDocs-Token auth. Two gaps remained: (1) the frontend sent the admin token on ALL SSR requests regardless of Keycloak session state; (2) the /projects and /boards API endpoints had no is_public filtering.

    Scope

    See original scope above. Both issues addressed.

    Deliverables

    • PR #185 (pal-e-docs) — is_public filtering on /projects and /boards endpoints using get_is_authenticated(). 7 endpoints updated, 22 new tests, 606 total passing. QA approved.
    • PR #39 (pal-e-app) — ApiFetchOptions.authenticated flag threaded through all 18 API functions and every +page.server.ts loader. Token only sent when Keycloak session active. Sign-in page replaced with "Contact Lucas for access" + admin login via ?admin=true. 11 new E2E tests. QA approved.
    • Data fixessop-secrets-management set to is_public=false. Private/Remember project page notes set to is_public=false. private-2026-03-16-chinese-room-devops journal set to is_public=false.
    • SOP audit — 5 infrastructure SOPs audited. 1 made private, 3 flagged for Tailscale URL redaction (deferred), 1 safe.
    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-private-notes — Phase F6, the foundation this builds on
    • phase-pal-e-docs-frontend-auth — Phase F5, Keycloak OIDC integration
    • phase-pal-e-docs-validation-hardening — Phase F10, prior auth hardening
  • Phase 1: Doc Drift Audit + Agent Alignment phase-2026-03-16-1-doc-drift-audit

    Phase 1: Doc Drift Audit + Agent Alignment

    Goal: Fix doc drift from pal-e-agency Phase 12 consolidation — align agent notes and SOPs to the actual 5-agent model (4 configs in claude-custom).

    Owner: Dottie (documentation execution under Betty Sue's direction)

    Repo: n/a (docs-only work in pal-e-docs)

    Depends on: None

    Scope

    • Deprecate 6 stale specialized agent notes: agent-dev-frontend, agent-dev-backend, agent-devops, agent-frontend-qa, agent-dev-qa, agent-devops-qa
    • Update agent-spawn-conventions to match agent-workflow (5-agent model: Betty Sue, Penny, Dev, QA, Dottie)
    • Verify Penny's status — note exists in pal-e-docs but no config in claude-custom. Clarify: is Penny active or aspirational?
    • Audit pal-e-agency done TODOs (22 notes) — confirm they're truly resolved, no orphan references

    Deliverables

    • 6 stale agent notes deprecatedagent-dev-frontend, agent-dev-backend, agent-devops, agent-frontend-qa, agent-dev-qa, agent-devops-qa. All set to status=deprecated, tags=agent,deprecated.
    • agent-spawn-conventions updated to 5-agent model — "Nine Agents" heading changed to "Five Agents". Agent table, Access Scope table, When to Spawn table, and Deprecated section all updated to reflect Betty Sue, Penny, Dev, QA, Dottie. Explanatory text updated to describe dynamic domain expertise model.
    • Penny clarified as active (not aspirational) — Penny appears in all agent-spawn-conventions tables with note that she is defined but not yet wired (no claude-custom config).
    • TODO created: todo-penny-claude-config — tracks the gap: Penny needs a claude-custom/agents/penny.md config to be spawnable.
    • Deferred: 22 done TODO audit — out of scope for this phase. Can be a subphase later.
    • plan-2026-03-16-knowledge-architecture — parent plan
    • plan-pal-e-agency Phase 12 — "consolidated to 5-agent model" but docs never updated
    • agent-workflow — the authoritative 5-agent SOP
    • agent-spawn-conventions — currently shows 9-agent model (stale)
  • Phase F13: Context Intelligence — Behavioral Memory + Dynamic State

    Goal: Separate behavioral knowledge (feedback/user memories — always relevant, stable) from state knowledge (project status — changes constantly, already in pal-e-docs) so session startup context is current, relevant, and doesn't truncate.

    Owner: Betty Sue + Dev agent

    Repo: forgejo_admin/claude-custom (hooks + memory), forgejo_admin/pal-e-docs (API if changes needed)

    Depends on: phase-pal-e-docs-f12-semantic-search-recovery (Ollama must be healthy for F13b)

    Scope

    Hypothesis: Session startup context has two problems: (1) MEMORY.md is 76% stale project state duplicated from pal-e-docs (224 lines, truncating at 200). (2) SessionStart hook injects ~4,000 tokens including all 9 plan TOCs (~1,800 tokens / 45% of injection) when most sessions touch 1-2 projects. Fix both by separating behavioral knowledge (always relevant) from state knowledge (query dynamically).

    Evidence (2026-03-15 analysis): SessionStart hook makes 25 API calls. Plan TOCs dominate: 9 plans × ~200 tokens each. MEMORY.md has "262 notes" when we have 500+, references completed phases as "IN PROGRESS." The hook already queries pal-e-docs for everything — adding one semantic search call is marginal cost. Design constraint: fail-open. If vectors are down, fall back to current static behavior. Zero risk.

    F13a: MEMORY.md diet (no vector dependency). Audit all 44 topic files + inline sections. Categorize as behavioral (keep) vs state (remove). Remove state content that has a pal-e-docs equivalent. Verify the remaining MEMORY.md is under 100 lines and only contains feedback, user preferences, and repo location shortcuts. Test: start a new session and verify Betty Sue still has the behavioral guardrails.

    F13b: Smart startup injection (two sub-steps). Sub-step 1 (no vector dependency): trim plan TOCs by board state — only inject TOCs for projects with in_progress board items. This is a tag-based query, saves ~1,200 tokens immediately. Sub-step 2 (depends on F12): add semantic search call using in-progress board item titles as the query. Inject top 10-15 blocks as a "dynamic briefing" section. Fail-open: if Ollama is down, skip silently — you still get the static injection. Repo: forgejo_admin/claude-custom (session-start-context.sh).

    F13c: Verification round. PARTIALLY COMPLETED (2026-03-16). Simulation test ran the hook directly: Dynamic Briefing section appeared with 10 results. Findings: (1) "Browse UX Enhancements" (completed plan) took 5/10 slots — per-slug cap needed. (2) Completed/deferred notes surfacing alongside active work — status filter needed. (3) Results are broad enough for enterprise overview but too noisy for focused work. Key discovery: Lucas runs 4 concurrent sessions from ~/pal-e-platform, each focused on a different project. The briefing is shared across all 4 sessions (same cwd = same hook output). This means the Dynamic Briefing should be a short enterprise dashboard (what's hot), not deep project context. Project-specific depth comes when Lucas says "focus on X" and Betty Sue does a targeted query. See feedback_session_workflow.md.

    F13d: Briefing quality tuning — DEFERRED (awaiting F13c real-world data). Three proposed jq-level fixes: (1) per-slug cap, (2) status filter, (3) total cap. But each has tradeoffs that can't be validated without real usage. Per-slug cap of 1 vs 2 — the Woodpecker experiment showed 2 relevant results from one SOP. Status filter risks excluding incidents (null status). Total cap of 8 is intuition, not measurement. Decision: let the briefing bake across 3+ real sessions before tuning. F13c verification will generate the data. F13d becomes a data-driven pass, not premature optimization. See feedback_session_workflow.md for the 4-concurrent-session workflow that informed this design.

    Deliverables

    • F13a: MEMORY.md under 100 lines — behavioral only. DONE. 224 → 60 lines.
    • F13b-1: Trim plan TOCs by board state. DONE. PR #114 merged. ~1,200 tokens saved.
    • F13b-2: Vector-powered startup briefing. DONE. PR #116 merged. Dynamic Briefing section live.
    • F13c: Verification round. IN PROGRESS. Simulation complete. 3-session live verification remaining — observe, document, then inform F13d.
    • F13d: Briefing quality tuning. DEFERRED. Awaiting F13c real-world data. Premature optimization without production usage.
    • plan-pal-e-docs — parent plan (capability lives here: vectors, API, semantic search)
    • plan-pal-e-agency — cross-cutting: this phase changes the operating model (how Betty Sue bootstraps, what memory means, session context architecture). Tracked on board-pal-e-agency as well.
    • phase-pal-e-docs-f12-semantic-search-recovery — prerequisite (Ollama must be healthy for F13b)
    • template-ticket — ticket labels could become additional query dimensions for vector search
    • Boards: board-pal-e-docs (item #97) + board-pal-e-agency (item #98) — cross-cutting ticket
  • Phase F12: Semantic Search Recovery phase-pal-e-docs-f12-semantic-search-recovery

    Phase F12: Semantic Search Recovery

    Goal: Restore semantic search to working state and add alerting so Ollama/embedding failures are detected within 10 minutes instead of silently rotting.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs, forgejo_admin/pal-e-platform, forgejo_admin/pal-e-deployments

    Depends on: None (Ollama and pgvector already deployed from Act 2 Phase 6a)

    Scope

    The semantic_search MCP tool returns 503. Root cause diagnosed (2026-03-15): Ollama pod is healthy (running 6 days, 0 restarts) but the qwen3-embedding:4b model is NOT loaded. The 10Gi PVC was recreated 6 days ago and only chat models (qwen3.5:4b, qwen3:4b) were pulled. Embedding worker has 152 errors, 0 successful embeddings. embedding_queue_depth reads 0 because failed blocks are marked error, not pending — this is important for alerting strategy.

    F12-1: Fix Ollama model persistence. Swap PVC for hostPath volume mount (e.g. /var/lib/ollama) in the Ollama Terraform deployment. This ensures models survive any k8s lifecycle event (pod restart, deployment recreate, terraform apply). Then pull qwen3-embedding:4b (~3GB). Repos: pal-e-platform (Terraform), pal-e-deployments (if kustomize overlay). Hardware: GTX 1070 (8GB VRAM), model fits comfortably alongside chat models.

    F12-2: Backfill stale embeddings. Query SELECT embedding_status, count(*) FROM blocks GROUP BY embedding_status to assess damage. Reset error blocks to pending. Run python -m pal_e_docs.embedding_worker --backfill. Verify semantic_search returns results.

    F12-3: Add alerting. The worker exposes Prometheus metrics on :8001/metrics. IMPORTANT: embedding_queue_depth is NOT sufficient — it reads 0 during failures because blocks get marked error after 3 retries. The correct alerts are: rate(embedding_errors_total[5m]) > 0 → warning (active failures), and embedding_total == 0 for > 10 minutes while embedding_errors_total is increasing → critical (complete embedding failure). Add Prometheus scrape config for the worker. Route to existing Slack/Telegram pipeline (Phase 16 infra).

    Deliverables

    • semantic_search MCP tool returns results (not 503)
    • All blocks have embedding_status = completed or skipped (zero pending/error)
    • Prometheus alert fires within 10 minutes if embeddings stop processing
    • plan-pal-e-docs — parent plan
    • plan-pal-e-platform — alerting infrastructure (Phase 16)
    • phase-pal-e-docs-design-overhaul — sibling phase (F11, in progress)
  • Phase: Orchestration Automation phase-pal-e-docs-orchestration-automation

    Goal: Close the loop — agent actions automatically trigger the next step without Betty Sue manually intervening. status:qa auto-spawns QA agent. Merge auto-updates sprint board.

    Owner: Betty Sue (Claude config development per sop-claude-config-development)

    Repo: forgejo_admin/claude-custom (symlinked as ~/.claude/)

    Forgejo Issue: TBD

    Architecture

    Phases 3 and 4 make the workflow work manually — hooks set labels, Betty Sue runs skills to sync boards. Phase 5 makes it automatic — hooks trigger the next agent in the chain without Betty Sue initiating.

    Trigger Automation What Happens
    Dev submits PR (status:qa set by Phase 3 hook) Auto-QA trigger QA agent spawned automatically to review the PR
    PR merged Auto-board sync Sprint item moved to done, plan phase updated
    QA sets status:needs-fix Auto-dev respawn Dev agent respawned with fix instructions (stretch goal)

    Design Considerations

    • Hooks must be lightweight — spawn a subagent for heavy work, don't block the hook
    • Auto-QA trigger must pass the PR number and issue number to the QA agent
    • Auto-board sync must identify which sprint item corresponds to the merged PR
    • All auto-spawns must follow agent-spawn-conventions (plan slug, boundary statement)
    • Consider a "manual mode" flag to disable auto-triggers during debugging

    Implementations

    1. Auto-QA trigger (PostToolUse on mcp__forgejo__submit_pr):

    • After Phase 3's label-on-pr hook sets status:qa, inject context telling Betty Sue to spawn QA
    • Or: directly spawn QA subagent from the hook (if hooks can spawn agents)
    • Fallback: PostToolUse injects "status:qa set — run /review-pr owner/repo#N" as additionalContext

    2. Auto-board sync — ABSORBED by plan-pal-e-agency Phase 11:

    • DONE — Delivered by Agency Phase 11e (board-item-on-merge.sh hook, PR #98 on claude-custom)
    • Post-merge hook calls pal-e-docs API to move board item to done column
    • Session-start auto-sync also deployed (Agency Phase 11d)

    3. Auto-dev respawn (stretch goal):

    • When QA sets status:needs-fix, auto-spawn dev agent with fix instructions
    • Only viable if QA comment clearly describes what needs fixing
    • Defer to Phase 6 or a future plan if too complex

    Steps

    1. Determine whether hooks can spawn subagents or only inject context
    2. Implement auto-QA trigger (extend label-on-pr.sh or new hook)
    3. Implement auto-board sync (extend post-mcp-merge-rebase.sh)
    4. Add manual mode flag (.claude-no-auto-trigger file disables auto-spawns)
    5. Test full cycle: issue → dev → PR → auto-QA → verdict → merge → auto-board-sync
    6. Evaluate auto-dev-respawn feasibility

    Deliverable: Dev submits PR → QA automatically reviews → verdict auto-sets label → merge auto-updates board. The full loop runs with minimal Betty Sue intervention.

    Depends on: Phase 3 (hooks set labels), Phase 4 (skill exists to sync boards).

  • Phase: Repo Renames phase-pal-e-docs-repo-renames

    Goal: Rename repos to match new architecture. pal-e-docs → pal-e-api, pal-e-docs-mcp → pal-e-mcp, pal-e-docs-sdk → pal-e-sdk.

    Owner: Betty Sue (coordination), Dev agent (code changes)

    Repo: All pal-e-docs ecosystem repos + claude-custom (hooks reference MCP tool names)

    Depends on: pal-e-app working against current API (renames shouldn't block new work)

    Scope

    • Forgejo API repo renames
    • ArgoCD application updates
    • k8s manifest image references
    • Python package name changes (import paths)
    • MCP server config key change (affects ALL tool name prefixes)
    • claude-custom hooks that pattern-match on mcp__pal-e-docs__*
    • Agent profiles, spawn conventions
    • CLAUDE.md files across repos
    • plan-pal-e-docs — parent plan
  • Phase: Token Metrics phase-pal-e-docs-token-metrics

    Goal: Capture token usage per item, per sprint, per activity category. Feed into DORA correlation.

    Owner: Dev agent

    Repo: pal-e-docs

    Issue: TBD

    Model:

    • TokenUsage — id, sprint_item_id (FK nullable), sprint_id (FK nullable), repo_slug, activity (enum: planning/development/review/documentation), input_tokens (int), output_tokens (int), session_id (string, nullable), agent_type (string, nullable — betty-sue/dev/qa), recorded_at (datetime)

    API routes:

    • POST /token-usage — record token usage
    • GET /token-usage/summary — aggregate by sprint, repo, activity
    • GET /sprints/{slug}/token-usage — token breakdown for a sprint

    Integration points:

    • Hooks log token counts per agent spawn
    • DORA exporter pulls token metrics for Grafana
    • Sprint retro: review token spend breakdown from previous sprint

    Deliverable: Token usage tracked per sprint item. Summary API shows cost per plan/phase/issue and per sprint.

  • Phase: Auto-Population and Sync phase-pal-e-docs-auto-population

    Goal: Auto-discover items and keep boards in sync with source systems. Eliminate manual board maintenance.

    Owner: Dev agent

    Repo: pal-e-docs

    Depends on: Phase 1 (Board Data Model) — COMPLETED

    Scope

    Sub-phase 5b-1: Plan/Phase Sync (internal)

    Issue: forgejo_admin/pal-e-docs #165 (CLOSED) — PR #166 merged 2026-03-14. POST /boards/{slug}/sync endpoint + update_note hook. 16 tests, all 513 pass.
    SDK: forgejo_admin/pal-e-docs-sdk #28 (CLOSED) — PR #29 merged 2026-03-14. sync_board(slug) method added, SDK v0.4.0 published.
    MCP: forgejo_admin/pal-e-docs-mcp #37 (CLOSED) — PR #38 merged 2026-03-14. sync_board MCP tool added, MCP v0.3.0.
    5b-1 COMPLETE — full stack: API → SDK v0.4.0 → MCP v0.3.0. Needs session restart to activate.

    • POST /boards/{slug}/sync — scans board for plan items, auto-creates BoardItems for child phases, maps note status → board column
    • Hook in update_note — when a phase note's status changes, find matching BoardItem by note_slug and move to the correct column
    • Status → Column mapping: not-started→backlog, in-progress→in_progress, completed→done, deferred→done
    • Duplicate prevention: skip phases already on the board (existing unique constraint on note_slug per board)
    • No new dependencies — purely internal to pal-e-docs DB

    Sub-phase 5b-2: Forgejo Issue Sync (external)

    Issue: forgejo_admin/pal-e-docs #170 (CLOSED) — PR #171 merged 2026-03-14. POST /boards/{slug}/sync-issues endpoint + Forgejo API client + config extension + k8s env vars. 9 tests, all passing.
    Pre-deploy: forgejo-api-token key needed in pal-e-docs-secrets k8s secret.

    • POST /boards/{slug}/sync-issues — pull open issues from Forgejo repos via API
    • Match issues to existing BoardItems by forgejo_issue_url
    • New issues auto-create as backlog BoardItems
    • Closed issues auto-move to Done
    • Needs: Forgejo API token in env, project → repo mapping

    Sub-phase 5b-3: Stale Detection

    Issue: TBD

    • Flag items whose source was closed/completed outside the board
    • Flag items stuck in a column for too long
    • Add stale_at field to BoardItem (schema migration)
    • Periodic sweep (cron-like, similar to embedding worker pattern)

    Deliverable

    Adding a plan to a board auto-populates its child phases. update_note(status=...) auto-moves the corresponding board item. Forgejo issues sync into the board. Status changes propagate bidirectionally. All boards stay current with zero manual maintenance.

    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-board-data-model — Phase 1 (prerequisite)
    • phase-pal-e-docs-sprint-board-component — Phase 3 (Kanban UI)
  • Phase F10: Validation, Auth Hardening, and Design QA phase-pal-e-docs-validation-hardening

    Goal: Validate all new frontend features with E2E tests, audit design quality, and harden edge cases.

    Owner: Betty Sue (coordination) + Dev agent (code)

    Repo: forgejo_admin/pal-e-app

    Depends on: F6 (private notes), F8 (note editing), F9 (board CRUD) — all COMPLETED

    Scope

    F10a: Auth E2E Tests + Hardening

    • Playwright tests: signin redirect, role display, FAB visibility, POST /api/notes 401 for anon
    • Auth check verification on PUT /api/notes/[slug], POST/PATCH/DELETE board item proxies
    • Private notes: verify lock icon renders, Quick-Jot private toggle works

    F10b: Design Audit + Critique

    • Run /audit on edit form, board CRUD UI, private notes UI
    • Run /critique for UX quality and AI slop detection
    • Lighthouse audit on key pages via Chrome DevTools
    • Screenshot validation of new features

    F10c: Hardening + Polish

    • Address audit/critique findings
    • Edge cases: empty states, error handling, text overflow
    • Final spacing/alignment/consistency pass
    • Lock in validated behavior with additional E2E tests
    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-private-notes — F6, dependency
    • phase-pal-e-docs-note-editing — F8, dependency
    • phase-pal-e-docs-board-item-management — F9, dependency
  • Phase: CI/Infra Hardening phase-pal-e-docs-ci-infra

    Goal: Fix outstanding infra bugs and harden CI pipelines.

    Owner: Dev agent

    Repo: Multiple — see child TODOs

    Depends on: None — independent of feature work

    Scope

    Collects outstanding TODOs and bugs. See child notes:

    • todo-fix-mcp-pypi-publish — MCP PyPI publish pipeline failure
    • todo-migration-testing-ci-pal-e-docs — Alembic migration testing in CI
    • bug-woodpecker-smoke-test-empty-logs — Woodpecker smoke test failure
    • todo-pal-e-docs-deployment-reliability — Zero-downtime deploy improvements
    • todo-argocd-rewire-deployments-repo — ArgoCD rewire to pal-e-deployments
    • plan-pal-e-docs — parent plan
  • Phase F9: Board Item Management phase-pal-e-docs-board-item-management

    Goal: Enable authenticated users to create and delete board items from the browser, and secure the existing move proxy.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-app

    Depends on: Phase F5 (auth) — COMPLETED

    Scope

    • Auth check on existing PATCH (move) proxy — was completely unprotected
    • POST proxy: src/routes/api/boards/[slug]/items/+server.ts for creating items (authenticated)
    • DELETE proxy + handler on src/routes/api/boards/[slug]/items/[id]/+server.ts (authenticated)
    • "+" create button in column headers (authenticated only) with create modal
    • "x" delete button on cards with confirmation dialog (authenticated only)
    • createBoardItem() and deleteBoardItem() functions in api.ts
    • Optimistic UI updates for create and delete (same pattern as existing move)

    Deliverables

    • PR #31 MERGEDfeat: board item management (create, delete + auth on move)
    • Issue #28 closed
    • 4 files changed
    • QA found 2 blockers (inconsistent auth pattern in deleteBoardItem, forgejo_issue_url not whitelisted in POST proxy) — both fixed

    QA Nits (Epilogue)

    • Create flow is not truly optimistic (waits for API response)
    • deleteSubmitting state effectively dead code
    • ITEM_TYPES hardcoded — could drift from backend
    • Hover-based delete button may have sticky hover on touch devices
    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-private-notes — F6, sibling phase
    • phase-pal-e-docs-note-editing — F8, sibling phase
  • Phase F8: Note Editing phase-pal-e-docs-note-editing

    Goal: Allow authenticated users to edit existing notes from the browser.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-app

    Depends on: Phase F5 (auth) — COMPLETED

    Scope

    • PUT proxy: src/routes/api/notes/[slug]/+server.ts — authenticated, forwards to backend PUT /notes/{slug}
    • Edit page: /notes/[slug]/edit with form for title, body (raw HTML textarea), note_type (all 16 types), dynamic status per note_type, project dropdown, tags (comma-separated), parent_slug
    • Edit button on NoteLayout component (visible only to authenticated users)
    • updateNote(slug, data) function in api.ts
    • Session data plumbed through +page.server.ts to NoteLayout via isAuthenticated prop

    Deliverables

    • PR #30 MERGEDfeat: note editing (edit form + PUT proxy + Edit button)
    • Issue #27 closed
    • 7 files changed (498 additions, 4 deletions)
    • QA approved with 3 nits (non-blocking)

    QA Nits (Epilogue)

    • Use API response slug for redirect after save (future-proofs slug editing)
    • E2E tests for edit flow
    • Rebase overlap cleanup (cosmetic)
    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-private-notes — F6, sibling phase
    • phase-pal-e-docs-frontend-auth — F5, dependency
  • Phase F6: Private Notes Enforcement phase-pal-e-docs-private-notes

    Phase F6: Private Notes Enforcement

    Status: COMPLETED

    Priority: HIGHEST — security gap (now closed)

    Backend (pal-e-docs) — PR #179 MERGED

    • Issue #178 closed
    • New file: src/pal_e_docs/auth.py — reusable get_is_authenticated() FastAPI dependency
    • Filtering on: list_notes, get_note, search_notes, semantic_search, all sub-resource endpoints (toc, blocks, revisions, compiled, links)
    • Write protection: PUT and DELETE require auth when API key configured
    • is_public added to NoteSearchResult and SemanticSearchResult schemas
    • 30 new tests (554 total passing)

    Frontend (pal-e-app) — PR #29 MERGED

    • Issue #26 closed
    • X-PaleDocs-Token header in apiFetch() via PAL_E_DOCS_API_KEY env var
    • Private toggle in QuickJot modal (sets is_public=false)
    • Lock icon on private notes in note lists and search results
    • is_public forwarded through note creation proxy
    • k8s/deployment.yaml updated with PAL_E_DOCS_API_KEY secret ref

    Deployment Note

    PAL_E_DOCS_API_KEY env var must be set on both pal-e-docs and pal-e-app. SOPS secret k8s/pal-e-auth-secrets.enc.yaml needs the actual key value added.

  • Phase: Activate Semantic Search Pipeline phase-pal-e-docs-activate-semantic-search

    Goal: Activate the dormant embedding pipeline and semantic search, giving every agent contextual awareness across the full 260+ note knowledge base.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs

    Depends on: None (all infrastructure already built — pgvector, Ollama, embedding worker code, search API, SDK, MCP tools)

    Scope

    The entire semantic search stack was built during Act 2 but left dormant (replicas: 0). This phase activates it:

    1. Scale embedding worker — change k8s/embedding-worker.yaml replicas from 0 to 1
    2. Verify image currency — ensure the manifest image tag matches a build that contains embedding_worker.py. Update if stale.
    3. Verify connectivity — worker must reach Ollama at http://ollama.ollama.svc.cluster.local:11434 and Postgres via PALDOCS_DATABASE_URL
    4. Initial backfill — 5,643 blocks with embedding_status='pending' need embedding. Worker has --backfill mode or will process via LISTEN/NOTIFY loop.
    5. Verify search modes — confirm /search?mode=semantic and /search?mode=hybrid return results via API
    6. Verify MCP tool — confirm semantic_search() MCP tool returns ranked results

    Progress (2026-03-14)

    • PR #155 merged — replicas 0→1, image tag updated. Embedding worker running.
    • PR #161 merged — Fixed SQLAlchemy text() parameter binding bug: :query_vec::vectorCAST(:query_vec AS vector). Semantic search SQL now works. Issue #160 closed.
    • SDK v0.3.0 published — to Forgejo PyPI. MCP server venv updated. MCP process needs restart to load new SDK.
    • Backfill in progress — 266/5,921 blocks completed (~4.5%). Batch size patched to 50, poll interval to 10s for ~10x speedup. ETA ~1 hour.
    • Remaining — verify search endpoints return results after backfill progresses further; verify MCP tool after session restart.

    Key Context

    • Embedding worker: src/pal_e_docs/embedding_worker.py (610 lines, production-ready)
    • K8s manifest: k8s/embedding-worker.yaml (replicas: 1, same image as API with different entrypoint)
    • Search service: src/pal_e_docs/services/search.py (RRF fusion — keyword + semantic)
    • Model: qwen3-embedding:4b (2560-dim vectors, 3.5GB VRAM, loaded in Ollama)
    • DB state: ~5,639 pending, 16 skipped, ~266 completed (actively backfilling)
    • ArgoCD reads from pal-e-docs/k8s/ — push to main triggers deploy

    Deliverables

    • Embedding worker running in production (1 replica) ✓
    • All embeddable blocks have embedding_status='completed' — in progress
    • Hybrid search returns meaningful results via API and MCP tool — SQL fix merged, pending verification
    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-5a-embedding-dimension-fix — sub-phase (completed)
    • bug-mcp-silent-load-failure — semantic search could help agents find recovery SOPs
    • plan-2026-03-09-template-rendering — sibling capability (template rendering)
  • Phase: Fix embedding dimension mismatch (768 → 2560) phase-pal-e-docs-5a-embedding-dimension-fix

    Goal: Fix vector dimension mismatch so embedding worker can store qwen3-embedding:4b output (2560-dim) in pgvector column.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs

    Depends on: phase-pal-e-docs-activate-semantic-search (parent — worker is deployed but erroring)

    Problem

    The blocks.embedding column is vector(768) but qwen3-embedding:4b now produces 2560-dim vectors. The model was likely updated upstream since Phase 6a verification. Worker logs show: expected 768 dimensions, not 2560. All blocks are marked as error status.

    Fix

    • Alembic migration: ALTER TABLE blocks ALTER COLUMN embedding TYPE vector(2560)
    • Update any code/config that hardcodes 768 (search for 768 in models, schemas, tests)
    • Reset errored blocks back to pending: UPDATE blocks SET embedding_status = 'pending' WHERE embedding_status = 'error'
    • Verify worker starts processing successfully after deploy
    • phase-pal-e-docs-activate-semantic-search — parent phase
    • plan-pal-e-docs — grandparent plan
  • Phase: Jinja2 Removal (pal-e-docs backend) phase-pal-e-docs-4b-jinja2-removal

    Goal: Remove the dead Jinja2 frontend from the pal-e-docs backend now that SvelteKit serves all routes.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs

    Depends on: phase-pal-e-docs-note-renderer

    Problem

    SvelteKit (pal-e-app) now renders all note types from the blocks API. The Jinja2 frontend (/browse/* routes) is dead code still deployed in the backend. ~320 lines of routing + ~460 lines of templates + dependencies.

    Fix

    • Delete src/pal_e_docs/routes/frontend.py
    • Delete src/pal_e_docs/templates/ directory (base.html, note.html, landing.html, projects.html, project_notes.html, tags.html, tag_notes.html, repos.html, login.html)
    • Remove Jinja2Templates import and setup
    • Remove frontend.router from the FastAPI app includes
    • Remove session auth middleware if only used by frontend (check if API uses it too)
    • Remove autolink.py, sanitize.py, wrap_tables.py if only consumed by frontend routes (check if blocks/sync.py or API routes use them)
    • Remove Jinja2 and itsdangerous (sessions) from dependencies if no longer needed
    • Run tests: pytest tests/ -v
    • phase-pal-e-docs-note-renderer — parent phase
    • plan-pal-e-docs — parent plan
  • Phase: Dead Code Cleanup (pal-e-app) phase-pal-e-docs-4a-dead-code-cleanup

    Goal: Remove dead listNoteSlugs() export from api.ts after slug cache replaced it.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-app

    Depends on: phase-pal-e-docs-note-renderer

    Problem

    QA nit from PR #9 review: listNoteSlugs() at src/lib/api.ts is exported but never imported. It was replaced by getCachedSlugs() in src/lib/slugCache.ts.

    Fix

    • Remove listNoteSlugs() function from src/lib/api.ts
    • Verify no other imports reference it
    • Run npm run check && npm run build
    • phase-pal-e-docs-note-renderer — parent phase
    • plan-pal-e-docs — parent plan
  • Phase: Block Renderer + Jinja2 Sunset phase-pal-e-docs-note-renderer

    Goal: Build SvelteKit block-based rendering for all note types and remove the Jinja2 frontend entirely.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-app (primary), forgejo_admin/pal-e-docs (Jinja2 removal)

    Depends on: phase-pal-e-docs-app-scaffold, phase-pal-e-docs-sprint-board-component

    Scope

    Absorbs the original Phase 4 (Note Renderer) and Phase 9 (Jinja Sunset) into a single deliverable. The Jinja2 frontend is ~320 lines of Python routing + ~460 lines of templates. The block API already exists. The gap is smaller than the plan assumed — no reason to maintain two frontends in parallel.

    Deliverables

    • 7 block renderer componentsHeadingBlock, ParagraphBlock, TableBlock, ListBlock, CodeBlock, MermaidBlock, BlockRenderer (dispatcher). All render from blocks API JSON.
    • /notes/[slug] route — fetches blocks from API, renders with block components. Breadcrumb, metadata badges, TOC sidebar, child notes sidebar, anchor links on headings.
    • /notes listing route — search, type-grouped display, tag/project/note_type filters.
    • /projects and /projects/[slug] routes — project list + detail with notes grouped by type.
    • /tags and /tags/[name] routes — tag cloud + filtered note listing.
    • /repos route — repos grouped by project.
    • Autolink support<code>slug-ref</code> patterns in paragraph/list blocks auto-link to /notes/{slug}. Server-side slug cache with 60s TTL.
    • Landing page — overview dashboard with projects, boards, and top tags.
    • DOMPurify sanitization — all {@html} inputs sanitized via isomorphic-dompurify. Defense-in-depth.
    • Shared color systemsrc/lib/colors.ts with 16-type color map extracted from duplicated constants.
    • PR #9 merged — 30 files changed, +2609/-183. Closes Issue #8.
    • Jinja2 removal NOT YET DONE — SvelteKit serves all routes. Jinja2 templates still in pal-e-docs backend (separate repo, separate PR). Tracked as follow-up.

    Out of Scope

    • Auth (login/logout) — deferred, tracked by pal-e-docs Issue #2
    • Dropping html_content column — keep as cache for now, remove after search migration
    • In-browser editing — future phase

    Key Architectural Decision

    SvelteKit renders from blocks, not from html_content. The blocks table (5,197 rows, 6 types) is the source of truth. html_content is a denormalized cache maintained by recompile() — kept for search compatibility but not consumed by the frontend.

    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-jinja-sunset — absorbed into this phase
    • phase-pal-e-docs-sprint-board-component — board kanban (already built, pattern to follow)
  • Phase: Jinja Sunset phase-pal-e-docs-jinja-sunset

    Goal: Absorbed into phase-pal-e-docs-note-renderer (Phase 4: Block Renderer + Jinja2 Sunset).

    Owner: n/a

    Repo: n/a

    Depends on: n/a

    Scope

    This phase was merged into Phase 4. The Jinja2 frontend is small enough (~780 lines total) that building the SvelteKit replacement and removing Jinja2 should happen as one deliverable rather than maintaining two frontends in parallel.

    • phase-pal-e-docs-note-renderer — absorbing phase
    • plan-pal-e-docs — parent plan
  • Phase: Board Kanban Component phase-pal-e-docs-sprint-board-component

    Goal: Design and build the interactive kanban board component. Playground-first — nail the UX before wiring to production.

    Owner: Lucas + Betty Sue (design), Dev agent (build)

    Repo: forgejo_admin/html-playground (design), forgejo_admin/pal-e-app (promote)

    Depends on: phase-pal-e-docs-app-scaffold (for promotion), board API (for real data)

    Scope

    • Kanban board: 7 columns (backlog→done), cards with title/points/labels, drag-and-drop — PR #6
    • Board tabs with item counts, navigate between project boards — PR #6
    • Card type differentiation: plan=gold, phase=green, issue=blue, todo=red, project=purple, repo=lavender — PR #6
    • Desktop HTML5 drag-and-drop + mobile touch long-press + tap-to-move fallback — PR #6
    • Server-side API proxy for browser PATCH calls (sanitized error responses, validated inputs) — PR #6
    • Dark theme across all pages — PR #6
    • Optimistic local state updates on drag-and-drop — PR #6
    • Deferred: Drill-down (click plan → see phases), add/remove items, inline point editing
    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-board-data-model — provides the API
  • Phase: pal-e-app Scaffold + Docker Compose phase-pal-e-docs-app-scaffold

    Goal: Create the pal-e-app repo with SvelteKit scaffold and Docker Compose dev environment connecting to pal-e-docs API.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-app (new)

    Depends on: None — connects to existing API

    Scope

    • SvelteKit project with adapter-node, TypeScript — PR #2
    • Docker Compose: postgres + pal-e-api + pal-e-app (dev server) — PR #2
    • Board list + detail pages with typed API client — PR #2
    • Woodpecker CI pipeline (check/lint/build) — PR #2
    • k8s manifests (deployment, service, kustomization) + CI build-and-push — PR #5
    • Service onboarding: namespace, Harbor project, Woodpecker secrets, ArgoCD — tofu apply + manual
    • plan-pal-e-docs — parent plan
  • Phase: Board Data Model phase-pal-e-docs-board-data-model

    Phase: Board Data Model

    Goal: Replace sprints with boards. One permanent kanban board per project. Drop sprint tables entirely.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs (DB + API), forgejo_admin/pal-e-docs-mcp (MCP tools)

    Depends on: Phase 0 (Project Taxonomy Cleanup) — clean project list is prerequisite

    Scope

    Data Model

    boards table:

    Column Type Notes
    id int PK auto
    slug varchar(200) unique Convention: board-{project-slug}
    name varchar(200) Display name
    project_id FK → projects.id, unique One board per project. NOT NULL.
    created_at datetime server_default=now()
    updated_at datetime server_default=now(), onupdate=now()

    board_items table:

    Column Type Notes
    id int PK auto
    board_id FK → boards.id, CASCADE NOT NULL
    item_type varchar(20) Denormalized from note_type or "issue". Values: plan, phase, issue, repo, project, todo
    column varchar(20) 7 values: backlog, todo, next_up, in_progress, qa, needs_approval, done
    position int Order within column. Default 0.
    note_slug varchar(500) nullable Points to a pal-e-docs note
    forgejo_issue_url varchar(500) nullable Points to a Forgejo issue
    title varchar(500) nullable Display name (derived from note/issue title)
    points int nullable Velocity tracking
    labels text nullable Comma-separated labels
    created_at datetime server_default=now()
    updated_at datetime server_default=now(), onupdate=now()

    API Endpoints

    Method Path Description
    GET /boards List all boards (unified view across projects)
    POST /boards Create board (requires project slug)
    GET /boards/{slug} Get board with item counts
    PATCH /boards/{slug} Update board name
    DELETE /boards/{slug} Delete board (reject if items exist)
    GET /boards/{slug}/items List items (filter by column, item_type)
    POST /boards/{slug}/items Add item to board
    PATCH /boards/{slug}/items/{id} Update item (move column, change points)
    DELETE /boards/{slug}/items/{id} Remove item from board
    PATCH /boards/{slug}/items/bulk Bulk move items between columns
    GET /boards/backlog/items Cross-board backlog view

    Migration

    • Create boards + board_items tables
    • Drop sprints + sprint_items tables (no data migration — Sprint 3 data discarded)
    • Remove sprint enums (SprintStatus, SprintItemType, SprintColumn)

    Decisions

    Decision Rationale
    Keep item_type (denormalized) Enables filtering ("show me just phases") without joining to notes table. Set at add-time from note's note_type or "issue" for Forgejo issues.
    Keep all 7 columns qa and needs_approval are critical workflow statuses for the PR review-fix loop.
    Keep labels field Used for DORA instrumentation (status:approved, etc.)
    Hard cut from sprints No migration of Sprint 3 data. Drop tables. Clean start.
    Board slug convention: board-{project-slug} Predictable, human-readable. Auto-generated on board creation.
    project_id UNIQUE on boards Enforces one board per project at the DB level.

    Sub-phases

    • 1a: DB + API — Tables, Alembic migration (drop sprints, create boards), all API endpoints, tests. Repo: pal-e-docs.
    • 1b: MCP tools — Replace 25 sprint MCP tools with board equivalents. Repo: pal-e-docs-mcp.
    • 1c: SDK — Update pal-e-docs-sdk with board operations. Repo: pal-e-docs-sdk.
    • plan-pal-e-docs — parent plan
    • plan-2026-03-01-pal-e-sprints — predecessor (completed, sprint tables being replaced)
    • phase-pal-e-docs-sprint-board-component — Phase 3, the SvelteKit UI that renders these boards
    • feedback_one_plan_per_project — one plan per project, one board per project
  • Phase: Project Taxonomy Cleanup phase-pal-e-docs-project-taxonomy

    Phase: Project Taxonomy Cleanup

    Goal: Clean project hierarchy so every active project follows the new template. One plan per project, one board per project. Prerequisite for Phase 1 (Board Data Model) — boards don't work if the project taxonomy is garbage.

    Owner: Betty Sue (main session)

    Repo: pal-e-docs (API change for delete_project), pal-e-docs-mcp (new tool)

    Depends on: Nothing

    Scope

    Completed (2026-03-13)

    • Created conventions: convention-todo-lifecycle (TODO graduation rules), template-project-page (Vision/Plan/Board/Status/Architecture/Repos/Inbox)
    • Consolidated pal-e-platform: 1 active + 4 deferred plans + 4 stubs → 1 unified plan-pal-e-platform with 13 phases. 4 orphan TODOs parked under phases. Project page rewritten to new template.
    • Consolidated pal-e-docs: 3 active plans → 1 unified plan-pal-e-docs with 10 phases. 6 TODOs parked. Project page rewritten.
    • Merged pal-e-config → pal-e-agency: 13 notes moved (6 completed plans, 1 deferred plan, 1 SOP, 2 conventions, 2 open TODOs). All pal-e-config content now lives under pal-e-agency.
    • Moved pal-e-services → pal-e-platform: 4 notes (service-onboarding-sop, deployment-lessons, namespace-conventions, argocd-image-updater). pal-e-services is a repo under pal-e-platform, not its own project.
    • Deleted 18 stale notes: 5 legacy pal-e (GitHub-era), 1 babylist, 2 pal-e-services stale, 10 pal-e-config stale (old project pages, archived repo pages, done TODOs)
    • Saved feedback: pal-e-platform is the axiom project (project=repo exception, DORA and observability live at foundation)

    Remaining: Subphase — delete_project endpoint + empty shell cleanup

    Forgejo Issue: forgejo_admin/pal-e-docs #144

    • DONE — Add DELETE /projects/{slug} to src/pal_e_docs/routes/projects.py — PR #145 merged 2026-03-13. Issue #144 closed.
    • Add delete_project tool to pal-e-docs-mcp (follow-up issue)
    • Deploy new image with delete_project endpoint
    • Delete 7 empty project shells: ai-agency, claude-config, babylist, pal-e-sprints, pal-e, pal-e-services, pal-e-config
    • Verify: list_projects returns only 7 active projects (pal-e-platform, pal-e-docs, pal-e-agency, westside-basketball, pal-e-world, posts, private)

    Westside Basketball alignment

    • Project page already updated to new template by Lucas
    • Tryout prep plan needs status update (tryout was 2026-03-13)
    • Confirm all basketball TODOs are properly parented
    • convention-todo-lifecycle — created this session
    • template-project-page — created this session
    • phase-pal-e-docs-board-data-model — Phase 1, depends on clean taxonomy
  • Phase 4: Betty Sue Sprint Skill (Commands + Board Sync) phase-2026-03-03-4-betty-sue-skill

    Goal: Betty Sue has skill commands for sprint management — creating items, syncing board state from labels, linking PRs, and sprint status. Skill encapsulates MCP tool sequences into reusable commands.

    Owner: Betty Sue (Claude config development per sop-claude-config-development)

    Repo: forgejo_admin/claude-custom (symlinked as ~/.claude/)

    Forgejo Issue: TBD

    Architecture

    Skills encapsulate multi-step MCP workflows into single commands. Each skill command is a recipe that calls MCP tools in sequence. The skill file defines the steps; the agent (Betty Sue) executes them.

    LayerWhatExample
    Skill commandUser-invocable workflow/sprint-sync — read labels on all sprint items, move board columns to match
    MCP toolsIndividual API calls the skill orchestratesmcp__forgejo__list_issues, mcp__pal-e-docs__move_sprint_item
    HooksEnforcement that fires on tool eventsPostToolUse after merge → remind Betty Sue to move sprint item to done

    Skill Commands to Implement

    • /sprint-sync — Read Forgejo labels on all sprint items' issues, update board columns to match label state using the label-to-column mapping from agent-workflow
    • /sprint-status — Summary of all boards: what's in each column, what's blocked, what moved since last check
    • /sprint-add — Create sprint item from Forgejo issue URL. Calls add_sprint_item() with title convention from template-sprint-item
    • /sprint-kickoff — For all items in next_up, spawn dev agents with Forgejo issue URLs. Follows agent-spawn-conventions.

    Hook: Post-Merge Sprint Update

    • Extend existing PostToolUse on mcp__forgejo__merge_approved_pr
    • Inject context: "PR merged. If this issue has a sprint item, move it to done and update the plan phase."
    • This is a reminder hook (like remind-update-docs.sh), not automatic — Betty Sue decides whether to act

    Steps

    1. Review sop-claude-config-development and existing skill file patterns
    2. Create skills/sprint-sync/SKILL.md
    3. Create skills/sprint-status/SKILL.md
    4. Create skills/sprint-add/SKILL.md
    5. Create skills/sprint-kickoff/SKILL.md
    6. Extend post-merge hook with sprint item reminder
    7. Create skill notes in pal-e-docs for each command
    8. Test each command against Sprint 1 data

    Deliverable: Betty Sue can /sprint-sync to update boards from labels, /sprint-status for a quick view, /sprint-add to onboard issues, /sprint-kickoff to start work on next_up items.

    Depends on: Phase 3 (hooks set labels that /sprint-sync reads).

  • Goal: Agents automatically signal workflow state via Forgejo labels at every stage transition. Enforced by hooks — not dependent on agent memory.

    Owner: Betty Sue (Claude config development per sop-claude-config-development)

    Repo: forgejo_admin/claude-custom (symlinked as ~/.claude/)

    Forgejo Issue: TBD

    Architecture: Three Enforcement Layers

    LayerWhatEnforcement
    Hooks (PostToolUse)Set labels + comment on issue automatically when tools fireStrong — can't be skipped, fires regardless of agent prompt
    Skills (/review-pr)Structured workflow with parseable verdict outputMedium — agent follows recipe, hook parses output
    Profiles (dev.md, qa.md)Mention labels so agents understand workflow contextWeak — awareness only, not enforcement

    Key insight: Hooks are the enforcement layer, not prompts. The forgejo-helper.sh already has curl patterns + credential loading. PostToolUse hooks run as shell scripts with full API access regardless of agent permissions. Agents don't need set_label MCP tools — the hooks handle it.

    MCP Gap (discovered scope)

    The forgejo-mcp server has NO set_label or comment_on_issue tools. This is why hooks are the right approach for now — they bypass the gap. A TODO for forgejo-mcp should be created to add these tools properly.

    Hook Implementations

    1. PostToolUse on mcp__forgejo__create_issue_and_branch:

    • Extract owner, repo, issue number from tool input
    • curl: set status:in-progress label on the issue
    • Inject context: "Label status:in-progress set on issue #N"

    2. PostToolUse on mcp__forgejo__submit_pr:

    • Extract owner, repo from tool input; extract issue number from branch name or PR body
    • curl: set status:qa label on the parent issue (replace any existing status label)
    • curl: comment on the issue with PR URL ("PR #N submitted: [url]")
    • Inject context: "Label status:qa set + PR linked on issue #N"
    • Extends existing remind-mcp-review-loop.sh — or chains after it

    3. PostToolUse on mcp__forgejo__comment_on_pr:

    • Parse the comment body for verdict: look for "VERDICT: APPROVED" or "VERDICT: NOT APPROVED"
    • If APPROVED: curl set status:approved label on parent issue, curl comment approval summary on issue
    • If NOT APPROVED: curl set status:needs-fix label on parent issue, curl comment required fixes on issue
    • If no verdict found: no-op (not all PR comments are review verdicts)

    Skill Update: /review-pr

    • Update skill-review-pr note to require VERDICT line in exact format: ### VERDICT: APPROVED or ### VERDICT: NOT APPROVED
    • This makes the verdict parseable by the PostToolUse hook
    • QA continues to comment on PRs (existing capability); hook mirrors findings to the issue

    Profile Updates: dev.md + qa.md

    • Add awareness section: "Hooks automatically set Forgejo labels when you use MCP tools"
    • Dev: mention that status:in-progress and status:qa are set by hooks after create_issue_and_branch and submit_pr
    • QA: mention that status:approved/status:needs-fix is set by hook based on VERDICT line in PR comment
    • These are informational — the hooks enforce regardless

    Helper Function Additions: forgejo-helper.sh

    • forgejo_set_label() — set a label on an issue (replace existing status: labels)
    • forgejo_comment_on_issue() — comment on an issue (not a PR)
    • forgejo_get_issue_number_from_branch() — extract issue # from branch name pattern

    Steps

    1. Add helper functions to forgejo-helper.sh
    2. Create label-on-branch.sh — PostToolUse hook for create_issue_and_branch
    3. Create label-on-pr.sh — PostToolUse hook for submit_pr (chains with existing remind-mcp-review-loop.sh)
    4. Create label-on-verdict.sh — PostToolUse hook for comment_on_pr
    5. Register hooks in settings.json
    6. Update skill-review-pr note — require parseable VERDICT format
    7. Update dev.md and qa.md — awareness of label hooks
    8. Test: spawn dev agent on a real issue, verify labels set automatically
    9. Test: spawn QA agent, verify verdict parses and label sets

    Deliverable: Every submit_pr automatically sets status:qa. Every QA verdict automatically sets status:approved or status:needs-fix. Every branch creation sets status:in-progress. All enforced by hooks — zero agent memory required.

    Depends on: Phase 1 (labels exist on repos) — COMPLETED. Phase 2 (SOPs document the protocol) — COMPLETED.

  • Phase 2: SOP Updates (Document the Workflow) phase-2026-03-03-2-sop-updates

    Goal: SOPs document the full sprint workflow — label protocol, agent behavior, board sync — before any implementation begins.

    Owner: Betty Sue (pal-e-docs notes, no repo code)

    Repo: N/A — pal-e-docs notes only

    Forgejo Issue: N/A (Betty Sue executed directly — docs work)

    Deliverables (2026-03-03):

    1. agent-workflow updated:
      • Added rule 6: "Agents signal status via labels"
      • Expanded "The Flow" from 8 to 12 steps — includes Sprint Item creation, label signaling, board sync
      • Added "Label Signaling Protocol" section — status labels table with DORA data column, type labels, rules
      • Added "Sprint Board Sync" section — label-to-column mapping table
    2. pr-lifecycle updated:
      • Label steps added to Stages 1-4 (type labels at creation, status:in-progress at branch, status:qa at PR, status:approved/needs-fix at review)
      • Stage 5 added: Sprint Board Sync (Betty Sue reads labels, syncs columns, links PRs)
      • Stage 8 expanded: post-merge includes sprint item move to done, plan phase update, issue close
      • Mermaid diagram updated with QA agent and Betty Sue as participants
      • Documented QA comments on issues (not PRs) convention
    3. template-sprint-item created:
      • Design principle: sprint items are commitment wrappers, not issues
      • Fields: Forgejo Issue URL, Title, Column
      • Label-to-column mapping table
      • Betty Sue's workflow: create, sync, link PR, close
      • Title convention: "{repo}: {short description}"

    Depends on: Phase 1 (labels exist to reference in SOPs) — COMPLETED.

  • Phase 1: Forgejo Labels (Foundation) phase-2026-03-03-1-forgejo-labels

    Goal: Standard status and type labels exist on all Forgejo repos, enabling agents to signal workflow state.

    Owner: Betty Sue (Forgejo API calls, no repo code)

    Repo: N/A — Forgejo API across all repos

    Forgejo Issue: N/A (Betty Sue executed directly — no repo code involved)

    Labels created:

    Category Label Color Purpose
    Status status:in-progress #1d76db (blue) Dev agent is actively working
    Status status:qa #e4a224 (orange) PR submitted, awaiting QA review
    Status status:needs-fix #d93f0b (red) QA found issues, back to dev
    Status status:approved #0e8a16 (green) QA passed, awaiting merge approval
    Type type:feature #5319e7 (purple) New functionality
    Type type:bug #d93f0b (red) Bug fix
    Type type:devops #666666 (gray) Infrastructure/CI/config work

    Deliverables (2026-03-03):

    • 7 labels created across 29 Forgejo repos (202 labels created, 1 skipped as pre-existing)
    • All repos under forgejo_admin/ now have identical label sets
    • Labels verified via Forgejo API — all returned HTTP 201

    Depends on: Nothing — this was the foundation.

  • Goal: Expand sprint schema for five boards, human review gate, and item estimation.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs + forgejo_admin/pal-e-docs-mcp

    Scope

    1. repo/project item_types + needs_approval column — PR #67 merged (squash). Forgejo issue #66 (closed). DONE.
    2. Points field on sprint items — PR #69 merged (squash). Nullable integer points on SprintItem. Forgejo issue #68 (closed). DONE.
    3. MCP tool updates — PR #32 merged (squash, pal-e-docs-mcp). Forgejo issue #11 (closed). DONE. QA nits tracked as #33 and #34.

    Decisions

    Decision Rationale
    Points on items only, not plans/phases Plans are containers, phases are organizational. Items (issues) are the unit of work. Velocity = sum of pointed items completed per sprint.
    Points field is optional (nullable integer) Not all items need points. Unpointed items are valid (e.g. backlog items not yet estimated).
    Fibonacci scale (1/2/3/5/8) Standard estimation scale. Calibrate as we go.
    Separate PR for points (Option B) PR #67 was already scoped. Don't add scope to unreviewed PRs.
    Sprints = committed scope, not timebox A sprint is a set of well-scoped items we commit to. Not a fixed timeline. Keep pushing until done, then close.

    Status

    • PR #67 (repo/project + needs_approval): MERGED. Issue #66 closed.
    • PR #69 (points field): MERGED. Issue #68 closed.
    • PR #32 (MCP tools update, pal-e-docs-mcp): MERGED. Issue #11 closed. QA nits: #33 (clear points/labels), #34 (verify bulk points).

    Depends on: Phase 1 (tables + API) — COMPLETED

  • Phase 1: Sprint Tables, API, and MCP Tools phase-sprints-1-tables-api

    Goal: Sprint and SprintItem models with full CRUD API + MCP tools for Betty Sue to manage boards.

    Owner: Dev agent

    Repo: pal-e-docs + pal-e-docs-mcp

    Forgejo Issues: forgejo_admin/pal-e-docs #64 (tables+API, merged), forgejo_admin/pal-e-docs-mcp #8 (MCP tools, PR #9 merged 2026-03-02)

    Models:

    • Sprint — id, name, slug, goal, start_date, end_date, status (planning/active/completed/archived), created_at
    • SprintItem — id, sprint_id (FK, nullable for backlog), item_type (enum: plan/phase/issue), note_slug (nullable — for plans/phases), forgejo_issue_url (nullable — for issues), repo_slug (nullable — for issues), column (enum: backlog/todo/next_up/in_progress/qa/done), position (int), title_cache (string), created_at, updated_at

    API routes (DEPLOYED):

    • GET/POST /sprints
    • GET/PATCH /sprints/{slug}
    • DELETE /sprints/{slug}
    • GET /sprints/{slug}/items?item_type=plan|phase|issue — the three boards
    • POST /sprints/{slug}/items — add item to sprint
    • PATCH /sprints/{slug}/items/{id} — move column/position
    • PATCH /sprints/{slug}/items/bulk — multi-move
    • DELETE /sprints/{slug}/items/{id}
    • GET /sprints/backlog/items — items not in any active sprint

    MCP tools (DEPLOYED — PR #9 merged 2026-03-02):

    • create_sprint, get_sprint, list_sprints, update_sprint
    • add_sprint_item, move_sprint_item, remove_sprint_item
    • get_sprint_board — returns items filtered by item_type, grouped by column
    • get_backlog, bulk_move_items

    Key files: models.py, schemas.py, routes/sprints.py, main.py, alembic migration (pal-e-docs); tools/sprints.py (pal-e-docs-mcp)

    Deliverable: Betty Sue can create a sprint, add plans/phases/issues to it, move them between columns, and query any of the three boards. VERIFIED — tools live in Claude Code session.

  • Phase 6e: Hybrid Ranking (tsvector + vector) phase-postgres-6e-hybrid-ranking

    Goal: Unify keyword search (tsvector) and semantic search (pgvector) into a single endpoint with hybrid ranking, so agents get the best of both signals in one query.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs

    Depends on: phase-postgres-6d-semantic-search (COMPLETED)

    Scope

    • Add mode parameter to GET /notes/search endpoint — values: keyword (default, current behavior), semantic, hybrid
    • Implement hybrid ranking using Reciprocal Rank Fusion (RRF) — combines tsvector ts_rank and cosine similarity without needing score normalization
    • Configurable alpha parameter for weighting between keyword and semantic relevance (default: 0.5)
    • Results include score metadata so agents can assess relevance
    • Backward compatible — omitting mode or using mode=keyword returns identical results to current behavior
    • The /notes/semantic-search endpoint remains for now (deprecation is a separate decision)

    Deliverables

    • (to be filled after completion)
    • phase-postgres-6-vector-search — parent phase
    • phase-postgres-6d-semantic-search — prerequisite (semantic search API)
    • phase-postgres-5-fulltext-search — prerequisite (tsvector infrastructure)
    • plan-2026-02-26-tf-modularize-postgres — parent plan
  • Phase 6d-1: SDK Semantic Search Integration Test phase-postgres-6d1-sdk-integration-test

    Goal: Add integration tests for the SDK semantic_search method to verify the full round-trip: SDK → API → Ollama → pgvector → response.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs-sdk

    Depends on: phase-postgres-6d-semantic-search deliverable 2 (SDK method, PR #20)

    Problem

    The SDK's semantic_search method has unit tests (mock httpx) but no integration tests against a live instance. Semantic search has a unique dependency chain (SDK → API → Ollama → pgvector) that unit tests can't verify. The existing search_notes method has integration tests — semantic_search should too.

    Fix

    • Add integration tests to tests/integration/test_search.py (or new file) following the existing search_notes integration test pattern
    • Verify: results returned, response shape has block-level fields (anchor_id, similarity, content_snippet), similarity scores are ranked
    • phase-postgres-6d-semantic-search — parent phase
    • plan-2026-02-26-tf-modularize-postgres — parent plan
  • Phase 6d: Semantic Search API + SDK + MCP Tool phase-postgres-6d-semantic-search

    Goal: Add a semantic search endpoint so agents can find related knowledge by meaning (cosine similarity over pgvector embeddings), then expose it through the SDK and MCP tool.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs (API), forgejo_admin/pal-e-docs-sdk (SDK), forgejo_admin/pal-e-docs-mcp (MCP)

    Depends on: phase-postgres-6c (async embedding pipeline — embeddings must exist in blocks table)

    Why

    Phase 6a deployed Ollama with GPU. Phase 6b added pgvector schema. Phase 6c built the embedding pipeline and backfilled 5K+ blocks. But there's no way to query those embeddings yet. This phase closes the loop — agents can ask a natural language question and get ranked, relevant blocks back without reading every document.

    Scope

    Three deliverables across three repos, executed sequentially (each depends on the previous):

    # Deliverable Repo Forgejo Issue Status
    1 API endpoint: GET /notes/semantic-search pal-e-docs #137 COMPLETED (PR #138)
    2 SDK method: client.semantic_search(query, limit) pal-e-docs-sdk #18 COMPLETED (PR #20)
    3 MCP tool: semantic_search pal-e-docs-mcp #29 COMPLETED (PR #30)

    Architecture

    Query flow: Agent calls MCP tool → SDK calls API → API embeds query via Ollama → cosine similarity search on blocks.embedding → return ranked blocks with note context.

    The API endpoint mirrors GET /notes/search (tsvector) but operates at block granularity. Key differences:

    • Queries the blocks table (not notes), joining to notes for context
    • Uses pgvector <=> cosine distance operator (HNSW index: ix_blocks_embedding)
    • Requires an Ollama call to embed the query text before searching
    • Returns block-level results: anchor_id, block_type, content snippet, similarity score

    Deliverables

    • (filled after completion)
    • phase-postgres-6-vector-search — parent phase
    • plan-2026-02-26-tf-modularize-postgres — parent plan
    • decision-phase6-vector-search-architecture — embedding model research + architectural decisions
    • phase-postgres-6c — embedding pipeline (prerequisite)
  • Phase 7f: Doc Cleanup + SOP Hardening phase-postgres-7f-doc-cleanup-sop

    Goal: Clean up documentation debt accumulated during Act 2, harden SOPs for the four-agent model, ensure all notes are clean and well-attributed before Phase 6 vectorization, and automate post-merge documentation updates.

    Owner: Betty Sue + Dottie

    Repos: claude-custom (hooks/skills), pal-e-docs (app + doc content), pal-e-docs-mcp (API improvements)

    Depends on: Phase 7e (compiled pages) — COMPLETED

    Parent phase: Phase 7 (Block-Structured Content Model)

    Critical context: Phase 6 (vector search) will embed blocks. Dirty data = dirty embeddings. This phase ensures every note has proper type, hierarchy, project, and anchor_ids before vectorization begins.

    Sub-Phase Status

    # Sub-Phase Slug Status
    7f-1 Deprecate issue-creator + issue-gate phase-7f-1-deprecate-issue-creator COMPLETED — PR #58 merged (claude-custom)
    7f-2 Agent spawn requirements schema phase-7f-2-agent-spawn-schema COMPLETED
    7f-3 Template drift fix (issue migration) phase-postgres-7f-3-template-drift COMPLETED
    7f-4 Note attribute augmentation phase-postgres-7f-4-attribute-augmentation COMPLETED — 16-type taxonomy, 262 notes typed, issue archival (49 deleted), API enum (PR #116), project field (PR #118), Dottie audit (23 findings). Absorbed 7f-5 and 7f-6 deliverables.
    7f-5 Documentation cleanup + TODO triage absorbed by 7f-4 COMPLETED — 49 issue notes deleted, 22 TODO statuses fixed, 3 orphans resolved, pg_dump + JSON backup
    7f-6 SOP review and consolidation absorbed by 7f-4 COMPLETED — Dottie audit covered all 14 SOPs + 12 conventions. enforcement-architecture + sop-litestream-restore fixed. 1 empty convention deleted.
    7f-7 Post-merge automation hook phase-postgres-7f-7-post-merge-automation COMPLETED — PR #72 merged (claude-custom). /update-docs slash command deployed. Old skills/update-docs/SKILL.md removed.

    Why This Phase

    Act 2 moved fast and docs drifted. Phase notes are stale, orphaned notes exist, SOPs don't match the four-agent model, and post-merge documentation is a manual SOP that gets skipped. This phase makes the knowledge base as clean as the code.

    Added context (2026-03-08): Analysis revealed 83 untyped notes, 239 notes without parents, and ~307 notes with blocks that have null anchor_id (from the original backfill). The list_notes API also doesn't include the project field in summaries, making it impossible to compute orphan counts in one query. All of this must be fixed before Phase 6 vectorization.

    Execution note (2026-03-08): 7f-4 session used aggressive parallelization (5 agents: 1 Dottie, 2 Dev, 2 QA). Dottie's full 262-note audit naturally covered 7f-5 (doc cleanup + TODO triage) and 7f-6 (SOP review) deliverables in a single pass. Plan structure assumed sequential execution; actual execution compressed three subphases into one.

    Deliverables

    1. Note attribute augmentation (7f-4)

    Every note must have clean, queryable metadata before vectorization.

    • Type all 83 untyped notes — Done: 262 notes, 16 types, zero nulls
    • Re-save all notes — Done: parser re-run assigns anchor_ids
    • Assign project — Done: 0 null projects
    • Assign parents — Done: 3 orphans resolved
    • Add project field to list_notes API summaries — Done: PR #118

    2. Documentation cleanup + TODO triage (7f-5) — absorbed by 7f-4

    • Delete old issue notes — Done: 49 deleted
    • Review all 49 TODOs for staleness — Done: 22 statuses fixed by Dottie audit
    • Ensure all notes have appropriate parents — Done: 3 orphans resolved, 0 null projects
    • Backup Postgres before bulk cleanup — Done: pg_dump + JSON in MinIO

    3. SOP review and consolidation (7f-6) — absorbed by 7f-4

    • Review all 13 active SOPs for consistency with four-agent model — Done: Dottie audit + manual fixes
    • Ensure issue scoping is documented — Done: agent-spawn-conventions updated
    • Clean up any contradictions between SOPs — Done: enforcement-architecture, sop-litestream-restore fixed
    • Verify all conventions are current — Done: 1 empty convention deleted

    4. Post-merge automation (7f-7)

    • /update-docs slash command — Done: commands/update-docs.md (PR #72)
    • Old skill removed — Done: skills/update-docs/SKILL.md deleted (PR #72)
    • Command deployed — Done: copied to ~/.claude/commands/

    Acceptance Criteria

    • Zero notes with null note_type — DONE (262 notes, 16 types)
    • Zero blocks with null anchor_id — IN PROGRESS (re-save running)
    • Every note assigned to a project — DONE (0 null projects)
    • Zero orphaned notes without documented reason — DONE
    • All TODOs reviewed — DONE (22 statuses fixed)
    • SOPs consistent with four-agent model — DONE
    • list_notes API includes project in summaries — DONE (PR #118)
    • Post-merge hook fires automatically — DONE (/update-docs command + reminder hook)
    • Knowledge base ready for Phase 6 vectorization — READY (pending anchor re-save completion)
    • sop-post-merge-docs — the SOP being automated
    • skill-update-docs — the existing skill to be upgraded
    • agent-workflow — must reflect four-agent model
    • sop-postgres-restore — backup before cleanup
    • todo-sveltekit-frontend-migration — depends on this phase completing
    • phase-postgres-6-vector-search — blocked until this phase delivers clean data
  • Phase 7e: Compiled Page Architecture phase-postgres-7e-compiled-pages

    Goal: Make blocks the canonical source of truth and optimize session injection to use block-level tools instead of full note reads. Estimated ~90% token reduction at session startup.

    Owner: Dev agent (7e-1, 7e-2), Betty Sue (7e-3)

    Repos: pal-e-docs (7e-1, 7e-1a, 7e-2), claude-custom (7e-3)

    Depends on: Phase 7d (block API must exist) — COMPLETED

    Parent phase: Phase 7 (Block-Structured Content Model)

    Status: COMPLETED — all sub-phases done.

    Sub-Phases

    # Sub-Phase Repo Status Deliverable
    7e-1 Source-of-Truth Cutover pal-e-docs COMPLETED Blocks canonical — note writes parse to blocks first, then recompile. PR #110.
    7e-1a QA Nits Cleanup pal-e-docs COMPLETED Redundant assignment fix, test helper hygiene. PR #114.
    7e-2 Compiled Page API pal-e-docs COMPLETED GET /notes/{slug}/compiled endpoint. PR #113. SDK + MCP follow-on deferred.
    7e-3 Session Injection + Block-First Convention claude-custom + pal-e-docs COMPLETED Hook injects plan TOCs (PR #68). Convention note, agent personalities, SOP updated. 91% token reduction measured.

    What Was Delivered

    • 7e-1: Extracted recompile() and parse_and_store_blocks() into shared blocks/sync.py. create_note() and update_note() now parse to blocks first, then recompile. 14 new tests, 503 total passing. PR #110.
    • 7e-1a: QA nits — redundant html_content assignment, test helper session hygiene, detached ORM fix. PR #114.
    • 7e-2: GET /notes/{slug}/compiled endpoint with CompiledPageOut schema (slug, title, html, toc_json, content_hash, block_count, compiled_at). 8 new tests. PR #113.
    • 7e-3: Block-first established as platform convention:
      • Convention note: convention-block-first-access
      • Agent personalities updated: agent-betty-sue, agent-dottie — Knowledge Access sections added
      • SOP updated: agent-workflow — Rule 7 (block-first knowledge access) added
      • Session hook: session-start-context.sh injects plan TOCs inline. PR #68.
      • Re-backfill: 58 gap notes populated with blocks (307 total notes now have valid blocks)

    Measured Results

    Scenario Before After Savings
    Session startup (6 plans) ~11,640 tokens ~1,032 tokens 91.1%
    Read one plan section ~2,360 tokens (full note) ~200 tokens (one section) ~92%
    Update one plan section ~2,360 tokens (full note round-trip) ~100 tokens (block update) ~96%

    Lessons Learned

    • Merged ≠ deployed ≠ data consistent. 7e-1 fixed future writes but couldn't retroactively fix notes created between the 7c backfill and deployment. 58 notes had no blocks. Re-backfill was essential before 7e-3 could work.
    • Test before shipping infrastructure changes. Testing the TOC endpoint revealed 2 of 6 plans had empty TOCs. Would have shipped broken hook without testing.
    • Convention shifts need more than code. 7e-3 started as "change the hook" but became 4 deliverables: convention note, agent personalities, SOP, hook. The pattern must be encoded everywhere agents get their instructions.
    • convention-block-first-access — the convention established by 7e-3
    • decision-7e3-block-first-access — decision record with rationale
    • phase-postgres-7d-api-mcp-tools — block API (prerequisite, COMPLETED)
    • phase-postgres-8-mcp-optimization — SDK + MCP tools (prerequisite, COMPLETED)
    • benchmark-phase7-block-baseline — baseline token measurements
    • plan-2026-03-01-pal-e-sprints-frontend — sprint frontend consumes compiled pages (7e-2 SDK/MCP deferred until then)
  • Phase 5: Dogfood + Measure phase-note-decomp-5-dogfood

    Goal: Decompose real plans. Measure token savings vs baselines. Prove it works.

    Status: COMPLETE — 2026-03-02

    Done:

    • Decomposed Note Decomposition plan into 5 child phase notes
    • Decomposed pal-e-sprints plan into 3 child phase notes
    • Decomposed Knowledge System Consolidation plan into 6 child phase notes
    • Decomposed Platform Observability plan into 5 child phase notes
    • All 4 active plans now decomposed (19 total child phases created)
    • All parent plans slimmed to ~2KB summaries pointing to child phases
    • Proved queries work: list_notes(note_type="phase", status="in-progress") = 1KB/1 call (was 193KB/12 calls)
    • Proved phase update works: update_note(slug, status) = 0.5KB/1 call (was 39KB/2 calls)

    Measured Results:

    Story Before After Savings
    Update phase status 39KB, 2 calls 0.5KB, 1 call 98.7%
    Query in-progress phases 193KB, 12 calls 1KB, 1 call 99.5%
    Read plan summary 15KB 2KB 86.7%

    Remaining: PR #63 deploy + browser verification (tracked separately — not blocking plan completion).

  • Phase 8d: SDK Sprints Mixin phase-postgres-8d-sdk-sprints

    Goal: Add typed SDK methods for all sprint API endpoints, enabling the Phase 8f MCP rewrite to cover sprint management -- Betty Sue's primary daily workflow.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs-sdk

    Depends on: 8a (SDK core)

    Why

    Sprint management is Betty Sue's most frequent workflow. Every session starts with checking the board, every phase completion moves items. The current MCP tools (10 tools in pal-e-docs-mcp/tools/sprints.py) use raw httpx. Without SDK coverage, the Phase 8f MCP rewrite can't cover sprints, leaving an inconsistent architecture and no integration test coverage for sprint operations.

    Agent Workflows This Enables

    Workflow When SDK Methods Used
    Sprint planning Start of sprint create_sprint, add_sprint_item × N
    Session check-in Every session start list_sprints(status=), get_sprint_board(item_type=, column=)
    Work tracking During session move_sprint_item, update_sprint_item
    Backlog grooming Between sprints get_backlog, add_sprint_item, remove_sprint_item
    Sprint close End of sprint update_sprint(status=completed), bulk_move_items

    API → SDK → MCP Mapping

    # API Endpoint SDK Method MCP Tool (8f)
    1 GET /sprints list_sprints(status=) list_sprints
    2 POST /sprints create_sprint(...) create_sprint
    3 GET /sprints/{slug} get_sprint(slug) get_sprint
    4 PATCH /sprints/{slug} update_sprint(slug, ...) update_sprint
    5 DELETE /sprints/{slug} delete_sprint(slug) None (destructive, no MCP by design)
    6 GET /sprints/backlog/items get_backlog(item_type=) get_backlog
    7 GET /sprints/{slug}/items list_sprint_items(slug, item_type=, column=) get_sprint_board
    8 POST /sprints/{slug}/items add_sprint_item(slug, ...) add_sprint_item
    9 PATCH /sprints/{slug}/items/{id} update_sprint_item(slug, item_id, ...) move_sprint_item (subset)
    10 DELETE /sprints/{slug}/items/{id} delete_sprint_item(slug, item_id) remove_sprint_item
    11 PATCH /sprints/{slug}/items/bulk bulk_move_items(slug, items) bulk_move_items

    Implementation Notes

    • update_sprint uses PATCH (not PUT like notes) -- only send non-None fields
    • update_sprint_item uses PATCH with explicit null handling for points and labels (server uses model_fields_set)
    • delete_sprint and delete_sprint_item return None (204)
    • create_sprint has required fields: name, slug, status. Optional: goal, start_date, end_date
    • add_sprint_item has validation: plan/phase/todo require note_slug, issue requires forgejo_issue_url

    Deliverables

    • src/pal_e_docs_sdk/sprints.py -- SprintsMixin with 11 methods
    • tests/test_sprints.py -- httpx-mocked unit tests covering all methods
    • client.py updated -- SprintsMixin in PalEDocsClient MRO
    • plan-2026-03-01-pal-e-sprints -- the sprint backend plan
    • phase-postgres-8-mcp-optimization -- parent phase (SDK + MCP Rewrite)
    • plan-2026-03-03-sprint-workflow-automation -- agent behavior built on sprint backend
  • Phase 8d-1: Sprint SDK sentinel pattern consistency phase-postgres-8d1-sprint-sentinel-consistency

    Goal: Apply the _UNSET sentinel pattern to update_sprint optional fields (goal, start_date, end_date) so callers can explicitly clear them to null, matching the pattern already used in update_sprint_item.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs-sdk

    Depends on: 8d (sprint mixin merged)

    Problem

    update_sprint_item uses a sentinel (_UNSET = object()) for points and labels, allowing callers to distinguish "don't send this field" from "clear this field to null." But update_sprint uses plain None defaults for goal, start_date, end_date, so there's no way to explicitly clear those fields. This is an asymmetry in the API surface.

    Fix

    • Change update_sprint optional fields to use _UNSET sentinel
    • Add tests verifying explicit null is sent when None is passed
    • phase-postgres-8d-sdk-sprints -- parent phase where this was identified
    • QA finding on PR #8
  • Phase 8e: SDK Integration Tests phase-postgres-8e-integration-tests

    Goal: Add integration tests that run the SDK against the live pal-e-docs service, verifying all 8 endpoint families work end-to-end. These tests become the foundation for Phase 8g (CI smoke tests).

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs-sdk

    Depends on: 8b, 8c, 8d (all completed)

    Scope

    8 endpoint families × key methods = ~20-30 integration tests in tests/integration/. Read-only tests against known production data. No destructive operations (no delete, no create that leaves garbage).

    Endpoint Families

    Family Mixin Key Methods to Test
    Notes NotesMixin get_note, list_notes
    Search SearchMixin search_notes
    Tags TagsMixin list_tags
    Projects ProjectsMixin list_projects, get_project
    Links LinksMixin get_note_links
    Repos ReposMixin list_repos
    Blocks BlocksMixin get_note_toc, get_note_blocks, get_section
    Sprints SprintsMixin list_sprints, get_sprint, get_sprint_board, get_backlog

    Baseline Assertions

    • Notes count > 270
    • Blocks count > 5000 (Phase 7c backfill)
    • Search for "CNPG" returns sop-secrets-management
    • TOC for the plan note has > 8 headings
    • Sprint 2 exists and has items

    Test Infrastructure

    • tests/integration/conftest.pylive_client fixture using PALDOCS_BASE_URL env var
    • pytest.ini or pyproject.toml marker: integration so unit tests still run fast without the env var
    • Run command: PALDOCS_BASE_URL=https://pal-e-docs.tail5b443a.ts.net pytest tests/integration/ -v
    • phase-postgres-8-mcp-optimization — parent phase with example tests
    • qa-phase7c-backfill-2026-03-07 — baseline data the tests verify
  • Phase 8f-1: SDK Publish Pipeline phase-postgres-8f1-sdk-publish

    Goal: Get pal-e-docs-sdk published to Forgejo PyPI so the MCP server can depend on it as a registry package.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs-sdk

    Depends on: 8e (SDK code complete and tested)

    Scope

    The SDK repo has a .woodpecker.yaml with lint + test + publish steps, but the repo was never activated in Woodpecker. The publish pipeline has never run. The package is not on Forgejo PyPI (14 packages listed, pal-e-docs-sdk not among them). This sub-phase activates the pipeline and verifies end-to-end publish.

    Blocks: 8f-2 — MCP server's pyproject.toml can't resolve pal-e-docs-sdk>=0.1.0 without this.

    Progress

    • Woodpecker activated — repo ID 26, responding to push/pull_request events
    • Secrets configuredpaldocs_base_url repo secret created; global PyPI secrets already available
    • PR #15 merged — ruff formatting fix. QA approved. Squash merged to main.
    • Forgejo issue #14 — created by dev agent for the formatting fix
    • Pipeline #4 running — push-to-main with publish step. Awaiting completion.

    Remaining

    • Verify pipeline #4 passes (lint + test + publish)
    • Verify pal-e-docs-sdk v0.1.0 appears on Forgejo PyPI
    • Verify pip install from Forgejo PyPI works

    Deliverables

    • Woodpecker repo activation (ID 26)
    • Repo secret: paldocs_base_url
    • PR #15 — ruff formatting fix (merged)
    • Forgejo issue #13 (original), #14 (formatting fix by dev agent)
    • phase-postgres-8f-mcp-rewrite — parent sub-phase
    • todo-forgejo-pypi — Forgejo PyPI pattern (status: done)
    • phase-postgres-8f2-mcp-rewrite-core — blocked by this
  • Phase 8f-2: MCP Rewrite Core phase-postgres-8f2-mcp-rewrite-core

    Goal: Rewrite server.py, rewrite all 26 existing tools to SDK wrappers, add 7 new tools (6 block + delete_sprint). Achieve 32/32 SDK coverage. Bump version to fix publish pipeline.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs-mcp

    Depends on: 8f-1 (SDK must be published and importable)

    Scope

    server.py changes

    1. Replace import httpx with from pal_e_docs_sdk import PalEDocsClient, PalEDocsError, NotFoundError, ValidationError, ServerError
    2. get_client()get_sdk() — returns PalEDocsClient instead of httpx.Client
    3. _ok(response: httpx.Response)_ok(data: Any) — takes parsed data from SDK, returns json.dumps(data, indent=2). Returns {"ok": true} for None.
    4. _error_response() — catch PalEDocsError hierarchy instead of httpx.HTTPStatusError. Extract exc.status_code and exc.detail.

    pyproject.toml changes

    1. Replace httpx>=0.27 with pal-e-docs-sdk>=0.1.0
    2. Bump version from 0.1.00.2.0 (fixes publish pipeline #31 failure)
    3. Add Forgejo PyPI index configuration

    26 existing tools rewritten

    Each tool: validate/transform MCP params → get_sdk().method()_ok(result). Try/except catches SDK exceptions.

    Simple (~5 lines): get_note, delete_note, get_note_revisions, get_note_links, list_projects, list_tags, list_repos, get_sprint, list_sprints, remove_sprint_item, get_backlog, get_sprint_board (12 tools)

    Param bridging (~8-10 lines): create_note, update_note, search_notes, update_note_links, create_sprint, update_sprint, add_sprint_item, move_sprint_item, bulk_move_items, list_notes, create_project, create_repo, update_repo (14 tools)

    7 new tools

    Tool SDK Method Purpose
    get_note_toc get_note_toc(slug) Browse note heading structure. ~200 tokens vs ~5000.
    list_blocks list_blocks(slug) List all blocks with types and anchors.
    get_section get_section(slug, anchor_id) Read one heading + content blocks. The surgical read.
    update_block update_block(slug, anchor_id, ...) Edit one block without rewriting the note.
    create_block create_block(slug, ...) Insert a block at a position.
    delete_block delete_block(slug, anchor_id) Remove a block.
    delete_sprint delete_sprint(slug) Full sprint lifecycle. DB is backed up.

    New file: tools/blocks.py + tools/__init__.py update

    6 block tools in new file. Register in register_all_tools().

    Deliverables

    • To be filled after completion
    • phase-postgres-8f-mcp-rewrite — parent sub-phase
    • phase-postgres-8f1-sdk-publish — must be done first
    • phase-postgres-8f3-param-alignment — verifies the param bridging done here
  • Phase 8f-3: Param Alignment Audit phase-postgres-8f3-param-alignment

    Goal: Verify and resolve all parameter mismatches between MCP tool signatures and SDK method signatures. Ensure no existing agent workflow breaks.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs-mcp

    Depends on: 8f-2 (tools must be rewritten first)

    Scope

    MCP tools were designed for AI agents (string params, CSV lists, optional everything). SDK methods were designed for Python developers (typed params, list[str], some required). The rewrite in 8f-2 introduced translation layers in the MCP wrappers. This sub-phase verifies all translations are correct, backward-compatible, and handles edge cases.

    Audit Results (2026-03-07)

    Betty Sue audited every MCP tool on origin/main (commit 2c41b7a) against every SDK method signature. Found 8 documented mismatches (all implemented correctly) plus 5 additional items.

    8 Documented Mismatches — All Verified

    # MCP Tool Mismatch Resolution Verified
    1 search_notes MCP query → SDK q Passed as positional arg to get_sdk().search_notes(query, ...) YES
    2 create_note / update_note MCP content → SDK html_content html_content=content in both tools YES
    3 create_note / update_note MCP project → SDK project_slug project_slug=project in both tools YES
    4 create_note / update_note MCP tags CSV → SDK list[str] [t.strip() for t in tags.split(",")] YES
    5 update_note_links MCP target_slugs CSV → SDK list[str] [s.strip() for s in target_slugs.split(",")] YES
    6 create_sprint SDK requires status, MCP optional status=status or "planning" YES
    7 add_sprint_item SDK requires position; labels CSV→list position=0 hardcoded; [l.strip() for l in labels.split(",")] YES
    8 bulk_move_items MCP items JSON string → SDK list[dict] json.loads(items) with JSONDecodeError handling YES

    5 Additional Findings

    # Type Tool Finding Risk
    9 Translation update_sprint Uses _UNSET sentinel from SDK for goal, start_date, end_date. Passes None for name and status (correct — SDK uses None for "don't send" on those). LOW — correctly implemented
    10 Translation move_sprint_item → update_sprint_item MCP tool name move_sprint_item maps to SDK method update_sprint_item. Also uses _UNSET sentinel for labels. LOW — correct but undocumented name mapping
    11 Missing param add_sprint_item SDK has points param, MCP doesn't expose it. Not a bug — just not exposed to agents yet. NONE — intentional omission for now
    12 Edge case create_note Old MCP sent "tags": [] when tags not provided. New MCP sends tags=None to SDK. If API treats missing tags differently from empty list, this is a behavioral regression. MEDIUM — needs live test
    13 Edge case add_sprint_item Labels uses if labels else None (falsy check). Empty string "" treated as "no labels" — correct, but differs from if labels is not None pattern elsewhere. LOW — empty string is never a valid label input

    Backward Compatibility Rules

    • No MCP tool param names change
    • No required params become optional or vice versa (except adding defaults for newly-required SDK params)
    • CSV string convention stays — it's the agent interface contract
    • Error response format unchanged: {"error": true, "status_code": N, "detail": ...}

    Verification Plan

    • Live test #12: Call create_note without tags, verify the API receives it correctly and no tags are applied. Compare with old behavior ("tags": []).
    • CSV edge cases: Trailing commas, whitespace, empty strings for tools #4, #5, #7.
    • Sentinel handling: Verify update_sprint only sends explicitly-provided fields (not null for unset sentinel fields).
    • Test with actual MCP tool invocations via Claude — the real consumer.

    Issue Scope (for Forgejo issue)

    Items #12 is the only one that may require a code change. Items #9-11, #13 are documentation/verification only. The 8 documented mismatches are verified-correct and need no changes.

    Recommended issue deliverables:

    1. Live test for #12 (tags=None vs tags=[])
    2. Fix if behavioral regression confirmed
    3. CSV edge case tests for #4, #5, #7 (trailing comma, whitespace)
    4. Document #10 (method name mapping) in code comment

    Deliverables

    • To be filled after completion
    • phase-postgres-8f-mcp-rewrite — parent sub-phase
    • phase-postgres-8f2-mcp-rewrite-core — the rewrite these translations live in
  • Phase 8f-4: QA Nits Cleanup phase-postgres-8f4-qa-nits

    Goal: Address non-blocking QA nits from PR #23 review (Phase 8f-2).

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs-mcp

    Depends on: 8f-2 (PR #23 merged)

    Problem

    QA review of PR #23 found 4 non-blocking nits. 1 was a false positive (nit 1 — __init__.py exists). 2 are worth fixing. 1 is text-only (corrected in phase note).

    Fix

    • Nit 2: Add .claude/ to .gitignore — Safety measure to prevent worktree artifacts from being staged.
    • Nit 4: Rename llbl in sprints.py — Lines 162 and 199 use l as loop variable in CSV split comprehensions, triggering E741 ambiguous variable name lint. Rename to lbl and remove noqa comments.

    Not fixing:

    • Nit 1 (false positive — __init__.py already exists)
    • Nit 3 (tool count text — corrected in phase note, not a code issue)

    Deliverables

    • To be filled after completion
    • phase-postgres-8f-mcp-rewrite — parent sub-phase
    • phase-postgres-8f2-mcp-rewrite-core — PR #23 that surfaced these nits
  • Phase 8f: MCP Rewrite (SDK Wrappers) phase-postgres-8f-mcp-rewrite

    Goal: Rewrite all 26 MCP tools to wrap SDK methods instead of raw httpx. Add 7 new tools (6 block + delete_sprint). Achieve 32/32 SDK method coverage. Reduce tool code from ~794 lines to ~250 lines.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs-mcp

    Depends on: 8e (integration tests verify SDK works against live service)

    Vision

    The MCP server is the capability boundary of the AI Agency. Agents can only do what MCP tools allow. Today, 6 block SDK methods exist with no MCP tools — agents can't do block-level operations. This phase removes that bottleneck and delivers the primitives that skills (sprint-sync, update-docs, implement-phase) will compose into token-efficient workflows.

    After 8f: every SDK method has an MCP tool. 100% coverage. The skill design space opens up — skills can browse TOCs, read individual sections, update single blocks instead of downloading and rewriting entire notes.

    Sub-Phases

    # Sub-Phase Slug What Status
    8f-1 SDK Publish Pipeline phase-postgres-8f1-sdk-publish Activate pal-e-docs-sdk in Woodpecker, verify publish to Forgejo PyPI, configure MCP server to resolve SDK from registry COMPLETED
    8f-2 MCP Rewrite Core phase-postgres-8f2-mcp-rewrite-core Rewrite server.py + all 26 existing tools + add 7 new tools. PR #23 merged. v0.2.0 published. COMPLETED
    8f-3 Param Alignment Audit phase-postgres-8f3-param-alignment Verified 13 param translations (8 documented + 5 discovered). Fixed tags=None edge case. 25 tests. PR #25 merged. COMPLETED
    8f-4 QA Nits Cleanup phase-postgres-8f4-qa-nits .gitignore + l→lbl E741 rename. Batched into PR #25. COMPLETED

    SDK Coverage Target: 32/32

    Mixin SDK Methods MCP Tools Before MCP Tools After
    NotesMixin (6) list, get, create, update, delete, revisions 6/6 6/6
    SearchMixin (1) search_notes 1/1 1/1
    BlocksMixin (6) get_note_toc, list_blocks, get_section, update_block, create_block, delete_block 0/6 6/6
    SprintsMixin (11) list, create, get, update, delete, backlog, list_items, add_item, update_item, delete_item, bulk_move 10/11 11/11
    LinksMixin (2) get_note_links, update_note_links 2/2 2/2
    ProjectsMixin (2) list, create 2/2 2/2
    ReposMixin (3) list, create, update 3/3 3/3
    TagsMixin (1) list_tags 1/1 1/1
    Total 32 25/32 32/32

    Architectural Changes

    1. server.py: get_client() → get_sdk() — Swap httpx.Client for PalEDocsClient from SDK
    2. server.py: _ok(response) → _ok(data) — Takes parsed dict/list from SDK, not httpx.Response. Follows woodpecker-mcp pattern.
    3. server.py: _error_response() — Catches PalEDocsError and subclasses (NotFoundError, ValidationError, ServerError) instead of httpx.HTTPStatusError
    4. pyproject.toml — Replace httpx>=0.27 with pal-e-docs-sdk>=0.1.0. Configure Forgejo PyPI index.
    5. tools/__init__.py — Add blocks module registration

    Param Translation Map (13 items — audited 2026-03-07)

    See phase-postgres-8f3-param-alignment for the full audit. All 13 verified correct or fixed. Edge case #12 (tags=None) fixed in PR #25. CSV hardening (trailing comma filter) applied across notes.py, sprints.py, links.py.

    Reference Pattern

    woodpecker-mcp wrapping woodpecker-sdk is the proven pattern. Key differences: _ok(data: Any) instead of _ok(response: httpx.Response), SDK client instead of httpx.Client, typed exceptions instead of HTTP status errors.

    Skill Impact (delivered by 8f, consumed by 7f + Epilogue)

    8f delivers the block-level primitives. Downstream phases rewire skills to use them:

    Skill Current Pattern Enabled by 8f Rewired in
    /update-docs get_note ×4 (~20K tokens) get_section ×4 (~2K tokens) 7f
    /implement-phase get_note ×4 (~20K tokens) get_toc + get_section (~1.4K tokens) Epilogue item 3
    /sprint-sync No block need No change needed N/A
    Session injection 4× get_note (~8.7K tokens) get_toc + get_section (~400 tokens) Epilogue item 3
    • phase-postgres-8-mcp-optimization — parent phase
    • phase-postgres-8e-integration-tests — proves SDK works against live service
    • phase-postgres-7d-api-mcp-tools — block API endpoints the new tools wrap
    • phase-postgres-7f-doc-cleanup-sop — consumes block tools for skill rewrites
    • phase-postgres-epilogue-cleanup — item 3 consumes block tools for session upgrade
    • todo-forgejo-pypi — Forgejo PyPI pattern (established, SDK not yet published)
  • Phase 8g: Deploy Pipeline Smoke Tests phase-postgres-8g-smoke-tests

    Goal: Add a post-deploy smoke test step to the pal-e-docs Woodpecker pipeline that automatically verifies the live service is healthy after every deploy to main.

    Owner: Dev agent

    Repo: forgejo_admin/pal-e-docs-sdk (smoke module), forgejo_admin/pal-e-docs (pipeline step)

    Depends on: 8f (SDK published on Forgejo PyPI, all endpoints covered)

    Scope

    Two deliverables across two repos:

    1. Smoke test module in pal-e-docs-sdk — src/pal_e_docs_sdk/smoke_test.py. Runnable as python -m pal_e_docs_sdk.smoke_test. Hits 5 key endpoints, asserts minimum counts, exits 0/1/2. Includes retry loop for ArgoCD sync delay.
    2. Pipeline step in pal-e-docs — new smoke-test step in .woodpecker.yaml after update-deployment-tag. Installs SDK from Forgejo PyPI, runs smoke module against internal service URL.

    This completes Phase 8 (SDK + MCP Rewrite + Integration Tests). After 8g, the full stack has: typed SDK → integration tests → MCP tools → post-deploy smoke tests.

    Why

    Today there is zero automated verification that a deploy actually works. The Phase 5 deployment outage (incident-phase5-deployment-outage-2026-03-06) was discovered by manually curling endpoints. The pipeline that builds should be the pipeline that verifies — don't call it done until you've proven it works.

    Design Decisions

    Decision Rationale
    Smoke module lives in SDK repo Uses SDK methods directly. Published with the package. Runnable as python -m pal_e_docs_sdk.smoke_test.
    Pipeline step lives in pal-e-docs repo That's where .woodpecker.yaml is. The step installs SDK and runs the module.
    Internal service URL http://pal-e-docs.pal-e-docs.svc.cluster.local:8000. No Tailscale/TLS dependency. Pipeline runs in-cluster.
    Retry loop in smoke module ArgoCD sync is async (30s-90s after tag update). Module waits 30s initial, then retries up to 5 times at 15s intervals. Total window: ~105s.
    Health verification, not version check Verify the service is up and returning correct data. No /version endpoint needed. Simpler, sufficient.
    In-pipeline verification (Option A) Enterprise pattern. The pipeline that builds is the pipeline that verifies. Tight feedback loop. No detection delay.

    Smoke Test Endpoints

    Check SDK Call Assertion What It Proves
    API up + DB connected list_notes() count > 200 FastAPI serving, SQLAlchemy connected, notes table populated
    Full-text search search_notes("postgres") results > 0 tsvector index working, search endpoint functional
    Block content get_note_toc("plan-2026-02-26-tf-modularize-postgres") headings >= 5 Blocks table populated, TOC endpoint functional
    Sprint tables list_sprints() count >= 1 Sprint schema intact, endpoint functional
    Tags list_tags() count > 5 Tags table, lightweight health check

    Smoke Module Design

    # pal_e_docs_sdk/smoke_test.py
    """Post-deploy smoke test. Run as: python -m pal_e_docs_sdk.smoke_test
    
    Env vars:
      PALDOCS_BASE_URL  — service URL (default: http://localhost:8000)
    
    Exit codes:
      0 — all checks passed
      1 — one or more checks failed
      2 — could not connect after retries
    """
    import sys, time
    from pal_e_docs_sdk import PalEDocsClient
    
    MAX_RETRIES = 5
    RETRY_DELAY = 15  # seconds
    INITIAL_WAIT = 30  # seconds — ArgoCD sync time
    
    def run() -> bool:
        client = PalEDocsClient()
        notes = client.list_notes()
        assert len(notes) > 200, f"Expected >200 notes, got {len(notes)}"
        results = client.search_notes("postgres")
        assert len(results) > 0, "Search returned no results"
        toc = client.get_note_toc("plan-2026-02-26-tf-modularize-postgres")
        assert len(toc) >= 5, f"Expected >=5 TOC entries, got {len(toc)}"
        sprints = client.list_sprints()
        assert len(sprints) >= 1, "No sprints found"
        tags = client.list_tags()
        assert len(tags) > 5, f"Expected >5 tags, got {len(tags)}"
        return True
    
    def main():
        time.sleep(INITIAL_WAIT)
        for attempt in range(1, MAX_RETRIES + 1):
            try:
                run()
                print(f"Smoke test PASSED (attempt {attempt})")
                sys.exit(0)
            except ConnectionError:
                if attempt < MAX_RETRIES:
                    print(f"Attempt {attempt}/{MAX_RETRIES}: connection failed, retrying...")
                    time.sleep(RETRY_DELAY)
            except AssertionError as e:
                print(f"Smoke test FAILED: {e}")
                sys.exit(1)
        print(f"Could not connect after {MAX_RETRIES} attempts")
        sys.exit(2)
    

    Pipeline Step

    # In pal-e-docs/.woodpecker.yaml, after update-deployment-tag:
      - name: smoke-test
        image: python:3.12-slim
        environment:
          PALDOCS_BASE_URL: "http://pal-e-docs.pal-e-docs.svc.cluster.local:8000"
          FORGEJO_PYPI_URL: "http://forgejo-http.forgejo.svc.cluster.local/api/packages/forgejo_admin/pypi/simple/"
        commands:
          - pip install --index-url $$FORGEJO_PYPI_URL pal-e-docs-sdk
          - python -m pal_e_docs_sdk.smoke_test
        when:
          - event: push
            branch: main
    

    Execution Order

    Two repos means two PRs in sequence:

    1. PR on pal-e-docs-sdk: Add smoke_test.py + __main__.py wiring. Bump version to 0.2.0. Merge → publishes to PyPI.
    2. PR on pal-e-docs: Add smoke-test step to .woodpecker.yaml. Merge → next deploy triggers smoke test.

    Risks

    • ArgoCD sync delay: If sync takes longer than 105s window, smoke test exits 2. Can tune INITIAL_WAIT and MAX_RETRIES.
    • Forgejo PyPI in-cluster access: Pipeline already uses forgejo-http.forgejo.svc.cluster.local for git clone. PyPI should work the same way.
    • SDK version mismatch: Smoke test installs latest SDK. If SDK has breaking change, smoke fails even though API is fine. Low risk — we control both.

    Deliverables

    • To be filled after completion
    • phase-postgres-8-mcp-optimization — parent phase
    • phase-postgres-8f-mcp-rewrite — SDK must be published (done)
    • phase-postgres-8e-integration-tests — smoke test is a subset of integration tests, tuned for post-deploy
    • incident-phase5-deployment-outage-2026-03-06 — the incident that motivates this
  • Phase 7d: Block API + MCP Tools phase-postgres-7d-api-mcp-tools

    Goal: Expose block-level read/write operations via new API endpoints and MCP tools. Enable agents to read one section, update one section, and navigate via TOC — without touching the full document.

    Owner: Dev agent

    Repos: pal-e-docs (API endpoints), pal-e-docs-mcp (MCP tools)

    Depends on: Phase 7c (blocks populated in DB)

    Parent phase: Phase 7 (Block-Structured Content Model)

    Why This Phase

    Blocks exist in the database but agents can't use them yet. This phase adds the API and MCP surface that makes block-level access real. This is where the token savings become measurable.

    New API Endpoints

    EndpointMethodWhat It Does
    /notes/{slug}/tocGETReturns heading blocks only — note's table of contents with anchor IDs
    /notes/{slug}/blocksGETReturns all blocks for a note (ordered by position)
    /notes/{slug}/blocks/{anchor_id}GETReturns a single block (or section: heading + content blocks until next heading)
    /notes/{slug}/blocks/{anchor_id}PUTUpdates a single block's content. Triggers recompile of compiled_page.
    /notes/{slug}/blocksPOSTInsert a new block at a given position. Triggers recompile.
    /notes/{slug}/blocks/{anchor_id}DELETERemove a block. Triggers recompile.

    New MCP Tools (pal-e-docs-mcp)

    ToolWrapsToken Impact
    get_note_toc(slug)GET /notes/{slug}/toc~50 tokens vs ~2,360 for get_note (plan avg)
    get_block(slug, anchor_id)GET /notes/{slug}/blocks/{anchor_id}~200 tokens vs ~2,360 for get_note
    update_block(slug, anchor_id, content)PUT /notes/{slug}/blocks/{anchor_id}~100 tokens vs ~2,360 for update_note
    create_block(slug, block_type, content, after_anchor)POST /notes/{slug}/blocksTargeted insertion without full rewrite
    delete_block(slug, anchor_id)DELETE /notes/{slug}/blocks/{anchor_id}Targeted deletion without full rewrite

    Backward Compatibility

    • get_note continues to return html_content as before
    • update_note with html_content continues to work — internally re-parses into blocks and recompiles
    • New tools are additive — agents can gradually adopt block-level access
    • On block write (update/create/delete): recompile compiled_pages.html and update html_content on the note (keeps both in sync)

    Acceptance Criteria

    • All 6 API endpoints working with tests
    • All 5 MCP tools deployed and functional
    • get_note_toc returns heading structure for a plan in ~50 tokens
    • get_block returns one section of a plan in ~200 tokens
    • update_block updates one section without touching others
    • Block writes trigger recompile of compiled_pages and html_content
    • Existing get_note/update_note behavior unchanged

    Benchmark Target

    Run Phase 7 benchmark re-test after this phase ships. Compare against benchmark-phase7-block-baseline:

    • Targeted read: 2,360 tokens → ~200 tokens (target: 92% reduction)
    • Targeted update: 2,360 tokens → ~100 tokens (target: 96% reduction)
    • TOC navigation: 2,360 tokens → ~50 tokens (target: 98% reduction)

    Related

    • phase-postgres-7c-backfill-migration — blocks must be populated
    • phase-postgres-8-mcp-optimization — Phase 8 builds on these tools
    • benchmark-phase7-block-baseline — baseline for re-test
  • Phase 7: Block-Structured Content Model phase-postgres-7-block-content

    Goal: Replace HTML blob storage with typed content blocks. Enable compiled pages, per-section search, stable anchors, deterministic rendering, and flexible note hierarchy.

    Owner: Dev agent

    Repo: pal-e-docs, pal-e-docs-mcp

    Depends on: Phase 5 (full-text search) — COMPLETED

    Status: IN PROGRESS — 7a, 7b, 7c, 7d COMPLETED. 7f-1, 7f-2, 7f-3 COMPLETED. 7e NEXT.

    Why

    HTML blobs are opaque. You can't search within them meaningfully, can't deep-link to sections, can't reuse content across pages, can't generate a TOC, and can't embed per-section for AI retrieval. Blocks fix all of this. See benchmark-phase7-block-baseline for the quantitative case.

    Key Data (from baseline)

    • 256 notes, 1.09M chars (~272K tokens total corpus)
    • 84% have 4+ sections — would benefit from block-level access
    • Plans avg 9,441 chars — the largest type, most read, biggest savings target
    • 6 orphaned docs can't nest under phases due to type restriction
    • Estimated savings: 92% per targeted read, 96% per update, 95% session startup

    Sub-Phases

    #Sub-PhaseSlugDepends OnStatusDeliverable
    7aSchema + Hierarchy Relaxationphase-postgres-7a-schema-hierarchyPhase 5COMPLETEDblocks + compiled_pages tables, any-to-any hierarchy
    7bParser + Compilerphase-postgres-7b-parser-compiler7aCOMPLETEDHTML→blocks parser (6 types), blocks→HTML compiler, 121 tests. PR #97, #99.
    7cBackfill Migrationphase-postgres-7c-backfill-migration7a, 7bCOMPLETED274 notes → 5,197 blocks + 274 compiled pages. QA verified. See qa-phase7c-backfill-2026-03-07.
    7dBlock API + MCP Toolsphase-postgres-7d-api-mcp-tools7cCOMPLETED6 API endpoints (toc, list, get_section, update, create, delete), 6 MCP tools, recompilation on write
    7eCompiled Page Architecturephase-postgres-7e-compiled-pages7dNOT STARTEDSource-of-truth cutover (7e-1) + session injection optimization (7e-3). 7e-2 (compiled page API) DEFERRED.
    7fDoc Cleanup + SOP Hardeningphase-postgres-7f-doc-cleanup-sop7eIN PROGRESS (7f-1, 7f-2, 7f-3 COMPLETED)Doc debt cleanup, post-merge automation, SOP review

    Note: The original 7e (Per-Block Search + Hashing, phase-postgres-7e-block-search-optimization) has been renumbered. The sub-phases above reflect the current sequence.

    Dependency Chain

    graph LR
        P5[Phase 5 DONE] --> P7a[7a Schema
    + Hierarchy
    DONE] P7a --> P7b[7b Parser
    + Compiler
    DONE] P7a --> P7c[7c Backfill
    DONE] P7b --> P7c P7c --> P7d[7d Block API
    + MCP Tools
    DONE] P7d --> P7e[7e Compiled
    Pages] P7e --> P7f[7f Doc Cleanup
    + SOP] P7e --> P6[Phase 6
    pgvector]

    User Stories

    #StorySub-Phase
    A3As an agent, I can read one section of a note without fetching the entire document7d ✓
    A4As an agent, I can update one section without rewriting the entire document7d ✓
    A5As an agent, I can see a note's TOC and jump to the right section7d ✓
    K1Concept docs, benchmarks, incidents nest under the phase they belong to7a + 7c ✓
    H1As Lucas, I can see a table of contents on long documents7c + browse frontend

    Backward Compatibility

    html_content stays as a computed/cached field populated by the compiler. Existing MCP tools that read/write html_content continue to work unchanged. New tools work with blocks directly. Zero-breaking-change migration.

    Related

    • qa-phase7c-backfill-2026-03-07 — QA report for the backfill run
    • benchmark-phase7-block-baseline — quantitative baseline before blocks
    • decision-phase6-vector-search-architecture — per-block embedding depends on blocks
    • concept-phase5-database-side-intelligence — the database-side intelligence pattern this extends
  • Goal: Build a typed Python SDK for pal-e-docs, rewrite the MCP server to wrap it, and establish integration tests that verify the full stack against the live service. Eliminate raw httpx calls, add post-deploy smoke tests, and cut tokens per knowledge interaction by 90%+.

    Owner: Dev agent

    Repos: pal-e-docs-sdk (new), pal-e-docs-mcp (rewrite), pal-e-docs (API — no changes needed)

    Depends on: Phase 7d (block API endpoints deployed)

    Why

    The current MCP server (pal-e-docs-mcp) is a raw httpx passthrough — 26 tools making untyped HTTP calls with ad-hoc error handling. This has three problems:

    • No integration tests. We discovered a failed deploy today by manually curling endpoints. There's no automated verification that the live service works.
    • No reusability. The HTTP client logic is locked inside MCP tool functions. Scripts, CLI tools, and other services can't use it.
    • No type safety. Responses are json.dumps(response.json()) — raw dicts, no validation, no IDE support.

    The SDK pattern (proven with woodpecker-sdk, forgejo-sdk) fixes all three: typed client → integration tests → thin MCP wrapper.

    Architecture

    FastAPI app (pal-e-docs)
        ↑
    pal-e-docs-sdk (typed Python client)    ← integration tests run here
        ↑
    pal-e-docs-mcp (thin @mcp.tool wrappers)
        ↑
    AI agents (Betty Sue, Dottie, etc.)
    

    Sub-Phases

    #Sub-PhaseDeliverableDepends On
    8aSDK CoreNew repo pal-e-docs-sdk. Client class, auth, error handling, base HTTP layer, typed exceptions. Published to Forgejo PyPI.Nothing
    8bSDK: Notes + Search + Tags + Projects + Links + ReposTyped methods for all existing API endpoints. Pydantic response models. Covers the 21 current MCP tools.8a
    8cSDK: Blocks + TOC + SectionsTyped methods for the 6 new block API endpoints from Phase 7d. get_note_toc(), get_block(), update_block(), create_block(), delete_block().8a, 7d deployed
    8dSDK: SprintsTyped methods for sprint endpoints (create, get, list, update, add/move/remove items, board, backlog, bulk_move).8a
    8eIntegration Test SuiteSDK tests against the live service. Covers all endpoint families. Runnable locally and in CI. Baseline assertions (e.g. "blocks table has >5000 rows").8b, 8c, 8d
    8fMCP RewriteRewrite all pal-e-docs-mcp tools to wrap SDK methods instead of raw httpx. Add new block MCP tools (get_note_toc, get_block, update_block, create_block, delete_block). Tools become 3-5 lines each.8b, 8c, 8d
    8gDeploy Pipeline Smoke TestsNew smoke-test step in pal-e-docs .woodpecker.yaml — runs after deploy, hits key endpoints via SDK, fails pipeline if service is broken.8e

    Dependency Chain

    graph LR
        P7d[Phase 7d DONE
    Block API] --> P8a[8a SDK Core] P8a --> P8b[8b SDK: Notes
    Search, Tags, etc.] P8a --> P8c[8c SDK: Blocks
    TOC, Sections] P8a --> P8d[8d SDK: Sprints] P8b --> P8e[8e Integration
    Tests] P8c --> P8e P8d --> P8e P8b --> P8f[8f MCP
    Rewrite] P8c --> P8f P8d --> P8f P8e --> P8g[8g Deploy
    Smoke Tests]

    SDK Design

    Client Class

    from pal_e_docs_sdk import PalEDocsClient
    
    client = PalEDocsClient(
        base_url="https://pal-e-docs.tail5b443a.ts.net",
        timeout=30.0,
    )
    
    # Typed responses — Pydantic models, not raw dicts
    toc = client.get_note_toc("plan-2026-02-26-tf-modularize-postgres")
    # Returns: list[TocEntry]
    
    section = client.get_section("plan-2026-02-26-tf-modularize-postgres", "vision")
    # Returns: Section(heading=BlockOut(...), content_blocks=[...])
    
    results = client.search_notes("CNPG credentials secrets")
    # Returns: list[SearchResult] with slug, headline, rank
    

    Error Handling

    from pal_e_docs_sdk.exceptions import NotFoundError, ValidationError
    
    try:
        note = client.get_note("nonexistent-slug")
    except NotFoundError:
        print("Note not found")
    except ValidationError as e:
        print(f"Invalid request: {e.detail}")
    

    MCP Tool After Rewrite (8f)

    # Before (raw httpx):
    @mcp.tool()
    def get_note(slug: str) -> str:
        try:
            resp = get_client().get(f"/notes/{slug}")
            return _ok(resp)
        except Exception as exc:
            return _error_response(exc)
    
    # After (SDK wrapper):
    @mcp.tool()
    def get_note(slug: str) -> str:
        return _ok(get_sdk().get_note(slug))
    

    Integration Test Examples (8e)

    # tests/integration/test_notes.py
    def test_get_note_returns_content(live_client):
        note = live_client.get_note("plan-2026-02-26-tf-modularize-postgres")
        assert note.slug == "plan-2026-02-26-tf-modularize-postgres"
        assert len(note.html_content) > 0
        assert note.note_type == "plan"
    
    def test_toc_returns_headings(live_client):
        toc = live_client.get_note_toc("plan-2026-02-26-tf-modularize-postgres")
        assert len(toc) >= 8
        assert toc[0].anchor_id == "vision"
        assert toc[0].level == 3
    
    def test_blocks_populated(live_client):
        blocks = live_client.list_blocks("plan-2026-02-26-tf-modularize-postgres")
        assert len(blocks) > 20
        assert blocks[0].block_type == "heading"
    
    def test_search_returns_ranked_results(live_client):
        results = live_client.search_notes("CNPG credentials")
        assert len(results) > 0
        assert "sop-secrets-management" in [r.slug for r in results]
    

    Smoke Test Pipeline Step (8g)

    # In .woodpecker.yaml, after update-deployment-tag:
    - name: smoke-test
      image: python:3.12-slim
      environment:
        PALDOCS_BASE_URL: https://pal-e-docs.tail5b443a.ts.net
      commands:
        - pip install pal-e-docs-sdk
        - python -m pal_e_docs_sdk.smoke_test
      when:
        - event: push
          branch: main
    

    Before vs After (unchanged — still valid)

    # BEFORE: "What's our secrets strategy for CNPG?" (12 calls, ~15,000 tokens)
    list_notes(tags="sop,active")           # 11 results, no content
    get_note("agent-workflow")              # irrelevant, ~3,000 tokens wasted
    get_note("pr-lifecycle")                # irrelevant, ~2,000 tokens wasted
    get_note("sop-secrets-management")      # found it, ~5,000 tokens
    ... repeat for other SOPs until found
    
    # AFTER: same question (2 calls, ~700 tokens)
    search_notes("CNPG credentials secrets strategy")
    → [{slug: "sop-secrets-management", anchor: "sops-path", snippet: "Encrypt with Age..."}]
    
    get_block("sop-secrets-management", "sops-path")
    → Just the SOPS procedure section (~200 tokens)
    

    Cross-Cutting Optimizations (also delivered in 8f)

    • Response size limits: Add max_tokens param to search tools. Truncate long responses with "... (truncated, use get_block for full section)"
    • Batch queries: get_notes(slugs="sop-a,sop-b,sop-c") — fetch multiple note summaries in one call instead of N calls
    • Context-aware responses: Search results include anchor IDs so the agent can jump directly to the relevant section
    • Deprecation notices: Discourage raw html_content in update_note; prefer update_block

    Success Metrics

    • Average token cost per knowledge lookup: ~15,000 → <1,000
    • Average token cost per note update: ~5,000 → <500
    • Post-deploy verification: 0 checks → automated smoke test on every deploy
    • MCP tool line count: ~50 lines/tool → ~5 lines/tool
    • Integration test coverage: 0% → all endpoint families covered

    New Repo Setup (8a)

    ItemValue
    Repoforgejo_admin/pal-e-docs-sdk
    Packagepal-e-docs-sdk on Forgejo PyPI
    Python≥3.12
    Dependencieshttpx, pydantic
    Dev depspytest, ruff
    CIWoodpecker — test + publish to PyPI on main push

    Related

    • phase-postgres-7d-api-mcp-tools — block API endpoints this SDK wraps
    • qa-phase7c-backfill-2026-03-07 — baseline data the integration tests verify
    • plan-2026-02-28-woodpecker-sdk-mcp — the pattern this follows (SDK → MCP)
    • todo-forgejo-pypi — PyPI registry for publishing
  • Phase 7e: Per-Block Search + Content Hashing phase-postgres-7e-block-search-optimization

    Goal: Add per-block tsvector search indexing and content hashing for compiled pages. This is the bridge between Phase 7 (blocks) and Phase 6 (embeddings) — once blocks have their own search vectors, they can also have their own embedding vectors.

    Owner: Dev agent

    Repo: pal-e-docs

    Depends on: Phase 7d (block API working)

    Parent phase: Phase 7 (Block-Structured Content Model)

    Why This Phase

    Phase 5 added tsvector search at the note level. With blocks, we can now search at the section level — "find the section about credentials" returns a specific block, not a whole note. This is also the prerequisite for Phase 6: the embedding vector(768) column will go on the blocks table, right next to the tsvector column.

    Deliverables

    1. Per-Block Search Vector

    • Add search_vector tsvector column to blocks table
    • Add GIN index on blocks.search_vector
    • Trigger: auto-update search_vector when block content changes
    • Weight: block content (B), parent note title (A) — so searching "postgres plan" still ranks blocks from postgres-related notes higher

    2. Section-Level Search API

    • Update GET /notes/search to optionally return block-level results
    • Or add GET /blocks/search?q=... endpoint
    • Response: slug, note title, anchor_id, block_type, snippet, rank
    • Agent can jump directly from search result to get_block(slug, anchor_id)

    3. Content Hashing

    • compiled_pages.content_hash = SHA-256 of all block content
    • On note update: compare hash before recompiling. Skip recompile if unchanged.
    • On block write: always recompile (single block changed = new hash)
    • Saves CPU on bulk operations and prevents unnecessary revision creation

    4. Update search_notes MCP Tool

    • Add optional granularity param: "note" (default, backward compat) or "block"
    • Block-level results include anchor_id so agent can fetch just that section

    Acceptance Criteria

    • Block-level search returns section-level results with anchor IDs
    • Search for "credentials" returns the specific section of sop-secrets-management that discusses credentials, not the whole note
    • Content hashing prevents unnecessary recompiles
    • search_notes MCP tool supports both note and block granularity
    • Backfill: all existing blocks have search_vectors populated

    Bridge to Phase 6

    After this phase, the blocks table has: content jsonb, search_vector tsvector, and will gain embedding vector(768) in Phase 6. The same table, progressively smarter — the database-side intelligence pattern at work.

    Related

    • phase-postgres-7d-api-mcp-tools — blocks API must be working
    • phase-postgres-6-vector-search — this phase unblocks per-block embeddings
    • phase-postgres-5-fulltext-search — note-level search pattern we're extending to blocks
    • concept-phase5-database-side-intelligence — the architectural pattern
  • Phase 7c: Backfill Migration (HTML → Blocks) phase-postgres-7c-backfill-migration

    Goal: Convert all 256 existing notes from monolithic html_content to typed blocks. Populate compiled_pages with compiled HTML + TOC. One-time backfill.

    Owner: Dev agent

    Repo: pal-e-docs

    Depends on: Phase 7a (tables exist), Phase 7b (parser + compiler)

    Parent phase: Phase 7 (Block-Structured Content Model)

    Forgejo Issue: #100

    PR: #101 (MERGED)

    Status: IN PROGRESS — Deliverable 1 (migration script) complete. Deliverables 2-3 (run against production + validation) pending.

    Why This Phase

    The parser and compiler are proven by unit tests in 7b. Now we run them against the full corpus and populate the blocks table. This is a one-time data migration — the most critical step in Phase 7.

    Deliverables

    1. Migration script: For each note, run the parser on html_content, store resulting blocks in the blocks table with correct positions and anchor IDs. COMPLETE — PR #101 merged.
    2. Compiled pages: For each note, compile blocks back to HTML, generate TOC JSON, compute content hash, store in compiled_pages. PENDING — requires running script against production.
    3. Validation report: Compare compiled_pages.html vs original html_content for all 256 notes. Flag any semantic differences. PENDING — requires running script against production.
    4. Nest orphaned docs: After hierarchy relaxation (7a), set parent_note_id for the 6 identified orphans.

    Migration Strategy

    • Run as a one-time Alembic data migration or standalone script
    • html_content stays populated (backward compat — existing MCP tools still read it)
    • Blocks are additive — they coexist with html_content, not replace it
    • Use kubectl exec + kubectl cp pattern (port-forward is unreliable on k3s)

    Acceptance Criteria

    • All 256 notes have blocks in the blocks table
    • All 256 notes have a compiled_pages entry
    • Round-trip validation: compiled HTML is semantically equivalent to original for 95%+ of notes
    • Remaining notes (if any) have documented differences with manual review
    • 6 orphaned docs nested under their logical parents
    • Existing API / MCP behavior unchanged

    Expected Block Counts

    Based on baseline data (avg 7.7 headings/note, plus paragraphs/tables/lists between them):

    • Estimated ~15-25 blocks per note average
    • Total estimated blocks: ~4,000-6,500
    • This is well within Postgres performance for indexed queries

    Related

    • phase-postgres-7a-schema-hierarchy — tables must exist
    • phase-postgres-7b-parser-compiler — parser + compiler must be proven
    • benchmark-phase7-block-baseline — corpus metrics
  • Goal: Build the two core library functions: parse HTML into typed blocks, and compile blocks back into deterministic HTML with stable anchor IDs.

    Owner: Dev agent

    Repo: pal-e-docs

    Depends on: Phase 7a (schema must exist for block types)

    Parent phase: Phase 7 (Block-Structured Content Model)

    Completion Summary

    Forgejo Issue: #96

    PR: #97 (merged)

    Bug fix PR: #99 (merged) — fixed mermaid <br/> stripping and heading inline <code> loss. Forgejo issue #98.

    Deliverables: Parser (6 block types: heading, paragraph, table, code, list, mermaid), compiler (deterministic HTML + stable anchor IDs), 121 tests (105 original + 16 regression).

    QA: Both PRs approved. PR #97 had 5 non-blocking nits; PR #99 fixed 2 of them.

    Level 2 validation: Real-data round-trip test against 268 live notes. 99.3% semantic match (2 bugs fixed by PR #99). Remaining diffs are by design: anchor ID additions (112 notes) and table whitespace normalization (~129 notes).

    Why This Phase

    The parser and compiler are the heart of the block system. They must be correct, deterministic, and handle all existing content. This phase is pure library code — no DB writes, no API changes, no behavior change. Fully testable in isolation.

    Deliverables

    1. HTML-to-Blocks Parser

    Takes html_content (string) and returns a list of typed block dicts.

    Must handle all content found in the 256 existing notes (see benchmark-phase7-block-baseline):

    Block TypeHTML PatternContent JSONBNotes in Corpus
    heading<h2>, <h3>, <h4>{"level": 2, "text": "..."}229 notes (89%)
    paragraph<p>...</p>{"html": "..."}~256 notes
    table<table>...</table>{"headers": [...], "rows": [...]}118 notes (46%)
    code<pre><code>...</code></pre>{"language": "...", "content": "..."}72 notes (28%)
    list<ul>, <ol>{"ordered": false, "items": [...]}239 notes (93%)
    mermaid<pre class="mermaid">{"definition": "..."}37 notes (14%)
    calloutTBD — currently no callout pattern in corpus{"type": "info", "content": "..."}0 (future use)

    2. Blocks-to-HTML Compiler

    Takes a list of blocks and produces deterministic HTML with stable anchor IDs.

    • Deterministic: Same blocks → same HTML every time. No random IDs.
    • Stable anchor IDs: Each heading block generates an anchor from its text (slugified). Example: <h3 id="acceptance-criteria">Acceptance Criteria</h3>
    • Round-trip safe: compile(parse(html)) ≈ html (semantically equivalent, may normalize whitespace/formatting)

    Edge Cases to Handle

    • Paragraphs between headings (group with preceding heading or as standalone block?)
    • Nested lists (items containing sub-lists)
    • Tables inside other elements
    • Inline code vs code blocks
    • Empty content / notes with no headings (27 notes)
    • HTML entities and special characters
    • The nh3 sanitizer may have altered some HTML patterns

    Acceptance Criteria

    • Parser handles all 7 block types
    • Round-trip test: parse then compile 20 representative notes, verify semantic equivalence
    • Mermaid blocks preserved exactly (whitespace-sensitive)
    • Table structure preserved (headers vs body rows)
    • Anchor IDs are deterministic and unique within a note
    • Unit tests for each block type + edge cases

    Design Decision: Grouping Strategy

    Open question: how do we group content under headings?

    • Option A: Flat blocks. Every HTML element is its own block. A heading is one block, the paragraph after it is another. Simple, but no "section" concept.
    • Option B: Section blocks. A heading + everything until the next heading at the same or higher level = one "section" block. More useful for get_block("acceptance-criteria") returning the heading + its content.

    Recommendation: Option A (flat) with section grouping at query time. Store flat blocks but provide a "get section" query that returns a heading block + all blocks until the next heading. This keeps storage simple and grouping flexible.

    Related

    • phase-postgres-7a-schema-hierarchy — prerequisite (block types defined in schema)
    • benchmark-phase7-block-baseline — content type distribution in the corpus
    • html-style-guide — HTML patterns used in existing notes
  • Phase 7a: Schema + Hierarchy Relaxation phase-postgres-7a-schema-hierarchy

    Goal: Create the blocks and compiled_pages tables. Relax the parent-child hierarchy so any note type can nest under any other.

    Owner: Dev agent

    Repo: pal-e-docs

    Depends on: Phase 5 (COMPLETED)

    Parent phase: Phase 7 (Block-Structured Content Model)

    Why First

    The tables must exist before any parser, compiler, or migration code can run. The hierarchy relaxation is a simple constraint removal that immediately unblocks nesting 6 orphaned docs — value delivered on day one with zero risk.

    Deliverables

    1. Alembic migration: blocks table
      blocks
        id          serial PK
        note_id     FK → notes
        position    integer
        block_type  varchar (heading/paragraph/code/table/mermaid/list/callout)
        content     jsonb
        anchor_id   varchar (deterministic, generated from content)
        created_at  timestamp
        updated_at  timestamp
      
    2. Alembic migration: compiled_pages table
      compiled_pages
        id            serial PK
        note_id       FK → notes (unique)
        html          text
        toc_json      jsonb
        content_hash  varchar(64)
        compiled_at   timestamp
      
    3. Relax parent_note_id constraint: Remove the validation that only phase notes can have a parent and that parents must be plan type. Any note type can have a parent of any type.
    4. SQLAlchemy models: Block and CompiledPage models with relationships to Note.

    Acceptance Criteria

    • Tables exist in Postgres with correct columns, indexes, and FKs
    • Hierarchy relaxation works: update_note(slug="concept-phase5-database-side-intelligence", parent_slug="phase-postgres-5-fulltext-search") succeeds
    • All 6 orphaned docs can be nested under their logical parents
    • Existing behavior unchanged — blocks/compiled_pages tables are empty, no reads/writes touch them yet
    • All existing tests pass

    Immediate Value

    After this phase ships, Betty Sue can nest the 6 orphaned docs (see benchmark-phase7-block-baseline). No parser needed — just a constraint removal.

    Estimated Scope

    1 Alembic migration, 2 SQLAlchemy models, 1 validation change. Small, clean PR.

    Related

    • phase-postgres-7-block-content — parent phase
    • benchmark-phase7-block-baseline — 6 orphaned docs identified
    • doc-pal-e-docs-schema — current schema reference
  • Phase 7c-1: Fix backfill QA nits phase-7c-1-investigate-backfill-nits

    Goal: Address QA nits from PR #101 before running the backfill against production.

    Owner: Dev agent

    Repo: pal-e-docs

    Parent: Phase 7c (Backfill Migration)

    Nit Fixes

    1. Empty content hash sentinel

    When compiled_html is empty, _content_hash returns "" instead of None (column is nullable). Use None for empty content — it's the honest answer: "no content, no hash."

    2. sys.exit(1) in run_backfill

    Replace sys.exit(1) with raise RuntimeError("DATABASE_URL not set"). Let __main__ handle the exit. Makes the function importable and testable.

    Not Fixing

    Nit 3 (client fixture pattern) — inherited codebase convention. Not attributable to this PR. Would be a broader test infrastructure refactor.

    Related

    • phase-postgres-7c-backfill-migration — parent phase
  • Phase 5: Full-Text Search (tsvector) phase-postgres-5-fulltext-search

    Goal: Add full-text search to pal-e-docs using Postgres tsvector. Expose via API and MCP tool. Dramatically reduce token usage for AI queries.

    Owner: Dev agent

    Repos: pal-e-docs (API + migration), pal-e-docs-mcp (search MCP tool)

    Depends on: Phase 3 (Postgres migration) — COMPLETED

    Progress

    DeliverableStatusDetails
    PR #84: tsvector + search endpointMERGEDtsvector column, GIN index, trigger, GET /notes/search
    PR #93: image fix + RollingUpdate + CI commit-backMERGEDCorrect SHA, zero-downtime deploys, auto image tag updates
    PR #19 (pal-e-docs-mcp): search_notes toolMERGEDMCP tool wrapping search endpoint
    Search API liveDONE10 ranked results for ?q=postgres
    CI commit-backDONEWoodpecker auto-updates deployment.yaml after build
    Benchmark re-testDONE55% fewer API calls, 71% fewer tokens vs baseline

    Acceptance Criteria

    • Search returns ranked results with snippets — VERIFIED
    • search_notes() MCP tool works — VERIFIED
    • tsvector auto-updates on create/update — VERIFIED
    • Deployment uses RollingUpdate — VERIFIED

    Key Decisions

    • Postgres trigger for tsvector (always in sync, no app code)
    • Weighted search: title (A) > content (B) > slug (C)
    • Separate /notes/search endpoint (not a filter on list_notes)
    • RollingUpdate over Recreate (SQLite constraint gone with Postgres)
    • CI commit-back for image tags (no manual SHA management)

    Token Impact

    Before: 12+ MCP calls, ~11K tokens per 5 queries. After: 55% fewer calls, 71% fewer tokens. Validated by benchmark.

    Related Notes

    • incident-phase5-deployment-outage-2026-03-06 — outage root cause + timeline
    • concept-argocd-ghost-override — what ghost overrides are and prevention
    • concept-phase5-database-side-intelligence — why intelligence lives in Postgres
    • concept-phase5-self-hosted-rag — Act 2 RAG architecture vision
    • benchmark-phase5-knowledge-baseline — baseline measurements before search
  • Phase 3: pal-e-docs Owns Its Postgres phase-postgres-3-migrate-pal-e-docs

    Goal: pal-e-docs switches from SQLite to the already-running CNPG Postgres cluster. App code migrates to Postgres dialect, k8s manifests updated, Litestream removed, data migrated.

    Owner: Dev agent

    Repos: pal-e-docs (primary), pal-e-platform (Terraform — DB secret in app namespace)

    Issues:

    • forgejo_admin/pal-e-platform #22 — Terraform DB secret (Part A)
    • forgejo_admin/pal-e-docs #76 — Code + manifests + data migration (Parts B/C/D)

    Architecture (verified 2026-03-06)

    Key facts discovered during pre-issue research:

    ComponentCurrent State
    CNPG Clusterpal-e-postgres running in postgres namespace. Healthy. Postgres 17.4, 1 instance, 5Gi storage.
    Databasepaledocs database, owned by paledocs user. Bootstrapped by CNPG initdb.
    Credentialspaledocs-db-credentials secret in postgres namespace (manual kubectl). Contains username + password.
    Connection endpointpal-e-postgres-rw.postgres.svc.cluster.local:5432
    ArgoCD ApplicationSources directly from pal-e-docs/k8s/ (NOT a deployments overlay). Auto-sync + prune + self-heal.
    Current app secretspal-e-docs-secrets (manual kubectl), litestream-creds (manual kubectl), harbor-creds (Terraform)
    WAL archivingConfigured but failing (ContinuousArchivingFailing). Phase 4 concern, not blocking.
    SOPS+AgeAge keypair exists in Salt pillar but is NOT deployed to ArgoCD. No SOPS decryption wired up. Future work.

    Secrets Strategy

    Pattern: Terraform-managed k8s secret — matches how harbor-creds, cnpg-s3-creds, and other platform secrets are managed today.

    Add a kubernetes_secret_v1 resource to pal-e-platform/terraform/main.tf that creates a paledocs-db-url secret in the pal-e-docs namespace containing the full Postgres DSN. The password is sourced from a new tfvar (stored in k3s.tfvars, gitignored, sourced from Salt pillar).

    Future: When SOPS+Age is wired into ArgoCD, migrate this secret to SOPS-encrypted YAML in the repo. Document as tech debt.

    Connection String

    postgresql://paledocs:<password>@pal-e-postgres-rw.postgres.svc.cluster.local:5432/paledocs

    Steps

    Part A: Terraform — DB secret in app namespace (pal-e-platform #22)

    1. Add variable "paledocs_db_password" to terraform/variables.tf (sensitive, string)
    2. Add kubernetes_secret_v1.paledocs_db_url to terraform/main.tf — creates secret paledocs-db-url in pal-e-docs namespace with key DATABASE_URL containing the full DSN
    3. Add value to terraform/k3s.tfvars
    4. Run tofu plan + tofu apply

    Part B: App code — SQLite → Postgres (pal-e-docs #76, PR 1)

    1. Update src/pal_e_docs/config.py: add database_url: str | None = None setting. When set, takes precedence over database_path.
    2. Update src/pal_e_docs/database.py: use database_url if set, otherwise fall back to SQLite path. Remove SQLite-specific pragma listener when using Postgres.
    3. Update alembic/env.py: use database_url setting when available, fall back to SQLite for local dev.
    4. Add psycopg2-binary to dependencies.
    5. Fix SQLite-isms in migration files: (CURRENT_TIMESTAMP)sa.func.now(), boolean defaults, CHECK constraints.
    6. Test locally against Postgres.

    Part C: k8s manifests — deployment update (pal-e-docs #76, PR 2)

    1. Update k8s/deployment.yaml: replace env var, remove Litestream containers, remove volumes
    2. Update k8s/kustomization.yaml: remove pvc.yaml and litestream-configmap.yaml
    3. Delete k8s/pvc.yaml and k8s/litestream-configmap.yaml

    Part D: Data migration (one-time, maintenance window)

    1. Data migration script in scripts/migrate_sqlite_to_postgres.py
    2. Lucas runs Alembic + migration during maintenance window
    3. Deploy PR 2 after verification

    Deployment Sequence (CRITICAL)

    1. Merge PR 1 (code) → ArgoCD deploys. App still uses SQLite.
    2. Lucas runs tofu apply on pal-e-platform (creates DB secret)
    3. Lucas runs alembic upgrade head against Postgres
    4. Lucas runs data migration script
    5. Merge PR 2 (manifests) → ArgoCD deploys. App switches to Postgres.

    Risks

    • Data migration window: Short downtime while SQLite data is loaded into Postgres. Sessions fail-open.
    • Alembic dialect: Existing migrations have SQLite-isms (3 patterns identified). Must be fixed.
    • ArgoCD prune: Auto-prune will delete PVC when removed from kustomization. Data migration must be verified BEFORE merging PR 2.
    • Cross-namespace: No NetworkPolicy blocking. Future-proof with egress rule if policies are added.

    Depends on

    Phase 2b (platform cleanup) — COMPLETED.

    See also

    • sop-secrets-management — documents the Terraform secret pattern
    • sop-litestream-restore — will become obsolete after this phase
  • Phase 5: Claude-Config Skills Update phase-knowledge-5-skills

    Goal: Skills and hooks use note_type/status parameters instead of tag-based queries.

    Owner: Agent (worktree, claude-custom repo)

    Priority: Low — only needed after tag cleanup is complete. Tag-based queries work fine today.

  • Phase 3: Data Migration + Structural Cleanup phase-knowledge-3-data-migration

    Goal: Every note has a note_type, status, and project. All projects have page_note_id wired. Redundant tags retired.

    Owner: Main session (API calls)

    Priority: Low — cosmetic cleanup. Tags work today.

    Depends on: plan-2026-03-01-note-decomposition Phase 2 (schema must exist before data migration)

  • Phase 4: Privacy Audit phase-knowledge-4-privacy

    Goal: Infrastructure-sensitive notes are private. Repos visibility leak on landing page is fixed.

    Status: COMPLETE (2026-03-02)

    Delivered:

    1. Reviewed all is_public: true notes. (2026-03-01)
    2. 13 infra-sensitive notes in pal-e-platform made private. (2026-03-01)
    3. Public: plans, project pages, SOPs, architecture docs kept public.
    4. Repos query filter by project is_public — PR #59 merged (2026-03-02). Forgejo issue #58 closed.
    5. Verified from unauthenticated browser — private notes hidden, private project repos hidden.
  • Phase 2: Schema + API + MCP (ABSORBED) phase-knowledge-2-schema

    Goal: Add note_type + status columns to notes, create issues table, add repos page_note_id, update API routes and MCP tools with new query parameters.

    Status: ABSORBED into plan-2026-03-01-note-decomposition

    This phase was promoted to plan-2026-02-28-schema-api-mcp, which was subsequently absorbed into the decomposition plan. The decomposition plan adds parent_note_id + position columns alongside note_type + status, plus composition rendering. Issues table deferred to a future plan.

  • Phase 1: Convention Update (THE SPEC) phase-knowledge-1-convention

    Goal: Define the canonical note_type enum, status values per type, revised tag taxonomy, and README convention. This is the spec everything else builds on.

    Owner: Main session (docs only)

    Status: COMPLETE

    Delivered: Updated note-conventions with canonical note_type enum (10 values — bug merged into issue), status-per-type table, revised tag taxonomy (21 type tags + 9 lifecycle tags + 4 scope tags retiring, 13+ topic tags kept), README convention. Marked tagging-conventions deprecated.

  • Phase 4: Browse Frontend — Composition Rendering phase-note-decomp-4-composition

    Goal: Plan pages render child phases inline. Monolithic plans unchanged.

    Completed: 2026-03-02 (PR #63, 2 QA rounds, 252 tests)

    Forgejo: forgejo_admin/pal-e-docs PR #63, issue #62

    Scope: routes/frontend.py, templates/note.html, templates/base.html, tests/test_composition_rendering.py

    QA fix: Cartesian product from multiple joinedload on collections — switched to subqueryload.

  • Goal: MCP tools expose note_type, status, parent_slug, position.

    Completed: 2026-03-02 (PR #6 on pal-e-docs-mcp, 2 QA rounds)

    Forgejo: forgejo_admin/pal-e-docs-mcp PR #6, issue #5

    Deliverables: list_notes/create_note/update_note all accept new params. Renamed html_content→content, project_slug→project to match model instincts.

  • Goal: Add 4 columns to notes table. Validation. API filters.

    Completed: 2026-03-02 (PR #61, 2 QA rounds, 240 tests)

    Forgejo: forgejo_admin/pal-e-docs PR #61

    Incident: ~10 min outage from SQLite DDL auto-commit. See incident-2026-03-02-sqlite-migration-crash-pr61.

    Deliverables: Alembic migration (4 columns + index), model (self-referential FK), Pydantic (NoteType Literal, 11 values), route validation (VALID_STATUSES, ALLOWED_PARENT_TYPES), 68 new tests.

  • Goal: Measure today's token costs for 6 user stories. Write the decomposition spec.

    Completed: 2026-03-02

    Deliverables:

    • Baseline measurements: update phase = 39KB/2 calls, spawn agent = 19KB waste, query in-progress = 193KB/12 calls, create plan = 24KB/2 calls, elaborate phase = 40KB/2 calls
    • Updated note-conventions with phase note_type, decomposition section, baselines + targets
    • Updated template-plan
Review 38
  • Verdict: APPROVED

    Re-review after refinements. All five actionable findings from review-1432-2026-06-14 have been addressed. One deferred finding (missing arch-infra note) remains non-blocking. The issue is agent-ready.

    Previous Findings — Disposition

    1. [SCOPE] Cross-repo note — FIXED. Issue body now has a prominent "Note:" under the Repo section: "This issue is filed on pal-e-services for historical reasons (original triage). All file targets are in pal-e-platform. The agent must clone pal-e-platform, not pal-e-services."
    2. [BODY] outputs.tf promoted — FIXED. File Targets now explicitly lists terraform/modules/ops/outputs.tf as a definite restore target with exact output name (ollama_namespace). Verified: pre-removal file at 7872dac~1 contained exactly that output.
    3. [BODY] Backfill command specified — FIXED. AC 8 added: kubectl exec deployment/pal-e-docs-embedding-worker -n pal-e-docs -- python -m pal_e_docs.embedding_worker --backfill.
    4. [LABEL] Type corrected — FIXED. Issue ### Type is now "Feature". Board labels show type:feature.
    5. [LABEL] Titles aligned — FIXED. Board item and Forgejo issue both read "Restore Ollama Helm release to ops module (embedding pipeline broken)".
    6. [SCOPE] Missing arch-infra note — DEFERRED. No arch-infra note exists. Acknowledged as non-blocking by the caller. Does not prevent agent execution.

    Template Completeness

    • [x] Type — Feature
    • [x] Lineage — standalone, references removal commit 7872dac
    • [x] Repo — ldraney/pal-e-platform (terraform/modules/ops/main.tf), cross-repo note present
    • [x] User Story — platform operator, Ollama restored, embedding worker functional
    • [x] Context — thorough history of deploy, removal, and current state
    • [x] File Targets — 2 modify targets (main.tf, outputs.tf) + don't-touch list
    • [x] Feature Flag — none (correct for infra work)
    • [x] Acceptance Criteria — 8 items, all verifiable
    • [x] Test Expectations — 3 items with run command
    • [x] Constraints — 6 items, specific and actionable
    • [x] Checklist — present
    • [x] Related — references project, commits, related issue

    Traceability

    • [x] story:superuser-query label — "I can query the knowledge base by meaning (semantic search)"
    • [x] story note verified — found in project-pal-e-docs user-stories section, row 1
    • [x] arch:infra label — infrastructure component
    • [ ] arch note DEFERRED — no arch-infra note exists. Non-blocking per caller.
    • [x] Forgejo issue — ldraney/pal-e-services#114, open
    • [x] Cross-repo placement documented — Note in Repo section directs agent to clone pal-e-platform

    File Targets

    • [x] terraform/modules/ops/main.tf — verified: file exists (213 lines). Ollama resources (namespace + helm_release, ~70 lines) were removed between nvidia_device_plugin (line 26) and embedding_worker_metrics (line 28). Pre-removal config confirmed at 7872dac~1.
    • [x] terraform/modules/ops/outputs.tf — verified: file exists, is empty. Pre-removal version at 7872dac~1 had output "ollama_namespace". Now listed as definite restore target in issue.

    Don't-touch targets verified:

    • [x] terraform/main.tfmodule "ops" call at line 123, moved{} blocks present. No Ollama references remain. Correct to leave untouched.
    • [x] terraform/modules/ops/variables.tf — no Ollama variables existed before removal (confirmed identical at 7872dac~1). Correctly excluded from file targets.
    • [x] NVIDIA / embedding-worker-metrics resources — present in current main.tf, must not be modified.

    Repo Placement

    RESOLVED. Issue remains on pal-e-services (historical triage) but now has a prominent cross-repo note directing the agent to clone pal-e-platform. Agent spawn will not be confused.

    Dependencies

    • #1434 (Scale embedding worker to 0) — alternative stopgap. Becomes unnecessary if #1432 succeeds. Not a blocker.
    • #1433 (pg_stat_statements + Grafana) — independent, no dependency.
    • NVIDIA device plugin — already deployed in ops module. Issue correctly notes depends_on relationship. Not a blocker.
    • Embedding worker — downstream consumer. Config at src/pal_e_docs/config.py:11 hardcodes ollama.ollama.svc.cluster.local:11434. Will self-heal once Ollama is reachable.

    Acceptance Criteria

    8 AC items, all verifiable:

    • [x] AC 1-3: namespace + helm_release + output restored — verifiable via tofu plan
    • [x] AC 4: add-only plan — verifiable via tofu plan
    • [x] AC 5: tofu apply succeeds — verifiable (requires cluster access)
    • [x] AC 6: pods running — verifiable via kubectl
    • [x] AC 7: embedding worker connectivity — verifiable via logs
    • [x] AC 8: embedding backfill — now specifies exact kubectl exec command

    Blast Radius

    • pal-e-docs embedding worker — direct consumer. Will start working once Ollama is reachable. No code changes needed.
    • pal-e-docs semantic search — degraded to keyword-only. Will improve as backfill processes blocks.
    • No other services reference Ollama — grep across pal-e-deployments and pal-e-services/terraform found zero Ollama references.
    • GPU contention — Ollama will claim the GPU. Acknowledged in issue's Decision section.

    Decomposition Assessment

    No decomposition needed.

    • 2 file targets in 1 repo — under threshold
    • 8 AC but work is essentially a single git-restore operation — under 5 minutes
    • Single agent pass is appropriate

    Recommendations

    No action needed.

    All five actionable findings resolved. Deferred item (arch-infra note) tracked separately and does not block execution.

  • Verdict: NEEDS_REFINEMENT

    The issue body is thorough and well-structured. Five fixable issues prevent READY status: repo placement mismatch, missing architecture note, type label mismatch on board, incomplete file targets (outputs.tf), and board title mismatch with Forgejo issue title.

    Template Completeness

    • [x] Type — Feature
    • [x] Lineage — standalone, references removal commit 7872dac
    • [x] Repo — ldraney/pal-e-platform (terraform/modules/ops/main.tf)
    • [x] User Story — platform operator, Ollama restored, embedding worker functional
    • [x] Context — thorough history of deploy, removal, and current state
    • [x] File Targets — 3 modify targets + 3 don't-touch targets
    • [x] Feature Flag — none (correct for infra work)
    • [x] Acceptance Criteria — 7 items
    • [x] Test Expectations — 4 items with run command
    • [x] Constraints — 6 items, specific and actionable
    • [x] Checklist — present
    • [x] Related — references project, commits, related issue

    Traceability

    • [x] story:superuser-query label — "I can query the knowledge base by meaning (semantic search)"
    • [x] story note verified — found in project-pal-e-docs user-stories section, row 1
    • [x] arch:infra label — infrastructure component
    • [ ] arch note MISSING — [SCOPE] No arch-infra note exists in pal-e-docs. Create architecture note documenting the infra layer (ops module, NVIDIA plugin, Ollama, embedding worker metrics).
    • [x] Forgejo issue — ldraney/pal-e-services#114, open
    • [ ] Repo mismatch — [SCOPE] Issue filed on pal-e-services but all work targets pal-e-platform. Should be filed on pal-e-platform or documented why it lives on pal-e-services.

    File Targets

    • [x] terraform/modules/ops/main.tf — verified: file exists (213 lines). Ollama namespace + helm_release were surgically removed between nvidia_device_plugin (line 6-26) and embedding_worker_metrics (line 28-53). Git history at 7872dac~1 confirms the exact config to restore (namespace + helm_release, ~70 lines).
    • [x] terraform/modules/ops/variables.tf — verified: file exists. No Ollama variables existed before removal (confirmed via git show 7872dac~1). No changes needed here.
    • [ ] terraform/modules/ops/outputs.tf — ISSUE: [BODY] File exists but is EMPTY. The pre-removal version had output "ollama_namespace" that should be restored. Issue body says to check "if outputs existed" but doesn't explicitly list this as a target. The output existed and should be mentioned as a definite restore target.

    Don't-touch targets verified:

    • [x] terraform/main.tf — confirmed: module "ops" call at line 123, moved{} blocks exist for non-Ollama resources. No Ollama references remain. Correct to leave untouched.
    • [x] NVIDIA / embedding-worker-metrics resources — confirmed in current main.tf, should not be modified.

    Repo Placement

    MISMATCH. The Forgejo issue is filed on ldraney/pal-e-services but the issue body says the work is in ldraney/pal-e-platform (terraform/modules/ops/main.tf). The agent will need to clone pal-e-platform, not pal-e-services. This could cause confusion during agent spawn. Either move the issue to pal-e-platform or add a prominent note explaining the cross-repo placement.

    Dependencies

    • #1434 (Scale embedding worker to 0) — alternative stopgap. Becomes unnecessary if #1432 succeeds. Not a blocker.
    • #1433 (pg_stat_statements + Grafana) — independent, no dependency.
    • NVIDIA device plugin — already deployed and managed by ops module. Issue correctly notes depends_on relationship. Not a blocker.
    • Embedding worker — downstream consumer. Already deployed but failing due to missing Ollama. Will self-heal once Ollama is reachable. Confirmed: pal_e_docs/config.py hardcodes ollama.ollama.svc.cluster.local:11434.

    Acceptance Criteria

    7 AC items, all verifiable:

    • [x] AC 1-2: namespace + helm_release restored — verifiable via tofu plan output
    • [x] AC 3: add-only plan — verifiable via tofu plan
    • [x] AC 4: tofu apply succeeds — verifiable (requires cluster access)
    • [x] AC 5: pods running — verifiable via kubectl
    • [x] AC 6: embedding worker connectivity — verifiable via logs
    • [x] AC 7: embedding backfill — verifiable but vague. No specific command given. Agent may need guidance on how to trigger backfill (the embedding_worker.py has a run_backfill function invoked via CLI arg).

    AC 7 could be clearer: specify python -m pal_e_docs.embedding_worker --backfill or equivalent command.

    Blast Radius

    • pal-e-docs embedding worker — direct consumer. Config hardcodes the Ollama service URL. Will start working immediately once Ollama is reachable. No code changes needed in pal-e-docs.
    • pal-e-docs semantic search — degraded to keyword-only while embeddings are missing. Will gradually improve as backfill processes blocks.
    • No other services reference Ollama. The westside-ai-assistant that originally used it was decommissioned. Grep across pal-e-deployments found zero Ollama references.
    • GPU contention — Ollama will claim the GPU. If other GPU workloads are added later, contention may occur. Issue acknowledges this in the Decision section.

    Decomposition Assessment

    No decomposition needed.

    • 2-3 file targets in 1 repo — under threshold
    • 7 AC but work is essentially a single git-restore operation — under 5 minutes
    • Single agent pass is appropriate

    Recommendations

    • [SCOPE] Repo placement: Issue is on pal-e-services but work targets pal-e-platform. Either move the issue to pal-e-platform or add a note explaining cross-repo placement. Agent spawn must clone pal-e-platform.
    • [SCOPE] Create architecture note arch-infra documenting the ops module components (NVIDIA plugin, Ollama, embedding worker metrics, TF state backup).
    • [BODY] outputs.tf: Add explicit mention that outputs.tf needs the ollama_namespace output restored (confirmed it existed at 7872dac~1). Currently the issue says "if outputs existed" — they did.
    • [BODY] AC 7 (backfill): Specify the exact backfill command. The embedding worker supports --backfill mode per embedding_worker.py.
    • [LABEL] Board item type mismatch: Board label is type:bug but issue ### Type is Feature. Update board label to type:feature.
    • [LABEL] Board title mismatch: Board says "Redeploy Ollama" but Forgejo title is "Restore Ollama Helm release to ops module". Align titles.
  • Verdict: APPROVED

    Round 2 review of board item #972 (Forgejo forgejo_admin/pal-e-platform#278). Round 1 verdict was NEEDS_REFINEMENT (review-972-2026-04-11). All round 1 findings are resolved or explicitly accepted. Scope is now single-repo, two-new-files, additive-only, and fits the 5-minute rule.

    Round 1 Findings — Resolution Check

    # Round 1 finding Round 2 status Verified by
    1 [FALSE POSITIVE] claim that arch-domain-pal-e-docs does not exist Refuted — confirmed live get_note(slug="arch-domain-pal-e-docs") → id=1410, note_type=architecture, project=pal-e-docs, created 2026-04-10, status=active. Round 1 search missed it.
    2 [REAL] wrong file targets (terraform/k3s-funnels.tf does not exist) Resolved Round 2 pivoted to Option B. Verified precedent ~/pal-e-deployments/overlays/pal-e-production/prod/ingress.yaml exists (7-line Ingress using ingressClassName: tailscale + tailscale.com/funnel: "true"). Target files in the new overlay are plausible and do not collide with any existing file.
    3 [REAL] decomposition needed (5-minute rule) Resolved Scope shrunk to Step 1 only. Single repo (pal-e-deployments), 2 new files, 8 ACs (all curl/kubectl verifiable), no SDK/MCP/frontend changes. Steps 2-7 filed as forgejo_admin/pal-e-platform#280 (verified open).
    4 [REAL] arch:k8s-deploy has no backing arch note Accepted as known debt Ticket explicitly acknowledges the gap, cites sibling tickets #234/#613/#973 also using the label, and defers a proper arch-deployment-pal-e-docs note as out-of-scope. Consistent with the traceability triangle convention's allowance for foundational-work gaps.
    5 [REAL] deferred decisions in ticket body Resolved All redirect/decommission/swap choices moved to follow-up #280. This ticket has no scoping deferrals — every acceptance criterion is concrete.
    6 [REAL] same-namespace ingress backend constraint Trivially satisfied New Ingress and existing pal-e-docs Service both live in the pal-e-docs namespace. Verified via ~/pal-e-deployments/overlays/pal-e-docs/prod/kustomization.yaml which renames the base Service to pal-e-docs.

    Template Completeness

    • [x] Type — Feature
    • [x] Lineage — covers both discovery context and round 1 refutation
    • [x] Repo — forgejo_admin/pal-e-deployments
    • [x] User Story — reader-browse, correctly traced to project-pal-e-docs
    • [x] Architecture — arch:notes + arch:k8s-deploy (latter flagged as debt)
    • [x] Context — current-state table + target-state table, clear before/after
    • [x] Rationale — preserved verbatim from round 1 for decision audit trail
    • [x] File Targets — two new files with precedent path for templating
    • [x] Acceptance Criteria — 8 concrete, verifiable
    • [x] Test Expectations — 4 smoke tests including regression on existing hostname
    • [x] Constraints — reversibility explicit, "do not touch" list complete
    • [x] Checklist — standard
    • [x] Related — dependency, follow-up, arch note, convention, precedent path, round 1 review

    Traceability

    • [x] story:reader-browse label — "I can browse public notes, plans, and project pages in a web UI without authentication." Verified in project-pal-e-docs user-stories table (row 5).
    • [x] story note verified — foundation is live at project-pal-e-docs#user-stories
    • [x] arch:notes label — maps to the notes component row in arch-domain-pal-e-docs (id 1410). The new funnel exposes the FastAPI surface that serves note-shaped resources, so the label is load-bearing.
    • [x] arch note verified — arch-domain-pal-e-docs id=1410 exists (refutes round 1 false positive)
    • [!] arch:k8s-deploy label — no backing note. Accepted as known debt per ticket body and per sibling-ticket precedent (#234, #613, #973). Tracked for future arch-deployment-pal-e-docs. Not a blocker for this ticket.
    • [x] Forgejo issue — pal-e-platform#278, state=open

    File Targets

    • [x] overlays/pal-e-docs-api/prod/kustomization.yaml — NEW. Parent dir does not exist yet (verified: ~/pal-e-deployments/overlays/ lists 14 overlays, no pal-e-docs-api). Creating a new overlay directory is the correct pattern and matches how pal-e-production, pal-e-streamlit, westside-email, etc. are structured.
    • [x] overlays/pal-e-docs-api/prod/ingress.yaml — NEW. The template reference (overlays/pal-e-production/prod/ingress.yaml) exists and is 16 lines — literally just Ingress with ingressClassName: tailscale, tailscale.com/funnel: "true", and a defaultBackend.service ref. Copy-edit-commit is a <5-minute task.
    • [x] Unmanaged pal-e-docs-funnel — correctly flagged as "do not touch". Verified: ~/pal-e-deployments/overlays/pal-e-docs/prod/ contains kustomization.yaml, deployment-patch.yaml, embedding-worker.yaml, harbor-creds.enc.yamlno ingress.yaml, confirming the existing funnel is not managed by this overlay. Adding a new overlay next to it is safe and non-overlapping.
    • [x] Backend service ref pal-e-docs in namespace pal-e-docs — verified via overlays/pal-e-docs/prod/kustomization.yaml which renames the base Service to pal-e-docs. Port resolves at dev time via kubectl get svc -n pal-e-docs pal-e-docs (base service.yaml uses 8000). Not a scoping gap — one-command lookup.

    Repo Placement

    Correct. Issue is filed on forgejo_admin/pal-e-platform (the bootstrap repo, matching where the #278/#280/#234/#613 tickets live) but explicitly names forgejo_admin/pal-e-deployments as the implementation repo. The PR will open against pal-e-deployments. This is the standard split in this org: platform holds the meta-tickets, deployments holds the kustomize code.

    Dependencies

    • Soft dep on pal-e-api#256 (CORS middleware) — ticket correctly notes that Step 1 does not require CORS (no browser-side fetches change) but flags it as a prerequisite for later steps. Not a blocker for #278.
    • Unblocks follow-up #280 — verified open. Steps 2-7 depend on the additive funnel being live.
    • Board item #972 is not blocked by any other in-flight item in in_progress based on the board listing.

    Acceptance Criteria

    All 8 ACs are concrete and verifiable by a dev agent without judgment calls. The ArgoCD sync check and the dual-ingress check give clean go/no-go signals. The cert smoke test acknowledges Tailscale cert provisioning latency implicitly (the AC is phrased as "after Tailscale provisions the cert"). The MCP smoke test being explicitly marked as "smoke test only, not a permanent migration" is exactly right — prevents scope creep into Step 3.

    Blast Radius

    Minimal and well-contained.

    • Additive only: creates one new Ingress object in one namespace, zero modifications to existing resources.
    • Reversible: kubectl delete ingress pal-e-docs-api-funnel -n pal-e-docs (explicitly called out in ticket Constraints).
    • No SDK/MCP/frontend/CORS changes in this PR — all deferred to follow-up.
    • The existing pal-e-docs-funnel remains unmanaged and untouched, so there is zero risk to current MCP, SDK, and CLAUDE.md consumers.
    • Sibling overlay pattern (pal-e-production-funnel) has been in production for 7 days with no issues — precedent is battle-tested.

    Decomposition Assessment

    No decomposition needed. Against the 5-minute rule:

    • Files: 2 new files in 1 repo (limit: >3 files across >2 repos). Well under.
    • Acceptance criteria: 8 ACs (limit: >5). Slightly over the nominal threshold, BUT all 8 are mechanical verifications of the same single change (kustomize create → ArgoCD sync → kubectl get → curl). They are not independent work units — they are verification facets of one atomic change. Splitting would be artificial.
    • Estimated agent work: Copy precedent ingress.yaml (7 lines), change hostname, change service name, create kustomization.yaml referencing ingress.yaml, commit, PR. Realistic estimate: 3-5 minutes for the agent, plus ArgoCD sync + Tailscale cert provisioning wall-clock (not agent wall-clock).
    • Repos: 1 (pal-e-deployments). Limit: >2. Well under.

    Round 2 decomposition is exactly the right grain. Further splitting would be muda.

    Round 1 Findings Not Yet Addressed

    None. Every round 1 finding is either resolved (#2, #3, #5, #6), refuted with evidence (#1), or explicitly accepted as known debt with a forwarding path (#4).

    Minor Observations (non-blocking)

    • [LABEL] Board item #972 title on board-pal-e-docs still reads "Swap hostname routing — pal-e-docs serves frontend, api.pal-e-docs serves API" (the round 1 framing). The Forgejo issue title has been updated to "Hostname swap step 1 — add additive api.pal-e-docs funnel via pal-e-deployments kustomize". Consider updating the board item display title to match — but this is metadata hygiene, not a scope issue, and should not block advancement.
    • Round 1 review note review-972-2026-04-11 is referenced in the ticket body but a search of pal-e-docs returns no note with that slug. Not blocking — the round 1 findings are preserved verbatim in the ticket Lineage section, which is the load-bearing artifact.

    Recommendation

    APPROVED. Ticket is ready to advance backlog → todo. No blockers.

    • [LABEL] (optional, non-blocking) Update board item #972 display title on board-pal-e-docs to match the current Forgejo issue title.

    All other round 1 findings are resolved. Dispatch to dev when Ava is ready.

  • Verdict: APPROVED

    Round 2 of board item #973 (forgejo_admin/pal-e-platform#279). All four [BODY] refinements from review-973-2026-04-11 (round 1) are applied. Housekeeping scope is clean, dependencies are explicit, and the ticket fits comfortably in a single agent pass. Advance backlog → todo. (Execution order still gated on #278 per spawn prompt guidance, but review gate is satisfied.)

    Round 1 Findings Verification

    # Round 1 Finding Resolution in r2 body Status
    1 [BODY] pal-e-app#87 is already closed — treat as pointer-comment-only, not state change What Broke table row marks #87 as already closed (2026-03-28). Per-Ticket Action section says "NO state change. Add a single pointer comment." AC#2 says "pal-e-app#87 (already closed) receives one pointer comment citing #278 — state unchanged." Test Expectation confirms "state remains closed (was closed 2026-03-28; this housekeeping pass does not reopen it)." FIXED
    2 [BODY] Cite #278 by full URL / number in Related section Related section now lists forgejo_admin/pal-e-platform#278 explicitly with a parenthetical describing its role as "the canonical hostname swap ticket." Body references #278 by number throughout Per-Ticket Action and Expected Behavior. FIXED
    3 [BODY] Drop AC #5 (feedback_naming_convention update) as out of scope AC list no longer mentions feedback_naming_convention at all. AC count is now 4. Lineage section explicitly acknowledges: "(3) AC dropping the feedback_naming_convention lesson-capture (out of scope; that's a memory file, not a pal-e-docs note)." FIXED
    4 [BODY] Clarify AC #4 "no new tickets" vs "if anyone cares to file it" tension Per-Ticket Action for #255 and #257 now says "Do not file a new Keycloak rename ticket from this housekeeping pass — that decision belongs to whoever executes #278." AC#4 explicitly says "Forward-facing decisions about Keycloak client rename, namespace rename, or frontend validation are explicitly deferred under #278 — they are NOT this ticket's responsibility and are NOT filed as fresh tickets here." The "if anyone cares" language is gone. FIXED

    Template Completeness

    • [x] Type (Bug)
    • [x] Lineage (includes explicit round-1 delta summary)
    • [x] Repo
    • [x] What Broke (now with a 5-row per-item state table)
    • [x] Repro Steps
    • [x] Expected Behavior (distinguishes close-as-wontfix from pointer-comment-only)
    • [x] Environment
    • [x] Per-Ticket Action (per-item decisions, wontfix rationale, deferral language)
    • [x] Acceptance Criteria (4 ACs — #5 dropped per round 1)
    • [x] Test Expectations (verification queries that will be machine-checked)
    • [x] Constraints (tool choices named)
    • [x] Checklist
    • [x] Related (cites #278, #256, arch-domain-pal-e-docs, review-973-2026-04-11)
    • [ ] User Story / Architecture sections not in body — labels carry the triangle on the board item (story:superuser-maintain, arch:k8s-deploy). Acceptable per bug-template conventions.

    Traceability

    • [x] story:superuser-maintain label — verified on board item #973. Housekeeping closures are superuser maintenance via MCP.
    • [x] arch:k8s-deploy label — present on board item. Conceptually valid. Note (non-blocking): no dedicated arch-k8s-deploy note exists in pal-e-docs. Closest backing note is arch-domain-pal-e-docs. Creating a dedicated arch note is still out of scope for this housekeeping ticket. Round 1 already flagged this as a future separate ticket; not re-raising for r2.
    • [x] scope:discovered label — correct (discovered during 2026-04-11 routing review with Lucas).
    • [x] type:bug label — matches the body's "### Type Bug" header.
    • [x] Forgejo issue URL — forgejo_admin/pal-e-platform#279, state=open, verified via API 2026-04-11.
    • [ ] Forgejo issue labels are empty (body-only labeling). Still not a blocker; triangle lives on board item. [LABEL] recommendation is optional polish, not required for approval.

    File Targets

    N/A — this ticket operates entirely on Forgejo issues and pal-e-docs board items via MCP. All targets are identifier-based (issue numbers, board item IDs). Re-verified as of 2026-04-11:

    • [x] pal-e-platform#234 — state=open. Target valid.
    • [x] pal-e-platform#255 — state=open. Target valid.
    • [x] pal-e-platform#257 — state=open. Target valid.
    • [x] pal-e-app#87 — redirects to pal-e-production#87, state=closed (closed_at 2026-03-28T17:01:43Z). Body correctly treats this as pointer-comment-only.
    • [x] pal-e-app#88 — redirects to pal-e-production#88, state=open. Target valid. Note: the forgejo_admin/pal-e-app URL now 301s to pal-e-production; the dev agent should expect curl -L or use the canonical repo name when operating. Not a blocker — the MCP update_issue / comment_on_issue tools follow redirects.
    • [x] Board items #510 and #513 — both confirmed present on board-pal-e-docs, column backlog, linking to the correct pal-e-app issue URLs.

    Repo Placement

    OK. pal-e-platform is the correct umbrella repo for a cross-cutting housekeeping pass that closes tickets in both pal-e-platform and pal-e-app/pal-e-production. No repo code is touched.

    Dependencies

    Single dependency: forgejo_admin/pal-e-platform#278 (hostname swap), board item #972. State: open, backlog. The spawn prompt correctly gates execution on #278 landing first — this is an execution-order constraint, not a review-gate constraint. Review gate (backlog→todo) passes. Scheduling constraint (todo→in_progress) is a separate decision for Ava when #278 is in at least in_progress.

    No circular dependencies. No new dependencies introduced in r2.

    Acceptance Criteria

    4 ACs, each machine-verifiable:

    • [x] AC#1 — "four tickets closed as wontfix with a comment citing #278 by full URL" — verifiable via Forgejo API.
    • [x] AC#2 — "pal-e-app#87 receives one pointer comment, state unchanged" — verifiable via issue API + comments endpoint.
    • [x] AC#3 — "board items #510 and #513 removed from board-pal-e-docs" — verifiable via list_board_items.
    • [x] AC#4 — "no new tickets filed by this pass; forward-facing work deferred under #278" — verifiable by absence + explicit deferral language in body.

    Test Expectations section adds 5 concrete verification queries the QA agent can run directly. Strong.

    Blast Radius

    Low. All operations are reversible in Forgejo (reopen + re-add board item) and touch no code, no CI, no runtime services. The only residual risk — closing a ticket whose intent is still live — is addressed by the per-ticket action table and the explicit deferral-under-#278 language. Round 1's "intent-survives-rename" concern is now resolved by the "do not file new tickets from this pass" instruction.

    Decomposition Assessment

    No decomposition needed. 5-minute rule check:

    • File targets: 0 (API-only)
    • Acceptance criteria: 4 (under the 5-AC threshold)
    • Estimated agent work: ~2-3 minutes — 4 update_issue calls (close+wontfix), 5 comment_on_issue calls (4 closing comments + 1 pointer comment on #87), 2 remove_board_item calls. All independent and idempotent.
    • Repos touched: 1 umbrella (pal-e-platform) + cross-repo API calls (not a decomposition trigger).

    Housekeeping ticket remains flat and well-scoped for a single agent pass.

    Recommendation

    APPROVED. No action needed in the body. All four round 1 [BODY] fixes are cleanly applied and verifiable in the current issue text. Advance board item #973 from backlog to todo.

    Execution-order reminder (not a review gate, but relevant for Ava's scheduling): do not dispatch dev work on #973 until #278 is in at least in_progress, per the original spawn prompt.

    Optional non-blocking polish (do NOT hold the ticket for these):

    • [LABEL] (optional) Mirror board labels onto the Forgejo issue itself for consistency across surfaces.
    • [SCOPE] (future) File a separate backlog ticket if arch:k8s-deploy is going to keep appearing on tickets, to create an arch-k8s-deploy note. Still not this ticket's job.
  • Verdict: NEEDS_REFINEMENT

    Housekeeping ticket to close 5 obsolete rename-trail issues and remove 2 board items. Scope is sound in direction and reasoning, but one of the five listed tickets is already closed, and there are two small accuracy issues to fix before the ticket can move to todo. Once corrected this is a clean single-agent close-out job (well under 5 minutes).

    Template Completeness

    • [x] Type (Bug)
    • [x] Lineage
    • [x] Repo
    • [x] What Broke (with per-ticket table)
    • [x] Repro Steps
    • [x] Expected Behavior
    • [x] Environment
    • [x] Investigation / Decision Per Ticket (wontfix rationale each)
    • [x] Acceptance Criteria
    • [x] Related
    • [ ] User Story + Architecture sections missing from body — the bug template allows implicit story/arch via board labels, and story:superuser-maintain + arch:k8s-deploy are present on the board item (scope:discovered), so this is acceptable and not blocking.

    Traceability

    • [x] story:superuser-maintain label on board item — verified in project-pal-e-docs user-stories table (Superuser CRUD via MCP, not direct SQL). Housekeeping closures are exactly that kind of maintenance.
    • [x] arch:k8s-deploy label on board item — conceptually valid (touches deployment topology). Note: no dedicated arch-k8s-deploy note exists in pal-e-docs; the closest backing note is arch-domain-pal-e-docs. Creating a dedicated arch-k8s-deploy note is beyond this ticket's scope (housekeeping, not architecture). [SCOPE] — file a separate backlog ticket later if the arch:k8s-deploy label is going to keep being used across multiple issues.
    • [x] scope:discovered label — correct, this came out of the 2026-04-11 routing review with Lucas.
    • [x] Forgejo issue — forgejo_admin/pal-e-platform#279, open, URL valid.
    • [x] Forgejo labels on issue itself — empty. Not a blocker (board item carries the triangle) but worth a [LABEL] mirror for consistency if convention requires.

    File Targets

    N/A — no file targets. This ticket acts on Forgejo issues and board items via MCP tools (mcp__forgejo__update_issue, mcp__forgejo__comment_on_issue, mcp__pal-e-docs__remove_board_item). One exception: the last AC asks for a note added to feedback_naming_convention — see Accuracy Issues below.

    Repo Placement

    OK. pal-e-platform is the correct home — the work spans tickets in multiple repos (pal-e-platform, pal-e-apppal-e-production) but the umbrella action is cross-cutting housekeeping owned by the platform repo. Closures are API-level, no repo code touched.

    Dependencies

    Depends on forgejo_admin/pal-e-platform#278 (hostname swap). The ticket body references "the canonical hostname swap ticket" in Related but does not cite the number. This is the only dependency; it is live on the board as item #972, backlog, not in progress. The "after the dust settles" framing from the spawn prompt is sound: if #278 discovers that one of the five obsolete tickets still has salvageable intent at the new topology, that intent survives as a new ticket rather than a revival. No circular dependency. No other ticket blocks this one.

    Acceptance Criteria

    Each AC is machine-verifiable:

    • [x] Closing comments — verifiable via curl GET /issues/{n}/comments
    • [x] State = closed + wontfix label — verifiable via issue API
    • [x] Board items removed — verifiable via list_board_items(board-pal-e-docs)
    • [x] "No new tickets filed by this pass" — verifiable by absence
    • [x] feedback_naming_convention note updated — but see Accuracy Issues, the referenced note may not exist under that exact slug; needs slug confirmation

    Blast Radius

    Low. Closing wontfix issues and removing board items is reversible in Forgejo and pal-e-docs MCP. No CI triggered, no code changed, no downstream consumers affected. The only risk is mistaking a still-actionable ticket for an obsolete one — addressed by the per-ticket decision table in the body and double-checked below.

    Per-Ticket Verification

    Fetched each target via Forgejo API as of 2026-04-11:

    • pal-e-platform#234 (ImagePullBackOff) — state=open. Body references pal-e-docs-app namespace and Harbor image paths that no longer exist. Closing rationale is sound: pod is gone, namespace is gone, image path is obsolete. Close as wontfix. No salvageable intent at current topology (the "fix ImagePullBackOff" intent died with the namespace).
    • pal-e-platform#255 (Keycloak client rename pal-e-docs-apppal-e-app) — state=open. Direction is obsolete on both sides. Closing rationale is sound. Flag: the underlying intent — "the Keycloak client ID should match the canonical deployment name" — is still relevant. If the Keycloak client is currently named pal-e-app or pal-e-docs-app but the deployment is pal-e-production, this is live drift that Lucas may want fixed as part of #278 (hostname swap) or as a new standalone ticket. The body of #279 already covers this in its own decision note ("separate ticket if anyone cares to file it") — which conflicts with AC #4 ("no new tickets filed by this pass"). Not a blocker but worth calling out: intent-survives-rename means someone should decide whether to spin up a new Keycloak-rename ticket against the current deployment name, or explicitly defer.
    • pal-e-platform#257 (Namespace rename pal-e-docs-apppal-e-app) — state=open. Same analysis as #255: direction obsolete on both sides, underlying intent ("namespace should match deployment identity") is subsumed by the hostname-swap ticket #278 which already owns the k8s topology reshape. Close as wontfix. Intent covered by #278.
    • pal-e-app#87 (Rename pal-e-app → pal-e-docs-app) — state=CLOSED. Already closed on 2026-03-28T17:01:43Z. The ticket body of #279 lists this as one of the 5 to close, but it is already in the desired state. Any closing comment added now must acknowledge the ticket is already closed and only needs a pointer comment, not a state change. Board item #510 still needs removal regardless.
    • pal-e-app#88 (Validate session 2026-03-28 merges — 4 PRs) — state=open. Body is about validating pipelines #98-#101 and PRs #83-#86 from a session on the old pal-e-app repo. The repo is gone (redirects to pal-e-production). Validation target is moot. Close as wontfix. Board item #513 needs removal.

    Board Item Verification

    • [x] Board item #510 — present on board-pal-e-docs, backlog, title "Rename pal-e-app → pal-e-docs-app", links to pal-e-app/issues/87. Ready to remove.
    • [x] Board item #513 — present on board-pal-e-docs, backlog, title "Validate: pal-e-app (4 PRs, clone failure)", links to pal-e-app/issues/88. Ready to remove.

    Accuracy Issues (must fix before todo)

    1. [BODY] pal-e-app#87 is already closed (2026-03-28). The per-ticket decision table and Investigation section both imply #87 is currently open. Update the body to acknowledge the existing closed state: "Already closed on 2026-03-28 — add a pointer comment only, no state change needed. Board item #510 still requires removal."
    2. [BODY] Related section cites "the canonical hostname swap ticket" without a number. Add the explicit reference: forgejo_admin/pal-e-platform#278. The dev agent closing the tickets will paste this link into each closing comment; the link must be unambiguous in the source ticket.
    3. [BODY] AC #5 references feedback_naming_convention but this slug is not confirmed to exist. The memory index mentions feedback_naming_convention.md (a local memory file, not a pal-e-docs note). The last AC should clarify whether the lesson goes into (a) the user's memory file, (b) a new pal-e-docs note, or (c) is dropped entirely as out-of-scope for a housekeeping pass. Recommendation: drop this AC. Lessons about rename trails belong in a separate docs ticket, not bolted onto a closure pass — otherwise the housekeeping ticket quietly grows into a two-agent job.
    4. [SCOPE] Intent-survives-rename tension in AC #4. The decision notes for #255 and #257 say "separate ticket if anyone cares to file it" while AC #4 says "no new tickets filed by this pass". Resolve: explicitly defer any Keycloak/namespace rename-to-current-topology work to a follow-up under #278, and drop the "if anyone cares to file it" language so the agent doesn't interpret it as optional scope creep.

    Decomposition Assessment

    No decomposition needed. Applying the 5-minute rule:

    • File targets: 0 (API-only operations)
    • Acceptance criteria: 5 (under the 5-AC threshold; AC #5 should be dropped per accuracy issue #3, bringing it to 4)
    • Estimated agent work: ~2-3 minutes for a single agent — four mcp__forgejo__update_issue calls (close+wontfix for #234, #255, #257, #88), one mcp__forgejo__comment_on_issue pointer on the already-closed #87, five closing comments total, and two mcp__pal-e-docs__remove_board_item calls for #510 and #513. All operations are independent and idempotent.
    • Repos touched: 1 (pal-e-platform for the umbrella ticket, plus cross-repo API calls — not true decomposition triggers)

    Housekeeping tickets are inherently flat. One agent, one pass, done.

    Recommendation

    • [BODY] Note that pal-e-app#87 is already closed (2026-03-28); only a pointer comment + board item removal is needed.
    • [BODY] Cite forgejo_admin/pal-e-platform#278 explicitly in the Related section as "the canonical hostname swap ticket".
    • [BODY] Drop or clarify AC #5 (feedback_naming_convention update) — the slug is not a confirmed pal-e-docs note, and the lesson belongs in a separate docs ticket.
    • [BODY] Reconcile AC #4 with the per-ticket decision notes: explicitly defer any Keycloak/namespace rename-to-current-topology intent to follow-up work under #278, rather than leaving "file a new ticket if anyone cares" language in the body.
    • [SCOPE] Optional: flag for Ava that if the Keycloak client ID currently drifts from the pal-e-production deployment name, a forward-facing Keycloak rename ticket should be queued under #278 — separate decision, not this ticket's job.
    • Depends on #278 — confirm spawn prompt guidance (land this after the hostname swap dust settles) is respected on the board; do not advance #973 past todo until #278 is in at least in_progress.
  • Verdict: NEEDS_REFINEMENT

    Board item: #971 on board-pal-e-docs (backlog)
    Forgejo issue: forgejo_admin/pal-e-api#256
    Type: Bug (Lucas-authored)
    Review date: 2026-04-11

    Template Completeness

    Routed to template-issue-bug. All required sections present.

    • [x] Type — Bug
    • [x] Lineage — standalone, surfaced during westside-emails note creation
    • [x] Repo — forgejo_admin/pal-e-api
    • [x] What Broke — clear root cause with CORSMiddleware git-log evidence
    • [x] Repro Steps — 5 steps including shell curl repro
    • [x] Expected Behavior
    • [x] Environment — cluster, ingresses, repos, frontend config var
    • [x] Acceptance Criteria — 9 ACs, all testable
    • [x] Related — arch-westside-emails, #217 rename, file:line refs

    Traceability

    • [x] story:reader-browse label — "Reader browses public notes, plans, and project pages in a web UI without authentication"
    • [x] story note verified — exists in project-pal-e-docs user-stories table (row 5). This bug directly gates that story: the new pal-e-production hostname is the reader-facing surface and it cannot fetch any data today.
    • [x] arch:notes-api label — refers to the API-routes abstraction layer (routers registered in main.py), distinct from the notes entity component in arch-domain-pal-e-docs
    • [ ] arch note MISSING — no arch-notes-api note exists. arch-domain-pal-e-docs Components table lists notes (entity/table layer) but has no API-routes component entry. [SCOPE] Create arch-notes-api note (or add a "notes-api" / "routes layer" row to arch-domain-pal-e-docs Components table) documenting the FastAPI router layer. Non-blocking for fix — noted per skill.
    • [x] Forgejo issue — URL valid, issue open (created 2026-04-11T20:37:20Z)
    • [x] type:bug label — matches issue type
    • [x] scope:discovered label — matches Lineage

    File Targets

    • [x] src/pal_e_docs/main.py — verified. File exists. Line 49 is the FastAPI(...) instantiation as claimed. Router include_router calls are lines 51-58, so "add CORSMiddleware before router includes" is accurate (insert between line 49 and 51).
    • [x] CORSMiddleware absence verified — Grep "CORSMiddleware" across ~/pal-e-docs returns zero files. Middleware has never existed in this repo, matching the ticket claim.
    • [x] tests/ directory exists with test_health.py, conftest.py, and 17+ other test files — good home for a new test_cors.py or test addition to satisfy AC #7.
    • [x] ~/pal-e-app/src/lib/api-client.ts:16 — not reviewed on filesystem (referenced only for context on whether credentialed requests are sent; dev agent should confirm to decide allow_credentials).

    Repo Placement

    Correct. ~/pal-e-docs is the local checkout of forgejo_admin/pal-e-api (confirmed via git remote -v). Fix is single-repo, single-file. No cross-repo split needed.

    Dependencies

    Downstream blocker: Board item #972 ("Swap hostname routing — pal-e-docs serves frontend, api.pal-e-docs serves API", forgejo_admin/pal-e-platform#278) depends on this ticket. Without CORS middleware, the hostname swap in #972 will either (a) leave the frontend broken again post-swap or (b) force the swap design to route API and frontend under the same hostname to avoid CORS entirely. The #972 design decision is coupled to whether CORS is fixed first.

    The issue body does not mention #972 in the Related section. [BODY] recommendation below.

    Upstream blockers: none. No items in in_progress touch main.py or CORS config.

    Acceptance Criteria

    9 ACs, all testable by an agent after implementation:

    • AC1-4: code-level (middleware added, env var driven, default origins, methods/headers) — verifiable by reading the diff
    • AC5-6: runtime verification via curl with Origin + preflight OPTIONS — concrete commands, reproducible
    • AC7: new test in tests/ — concrete, tests/ exists, pytest infra in place
    • AC8: browser render check — validation step, not blocking merge but testable
    • AC9: CI deploy via existing Woodpecker — no new infra
    • AC10: no regression for non-browser callers — verifiable by existing test suite

    One nuance: AC4 says allow_credentials=True "only if the frontend sends credentialed requests." This is a conditional decision the dev agent must make by reading ~/pal-e-app/src/lib/api-client.ts. Acceptable — it's explicit and testable.

    Blast Radius

    Low.

    • Single file (main.py), single middleware registration, purely additive.
    • No other FastAPI app in the pal-e-api repo (only one main.py).
    • CORS headers on responses are ignored by non-browser clients (curl, SDK, MCP) — no regression risk for existing callers.
    • allow_origins is env-var gated (PAL_E_DOCS_CORS_ORIGINS), so non-prod environments can extend origins without code change — good pattern, matches the env-var approach already in config.py.
    • No sibling service with the same bug: pal-e-api is the only FastAPI service in this project. basketball-api and others are separate architectures and out of scope.

    Decomposition Assessment

    5-minute rule check:

    • File targets: 1 (main.py) + 1 test file addition. Well under the >3 files across >2 repos threshold.
    • Acceptance criteria count: 9. Above the soft ">5 ACs" threshold, but 6 of the 9 ACs are trivial verifications of a single middleware add (methods, headers, credentials, preflight response, regression). They are not distinct work units.
    • Estimated agent work: 3-5 minutes — add middleware + env var + 1 config setting + 1 test + 1 curl verification. Single commit, single PR.

    No decomposition needed. Fits cleanly in a single agent pass.

    Recommendation

    • [BODY] Add #972 / forgejo_admin/pal-e-platform#278 to the Related section with a note: "Blocks #972 — hostname swap design depends on whether CORS is fixed first." Non-blocking for fix, but improves traceability.
    • [SCOPE] Create an arch-notes-api architecture note (or add a "notes-api / API routes layer" row to arch-domain-pal-e-docs Components table) so the arch:notes-api label has a backing entity. Current arch-domain-pal-e-docs only documents the data/entity layer (notes, blocks, etc.), not the FastAPI router layer. Non-blocking for this fix — dev can proceed — but the backing note gap should be filled as a separate backlog item by Ava.

    Neither recommendation blocks dev dispatch. Both are documentation/traceability cleanups. If Ava chooses to move #971 straight to todo after a minor [BODY] tweak and file the arch note as a separate issue, that's appropriate — the scope and fix plan are solid.

    Summary

    Scope is sound. File target, line reference, git-log claim, and tests/ infra all verified. ACs are testable and concrete. Blast radius is minimal. Ticket is ready to implement once the two minor traceability gaps are noted — one line in the issue body and one new arch note tracked as a follow-up scope item.

  • Verdict: APPROVED

    Round 2 re-review after refinement. All four round-1 blockers resolved. Ticket is READY to move backlog → todo.

    Round 1 Blocker Resolution

    • [x] [SCOPE] arch-blocks backing note — RESOLVED. arch-domain-pal-e-docs created 2026-04-10 with a blocks row in the Components table covering SQLAlchemy Block, halfvec embeddings, mermaid-skip policy, and the (note_id, anchor_id) uniqueness that enables get_section. Ticket Architecture section now points at this note.
    • [x] [SCOPE] arch-notes backing note — RESOLVED. Same arch-domain-pal-e-docs note has a notes row covering SQLAlchemy Note, the html_content legacy vs blocks-authoritative split, and explicitly calls out the missing note_type index as the opportunity this ticket fills (“opportunity for partial index — see ticket pal-e-api#252”). Forward reference is wired.
    • [x] [LABEL] arch:notes vs arch:notes-api — RESOLVED. Verified sibling backlog item #908 points at Forgejo issue pal-e-api#251 titled “Create arch-landing-site architecture note for westside-landing”. Its arch:notes-api label refers to the API-routes layer (westside landing-site API). This ticket’s arch:notes refers to the notes DB entity row in arch-domain-pal-e-docs. Different components at different abstraction layers, not a spelling conflict. Ticket body documents the distinction explicitly.
    • [x] [BODY] Non-deterministic EXPLAIN ACs — RESOLVED. AC #5 and #6 now prescribe SET enable_seqscan = off; before the EXPLAIN, with a paragraph explaining why (planner may legitimately prefer Seq Scan at 25-row scale; disabling seqscan is the deterministic idiom to verify the partial index is query-planner-visible). Checklist item updated to match.

    Template Completeness

    • [x] Type (Feature)
    • [x] Lineage (includes round-1 review reference — good provenance)
    • [x] Repo (with rename-artifact note preserved)
    • [x] User Story
    • [x] Architecture (new section, names both arch:blocks and arch:notes with pointer to arch-domain-pal-e-docs)
    • [x] Context
    • [x] File Targets (with explicit exclusions)
    • [x] Acceptance Criteria (7 items, all verifiable)
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:superuser-query label — Superuser query path
    • [x] story note verified — row present in project-pal-e-docs user-stories table: “I can query the knowledge base by meaning (semantic search)…”
    • [x] arch:blocks label — backing component verified in arch-domain-pal-e-docs Components table
    • [x] arch:notes label — backing component verified in arch-domain-pal-e-docs Components table (and forward-references this ticket by number)
    • [x] area:db label — appropriate
    • [x] Forgejo issue — pal-e-api#252 open, refined body matches ticket brief

    File Targets

    • [x] alembic/versions/<next>_add_mermaid_and_architecture_partial_indexes.py — verified. Current Alembic head is t0o1p2q3r4s5_drop_legacy_boards_table.py (confirmed via Forgejo API listing of alembic/versions/). New migration’s down_revision must chain to this slug. Ticket explicitly instructs the agent to re-verify via alembic heads before writing, which is the right belt-and-suspenders.
    • [x] Exclusion of src/pal_e_docs/models.py preserved from round 1 — still correct (SQLAlchemy Index can’t express partial WHERE clauses cleanly).

    Repo Placement

    OK. pal-e-api is correct; the pal_e_docs package name inside pal-e-api is a known rename artifact, documented in the ticket body.

    Dependencies

    • [x] Current Alembic head satisfied (t0o1p2q3r4s5, no pending migrations).
    • [x] No in_progress board items on board-pal-e-docs touch the blocks or notes schema.
    • [x] Backing arch note now exists (arch-domain-pal-e-docs) — no longer blocked.

    Acceptance Criteria

    7 criteria. All verifiable by an agent post-implementation:

    • Two DDL statements with exact index names matching the ix_<table>_<columns> convention and partial predicates.
    • Downgrade symmetry (drops both).
    • alembic heads chain verification.
    • Two EXPLAIN assertions with SET enable_seqscan = off — now deterministic at any data volume.
    • Migration applies against current prod schema (dev overlay gate).

    Slightly over the 5-AC caution threshold (7), but all 7 are tightly coupled to a single migration file with no independent subtasks. Splitting would produce pointless ceremony. Holding the round-1 judgment: single-pass work.

    Blast Radius

    Minimal. Pure additive DDL. Partial indexes cost near-zero bytes (~25+25 rows). One file created, one repo, no application code changes, no downstream consumers. Rollback is trivial. blocks.content json→jsonb still explicitly deferred. Unchanged from round 1.

    Decomposition Assessment

    No decomposition needed.

    • Discrete changes: 1 (single migration file)
    • Files touched: 1 created, 0 modified
    • Repos touched: 1 (pal-e-api)
    • Acceptance criteria: 7 (over the 5-AC threshold, but cohesive — one file, one concern, no independent subtasks)
    • Estimated agent work: ~3–5 minutes
    • No parallelizable subtasks

    Recommendation

    No action needed. Move backlog → todo. Ticket is READY for dev dispatch.

    Drift Note (not a blocker)

    Round 1 noted that skill-review-ticket format says “Decomposition” but the template-review hook requires “Decomposition Assessment.” This review uses “Decomposition Assessment” to match the hook. Skill-vs-hook drift is a separate dogfooding ticket; not in scope for this review.

  • Verdict: NEEDS_REFINEMENT

    Scope is technically solid and executable in a single agent pass. Correct repo placement, verified file targets, idiomatic Alembic pattern, testable AC. Blocked on two missing backing architecture notes (traceability triangle) and one label-spelling reconciliation.

    Template Completeness

    • [x] Type (Feature)
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context (strong — explains semantic-documentation motivation vs performance)
    • [x] File Targets (with explicit exclusions)
    • [x] Acceptance Criteria (6 items, all verifiable)
    • [x] Test Expectations (up/down round-trip + EXPLAIN)
    • [x] Constraints (partial-index pattern, naming convention, jsonb out of scope)
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:superuser-query label — Superuser query path
    • [x] story note verified — found in project-pal-e-docs user-stories section (row: "I can query the knowledge base by meaning...")
    • [x] arch:blocks label present
    • [ ] arch-blocks note MISSING — [SCOPE] Create architecture note arch-blocks for the blocks-table component
    • [x] arch:notes label present
    • [ ] arch-notes note MISSING — [SCOPE] Create architecture note arch-notes for the notes-table component
    • [!] Label spelling inconsistency — sibling backlog ticket #908 (pal-e-api) uses arch:notes-api; this ticket uses arch:notes. Confirm canonical label before creating backing notes to avoid two arch notes for the same component.
    • [x] Forgejo issue — https://forgejo.tail5b443a.ts.net/forgejo_admin/pal-e-api/issues/252 (open)

    File Targets

    • [x] alembic/versions/<next>_add_mermaid_and_architecture_partial_indexes.py — verified: alembic/versions/ exists in forgejo_admin/pal-e-api. Current head is t0o1p2q3r4s5 (drop_legacy_boards_table). New migration's down_revision must chain to this.
    • [x] Ticket explicitly excludes src/pal_e_docs/models.py edits — verified correct: SQLAlchemy's Index(...) in __table_args__ does not express WHERE clauses for partial indexes. Existing convention in models.py (e.g. ix_blocks_note_id_position, ix_blocks_anchor_id) matches the ix_<table>_<columns> naming the ticket prescribes.
    • [x] Block model has block_type: Mapped[str] at models.py:221; Note model has note_type: Mapped[str | None] at models.py:112. Both columns exist and the types match the ticket's assumptions.

    Repo Placement

    OK. Initially flagged as a potential repo-placement bug because the task brief said "models.py is in ~/pal-e-docs." Verified on Forgejo: schema source lives at forgejo_admin/pal-e-api/src/pal_e_docs/models.py (the Python package is named pal_e_docs inside the pal-e-api repo — a naming artifact from the pal-e-docspal-e-api repo rename, board item #439). Alembic also lives in pal-e-api. Ticket is filed against the correct repo. No split across repos needed.

    Dependencies

    • [x] Current alembic head t0o1p2q3r4s5 — satisfied (no pending migrations on main)
    • [x] No blocking in_progress items on board-pal-e-docs touch the blocks or notes schema — satisfied
    • [!] arch-blocks and arch-notes backing notes — pending ([SCOPE] above)

    Acceptance Criteria

    All six AC are verifiable by an agent post-implementation:

    • Index existence: introspect pg_indexes or \d blocks / \d notes
    • EXPLAIN plans: verifiable, though at 23K blocks / 900 notes PostgreSQL may still prefer Seq Scan for the architecture-notes case (900 rows is tiny). Ticket body should note: if planner chooses Seq Scan, use SET enable_seqscan = off to force the partial index and confirm it is usable. Otherwise AC #4 and #5 may fail non-deterministically at current scale.
    • Round-trip: alembic upgrade head && alembic downgrade -1 && alembic upgrade head — real command, runnable in test DB.

    Blast Radius

    Minimal. Pure additive DDL. Partial indexes cost ~25 rows for mermaid, ~20 for architecture (near-zero bytes). One file created, one repo, no application code changes, no downstream consumers. Rollback is trivial (alembic downgrade -1 drops both indexes). blocks.content json→jsonb migration is explicitly deferred to separate scope. No sibling pattern exists elsewhere — this is a one-off declaration, not a bug being fixed in multiple places.

    Decomposition Assessment

    No decomposition needed.

    • Discrete changes: 1 (single migration file)
    • Files touched: 1 created, 0 modified
    • Repos touched: 1 (pal-e-api)
    • Acceptance criteria: 6 (under the 5-criterion caution threshold — all verifiable via a single agent session)
    • Estimated agent work: ~3 minutes (well under the 5-minute rule)
    • No independent subtasks to parallelize

    Recommendation

    1. [SCOPE] Create architecture note arch-blocks (or the canonical label spelling) documenting the blocks table as a first-class query surface. Should reference that mermaid is a partial-index query path.
    2. [SCOPE] Create architecture note arch-notes (or arch-notes-api — reconcile with #908's arch:notes-api label first) documenting the notes table. Should reference that architecture-type lookups are a partial-index query path.
    3. [LABEL] Reconcile canonical arch label spelling: arch:notes (this ticket) vs arch:notes-api (#908). If canonical is arch:notes-api, update this ticket's label to match before moving to todo.
    4. [BODY] Add a sentence to Acceptance Criteria noting that if PostgreSQL planner chooses Seq Scan over the partial index at current scale, the agent should verify with SET enable_seqscan = off to confirm the index is usable (idiomatic partial-index verification).

    Once the two arch notes exist and the label spelling is reconciled, this ticket is READY. File targets, decomposition, repo placement, and the story leg of traceability are already confirmed.

  • Verdict: BLOCK

    Template Completeness

    • [x] Type — Feature
    • [x] Lineage — board, story, arch, enables
    • [x] Repo — forgejo_admin/pal-e-api
    • [x] User Story — PM wants validation column between needs_approval and done
    • [x] Context — explains gap between SOP and API
    • [x] File Targets — 3 targets listed
    • [x] Acceptance Criteria — 5 criteria
    • [x] Test Expectations — 4 test items + run command
    • [x] Constraints — 3 constraints listed
    • [x] Checklist — 6 items
    • [x] Related — sop-board-workflow, convention-validation-checkpoints

    All template sections present and well-formed.

    Traceability

    • [x] story:superuser-maintain — present on board item
    • [x] arch:board-api — present on board item
    • [x] Forgejo issue — forgejo_admin/pal-e-api#241, open

    File Targets

    • [x] src/pal_e_docs/schemas.py — ALREADY DONE: BoardColumnType Literal at line 226 already includes "validation" (line 233)
    • [x] src/pal_e_docs/routes/boards.py — ALREADY DONE: All board routes already handle the validation column via the existing BoardColumn enum
    • [x] alembic/ — ALREADY DONE: Migration r8m9n0o1p2q3_add_validation_board_column.py exists
    • [x] src/pal_e_docs/models.py (not listed in issue but relevant) — ALREADY DONE: BoardColumn enum includes validation = "validation" at line 30

    All file targets verified — but the work has already been completed.

    Repo Placement

    Correct repo (pal-e-api). However, the Constraints section notes the MCP SDK docstring needs updating — that lives in pal-e-mcp (separate repo). The MCP docstrings are stale: list_board_items (line 127-129), create_board_item (line 170-171), and update_board_item (line 268-269) enumerate columns without validation. This is a separate ticket for pal-e-mcp.

    Dependencies

    • [x] Board item #522 (Validate: alembic upgrade head — NoteTypes + validation column, pal-e-api#232) — satisfied, in done
    • [x] Board item #524 (Fix 12 failing board_sync tests blocking CI, pal-e-api#233) — satisfied, in done

    Both prior items completed the exact work this ticket describes.

    Acceptance Criteria

    All 5 acceptance criteria are already satisfied in the current codebase:

    • update_board_item(column="validation") — works, tested in test_board_item_validation_column
    • create_board_item(column="validation") — works, BoardColumnType Literal accepts it
    • list_board_items(column="validation") — works, tested in test_board_item_filter_validation_column
    • sync_board handles new column — works, BoardColumn enum used throughout
    • Existing board items unaffected — confirmed, migration is no-op on VARCHAR

    Blast Radius

    MCP tool docstrings in pal-e-mcp are stale — three tool descriptions (list_board_items, create_board_item, update_board_item) enumerate column values without validation. Agents relying on MCP tool descriptions may not discover the column exists. This is real discovered scope for a separate ticket.

    Decomposition Assessment

    N/A — the work is already complete. No implementation needed. The ticket should be closed as duplicate rather than executed.

    • Discrete changes: 0 (already done)
    • Estimated agent time: 0 minutes
    • Independent subtasks: none

    Recommendation

    1. [SCOPE] Close pal-e-api#241 as duplicate. The validation column already exists in the model enum (models.py:30), schema Literal (schemas.py:233), alembic migration (r8m9n0o1p2q3), and has passing tests (test_note_type_enum.py). This work was completed as part of #232/#233.
    2. [BODY] Create a new Forgejo issue on forgejo_admin/pal-e-mcp for the stale MCP docstrings — 3 tool descriptions missing "validation" in their column value lists (list_board_items line 127, create_board_item line 170, update_board_item line 268).
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Task
    • [~] Lineage -- present but embedded in Scope, not its own header
    • [ ] Repo -- MISSING. No ### Repo section. Should be forgejo_admin/pal-e-app (and arguably forgejo_admin/pal-e-platform)
    • [x] User Story -- present (embedded in Scope)
    • [ ] Context -- MISSING as separate section. Background merged into Scope
    • [x] Scope -- present (replaces File Targets for Task type)
    • [x] Acceptance Criteria -- present (7 items)
    • [~] Test Expectations -- present but embedded in Scope, not its own header
    • [x] Constraints -- present
    • [ ] Checklist -- MISSING
    • [x] Related -- present

    Traceability

    • [x] story:reader-browse label -- reader browsing experience validation
    • [x] arch:frontend label -- frontend architecture component
    • [x] Forgejo issue -- forgejo_admin/pal-e-app#88, open

    All three legs present. However, arch:frontend only covers the validation target, not the root cause (cross-namespace k8s networking = arch:ci-pipeline or arch:k8s-deploy).

    File Targets

    N/A -- Task type uses Scope section instead of File Targets.

    Repo Placement

    MISMATCH. Issue is filed on pal-e-app but the investigation comment identifies cross-namespace networking as root cause. The fix involves kube-proxy, CoreDNS, NetworkPolicies, and iptables -- all pal-e-platform domain. The ticket conflates two concerns:

    1. Infrastructure fix (cross-namespace networking) -- belongs in pal-e-platform
    2. Frontend validation (4 merged PRs render correctly) -- correctly in pal-e-app

    The investigation comment itself says: "This is likely the same root cause as pal-e-deployments k8s API unreachable -- both are cross-namespace connectivity failures."

    Dependencies

    • Board item #515 (board-pal-e-platform, backlog): Validate: pal-e-deployments (k8s API unreachable) -- same root cause per investigation comment. Neither can proceed until networking is fixed.
    • Board item #411 (board-pal-e-platform, in_progress): Bug: Harbor connectivity timeout from Woodpecker CI agent -- related cross-namespace networking failure. Potentially the same underlying issue.
    • Board item #512 (board-pal-e-platform, backlog): Validate: pal-e-platform (3 merged + #222 pending) -- also blocked by same networking issue.

    Dependencies are NOT documented in the issue scope. The investigation comment identifies the connection but the ticket does not formally declare blockers.

    Acceptance Criteria

    7 ACs -- exceeds 5-rule threshold. Assessment:

    • AC 1-2 (diagnosis): Testable by an agent pulling logs and documenting findings
    • AC 3 (ArgoCD sync): Testable via kubectl
    • AC 4 (browser rendering): Requires manual spot-check -- not agent-automatable
    • AC 5-7 ("Pipeline verified", "Deployment confirmed", "Features validated"): Vague and redundant with ACs 1-4. Not independently testable.

    Blast Radius

    HIGH. Cross-namespace networking failure affects ALL Woodpecker pipelines cloning from Forgejo, not just pal-e-app. Every service with CI is impacted. The referenced commits are:

    • 992faf3 -- "refactor: modularize terraform monolith into 9 domain modules (#199)" -- confirmed in pal-e-platform
    • 6f80d16 -- "fix: allow argocd namespace ingress to forgejo (#202)" -- confirmed in pal-e-platform

    Both are infrastructure changes that could have altered NetworkPolicy or routing behavior platform-wide.

    Decomposition

    NEEDS DECOMPOSITION. 7 ACs across 3 repos (pal-e-app, pal-e-platform, pal-e-deployments), mixing diagnosis with verification. Two distinct work streams:

    1. Ticket A (pal-e-platform): Diagnose and fix cross-namespace networking. Blocks everything else. Should consolidate with item #411 and #515.
    2. Ticket B (pal-e-app): Validate 4 merged PRs render correctly. Can only proceed after Ticket A resolves.

    Current scope exceeds 5-minute agent rule. Recommend decomposition via template-board or splitting into two focused Forgejo issues.

    Recommendation

    • [BODY] Add missing ### Repo, ### Context, and ### Checklist sections
    • [BODY] Remove redundant ACs 5-7 ("Pipeline verified", "Deployment confirmed", "Features validated") -- they duplicate ACs 1-4
    • [BODY] Add explicit blocker note: "Blocked by cross-namespace networking fix (see board-pal-e-platform items #411, #512, #515)"
    • [LABEL] Add scope:blocked label to board item until networking is resolved
    • [SCOPE] Clarify ownership: is this ticket responsible for fixing networking, or only for validating AFTER networking is fixed? Investigation comment suggests diagnosis belongs here, but the fix is a platform concern.
    • [DECOMPOSE] Split into 2 tickets: (1) platform networking fix (consolidate with #411/#512/#515), (2) pal-e-app frontend validation (post-fix). Current scope spans 3 repos and 7 ACs.
  • Review: Drop legacy boards table (v3) review-318-2026-03-27-v3

    Verdict: READY

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- Standalone, kanban unification
    • [x] Repo -- forgejo_admin/pal-e-api (corrected from v2)
    • [x] User Story -- Platform owner, legacy table removal
    • [x] Context -- Final cleanup after consumer migration
    • [x] File Targets -- 4 source files + 8 test files + alembic migration
    • [x] Cross-Repo Blast Radius -- pal-e-app 2 files, alias approach documented
    • [x] Acceptance Criteria -- 6 items, all verifiable
    • [x] Test Expectations -- pytest command specified
    • [x] Constraints -- Pre-migration backup, consumer confirmation, cross-repo note
    • [x] Checklist -- Standard 3 items
    • [x] Related -- project-pal-e-docs, unlocks recursive kanban
    • [x] Notes for Betty Sue -- arch:board-api label request

    Traceability

    • [x] story:kanban-daily-review label -- kanban unification story
    • [ ] arch:board-api label -- MISSING on board item #318. Issue body notes it needs adding. Recommend adding before moving to next_up.
    • [x] Forgejo issue -- forgejo_admin/pal-e-api#199, open

    File Targets

    Source files:

    • [x] src/pal_e_docs/models.py -- verified: Board class at line 235, board_id on BoardItem at line 257
    • [x] src/pal_e_docs/schemas.py -- verified: BoardCreate at line 231, BoardUpdate at line 236
    • [x] src/pal_e_docs/routes/boards.py -- verified: 703 lines, heavily Board/board_id-dependent throughout
    • [x] alembic/versions/ -- verified: directory exists, existing migrations present

    Test files (all 8 verified to exist):

    • [x] tests/test_boards.py
    • [x] tests/test_board_sync.py
    • [x] tests/test_board_issue_sync.py
    • [x] tests/test_private_projects_boards.py
    • [x] tests/test_pagination_activity.py
    • [x] tests/test_note_type_enum.py
    • [x] tests/test_retype_migration.py
    • [x] tests/conftest.py

    Cross-repo (pal-e-app):

    • [x] src/lib/api-client.ts -- verified: board_id: number; at line 40
    • [x] src/routes/+page.svelte -- verified: boardMap[item.board_id] at line 139

    Repo Placement

    OK. Issue filed on forgejo_admin/pal-e-api (correct). Cross-repo impact on pal-e-app documented with backward-compat alias approach. pal-e-docs-sdk and pal-e-mcp have zero board_id references -- no blast radius there.

    Dependencies

    • Board item #314 "Add 'board' to NoteType" -- done
    • Board item #315 "Add board_note_id FK + data migration" -- done
    • Board item #316 "Update board API for board notes" -- done
    • Board item #317 "Update MCP + SDK + hooks for board-as-note" -- done

    All prerequisite migration tickets are in the done column. Dependency chain is satisfied.

    Acceptance Criteria

    6 AC, all agent-verifiable. Test command pytest tests/ -v is real. AC #4 now correctly reflects the alias+deprecation-header approach (contradiction from v2 resolved). No missing criteria.

    Blast Radius

    Checked pal-e-app (2 files, documented), pal-e-docs-sdk (0 refs), pal-e-mcp (0 refs). The backward-compat alias prevents breaking pal-e-app. A follow-up pal-e-app ticket to migrate off the alias is implied but not yet created -- acceptable as discovered scope after this PR lands.

    Decomposition

    13 files across 1 repo, 6 AC. Estimated agent time: borderline (large refactor of 700-line routes file + migration + 8 test files). However, the changes are tightly coupled -- dropping the Board model, rewriting routes, and updating tests MUST land atomically to avoid broken intermediate states. A database migration cannot be split across PRs. No decomposition needed -- atomic scope justifies the size.

    V2 Fix Verification

    • [x] Repo header corrected: forgejo_admin/pal-e-api (was pal-e-docs)
    • [x] Phantom notes.py removed from File Targets
    • [x] AC #4 rewritten: alias + X-Deprecated-Field header (was contradictory removal)
    • [x] Notes for Betty Sue section added with arch:board-api label request

    Recommendation

    • [LABEL] Add arch:board-api label to board item #318 before moving to next_up (per Notes for Betty Sue)

    All v2 issues resolved. Scope is solid. One housekeeping label remains.

  • Review: Drop legacy boards table review-318-2026-03-27

    Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type — Feature
    • [x] Lineage — Standalone, kanban unification
    • [x] Repo — present (but stale, see Recommendation)
    • [x] User Story — clear, well-formed
    • [x] Context — thorough, explains motivation and unlocks
    • [x] File Targets — present with source + test + migration breakdown
    • [x] Acceptance Criteria — 6 items
    • [x] Test Expectations — present with run command
    • [x] Constraints — present, includes rollback guidance
    • [x] Checklist — present
    • [x] Related — present
    • [x] Cross-Repo Blast Radius — bonus section, well-documented

    Traceability

    • [x] story:kanban-daily-review label — present on board item #318
    • [ ] arch:X label — missing. Recommend arch:board-api to match sibling tickets (#315, #316, #327)
    • [x] Forgejo issue — forgejo_admin/pal-e-api#199, open

    File Targets

    Source files:

    • [x] src/pal_e_docs/models.py — verified: Board model at line 235, board_id on BoardItem at line 257, board_note_id at line 260 (dual-FK state confirmed)
    • [x] src/pal_e_docs/schemas.py — verified: BoardCreate at line 231, BoardUpdate at line 236, board_id at line 242
    • [x] src/pal_e_docs/routes/boards.py — verified: ~15 board_id refs + ~122 Board refs (issue says "126 board_id/Board refs" — close enough, actual combined is ~137)
    • [ ] src/pal_e_docs/routes/notes.py — ISSUE: ticket claims "2 board_id refs" but grep finds 0 board_id refs and 0 Board model refs. File imports BoardItem and references BoardItem.note_slug but has no Board table dependency. Remove from file targets or clarify what "minor" change is intended.
    • [x] alembic/versions/ — new migration needed (verified alembic dir exists)

    Test files (8):

    • [x] All 8 test files verified — exact match: test_boards.py, test_board_sync.py, test_board_issue_sync.py, test_private_projects_boards.py, test_pagination_activity.py, test_note_type_enum.py, test_retype_migration.py, conftest.py

    Cross-repo (pal-e-app):

    • [x] src/lib/api-client.ts line 40 — verified: board_id: number;
    • [x] src/routes/+page.svelte line 139 — verified: boardMap[item.board_id]

    Repo Placement

    ISSUE: ### Repo header says forgejo_admin/pal-e-docs but the repo was renamed to forgejo_admin/pal-e-api (board item #439, done). The Forgejo issue is correctly filed on pal-e-api. The ### Repo line in the issue body is stale.

    Python package remains pal_e_docs so file paths are correct.

    Cross-repo impact (pal-e-app) is documented. No separate pal-e-app Forgejo issue exists yet — ticket says alias handles backward compat. Acceptable if alias is implemented; otherwise pal-e-app issue needed.

    Dependencies

    All three predecessor tickets are done:

    • #315 — Add board_note_id FK + data migration (done)
    • #316 — Update board API for board notes (done)
    • #317 — Update MCP + SDK + hooks for board-as-note (done)

    No blocking items in in_progress. SDK and MCP have zero board_id references — clean.

    Acceptance Criteria

    Contradiction found: AC #4 says "API response schemas no longer include board_id field" but the Cross-Repo Blast Radius section says "Alias board_note_id as board_id in API responses for backward compatibility." These contradict each other:

    • If we alias (keep board_id in responses), AC #4 fails
    • If we remove board_id from responses (AC #4 passes), pal-e-app breaks immediately

    This needs a human decision: either rewrite AC #4 to account for the alias, or remove the alias strategy and create a coordinated pal-e-app ticket.

    Remaining AC are verifiable by an agent via pytest + manual API calls.

    Blast Radius

    • pal-e-app: 2 files, 2 references — documented in issue. Handled by alias (if alias is kept) or follow-up ticket (if alias is dropped).
    • pal-e-docs-sdk: 0 board_id references — clean.
    • pal-e-mcp: 0 board_id references — clean.
    • claude-custom: 1 doc reference (update-docs.md line 119) — MCP tool parameter name, not a code consumer. No action needed.

    Decomposition

    14 files across 1 repo. 6 AC. Borderline on the 5-minute rule.

    However, the work is atomic and cohesive — you either drop the table or you don't. Test changes are mechanical (board_id to board_note_id). The migration is a single DDL. Splitting would create an artificial intermediate state harder to reason about than doing it in one pass.

    No decomposition needed. Single agent pass is appropriate.

    Recommendation

    • [BODY] Fix repo header: ### Repo should say forgejo_admin/pal-e-api (not forgejo_admin/pal-e-docs)
    • [BODY] Fix notes.py claim: remove src/pal_e_docs/routes/notes.py from file targets or clarify — grep finds 0 board_id/Board refs in that file
    • [SCOPE] Resolve AC #4 vs alias contradiction: either rewrite AC #4 to say "API response schemas use board_note_id as the canonical field; board_id returned as deprecated alias" OR remove alias strategy and create a pal-e-app coordination ticket
    • [LABEL] Add arch:board-api label to board item #318
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type — Feature
    • [x] Lineage — Standalone, board-pal-e-docs kanban MVP
    • [x] Repo — forgejo_admin/pal-e-app
    • [x] User Story — present, clear
    • [x] Context — present
    • [x] File Targets — present
    • [x] Acceptance Criteria — 4 items
    • [x] Test Expectations — 3 items
    • [x] Constraints — present (no Tailwind, copy-paste from playground)
    • [x] Checklist — present
    • [x] Related — present

    Traceability

    • [x] story:kanban-daily-review — daily review workflow
    • [ ] arch:X label — MISSING. Work touches frontend board component. Should be arch:frontend. Consistent gap across all kanban-daily-review items.
    • [x] Forgejo issue — forgejo_admin/pal-e-app#47, open

    File Targets

    • [x] src/routes/boards/[slug]/+page.svelte — verified: exists (31k), contains board component with drag-and-drop, column rendering, item management
    • [x] src/app.css — verified: exists (35k), contains design tokens and column color vars
    • [ ] src/routes/boards/[slug]/+page.server.ts — STALE REFERENCE: listed in "Files NOT to touch" but file no longer exists. App migrated to adapter-static + client-side fetching (board item #414, done). Issue body needs update to remove this reference.

    Repo Placement

    OK. Issue filed on forgejo_admin/pal-e-app, all file targets are in pal-e-app. Single-repo scope.

    Dependencies

    • BLOCKER: Board item #297 (Playground: kanban prototype, issue #46) is in_progress. This ticket explicitly depends on playground approval. Cannot move to next_up until #297 reaches done and Lucas approves the playground design.
    • Board item #474 (Port: board page, issue #72) is done — the current board page already ported from playground. This ticket would replace/enhance that work.

    Acceptance Criteria

    • "Board view matches playground-approved design" — requires human visual comparison, not agent-automatable. Suggest adding: "screenshot comparison posted on PR"
    • "Real API data renders correctly" — testable, but no specific test command given
    • "Drag-and-drop works with optimistic updates" — testable manually, no automated test infrastructure for DnD in this repo
    • "Auth gating: board mutations require login" — testable, matches existing pattern in the codebase

    Test Expectations reference "visual comparison" and manual interaction. No automated test commands specified — acceptable given frontend visual work, but agent verification will be limited to build success + no TypeScript errors.

    Blast Radius

    • src/app.css is shared across all routes (35k). Merging CSS vars from playground could affect other pages if var names collide or existing vars are modified.
    • src/lib/columns.ts and src/lib/colors.ts are imported by the board page — changes to column handling could affect the boards list page (src/routes/boards/+page.svelte).
    • No other board-specific components exist outside the boards route.

    Decomposition

    2 file targets, 1 repo, 4 acceptance criteria — under thresholds. However, the existing +page.svelte is 31k and this is a full design replacement with drag-and-drop + optimistic updates + auth gating. Estimated agent time: borderline 5 minutes. Single agent pass is feasible if playground CSS is finalized, but tight. No decomposition needed if scope stays to "copy playground HTML + wire data bindings."

    Recommendation

    • [LABEL] Add arch:frontend label to board item #298
    • [BODY] Remove stale reference to src/routes/boards/[slug]/+page.server.ts in "Files NOT to touch" — file no longer exists after adapter-static migration
    • [BODY] Add note that playground kanban board item #297 must be done before this moves to next_up
    • [SCOPE] Clarify: does "replace with playground-approved design" mean full rewrite of the 31k +page.svelte, or incremental CSS/layout changes on top of the existing ported board page (#72)?
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- Standalone, discovered during worktree lifecycle session
    • [x] Repo -- forgejo_admin/pal-e-app
    • [x] User Story -- As a platform operator...
    • [x] Context -- Naming convention evolution explained
    • [x] File Targets -- Blast radius list provided (but see issues below)
    • [x] Acceptance Criteria -- 7 criteria listed
    • [x] Test Expectations -- 3 expectations
    • [x] Constraints -- Coordination + maintenance window noted
    • [x] Checklist -- Present
    • [x] Related -- project-pal-e-docs + feedback_naming_convention referenced

    All template sections present. Template is complete.

    Traceability

    • [x] story:superuser-maintain -- board item has this label
    • [x] arch:naming -- board item has this label
    • [x] Forgejo issue -- forgejo_admin/pal-e-app#87, open
    • [x] scope:cross-repo -- correctly flagged on board item

    Traceability triangle complete.

    File Targets

    • [x] Forgejo repo name -- exists at forgejo_admin/pal-e-app, confirmed via API
    • [x] Harbor image -- confirmed: harbor.tail5b443a.ts.net/pal-e-app/app referenced in k8s/deployment.yaml and pal-e-deployments overlay
    • [ ] ArgoCD app reference in pal-e-services -- ISSUE: no pal-e-app references exist in pal-e-services/terraform/. This file target is WRONG.
    • [x] Kustomize overlay in pal-e-deployments -- confirmed: overlays/pal-e-app/prod/ exists with kustomization.yaml, ingress.yaml, deployment-patch.yaml, harbor-creds.enc.yaml
    • [ ] Tailscale funnel hostname -- NOT VERIFIED: no pal-e-app funnel found in pal-e-services or pal-e-platform terraform. Needs clarification.
    • [x] Woodpecker CI repo reference -- confirmed: .woodpecker.yaml references forgejo_admin/pal-e-app, Harbor repo pal-e-app/app, overlay pal-e-app
    • [x] Local directory -- confirmed: ~/pal-e-app exists
    • [x] MEMORY.md repo locations -- confirmed: 3 references to pal-e-app in MEMORY.md
    • [x] Board items with pal-e-app issue URLs -- confirmed: 15+ board items reference forgejo_admin/pal-e-app issues

    Undocumented file targets found:

    • [ ] MISSING: pal-e-platform/terraform/modules/monitoring/main.tf -- Blackbox exporter target (http://pal-e-app.pal-e-app.svc.cluster.local:3000)
    • [ ] MISSING: pal-e-platform/scripts/woodpecker-update-tag-step.yaml -- CI overlay mapping
    • [ ] MISSING: pal-e-platform/scripts/test-update-kustomize-tag.sh -- test Harbor image refs
    • [ ] MISSING: pal-e-app/package.json -- package name
    • [ ] MISSING: pal-e-app/e2e/*.spec.ts -- e2e test URLs
    • [ ] MISSING: pal-e-app/src/lib/keycloak.ts -- client config
    • [ ] MISSING: pal-e-app/playwright.config.ts -- base URL
    • [ ] MISSING: k8s namespace -- currently pal-e-app.pal-e-app.svc.cluster.local implies namespace = pal-e-app

    Repo Placement

    Issue filed on forgejo_admin/pal-e-app (correct as primary target). Changes span 4+ repos:

    • forgejo_admin/pal-e-app -- repo rename + internal refs
    • forgejo_admin/pal-e-deployments -- kustomize overlay rename
    • forgejo_admin/pal-e-platform -- monitoring TF + CI scripts
    • forgejo_admin/claude-custom -- MEMORY.md

    Each repo should get its own Forgejo issue per convention (one ticket = one agent = one PR).

    Dependencies

    • Board item #434 ("Delete stale pal-e-app overlay") marked done but overlay still exists locally -- verify if completed or local staleness.
    • Items #440 (SDK rename) and #441 (MCP rename) are done -- serve as precedent for rename pattern.
    • Item #444 (claude-custom + docs for renames) is done but MEMORY.md still shows old refs -- local may be stale.
    • Phase #37 (Repo Renames) is done -- this rename is a remaining piece.
    • 4 items in next_up reference pal-e-app issues (#69-#74) -- URLs break if Forgejo does not auto-redirect.

    Acceptance Criteria

    7 criteria listed. Most verifiable. Missing criteria:

    • Monitoring TF updated
    • CI scripts updated
    • Keycloak client config verified
    • k8s namespace decision documented

    Blast Radius

    • Monitoring: Blackbox exporter URL breaks if service name changes
    • CI scripts: hardcoded pal-e-app overlay references in pal-e-platform
    • k8s namespace: if namespace changes, ALL internal DNS changes
    • Keycloak OIDC: possible client ID + redirect URI changes
    • Tailscale funnel: hostname + TLS cert may change
    • Board items: 15+ items store raw pal-e-app issue URLs

    Decomposition

    NEEDS DECOMPOSITION: 10+ file targets across 4+ repos. 7 acceptance criteria. Estimated agent time 30+ minutes. Requires maintenance window for coordinated execution.

    Recommend decomposition via template-board into 5-7 tickets:

    1. Forgejo repo rename (admin action)
    2. pal-e-deployments overlay rename (single PR)
    3. pal-e-platform monitoring + CI script updates (single PR)
    4. pal-e-app internal refs -- package.json, k8s manifests, Woodpecker, e2e configs (single PR, done pre-rename or as part of rename)
    5. claude-custom MEMORY.md update (single PR)
    6. pal-e-docs board item URL fixup (MCP bulk update)
    7. Keycloak client config update (if applicable)

    Recommendation

    • [BODY] Remove "ArgoCD app reference in pal-e-services" from File Targets -- no pal-e-app references exist in pal-e-services
    • [BODY] Add missing file targets: pal-e-platform/terraform/modules/monitoring/main.tf, pal-e-platform/scripts/woodpecker-update-tag-step.yaml, pal-e-platform/scripts/test-update-kustomize-tag.sh, plus internal app files (package.json, keycloak.ts, playwright.config.ts, e2e specs)
    • [BODY] Add missing acceptance criteria: monitoring TF updated, CI scripts updated, Keycloak client verified, k8s namespace decision documented
    • [SCOPE] Clarify: does the k8s namespace change from pal-e-app to pal-e-docs-app? Massive blast radius on service DNS.
    • [SCOPE] Clarify: is pal-e-app registered as a Keycloak OIDC client? If so, client ID + redirect URIs need updating.
    • [SCOPE] Clarify: what is the Tailscale funnel hostname? Is it pal-e-app.tail5b443a.ts.net?
    • [DECOMPOSE] 10+ file targets across 4+ repos, 7 AC -- split into 5-7 tickets via template-board. One ticket per repo minimum, plus admin actions.
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] All 11 required sections present.

    Traceability

    • [x] story:reader-browse — [x] arch:frontend — [x] Forgejo issue pal-e-app#74, open

    File Targets

    • [x] src/routes/graph/+page.svelte — CORRECT. Does not exist yet. New route. Playground graph.html exists as source.
    • [ ] src/routes/graph/+page.ts — DOES NOT FIT PATTERN. No +page.ts files in the app. Wrong file target.

    Dependencies

    • #68 resolved.
    • Ticket says "note links API" — need to verify REST endpoint exists (MCP has get_note_links, but agent needs a REST route).

    Acceptance Criteria

    4 criteria, testable. SVG force-directed layout is the most complex port. Playground graph.html has ~200+ lines of JS for force simulation. Borderline for 5-minute rule but feasible as copy-paste port.

    Blast Radius

    • New route — low blast radius.
    • Sidebar in +layout.svelte may need a /graph nav link added. Ticket doesn't mention this.

    Recommendation

    1. Remove +page.ts from file targets.
    2. Verify note-links REST API endpoint exists in pal-e-api.
    3. Add sidebar nav link update to scope (or create discovered-scope issue).
    4. Note: force-directed layout JS may need Svelte adaptation.
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] All 11 required sections present.

    Traceability

    • [x] story:reader-browse — [x] arch:frontend — [x] Forgejo issue pal-e-app#73, open

    File Targets

    • [ ] src/routes/notes/[slug]/+page.svelte — WRONG PATH. Project pages live at src/routes/projects/[slug]/+page.svelte. Ticket says "note_type=project-page" but projects have a dedicated route at /projects/{slug} that loads via listProjects API.
    • [x] src/routes/projects/[slug]/+page.svelte — actual target. Shows project notes, board link, note type breakdown.
    • [!] "May need project-specific layout component" — vague. Current page is self-contained.

    Dependencies

    #68 resolved. Project page imports from $lib/colors and $lib/columns.

    Acceptance Criteria

    3 criteria. "Architecture diagram section renders" — this is net-new functionality (current page has no architecture section). Needs clarification on how diagrams are stored.

    Blast Radius

    Low — /projects list and sidebar nav link to projects. Must continue working.

    Recommendation

    1. Fix file target: should be src/routes/projects/[slug]/+page.svelte.
    2. Replace vague "May need" with a concrete decision.
    3. Clarify how architecture diagrams are stored and rendered (blocks? mermaid? inline HTML?).
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] All 11 required sections present.

    Traceability

    • [x] story:reader-browse — [x] arch:frontend — [x] Forgejo issue pal-e-app#72, open

    File Targets

    • [ ] src/routes/notes/[slug]/+page.svelte — WRONG PATH. Board pages live at src/routes/boards/[slug]/+page.svelte. The note detail page redirects board notes to /boards/{slug}. Ticket points to the wrong file.
    • [x] src/routes/boards/[slug]/+page.svelte — actual target. Full kanban with drag-drop, column filtering, item CRUD.
    • [!] "Existing board components" — vague. Board is one large Svelte file, not a component library.

    Dependencies

    #68 resolved. Board page imports from $lib/colors and $lib/columns.

    Acceptance Criteria

    3 criteria — missing: "Drag-and-drop still functional" and "Board item CRUD preserved." Existing board is feature-rich. Restyle must not regress.

    Blast Radius

    High — board item management (create/move/delete) is used actively by kanban workflow.

    Recommendation

    1. Fix file target: should be src/routes/boards/[slug]/+page.svelte.
    2. Add acceptance criteria for drag-drop and CRUD preservation.
    3. Clarify "existing board components" — single file, not component library.
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] All 11 required sections present.

    Traceability

    • [x] story:reader-browse — [x] arch:frontend — [x] Forgejo issue pal-e-app#71, open

    File Targets

    • [x] src/routes/notes/[slug]/+page.svelte — EXISTS. Renders via NoteLayout with blocks, TOC, child notes, parent breadcrumb.
    • [ ] src/routes/notes/[slug]/+page.ts — DOES NOT EXIST. Wrong file target.

    Dependencies

    • #68 resolved.
    • Ticket says "Reuse existing BlockRenderer" — component is actually NoteLayout.svelte at src/lib/components/NoteLayout.svelte. Block rendering in src/lib/components/blocks/. Name "BlockRenderer" does not exist as a component.

    Acceptance Criteria

    4 criteria, testable. "All block types render via existing BlockRenderer" — wrong component name.

    Blast Radius

    • Note detail has board redirect: note_type === 'board' sends to /boards/{slug}. Must preserve.
    • /notes/[slug]/edit links back to detail. Must not break edit flow.

    Recommendation

    1. Remove +page.ts from file targets.
    2. Fix component name: "BlockRenderer" should be "NoteLayout" ($lib/components/NoteLayout.svelte) + block components from $lib/components/blocks/.
    3. Add constraint: preserve note_type === 'board' redirect.
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type — [x] Lineage — [x] Repo — [x] User Story — [x] Context — [x] File Targets — [x] Acceptance Criteria — [x] Test Expectations — [x] Constraints — [x] Checklist — [x] Related

    Traceability

    • [x] story:reader-browse — board item label present
    • [x] arch:frontend — board item label present
    • [x] Forgejo issue — pal-e-app#70, open

    File Targets

    • [x] src/routes/notes/+page.svelte — EXISTS. Currently has type-colored cards with filter bar. Clean port target.
    • [ ] src/routes/notes/+page.ts — DOES NOT EXIST. App uses client-side fetching. Wrong file target.

    Repo Placement

    OK

    Dependencies

    #68 resolved. Existing page imports typeColor from $lib/colors (still exists).

    Acceptance Criteria

    4 criteria, testable. This is a restyle of an existing page, not greenfield. Ticket should note that.

    Blast Radius

    Low — standalone page.

    Recommendation

    1. Remove src/routes/notes/+page.ts from file targets.
    2. Add context that page already exists with filtering — this is a restyle, not new build.
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:reader-browse — board item label present
    • [x] arch:frontend — board item label present
    • [x] Forgejo issue — pal-e-app#69, open

    File Targets

    • [!] src/routes/+page.svelte — EXISTS but ambiguity: there is ALSO src/routes/dashboard/+page.svelte (a board-centric dashboard). Ticket says "replace current home with dashboard from playground" but doesn't acknowledge the existing /dashboard route. Which one survives?
    • [ ] src/routes/+page.ts — DOES NOT EXIST. App uses client-side fetching via onMount, not SvelteKit load functions. No +page.ts files exist anywhere in the routes. Wrong file target.

    Repo Placement

    OK — issue filed on pal-e-app, work is in pal-e-app.

    Dependencies

    • #68 (CSS + sidebar foundation) — closed, done on board. Resolved.
    • Note: colors.ts still exists despite #68 scope saying to delete it. Current home page does NOT import it, but nearby files do.

    Acceptance Criteria

    4 criteria, all testable. Should clarify what happens to the /dashboard route.

    Blast Radius

    The existing /dashboard route may become orphaned or redundant. No ticket addresses this.

    Recommendation

    1. Remove src/routes/+page.ts from file targets — data fetching stays in onMount.
    2. Clarify what happens to src/routes/dashboard/+page.svelte — delete? keep? merge?
  • Review: MCP board item move tool review-282-2026-03-27

    Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Traceability

    • [ ] story:X label — missing. Board item #282 has no story label. This is foundational MCP tooling, so arguably acceptable, but a story like story:superuser-maintain or story:agent-write would complete the triangle.
    • [ ] arch:X label — missing. Should be arch:mcp to match the component being modified.
    • [x] Forgejo issue — forgejo_admin/pal-e-mcp#45, open

    File Targets

    • [ ] src/pal_e_docs_mcp/tools/boards.py — ISSUE: path is stale. Repo was renamed from pal-e-docs-mcp to pal-e-mcp; package is now pal_e_mcp. Correct path: src/pal_e_mcp/tools/boards.py
    • [x] update_board_item function — verified at line 207 (issue says ~line 209, close enough). Confirmed: title parameter is absent. create_board_item already has it (line 163). Pattern to follow is clear.
    • [x] SDK support — verified. ~/pal-e-docs-sdk/src/pal_e_sdk/boards.py lines 106 and 139 both accept title. No SDK changes needed.

    Repo Placement

    Issue is filed on forgejo_admin/pal-e-mcp (redirects from old name pal-e-docs-mcp). Correct — only the MCP tool layer needs the change. SDK and API already support title. Single-repo fix.

    Dependencies

    • Board item #281 (pal-e-docs#192, sync_board title drift detection) is the companion issue in backlog. No dependency — they were intentionally split. This ticket can proceed independently.
    • No blockers in in_progress column that affect this work.

    Acceptance Criteria

    3 criteria, all verifiable by an agent. Test command pytest tests/ -k update_board_item is real — tests/test_param_alignment.py has a TestUpdateBoardItem class with 5 existing tests that exercise column, labels, whitespace, trailing comma, and omitted fields. New tests for title would follow the same pattern.

    Blast Radius

    • create_board_item already has title — no drift.
    • bulk_move_board_items does not support title, but it is specifically a column-move batch tool. No concern.
    • No downstream consumers affected. The change is additive and backward compatible.

    Recommendation

    Two fixes needed before READY:

    1. Update file path in issue body: Change src/pal_e_docs_mcp/tools/boards.py to src/pal_e_mcp/tools/boards.py. The repo rename means the old path will confuse the implementing agent.
    2. Add traceability labels to board item #282: Add arch:mcp at minimum. Consider adding a story label (e.g. story:agent-write).

    Also note: the ### Repo field says forgejo_admin/pal-e-docs-mcp which redirects but should be updated to forgejo_admin/pal-e-mcp for clarity.

  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- references pal-e-docs-playground#1
    • [x] Repo -- forgejo_admin/pal-e-app
    • [x] User Story -- well-formed As/I want/So that
    • [x] Context -- thorough background
    • [x] File Targets -- present with read-only and do-not-touch sections
    • [x] Acceptance Criteria -- 6 criteria listed
    • [x] Test Expectations -- N/A acknowledged (review ticket)
    • [x] Constraints -- present
    • [x] Checklist -- present
    • [x] Related -- present with story and board references

    Traceability

    • [x] story:reader-browse label -- matches user story for note browsing
    • [x] arch:frontend label -- correct, this is frontend alignment work
    • [x] Forgejo issue -- forgejo_admin/pal-e-app#65, open

    File Targets

    • [x] ~/pal-e-docs-playground/*.html -- verified: 6 HTML files exist (index.html, notes.html, note.html, note-board.html, note-project.html, graph.html)
    • [x] ~/pal-e-docs-playground/app.css -- verified: exists
    • [x] ~/pal-e-app/src/app.css -- verified: exists
    • [x] ~/pal-e-app/src/lib/components/blocks/ -- verified: 7 files (BlockRenderer.svelte + 6 type-specific components)
    • [x] ~/pal-e-app/src/routes/ -- verified: exists with dashboard, notes, boards, projects, repos, search, tags routes
    • [ ] ~/pal-e-app/src/lib/api.ts -- WRONG PATH: file does not exist. Actual path is ~/pal-e-app/src/lib/api-client.ts
    • [x] ~/pal-e-docs/src/pal_e_docs/routes/ -- verified: exists with notes.py, boards.py, blocks.py, projects.py, tags.py, repos.py, links.py, health.py

    Repo Placement

    OK -- filed on pal-e-app which is the primary consumer. File targets correctly span three repos (pal-e-docs-playground, pal-e-app, pal-e-docs) which is appropriate for a cross-repo alignment review.

    Dependencies

    • Board item #422 (pal-e-docs-playground#1 -- "Playground: note detail page prototype") is in todo column with same labels. The issue Lineage says this ticket follows that playground prototype. If playground#1 is incomplete, this review may be premature or scoped to only the pages that exist.
    • Board item #93 (Phase F11: Design System Overhaul) is in_progress with story:reader-browse. Active design work could change what the alignment review finds.
    • Board item #297 (Playground: kanban prototype) is in_progress. Kanban board page may evolve during this review.
    • Dependencies are not documented in the issue scope. The Lineage section mentions the playground prerequisite but does not flag these as blocking risks.

    Acceptance Criteria

    • Criterion 1 ("Every @data field maps to a real API response field") -- Testable by an agent. Can compare data contracts against API response schemas.
    • Criterion 2 ("Every @api endpoint exists in backend") -- Testable. However, already partially falsifiable: the playground index.html references GET /api/boards/items?column=in_progress but the actual endpoint is GET /boards/activity?column=in_progress. The notes list endpoint has no sort parameter -- it always sorts by updated_at desc by default. The graph page has no corresponding backend API endpoint at all.
    • Criterion 3 ("Block types match BlockRenderer dispatch, 6 types, no callouts") -- Contradicts the playground data contract at note.html:18 which lists "callout" as a block type. The playground CSS also has callout styles (.block--callout, .callout-label, variants). The BlockRenderer handles 6 types (heading, paragraph, code, table, list, mermaid) with no callout. This contradiction should be resolved in the issue scope.
    • Criterion 4 ("Gap list produced") -- Agent-verifiable as a deliverable.
    • Criterion 5 ("Search page spec added to playground") -- This is a code change, not a review. The issue says "Read-only review -- no code changes" in Constraints, but this criterion requires creating a new playground page. Contradiction.
    • Criterion 6 ("Alignment doc written") -- Agent-verifiable as a deliverable.

    Blast Radius

    • The wrong API client path (api.ts vs api-client.ts) is a minor issue but if an agent follows the ticket literally, it will fail to find the file.
    • The callout block type discrepancy between playground and app needs resolution -- either the playground contract is aspirational (add callout support later) or the playground is wrong. If callout is needed, that is new scope for the port.
    • The graph page has no backend endpoint. The existing /notes/{slug}/links endpoint returns per-note links, not a full graph. Building a graph API would be significant new scope not captured here.
    • The existing pal-e-app already has a search route, but the playground does not have a search page. The ticket flags this gap correctly but mixing "add search page to playground" with "review alignment" conflates review and implementation scope.

    Recommendation

    Four issues to fix before READY:

    1. Fix file path: Change ~/pal-e-app/src/lib/api.ts to ~/pal-e-app/src/lib/api-client.ts in File Targets.
    2. Resolve callout contradiction: Acceptance criterion 3 says "no callouts" but the playground data contract and CSS include callouts. Clarify whether the review should flag callout as a gap or confirm its exclusion.
    3. Remove or split search page criterion: Acceptance criterion 5 ("Search page spec added to playground") is a code change that contradicts the "Read-only review -- no code changes" constraint. Either remove it from this ticket and create a separate issue, or remove the no-code-changes constraint.
    4. Document dependency risk: Board item #422 (playground#1) is still in todo. If this review depends on a complete playground, that dependency should be explicit. If it can proceed with the 6 existing pages, state that.
  • Verdict: READY

    Re-review after full rewrite. Original issue (pal-e-app#62) had 6 blocking issues. All 6 resolved in claude-custom#174.

    Previous Issues -- Resolution Status

    1. Wrong repo -- FIXED. Issue now filed on forgejo_admin/claude-custom#174 (correct repo).
    2. 11 missing .md files -- FIXED. All 11 hook files, 5 agent files, 2 skill files, 2 root files listed with specific rename instructions.
    3. 3 MEMORY.md files -- FIXED. All 3 MEMORY.md files listed plus "Individual memory .md files referencing old repo names."
    4. Blast radius in minio-sdk -- FIXED. ~/minio-sdk/CLAUDE.md explicitly listed under "Other repos' CLAUDE.md files."
    5. Architecture diagrams unspecified -- FIXED. project-pal-e-docs listed with "architecture diagrams (3 Mermaid diagrams reference pal-e-docs-mcp node and pal-e-docs namespace)." Acceptance criteria includes "Architecture diagrams updated with new names."
    6. Acceptance criteria gaps -- FIXED. 8 concrete acceptance criteria with specific grep commands. 4 test expectations with runnable commands.

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- traces to pal-e-app#62 (moved)
    • [x] Repo -- forgejo_admin/claude-custom
    • [x] User Story -- "As the platform / I want all cross-references updated / So that hooks, agents, docs, and memory files reflect the new names"
    • [x] Context -- explains three renames, clarifies MCP prefix stays unchanged
    • [x] File Targets -- comprehensive: 11 hooks, 5 agents, 2 skills, 2 root, 3 memory, 4 pal-e-docs notes, 3 other repos' CLAUDE.md, plus explicit "NOT touch" list
    • [x] Acceptance Criteria -- 8 items with grep verification commands
    • [x] Test Expectations -- 4 items with runnable commands
    • [x] Constraints -- 5 constraints including MCP prefix preservation
    • [x] Checklist -- 6 items covering PR, notes, memory, other repos, tests
    • [x] Related -- 4 items: 3 prerequisite issues + deferred phase

    Traceability

    • [x] story:superuser-maintain label -- present on board item #444
    • [x] arch:convention label -- present on board item #444
    • [x] Forgejo issue -- forgejo_admin/claude-custom#174, open

    File Targets

    claude-custom hooks (11 files):

    • [x] hooks/session-start-context.sh -- verified: contains PAL_E_DOCS_URL (12 occurrences)
    • [x] hooks/check-note-template.sh -- verified: contains PAL_E_DOCS_URL (2 occurrences)
    • [x] hooks/check-pr-template.sh -- verified: contains PAL_E_DOCS_URL (2 occurrences)
    • [x] hooks/check-issue-template.sh -- verified: contains PAL_E_DOCS_URL (2 occurrences)
    • [x] hooks/board-item-on-merge.sh -- verified: contains PAL_E_DOCS_URL (3 occurrences)
    • [x] hooks/session-start-board-sync.sh -- verified: contains PAL_E_DOCS_URL (2 occurrences)
    • [x] hooks/cleanup-worktrees.sh -- verified: contains $HOME/pal-e-docs, $HOME/pal-e-docs-sdk, $HOME/pal-e-docs-mcp
    • [x] hooks/block-dottie-code-writes.sh -- verified: contains /home/ldraney/pal-e-docs-sdk/* and /home/ldraney/pal-e-docs-mcp/*
    • [x] hooks/inject-subagent-context.sh -- verified: prose references to "pal-e-docs" (repo context in agent descriptions)
    • [x] hooks/stop-doc-checkin.sh -- verified: prose references to "pal-e-docs"
    • [x] hooks/remind-review-loop.sh -- verified: prose references to "pal-e-docs"

    claude-custom agents (5 files):

    • [x] agents/betty-sue.md -- verified: exists, references pal-e-docs
    • [x] agents/dev.md -- verified: exists, references pal-e-docs
    • [x] agents/qa.md -- verified: exists, references pal-e-docs
    • [x] agents/dottie.md -- verified: exists, references pal-e-docs
    • [x] agents/penny.md -- verified: exists, references pal-e-docs

    claude-custom skills (2 files):

    • [x] skills/review-ticket/SKILL.md -- verified: exists, references pal-e-docs
    • [x] skills/plan/SKILL.md -- verified: exists, references pal-e-docs

    claude-custom root (2 files):

    • [x] CLAUDE.md -- verified: exists, references pal-e-docs
    • [x] README.md -- verified: exists, references pal-e-docs

    Memory files (3 MEMORY.md + individual files):

    • [x] ~/.claude/projects/-home-ldraney-pal-e-platform/memory/MEMORY.md -- verified: contains ~/pal-e-docs, ~/pal-e-docs-sdk, ~/pal-e-docs-mcp (lines 103-105, 127)
    • [x] ~/.claude/projects/-home-ldraney-pal-e-docs/memory/MEMORY.md -- verified: contains pal-e-docs-mcp reference (line 69)
    • [x] ~/.claude/projects/-home-ldraney-pal-e-services/memory/MEMORY.md -- verified: contains pal-e-docs-mcp reference (line 5)
    • [x] Individual files -- verified: feedback_naming_convention.md contains old repo names

    pal-e-docs notes (via MCP):

    • [x] project-pal-e-docs repos table -- verified: still shows old names (pal-e-docs, pal-e-docs-mcp, pal-e-docs-sdk with "planned rename" notes)
    • [x] project-pal-e-docs architecture diagrams -- verified: 3 Mermaid diagrams reference pal-e-docs-mcp node name and pal-e-docs namespace
    • [x] worktree-workflow remote conventions table -- verified: still lists pal-e-docs, pal-e-docs-sdk, pal-e-docs-mcp as repo names
    • [x] convention-sveltekit-spa -- verified: no old repo name references found (clean)

    Other repos' CLAUDE.md (3 files):

    • [x] ~/pal-e-app/CLAUDE.md -- verified: exists, references "pal-e-docs API" and PAL_E_DOCS_API_URL
    • [x] ~/pal-e-services/CLAUDE.md -- verified: exists, no pal-e-docs references found (clean)
    • [x] ~/minio-sdk/CLAUDE.md -- verified: contains "Follow pal-e-docs-sdk patterns" (line 53)

    Repo Placement

    OK. Issue filed on forgejo_admin/claude-custom -- correct, since the primary work is in claude-custom. The issue clearly documents that pal-e-docs notes are updated via MCP (not file writes), other repos' CLAUDE.md files are secondary targets, and memory files are in ~/.claude/. Multi-repo scope is well-documented and appropriate for a single coordinating issue.

    Dependencies

    • Board item #439 (pal-e-docs#217 -- API rename) -- backlog on board-pal-e-docs. PREREQUISITE.
    • Board item #440 (pal-e-docs-sdk#38 -- SDK rename) -- backlog on board-pal-e-docs. PREREQUISITE.
    • Board item #441 (pal-e-docs-mcp#50 -- MCP rename) -- backlog on board-pal-e-docs. PREREQUISITE.
    • Constraints section correctly states: "All three repo renames must complete before this ticket starts."
    • All three prerequisites are on the same board (board-pal-e-docs) and currently in backlog.

    Acceptance Criteria

    All 8 criteria are machine-verifiable:

    • [x] 4 grep commands with expected 0-match results -- agent can run these
    • [x] 1 pal-e-docs project page check -- agent can verify via MCP
    • [x] 1 architecture diagrams check -- agent can verify via MCP get_section
    • [x] 1 session start check -- agent can verify by starting a fresh session
    • [x] 1 hooks check -- agent can verify by triggering hooks

    Test expectations include 4 items with runnable commands. All verifiable.

    Blast Radius

    Minor observation (not blocking): The ~/pal-e-app/CLAUDE.md references PAL_E_DOCS_API_URL as an env var and pal-e-docs-api.tail5b443a.ts.net as a Tailscale funnel hostname. These are infrastructure-level references (env var name, DNS hostname) that may or may not change with the repo rename. The issue lists ~/pal-e-app/CLAUDE.md as a target with "repo references" but does not specify whether PAL_E_DOCS_API_URL and the Tailscale funnel hostname should change. The implementing agent can determine this from context -- the env var refers to the API service, not the repo, and may intentionally keep the "pal-e-docs" project branding even after the repo is renamed to pal-e-api. This is a judgment call for the implementer, not a scope gap.

    No unidentified blast radius. Grep across claude-custom, memory files, and referenced repos confirms the issue's file targets are comprehensive.

    Recommendation

    No action needed. The rewrite addresses all 6 issues from the previous review. File targets are comprehensive and verified against the filesystem and pal-e-docs. Acceptance criteria are machine-testable. Dependencies are documented and all sit on the same board. Repo placement is correct. This ticket is ready to move from backlog to todo.

  • Verdict: READY

    Re-review after refinements. All three issues from the 2026-03-26 initial review have been addressed.

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- references repo rename initiative + SDK dependency
    • [x] Repo -- correctly identifies source and target
    • [x] User Story -- present and clear
    • [x] Context -- good explanation of naming convention motivation, correctly notes MCP key stays pal-e-docs. Now includes CRITICAL callout for ~/.mcp.json atomic update.
    • [x] File Targets -- present with "modify," "don't touch," and "does not exist" lists
    • [x] Acceptance Criteria -- 6 criteria, all testable
    • [x] Test Expectations -- includes run command
    • [x] Constraints -- 4 constraints documented including atomic ~/.mcp.json requirement
    • [x] Checklist -- present, includes ~/.mcp.json
    • [x] Related -- references SDK rename dependency and deferred phase

    All required sections present. Template is complete.

    Traceability

    • [x] story:superuser-maintain label -- board item has it
    • [x] arch:mcp label -- board item has it
    • [x] type:feature label -- board item has it
    • [x] Forgejo issue -- forgejo_admin/pal-e-docs-mcp#50, open

    Traceability triangle is complete.

    File Targets

    • [x] pyproject.toml -- verified: exists. Ticket now correctly lists ALL 5 fields: package name (line 6), script entry point (line 18), wheel path (line 28), uv source (line 42), SDK dependency (line 14). Previously only mentioned SDK dep.
    • [x] ~/.mcp.json -- verified: exists at /home/ldraney/.mcp.json. Contains hardcoded directory path (line 32: /home/ldraney/pal-e-docs-mcp) and module name (line 35: pal_e_docs_mcp). Ticket now lists this as CRITICAL with atomic update requirement. Previously missing entirely.
    • [x] src/pal_e_docs_mcp/ directory rename -- verified: directory exists with __init__.py, __main__.py, server.py, tools/
    • [x] from pal_e_docs_sdk import statements -- verified: 1 occurrence in src/pal_e_docs_mcp/server.py:9
    • [x] src/pal_e_docs_mcp/__main__.py -- verified: imports from pal_e_docs_mcp.server import main (covered by package dir rename)
    • [x] .woodpecker.yml -- verified: exists, uses generic python:3.12-slim image. No repo name references. No changes needed.
    • [x] tests/ -- verified: conftest.py has 5 pal_e_docs_mcp imports, test_param_alignment.py has 6 pal_e_docs_mcp imports. All need updating.
    • [x] CLAUDE.md -- confirmed does not exist. Ticket now explicitly documents this under "Files that do NOT exist" with instruction not to create. Previously was incorrectly listed as a modification target.

    Nits (not blocking)

    • README.md -- references pal-e-docs-mcp (line 1) and pal_e_docs_mcp (line 12). Not listed in file targets. Any rename agent would catch this, but could be explicit.
    • uv.lock -- contains name = "pal-e-docs-mcp" (line 302). Will regenerate automatically from pyproject.toml changes. Standard practice.

    Repo Placement

    Issue is filed on forgejo_admin/pal-e-docs-mcp which is the correct repo being renamed. Downstream changes (claude-custom hooks, MEMORY.md) are correctly deferred to board item #444.

    Dependencies

    • BLOCKER: Board item #440 (forgejo_admin/pal-e-docs-sdk#38) -- "Rename pal-e-docs-sdk to pal-e-sdk + update package" is in backlog. The issue correctly states "SDK rename must complete first." This ticket cannot move to next_up until #440 completes (import paths depend on it).
    • Board item #444 ("Update claude-custom + docs for repo renames") -- downstream coordination for hooks and MEMORY.md. Must happen after this rename.
    • Board item #439 ("Rename pal-e-docs repo to pal-e-api") -- sibling rename, no direct dependency.

    Dependencies are correctly documented in the issue body.

    Acceptance Criteria

    • [x] "Forgejo repo accessible at forgejo_admin/pal-e-mcp" -- testable via API
    • [x] "MCP server starts and all 36+ tools are available" -- testable
    • [x] "Tool prefix remains mcp__pal-e-docs__*" -- testable, correctly documents the invariant
    • [x] "SDK imports use new pal_e_sdk package name" -- testable via grep
    • [x] "~/.mcp.json points to new path and module" -- now present (was missing in initial review)
    • [x] "CI pipeline works on renamed repo" -- testable via Woodpecker

    All criteria are testable and complete.

    Blast Radius

    • ~/.mcp.json -- now explicitly handled in this ticket as CRITICAL. Atomic update with directory rename. Good.
    • claude-custom hooks -- block-dottie-code-writes.sh (line 31) and cleanup-worktrees.sh (line 19) reference pal-e-docs-mcp path. Correctly deferred to board item #444.
    • MEMORY.md -- ~/pal-e-docs-mcp repo location reference. Correctly deferred to #444.
    • Woodpecker CI -- repo webhook URL will need re-configuration after Forgejo rename. Not explicitly called out but standard Forgejo rename behavior.
    • MCP process cache -- issue correctly notes session restart is needed.

    Previous Review Resolution

    1. CLAUDE.md removed from file targets -- FIXED. Now listed under "Files that do NOT exist" with explicit "do not create" instruction.
    2. ~/.mcp.json added as CRITICAL target -- FIXED. Added to File Targets with atomic update requirement, added to Context, added to Acceptance Criteria, added to Constraints, added to Checklist.
    3. pyproject.toml scope expanded -- FIXED. Now lists all 5 fields: package name, script entry point, wheel path, uv source, SDK dependency name.

    Recommendation

    No action needed. All three issues from the initial review are resolved. Two minor nits (README.md and uv.lock not in file targets) are standard rename artifacts that any agent would handle. Ticket is ready for execution once dependency #440 (SDK rename) completes.

  • Verdict: READY

    Previous Review Issues (all resolved)

    • [x] API method POST→PATCH — FIXED: issue now specifies PATCH /api/v1/repos/forgejo_admin/pal-e-docs
    • [x] File extension .yml→.yaml — FIXED: issue now references .woodpecker.yaml
    • [x] ArgoCD update unspecified — FIXED: issue now includes exact kubectl patch command targeting spec.source.repoURL

    Template Completeness

    • [x] Type — Feature
    • [x] Lineage — Standalone, references phase-pal-e-docs-repo-renames and feedback_naming_convention.md
    • [x] Repo — forgejo_admin/pal-e-docs → forgejo_admin/pal-e-api
    • [x] User Story — As the platform / I want repo named pal-e-api / so name matches role
    • [x] Context — Explains naming confusion, Forgejo redirect behavior, MCP key stability
    • [x] File Targets — Lists API call, kubectl patch, and per-file verify/update scope
    • [x] Acceptance Criteria — 5 criteria covering rename, redirect, ArgoCD, CI, API
    • [x] Test Expectations — 3 test commands
    • [x] Constraints — 4 explicit exclusions (namespace, MCP key, local dir, redirect behavior)
    • [x] Checklist — 4 items
    • [x] Related — References plan phase, project page, and 2 downstream renames

    Traceability

    • [x] story:superuser-maintain label — platform maintainer story
    • [x] arch:api label — API component
    • [x] Forgejo issue — forgejo_admin/pal-e-docs#217, open

    File Targets

    • [x] PATCH /api/v1/repos/forgejo_admin/pal-e-docs — verified: correct method for Forgejo repo rename
    • [x] kubectl patch application -n argocd pal-e-docs — verified: ArgoCD app exists, current repoURL is https://forgejo.tail5b443a.ts.net/forgejo_admin/pal-e-docs.git, path is k8s (within-repo, not pal-e-deployments). Patch command targets correct field.
    • [x] CLAUDE.md — verified: exists, contains # pal-e-docs header that needs updating
    • [x] .woodpecker.yaml — verified: exists. Contains repo: pal-e-docs/api (Harbor image path, not git repo) and pal-e-docs.pal-e-docs.svc.cluster.local (k8s namespace). Both correctly scoped as "verify only" — no change needed for git rename.
    • [x] DO-NOT-TOUCH list is accurate — MCP keys, settings.json, hook matchers, k8s namespace are all correctly excluded

    Repo Placement

    Issue filed on forgejo_admin/pal-e-docs — correct, this is the repo being renamed. Downstream impacts correctly scoped as sibling tickets (#440 SDK, #441 MCP) and follow-up (#444 convention updates via claude-custom#174).

    Dependencies

    • Board #440 (SDK rename) — sibling, no ordering dependency. SDK imports reference Python package names, not git URLs.
    • Board #441 (MCP rename) — sibling, no ordering dependency. MCP depends on SDK package name, not repo URL.
    • Board #444 (claude-custom + docs update) — should execute AFTER this ticket. Convention docs, CLAUDE.md memory references, repo location table all reference ~/pal-e-docs.
    • No blocking items currently in_progress.

    Acceptance Criteria

    All 5 criteria are testable with specific commands. ArgoCD criterion now has a concrete mechanism (kubectl patch). CI criterion is reasonable — Forgejo redirects handle webhook delivery after rename. API health check is straightforward.

    Blast Radius

    • pal-e-deployments: 30+ references to "pal-e-docs" in overlays/pal-e-docs/prod/ — all reference k8s namespace, Harbor project, or service names. NOT the git repo name. ArgoCD sources from the repo's own k8s/ directory (not the deployments overlay). No changes needed here.
    • Harbor project: Named pal-e-docs per image paths (repo: pal-e-docs/api). Independent of git repo name. No change needed.
    • Woodpecker CI: .woodpecker.yaml references Harbor paths and k8s service URLs, not git repo names. Webhook integration should survive via Forgejo redirect.
    • pal-e-services: Only a comment in cnpg.tf references "pal-e-docs". No functional impact.
    • pal-e-docs-sdk / pal-e-docs-mcp: Reference Python package names and API URLs, not git repo names. No functional impact from this rename alone.

    Recommendation

    No action needed. All three issues from the previous review have been resolved. The scope is clean, file targets are verified, and blast radius is well-contained. This ticket is ready for execution.

  • Verdict: READY

    Re-review after refinements. All three issues from initial NEEDS_REFINEMENT review have been resolved.

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- references repo rename initiative + upstream dependency on pal-e-docs rename
    • [x] Repo -- correctly identifies forgejo_admin/pal-e-docs-sdk as source and target name
    • [x] User Story -- clear platform naming consistency motivation
    • [x] Context -- adequate background on SDK purpose and rename rationale
    • [x] File Targets -- complete and verified (see below)
    • [x] Acceptance Criteria -- 5 criteria, all testable
    • [x] Test Expectations -- pytest command with relative path, no stale directory reference
    • [x] Constraints -- documents PyPI cleanup, MCP breakage timing, local dir note
    • [x] Checklist -- 5 items covering full delivery
    • [x] Related -- references upstream #217 and downstream #50

    Traceability

    • [x] story:superuser-maintain label -- on board item #440
    • [x] arch:sdk label -- on board item #440
    • [x] Forgejo issue -- forgejo_admin/pal-e-docs-sdk#38, open

    Traceability triangle is complete.

    File Targets

    • [x] pyproject.toml -- verified: contains name = "pal-e-docs-sdk", packages = ["src/pal_e_docs_sdk"], version 0.4.0
    • [x] src/pal_e_docs_sdk/ -- verified: directory exists with 14 modules, 2 internal import references to update
    • [x] .woodpecker.yml -- verified: exists, uses generic paths (src/, tests/, pip install -e). Publish step uses python -m build which reads pyproject.toml -- will work after rename
    • [x] tests/ -- verified: 11 test files + integration/ directory, 31 total pal_e_docs_sdk references that need updating
    • [x] CLAUDE.md -- correctly listed under "Files that do NOT exist" with "do not create" directive (previous issue #1 resolved)
    • [x] Downstream pal-e-docs-mcp -- verified: pyproject.toml dependency, uv.lock refs, server.py import. Correctly scoped out to ticket #441
    • [x] Downstream pal-e-docs-playground/note-project.html -- verified: 2 references to SDK. Now listed in downstream awareness (previous issue #3 resolved)
    • [x] Downstream claude-custom -- verified: 2 files reference repo path. Correctly scoped out to ticket #442

    Repo Placement

    OK. Issue is filed on forgejo_admin/pal-e-docs-sdk which is the repo being renamed. All downstream consumers explicitly scoped out with separate board items (#441 MCP, #442 claude-custom + docs, #444 claude-custom + docs update).

    Dependencies

    • Upstream: Board item #439 "Rename pal-e-docs repo to pal-e-api" (pal-e-docs#217, open, backlog). Documented in Lineage section. Technically the SDK rename could proceed independently since the SDK communicates via URL config, not repo name.
    • Downstream: Board item #441 (pal-e-docs-mcp#50, open) -- MCP server imports from the SDK. Must update after this completes.
    • Downstream: Board item #442 + #444 (claude-custom + docs update) -- depends on all renames completing.

    Acceptance Criteria

    All 5 criteria are testable. Forgejo API rename is covered in File Targets as a PATCH operation. Test command uses relative path source .venv/bin/activate && pytest (previous issue #2 resolved -- Constraints section clarifies local dir stays at ~/pal-e-docs-sdk until manually renamed).

    Blast Radius

    • pal-e-deployments -- no references. Clean.
    • pal-e-services -- no references. Clean.
    • ~/.claude/ MEMORY.md -- lists ~/pal-e-docs-sdk under "Repo Locations." Covered by #442.
    • Forgejo PyPI: old pal-e-docs-sdk package will remain in registry. Constraints section acknowledges "may need manual cleanup" -- adequate.

    Previous Issues Resolution

    1. CLAUDE.md -- RESOLVED. Now listed under "Files that do NOT exist (confirmed by review)" with explicit "do not create" directive.
    2. Test command path -- RESOLVED. Test Expectations now uses relative path source .venv/bin/activate && pytest. Constraints section adds note: "local directory stays at ~/pal-e-docs-sdk until manually renamed."
    3. pal-e-docs-playground missing from downstream -- RESOLVED. Now listed under "Files in OTHER repos that depend on this" with specific file reference.

    Recommendation

    No action needed. All three previous issues have been addressed. Ticket is ready for execution.

  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:superuser-maintain label -- superuser maintenance story
    • [x] arch:convention label -- convention architecture component
    • [x] Forgejo issue -- forgejo_admin/pal-e-app#62, open
    • [ ] Repo placement -- issue filed on pal-e-app but primary work is in claude-custom (see below)

    File Targets

    • [x] hooks/session-start-context.sh -- verified: 18 occurrences of PAL_E_DOCS_URL
    • [x] hooks/cleanup-worktrees.sh -- verified: references $HOME/pal-e-docs, $HOME/pal-e-docs-sdk, $HOME/pal-e-docs-mcp
    • [x] hooks/block-dottie-code-writes.sh -- verified: references /home/ldraney/pal-e-docs-sdk/*, /home/ldraney/pal-e-docs-mcp/*
    • [x] hooks/board-item-on-merge.sh -- verified: 3 occurrences of PAL_E_DOCS_URL (not named in ticket but caught by "All hooks with PAL_E_DOCS_URL")
    • [x] hooks/check-note-template.sh -- verified: 2 occurrences of PAL_E_DOCS_URL (not named in ticket)
    • [x] hooks/check-issue-template.sh -- verified: 2 occurrences of PAL_E_DOCS_URL (not named in ticket)
    • [x] hooks/session-start-board-sync.sh -- verified: 2 occurrences of PAL_E_DOCS_URL (not named in ticket)
    • [x] hooks/check-pr-template.sh -- verified: 2 occurrences of PAL_E_DOCS_URL (not named in ticket)
    • [ ] Agent/skill .md files in claude-custom -- MISSING from ticket. 11 .md files reference pal-e-docs as a repo path (agents/betty-sue.md, agents/dev.md, agents/qa.md, agents/dottie.md, agents/penny.md, skills/review-ticket/SKILL.md, skills/plan/SKILL.md, commands/update-docs.md, CLAUDE.md, README.md, docs/superpowers/specs/2026-03-18-review-ticket-design.md)
    • [ ] MEMORY.md files -- MISSING from ticket. 3 MEMORY.md files in ~/.claude/projects/ reference old repo names: -home-ldraney-pal-e-platform, -home-ldraney-pal-e-docs, -home-ldraney-pal-e-services
    • [ ] ~/minio-sdk/CLAUDE.md -- MISSING from ticket. References "pal-e-docs-sdk patterns"

    Repo Placement

    MISMATCH. The Forgejo issue is filed on forgejo_admin/pal-e-app but the ticket's own Repo section says the primary work is in forgejo_admin/claude-custom. The issue should be moved to or re-filed on forgejo_admin/claude-custom. The pal-e-docs note updates are done via MCP (no repo needed), so claude-custom is the correct home.

    Dependencies

    Three hard prerequisites, all correctly documented in Constraints:

    • Board #439: Rename pal-e-docs repo to pal-e-api (backlog) -- NOT STARTED
    • Board #440: Rename pal-e-docs-sdk to pal-e-sdk (backlog) -- NOT STARTED
    • Board #441: Rename pal-e-docs-mcp to pal-e-mcp (backlog) -- NOT STARTED

    All three prerequisites are in backlog. This ticket cannot move to next_up until all three are done. The dependency chain is correctly documented.

    Acceptance Criteria

    Mostly testable. The grep commands in Test Expectations are concrete and verifiable. However:

    • "Session starts without errors" -- testable but vague. Should specify which hook outputs to verify.
    • "All hooks fire correctly" -- needs specificity. Which hooks, what trigger?
    • Missing criterion: agent .md files should also be grepped for old repo paths
    • Missing criterion: MEMORY.md files should be verified
    • Missing criterion: pal-e-docs project page architecture diagrams should be verified post-update

    Blast Radius

    • Architecture diagrams in project-pal-e-docs note -- The flowchart references "pal-e-docs-mcp" as a node label. The deployment diagram references namespace "pal-e-docs". These need updating but the ticket only says "architecture diagrams updated" without specifying which diagrams or what changes.
    • minio-sdk/CLAUDE.md -- References "pal-e-docs-sdk patterns" on line 53. Not covered by the ticket scope (different repo). Should be a separate issue or explicitly scoped in.
    • pal-e-deployments -- Verified clean. No old references found.
    • pal-e-services -- Verified clean. No old references found.

    Recommendation

    Before moving to next_up, fix these issues:

    1. Re-file the issue on forgejo_admin/claude-custom (or document why pal-e-app is correct). The repo placement mismatch will confuse the executing agent.
    2. Add agent/skill .md files to File Targets -- 11 markdown files in claude-custom reference old repo paths. These are not MCP tool matchers and need updating.
    3. Add MEMORY.md files to File Targets -- 3 MEMORY.md files in ~/.claude/projects/ reference old repo names.
    4. Add minio-sdk/CLAUDE.md to blast radius or create a separate issue -- It references "pal-e-docs-sdk patterns".
    5. Specify architecture diagram changes -- The project-pal-e-docs note has 3 Mermaid diagrams referencing old names (pal-e-docs-mcp in flowchart, pal-e-docs namespace in deployment diagram).
    6. Tighten acceptance criteria -- Add grep verification for .md files and MEMORY.md files, not just hooks.
  • Verdict: NEEDS_REFINEMENT

    Re-review after full rewrite. 4 of 5 original issues resolved. 1 remaining issue plus 1 new finding.

    Previous Issues Resolution

    • [x] Issue 1: Wrong file targets — FIXED. Rewrite lists actual files: kustomization.yaml, deployment-patch.yaml, ingress.yaml, pal-e-auth-secrets.enc.yaml, harbor-creds.enc.yaml. All verified to exist at overlays/pal-e-app/prod/.
    • [x] Issue 2: Scope decision (update vs delete) — FIXED. Title changed to "Delete stale overlay." Context explicitly states the overlay is unused and ArgoCD points to in-repo k8s/. Scope is now "delete entire directory."
    • [ ] Issue 3: Repo placement — PARTIALLY FIXED. The ### Repo field correctly says forgejo_admin/pal-e-deployments. Constraints section says "This is a pal-e-deployments repo change." However, the Forgejo issue is still filed on forgejo_admin/pal-e-app repo, not forgejo_admin/pal-e-deployments. An agent spawned against this issue URL will clone pal-e-app, not pal-e-deployments.
    • [x] Issue 4: Missing ArgoCD acceptance criterion — FIXED. Test expectations now include kubectl get application -n argocd pal-e-app sync status check and cross-overlay render test.
    • [x] Issue 5: Missing scope (env vars, probes, memory, ingress) — FIXED via delete-all approach. Deleting the entire directory eliminates all stale references at once. No need to enumerate individual line changes.

    Template Completeness

    • [x] Type — Feature
    • [x] Lineage — Discovered scope from PR #57 (issue #53)
    • [x] Repo — forgejo_admin/pal-e-deployments
    • [x] User Story
    • [x] Context — clear explanation of why overlay is stale and why delete (not update)
    • [x] File Targets — all 5 files listed, all verified to exist
    • [x] Acceptance Criteria
    • [x] Test Expectations — includes ArgoCD sync check and sibling overlay render
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:spa-convention label — SPA convention migration story
    • [x] arch:deploy label — deployment architecture component
    • [x] Forgejo issue — forgejo_admin/pal-e-app#58, open

    File Targets — all verified

    • [x] overlays/pal-e-app/prod/kustomization.yaml — verified: exists, references port 3000, pal-e-auth-secrets, harbor-creds, stale image tag
    • [x] overlays/pal-e-app/prod/deployment-patch.yaml — verified: exists, contains server env vars (PAL_E_DOCS_API_URL, AUTH_TRUST_HOST), port 3000 probes, pal-e-auth-secrets envFrom
    • [x] overlays/pal-e-app/prod/ingress.yaml — verified: exists, service port 3000
    • [x] overlays/pal-e-app/prod/pal-e-auth-secrets.enc.yaml — verified: exists, SOPS-encrypted dead secrets
    • [x] overlays/pal-e-app/prod/harbor-creds.enc.yaml — verified: exists, SOPS-encrypted. Issue correctly flags for evaluation.

    Harbor-creds Safety Analysis

    The harbor-creds secret in the pal-e-app namespace is managed by HashiCorp (Terraform/OpenTofu), NOT by the kustomize overlay. The in-repo k8s/deployment.yaml references harbor-creds as an imagePullSecret (line 21) but the in-repo k8s/kustomization.yaml does NOT include harbor-creds.enc.yaml as a resource. Deleting the overlay copy is safe — the secret is provisioned independently.

    Repo Placement

    STILL MISMATCHED. The ### Repo field and ### Constraints correctly identify pal-e-deployments as the target. But the Forgejo issue itself is filed on forgejo_admin/pal-e-app, not forgejo_admin/pal-e-deployments. The agent spawn hook reads the issue URL to determine which repo to clone. An agent spawned against forgejo_admin/pal-e-app#58 will clone pal-e-app and not find the overlay files.

    Dependencies

    • Board item #414 (pal-e-app#53 — adapter-static switch) is in done column — parent work complete.
    • Board item #413 (pal-e-app#52 — client-side auth migration) is in done column — auth removal complete.
    • No blockers. This ticket is unblocked.

    Acceptance Criteria

    All criteria are testable:

    • "overlays/pal-e-app/ directory is deleted" — verifiable via ls
    • "No other overlays affected" — verifiable via kubectl kustomize on sibling overlays
    • "ArgoCD pal-e-app application still syncs correctly" — verifiable via kubectl get application

    Blast Radius

    • ArgoCD confirmed using in-repo k8s/: repoURL: forgejo_admin/pal-e-app.git, path: k8s — the overlay is genuinely unused.
    • No other overlays in pal-e-deployments reference pal-e-app.
    • 10 other overlays in pal-e-deployments each have their own harbor-creds.enc.yaml — deleting one overlay's copy does not affect others.

    Discovered Scope: in-repo k8s/ is stale

    The in-repo ~/pal-e-app/k8s/deployment.yaml still references port 3000, pal-e-auth-secrets envFrom, PAL_E_DOCS_API_URL, AUTH_TRUST_HOST, and 128Mi memory limit. Out of scope for this ticket (which targets pal-e-deployments), but indicates discovered scope: the in-repo k8s/ manifests need their own cleanup ticket on forgejo_admin/pal-e-app.

    Recommendation

    One remaining issue before READY:

    1. Move issue to correct repo — The Forgejo issue must be filed on forgejo_admin/pal-e-deployments, not forgejo_admin/pal-e-app. An agent spawned against the current URL will clone the wrong repo. Either create a new issue on pal-e-deployments and close this one, or use Forgejo transfer if available.

    Discovered scope (separate ticket needed):

    1. In-repo k8s/ cleanuppal-e-app/k8s/deployment.yaml still has stale port 3000, server env vars, and auth secrets references. This should be a separate Forgejo issue on forgejo_admin/pal-e-app.
  • Verdict: READY

    Re-review after refinements. Both issues from prior NEEDS_REFINEMENT review have been addressed.

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- "Discovered scope from QA review of PR #55 (issue #52)"
    • [x] Repo -- forgejo_admin/pal-e-app
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    All required sections for the Feature template are present. Template is complete.

    Traceability

    • [x] story:spa-convention label -- present on board item
    • [x] arch:app label -- present on board item
    • [x] Forgejo issue -- forgejo_admin/pal-e-app#56, state: open
    • [x] scope:discovered label -- correctly identifies this as discovered scope from PR #55 QA

    File Targets

    • [x] src/lib/keycloak.ts line 20 -- VERIFIED: Misleading "Uses check-sso" comment present. init() call (lines 26-29) does NOT pass onLoad: 'check-sso'.
    • [x] src/lib/api-client.ts line 106 (NoteLink) -- VERIFIED: NoteLink interface defined at lines 106-111. Only appears in api-client.ts, never imported by any other file. Dead code confirmed.
    • [x] .env.example -- VERIFIED: Contains stale server-side vars PAL_E_DOCS_API_URL and PAL_E_DOCS_API_KEY. No VITE_* vars present. The old X-PaleDocs-Token/API key pattern is dead after auth migration.
    • [x] src/lib/api-client.ts line 27 + src/lib/columns.ts line 8 (COLUMNS duplicate) -- VERIFIED: Both files define identical COLUMNS arrays. Issue now specifies direction: "Remove and import from $lib/columns instead (canonical source, already exported and used by 3 pages)." FIX CONFIRMED.
    • [x] src/routes/+layout.svelte lines 171-198 (nav CSS) -- VERIFIED: .nav-logout-btn (lines 171-184) and .nav-login-link (lines 186-199) have identical CSS properties. Should consolidate.

    Repo Placement

    OK. Issue filed on forgejo_admin/pal-e-app, all file targets in pal-e-app repo. Single-repo scope.

    Dependencies

    • Parent issue #52 (auth migration) -- board item #413, done. No blocker.
    • PR #55 (where nits were found) -- merged and closed. No blocker.
    • Issue #53 (adapter-static) -- board item #414, done. No blocker.
    • Issue #58 (deployment overlay) -- board item #427, todo. Independent scope.
    • No items in in_progress block this ticket.

    Acceptance Criteria

    • [x] "No misleading comments about check-sso" -- Verifiable by grep.
    • [x] "No dead interfaces in api-client.ts" -- Verifiable by grep.
    • [x] ".env.example reflects new client-side env vars (VITE_*)" -- Verifiable by reading file.
    • [x] "COLUMNS imported from $lib/columns (single source of truth)" -- Verifiable by grep. Direction now specified: import from $lib/columns. FIX CONFIRMED.
    • [x] "Login/logout button CSS consolidated" -- Verifiable by grep.

    All criteria are agent-verifiable. Test commands (npm run check && npm run build) are real.

    Blast Radius

    • mcd-tracker-app: ~/mcd-tracker-app/src/lib/keycloak.js line 22 has identical misleading "Uses check-sso" comment. Explicitly scoped out in Constraints section: "that's a separate discovered-scope issue, not in scope here." FIX CONFIRMED.
    • westside-app is correct: Actually uses onLoad: 'check-sso', so its comment is accurate. No action needed.
    • No other sibling apps have duplicate COLUMNS or dead NoteLink patterns.

    Recommendation

    No action needed. Both issues from the prior NEEDS_REFINEMENT review have been resolved:

    1. COLUMNS consolidation direction missing -- Now specifies: import from $lib/columns (canonical source).
    2. mcd-tracker blast radius undocumented -- Now noted in Constraints as out-of-scope.

    Ticket is ready to move from todo to next_up.

  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type — Feature
    • [x] Lineage — Standalone, feeds into pal-e-app after phone approval
    • [x] Repo — forgejo_admin/pal-e-docs-playground
    • [x] User Story — As a reader browsing pal-e-docs...
    • [x] Context — Design vision, navigation paradigms, note_type rendering
    • [x] File Targets — index.html, app.css (greenfield creates)
    • [x] Acceptance Criteria — 9 items covering responsive, block types, sidebars, tokens
    • [x] Test Expectations — DevTools mobile/desktop, Mermaid, http.server command
    • [x] Constraints — convention-frontend-css, pure HTML/CSS/JS, mobile-first
    • [x] Checklist — PR, phone approval, no unrelated changes
    • [x] Related — design vision, SOP, board, parent phase

    All required sections present. Template is complete.

    Traceability

    • [x] story:reader-browse label — reader browsing and navigating the knowledge base
    • [x] arch:frontend label — frontend component
    • [x] Forgejo issue — forgejo_admin/pal-e-docs-playground#1, open

    Full traceability triangle intact.

    File Targets

    • [x] index.html — to be created in greenfield repo (only README.md exists). Valid.
    • [x] app.css — to be created in greenfield repo. Valid.
    • [ ] Design token source reference pal-e-playground/pal-e-app/app.cssISSUE: File does not exist. The pal-e-playground repo has no CSS files. The actual design tokens are documented in convention-frontend-css (pal-e-docs note) and the production implementation lives at pal-e-app/src/app.css (pal-e-app repo). An agent following this reference path would hit a dead end.

    Repo Placement

    OK. Issue filed on forgejo_admin/pal-e-docs-playground, work happens in same repo. Follows the {project}-playground naming convention. Repo exists on Forgejo, is non-empty (has README.md), and the deployment hookup is already wired (Forgejo issue pal-e-deployments#52 is closed — playground.tail5b443a.ts.net/pal-e-docs/ is live).

    Dependencies

    • Deployment hookup (board #421, pal-e-deployments#52) — prerequisite, already done (Forgejo issue closed). Note: board item #421 still shows in_progress despite the issue being closed — minor board sync gap, not a blocker.
    • Phase F11 (board #93) — parent phase, in_progress. This ticket is scoped work under F11. Consistent.
    • convention-frontend-css — referenced in Constraints. Note exists and is active. Tokens documented there match what the issue describes.
    • No blocking dependencies from other in_progress items.

    Acceptance Criteria

    8 of 9 criteria are verifiable by an agent or by visual inspection:

    • "Page opens directly in browser" — verifiable via http.server
    • "All 6 block types render" — verifiable by inspecting DOM
    • "Mermaid diagram renders via CDN" — verifiable (SVG present, not raw text)
    • "Responsive at 390px and 1200px+" — verifiable via Playwright viewport
    • "Zero hardcoded hex" — verifiable via grep on the CSS file
    • "Left sidebar shows note tree" — verifiable by DOM inspection
    • "Right sidebar shows TOC + backlinks" — verifiable by DOM inspection
    • "Type badge, status, tags, recency in metadata bar" — verifiable by DOM inspection
    • "Lucas approves on phone" — human gate, appropriate for playground workflow

    Acceptance criteria are well-structured and testable.

    Blast Radius

    Minimal. This is a greenfield prototype in an isolated playground repo. No production code is modified. However, the design decisions made here become the visual spec for pal-e-app note rendering (the playground-first pipeline). Downstream impact is intentional and gated by phone approval.

    Recommendation

    One fix needed before READY:

    1. Fix the design token source reference. The File Targets section references pal-e-playground/pal-e-app/app.css as the design token source, but this file does not exist. Update to reference convention-frontend-css (the pal-e-docs note that documents all tokens) and optionally pal-e-app/src/app.css (the production implementation). Without this fix, an agent would not know where to find the design tokens.
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- depends on pal-e-app #1
    • [x] Repo -- forgejo_admin/pal-e-app
    • [x] User Story -- well-formed As/I want/So that
    • [x] Context -- good background, references Phase 29 and mcd-tracker-app pattern
    • [x] File Targets -- present but incomplete (see below)
    • [x] Acceptance Criteria -- 7 criteria
    • [x] Test Expectations -- e2e tests + run command
    • [x] Constraints -- 5 constraints listed
    • [x] Checklist -- standard 3-item
    • [x] Related -- 3 references

    Traceability

    • [ ] story:X label -- MISSING. Board item #413 has labels type:feature,arch:auth,arch:app but no story label. This is a significant feature that should map to a user story.
    • [x] arch:X label -- arch:auth,arch:app present, correctly identifies affected architecture components
    • [x] Forgejo issue -- forgejo_admin/pal-e-app#52, open

    File Targets

    Files to create (verified):

    • [x] src/lib/keycloak.js -- does not exist yet, model file ~/mcd-tracker-app/src/lib/keycloak.js confirmed present
    • [x] src/lib/api-client.js -- does not exist yet, model file ~/mcd-tracker-app/src/lib/api.js confirmed present

    Files to modify (verified with issues):

    • [x] src/routes/+layout.svelte -- exists, uses $page.data.session and Auth.js session pattern
    • [x] src/routes/+page.svelte -- exists
    • [x] src/routes/boards/+page.svelte -- exists
    • [x] src/routes/boards/[slug]/+page.svelte -- exists
    • [x] src/routes/notes/+page.svelte -- exists
    • [x] src/routes/notes/[slug]/+page.svelte -- exists
    • [x] src/routes/projects/+page.svelte -- exists
    • [x] src/routes/projects/[slug]/+page.svelte -- exists
    • [x] src/routes/repos/+page.svelte -- exists
    • [x] src/routes/search/+page.svelte -- exists
    • [x] src/routes/dashboard/+page.svelte -- exists
    • [x] src/routes/tags/+page.svelte -- exists
    • [x] package.json -- has @auth/sveltekit: ^1.11.1
    • [ ] MISSING: src/routes/notes/[slug]/edit/+page.svelte -- exists, has server data, needs client-side migration
    • [ ] MISSING: src/routes/tags/[name]/+page.svelte -- exists, has server data, needs client-side migration
    • [ ] MISSING: src/lib/components/QuickJot.svelte -- uses fetch('/api/notes') which will break when src/routes/api/ is removed. Must migrate to api-client.js
    • [ ] MISSING: src/lib/slugCache.ts -- imports from $lib/api which is being removed. Needs migration or removal

    Files to remove (verified with issues):

    • [x] src/routes/+layout.server.ts -- exists, loads session + projects via server-side API
    • [x] src/routes/+page.server.ts -- exists
    • [x] src/routes/boards/+page.server.ts -- exists
    • [x] src/routes/boards/[slug]/+page.server.ts -- exists
    • [x] src/routes/notes/+page.server.ts -- exists
    • [x] src/routes/notes/[slug]/+page.server.ts -- exists
    • [x] src/routes/projects/+page.server.ts -- exists
    • [x] src/routes/projects/[slug]/+page.server.ts -- exists
    • [x] src/routes/repos/+page.server.ts -- exists
    • [x] src/routes/search/+page.server.ts -- exists
    • [x] src/routes/dashboard/+page.server.ts -- exists
    • [x] src/routes/tags/+page.server.ts -- exists
    • [x] src/routes/signin/ -- exists (2 files)
    • [x] src/routes/signout/ -- exists (2 files)
    • [x] src/routes/api/ -- exists (4 server files across boards + notes)
    • [x] src/lib/api.ts -- exists, uses $env/dynamic/private (server-only)
    • [ ] MISSING: src/auth.ts -- Auth.js config file with SvelteKitAuth, Keycloak provider, JWT/session callbacks. Must be removed.
    • [ ] MISSING: src/hooks.server.ts -- imports and re-exports Auth.js handle. Must be removed.
    • [ ] MISSING: src/routes/notes/[slug]/edit/+page.server.ts -- exists, loads note + projects + tags for edit form
    • [ ] MISSING: src/routes/tags/[name]/+page.server.ts -- exists, loads notes filtered by tag name

    Type export concern: Multiple components import types from $lib/api (BlockRenderer.svelte, NoteLayout.svelte, QuickJot.svelte, +page.svelte files). When api.ts is removed, these type imports break. The ticket should specify where types move (likely a new src/lib/types.ts or co-located in api-client.js).

    Repo Placement

    OK -- issue filed on forgejo_admin/pal-e-app, all file targets are within that repo.

    Dependencies

    • BLOCKER: Issue #51 (convention-sveltekit-spa) -- explicitly declared dependency in Lineage. Issue #51 is still open. The convention-sveltekit-spa note does not exist in pal-e-docs. This ticket cannot proceed until the convention is written and approved. Board item #412 tracks this.
    • Board item #414 (adapter-static switch) -- downstream of this ticket. Correctly scoped as separate. No conflict.
    • Board item #297 (kanban prototype) -- in_progress, no conflict.
    • Keycloak client creation -- acceptance criteria says "Keycloak client pal-e-app created with correct redirect URIs." This is infrastructure work that should either be documented as a prerequisite or explicitly included in the file targets (e.g., a script or Terraform resource).

    Acceptance Criteria

    • [x] "keycloak-js handles login/logout/token refresh" -- testable via e2e
    • [x] "All routes load data client-side with Bearer tokens" -- testable via e2e
    • [x] "No +page.server.ts files remain" -- testable via find/glob
    • [x] "Auth.js fully removed from package.json and code" -- testable via grep
    • [x] "Public routes still work without login" -- testable via e2e
    • [x] "Authenticated routes redirect to Keycloak login" -- testable via e2e
    • [ ] "Keycloak client pal-e-app created with correct redirect URIs" -- NOT testable by agent without Keycloak admin access. Needs clarification: is this done via Terraform, manual admin console, or a script? Which redirect URIs?
    • [ ] MISSING criterion: "No $env/dynamic/private imports remain" -- server-only env vars must all be replaced with VITE_ prefixed public env vars
    • [ ] MISSING criterion: "Type imports resolve after api.ts removal" -- components using types from $lib/api must still compile

    Blast Radius

    • QuickJot component -- uses fetch('/api/notes') proxy route. When src/routes/api/ is removed, QuickJot breaks. Not mentioned in file targets.
    • Keycloak realm mismatch -- current auth.ts uses master realm. The issue says "pal-e realm." If the realm hasn't been created yet, this is additional infrastructure work not scoped in the ticket.
    • $env/dynamic/private to public env migration -- api.ts uses PAL_E_DOCS_API_URL from server-side env. The client-side replacement needs VITE_PAL_E_DOCS_API_URL or equivalent. This affects k8s deployment config (even though k8s/ is out of scope, the env var naming must be documented).
    • Type system -- 6 files import TypeScript types from $lib/api. The new api-client.js is JavaScript, not TypeScript. Type definitions need a new home.
    • No other pal-e repos affected -- this is frontend-only, no backend API changes needed.

    Recommendation

    Seven issues must be resolved before this ticket is READY:

    1. Add missing file targets: src/auth.ts, src/hooks.server.ts, notes/[slug]/edit/, tags/[name]/, QuickJot.svelte, slugCache.ts
    2. Document type migration strategy: Where do TypeScript types go when api.ts is removed? Recommend a new src/lib/types.ts
    3. Clarify Keycloak realm: Is it pal-e (stated) or master (current)? How is the client created?
    4. Add missing acceptance criteria: No $env/dynamic/private remaining; type imports resolve
    5. Add story label: Board item #413 needs a story:X label for traceability
    6. Wait for #51: convention-sveltekit-spa must be written first (declared dependency)
    7. Document env var naming: Which VITE_ env vars replace server-side env vars?
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- present but contains wrong issue reference (see Recommendation)
    • [x] Repo -- forgejo_admin/pal-e-app
    • [x] User Story -- well-formed
    • [x] Context -- clear motivation
    • [x] File Targets -- specific files listed with modify/remove/don't-touch sections
    • [x] Acceptance Criteria -- 6 items
    • [x] Test Expectations -- 3 items with run command
    • [x] Constraints -- 4 items
    • [x] Checklist -- present
    • [x] Related -- 3 references

    Traceability

    • [ ] story:X label -- MISSING. Platform convention migration, not directly user-facing. Foundational work -- acceptable if intentional, but should be explicitly noted.
    • [x] arch:app label -- present (arch:app, arch:deploy)
    • [x] Forgejo issue -- forgejo_admin/pal-e-app#53, open

    File Targets

    • [x] svelte.config.js -- verified: currently imports @sveltejs/adapter-node (line 1)
    • [x] Dockerfile -- verified: currently uses node:22-alpine multi-stage with Node.js runtime (27 lines)
    • [x] k8s/deployment.yaml -- verified: contains pal-e-auth-secrets secretRef (line 29-30), PAL_E_DOCS_API_URL env (line 32-33), PAL_E_DOCS_API_KEY env (line 34-38), containerPort 3000 (line 27), AUTH_TRUST_HOST env (line 39-40)
    • [x] package.json -- verified: has @sveltejs/adapter-node in devDependencies (line 18)
    • [x] k8s/pal-e-auth-secrets.enc.yaml -- verified: file exists, contains AUTH_SECRET
    • [x] Reference pattern ~/mcd-tracker-app/Dockerfile -- verified: nginx:alpine pattern exists and matches description

    Repo Placement

    OK. Issue is filed on forgejo_admin/pal-e-app and all file targets live in that repo. The k8s/ manifests are in-repo (not in pal-e-deployments), consistent with the Constraints section.

    Dependencies

    • ISSUE: Wrong dependency reference. The Lineage section says "Depends on forgejo_admin/pal-e-app #2" but issue #2 is "Scaffold SvelteKit app with board pages and dev environment" (closed). The actual prerequisite is issue #52 ("Migrate pal-e-app auth + data fetching to client-side"), which is open and in backlog (board item #413).
    • Server-side code is extensive. Verified 14 +page.server.ts files and 3 +server.ts API routes that import from $lib/api, which uses $env/dynamic/private (server-only). The src/hooks.server.ts wires Auth.js server-side handle. The src/auth.ts uses @auth/sveltekit with server-side JWT callbacks. All of this MUST be removed/migrated by issue #52 before this ticket can execute.
    • Board item #412 (issue #51: "Write convention-sveltekit-spa convention note") is referenced in Related. This convention note does not yet exist in pal-e-docs (search returned empty). Not a hard blocker but the spec this ticket claims to follow doesn't exist yet.
    • Board item #413 (issue #52: client-side auth migration) is in backlog. This ticket cannot move to next_up until #52 is at minimum in_progress or done.

    Acceptance Criteria

    • [x] "npm run build produces static files in build/ directory" -- verifiable by agent
    • [x] "Dockerfile builds nginx image that serves the SPA" -- verifiable via docker build
    • [x] "SPA fallback works" -- verifiable but test method not specified (needs curl or browser check)
    • [x] "k8s deployment has no secrets or server env vars" -- verifiable by grep on deployment.yaml
    • [ ] "ArgoCD syncs successfully with new image" -- NOT verifiable by agent in isolation. Requires deployed cluster. Should specify observable check command.
    • [x] "All existing routes work in production" -- verifiable via E2E tests

    Test Expectations include npm run build && npm run test:e2e which is correct. However, E2E tests currently run against the live deployment (PLAYWRIGHT_BASE_URL: https://pal-e-app.tail5b443a.ts.net in .woodpecker.yaml), not against a local build. The test command alone won't validate the nginx image serves correctly.

    Blast Radius

    • mcd-tracker-app already uses adapter-static + nginx pattern. Proven reference implementation. Low risk for the Dockerfile/adapter change itself.
    • westside-app and westside-contracts still use adapter-node. May follow the same pattern later. No immediate downstream impact.
    • Woodpecker CI (.woodpecker.yaml) -- build step runs npm run build (works for both adapters). The kaniko build pushes to pal-e-app/app matching Constraints. The update-deployment-tag step uses sed on k8s/deployment.yaml which still works. CI is safe.
    • @auth/sveltekit in package.json -- listed in dependencies (server-side auth library). The ticket says "don't touch src/" but package.json IS in scope. The agent will need clarity on whether @auth/sveltekit removal is this ticket's responsibility or was already handled by #52.

    Recommendation

    Three issues must be fixed before this ticket is READY:

    1. Fix Lineage reference -- Change "Depends on forgejo_admin/pal-e-app #2" to "Depends on forgejo_admin/pal-e-app #52" (client-side auth + data fetching migration).
    2. Add explicit blocker note -- This ticket CANNOT execute until issue #52 is complete. All 14 +page.server.ts files, 3 +server.ts API routes, hooks.server.ts, and auth.ts must be gone first. Consider adding: "BLOCKED: Do not start until #52 is merged and all server-side code is removed."
    3. Clarify package.json scope -- The File Targets say to swap adapter-node for adapter-static, but @auth/sveltekit is also in dependencies. Explicitly state whether @auth/sveltekit removal is in scope here or was already handled by #52.

    Optional improvements:

    • Add a story label if one applies, or note "foundational work" in the ticket.
    • Strengthen the ArgoCD acceptance criterion with a specific observable check command.
    • Note that convention-sveltekit-spa (#51) should ideally be written before this ticket executes, so the agent has the spec to follow.
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type — Feature
    • [x] Lineage — Related to phase-pal-e-platform-29-sveltekit-convention
    • [x] Repo — forgejo_admin/pal-e-app
    • [x] User Story — well-formed As/I want/So that
    • [x] Context — explains mcd-tracker-app as proven pattern, Phase 29 origin, capacitor-mobile dependency
    • [x] File Targets — documentation-only with reference files listed
    • [x] Acceptance Criteria — 4 criteria, all verifiable
    • [x] Test Expectations — includes exact MCP tool commands
    • [x] Constraints — extract-only, follow convention-frontend-css style, cover Capacitor detection
    • [x] Checklist — 3 items
    • [x] Related — 4 related notes listed

    Traceability

    • [ ] story:X label — MISSING. Board item #412 has labels type:feature,arch:convention but no story: label. This work serves the developer-building-apps user story. Needs a story label (e.g. story:dev-execute or a capacitor-specific story).
    • [x] arch:convention label — present on board item
    • [x] Forgejo issue — forgejo_admin/pal-e-app#51, open, valid

    File Targets

    • [x] ~/mcd-tracker-app/svelte.config.js — verified: adapter-static with fallback: 'index.html', strict: true
    • [x] ~/mcd-tracker-app/src/lib/keycloak.js — verified: keycloak-js, PKCE (S256), check-sso init, Capacitor platform detection via getBaseUrl(), auto token refresh
    • [x] ~/mcd-tracker-app/src/lib/api.js — verified: client-side fetch with Bearer token from keycloak, VITE_API_URL env var with fallback
    • [x] ~/mcd-tracker-app/Dockerfile — verified: multi-stage node:22-alpine build, nginx:alpine serving, SPA fallback try_files, static asset caching

    Repo Placement

    Issue filed on forgejo_admin/pal-e-app. The work creates a pal-e-docs convention note — no code files are modified. The repo choice is acceptable since the convention directly serves pal-e-app architecture, and sibling items #413/#414 on the same repo consume this convention. However, Phase 29 (the parent phase) lives on board-pal-e-platform, not board-pal-e-docs. This is a minor mismatch but not blocking — the board item itself is correctly placed on board-pal-e-docs (id: 412).

    Dependencies

    • Downstream (undocumented): Board item #413 ("Migrate pal-e-app auth + data fetching to client-side") and #414 ("Switch pal-e-app to adapter-static + nginx") both depend on this convention existing. Neither has a depends:412 label.
    • Parent phase: Phase 29 (board item #286 on board-pal-e-platform) lists this as Deliverable 1. Phase is in backlog column.
    • Blocked consumer: project-capacitor-mobile > sveltekit-spa-configuration section is confirmed empty (0 content blocks), waiting for this convention content.
    • No upstream blockers: All reference files exist and contain the expected patterns. No blocking dependencies.

    Acceptance Criteria

    All 4 acceptance criteria are agent-verifiable via MCP tools. Test expectations include exact tool calls (get_note, get_section). The "Run command: N/A" is appropriate for documentation-only work. One gap: no criterion verifies that the convention covers all 6 topics listed (stack, auth, CSS, data fetching, routing, API config) — the criteria only check existence and that it "covers" them, which is subjective. An agent could create a skeleton note and pass. Consider adding section-level verification.

    Blast Radius

    • westside-app also uses adapter-static (confirmed in svelte.config.js). The convention should be written to cover this second consumer, but the issue only references mcd-tracker-app as the reference implementation. Not blocking, but the agent should be aware.
    • pal-e-app currently uses adapter-node — items #413/#414 would migrate it to match this convention.
    • westside-contracts uses adapter-node — different pattern (server-side), not a consumer.
    • convention-frontend-css exists (verified) and provides good format reference as the ticket claims.

    Recommendation

    Two items to fix before READY:

    1. Add story: label to board item #412. Suggested: story:dev-execute or create a new story for convention documentation work.
    2. Add dependency labels on items #413 and #414: depends:412 (or equivalent), so the board reflects execution order.

    Optional improvements (not blocking):

    • Mention westside-app as a second consumer in the Context or Related section of the issue.
    • Add section-level acceptance criteria (e.g., "convention has sections for: stack, auth, css, data-fetching, routing, api-config").
  • Verdict: NEEDS_REFINEMENT

    Re-Review Summary (2026-03-24)

    Re-reviewing after refinement comment #6708. Two of three fixes verified; one remains unresolved.

    • Fix 1 (session-start-context.sh) — VERIFIED. File exists at ~/.claude/hooks/session-start-context.sh (576 lines). Reads board API at lines 157 (GET /boards/{slug}/items?column=in_progress), 398 (GET /boards/{slug}/items), 446 (GET /boards/{slug}/items). Consumes .column, .title, .item_type fields from response JSON. Correctly proposed as verify-only target.
    • Fix 2 (method count) — VERIFIED. SDK BoardsMixin has 12 methods: list_boards, create_board, get_board, update_board, delete_board, sync_board, get_backlog, list_board_items, add_board_item, update_board_item, delete_board_item, bulk_move_items. Docstring on line 12 says "11 endpoints" — needs correction. Refinement correctly identifies the fix.
    • Fix 3 (#316 board state) — NOT RESOLVED. Refinement comment says "Already resolved — board item #316 is in done column. Reviewer saw stale state." However, board item #316 is currently in todo (position 2). Forgejo issue #197 is closed. The board-item-on-merge hook either did not fire or failed silently. Board item must be moved to done before this ticket starts.

    Template Completeness

    • [x] Type
    • [x] Lineage (variant of Plan — acceptable)
    • [x] Repo (3 repos correctly listed)
    • [x] User Story
    • [x] Context
    • [x] File Targets (6 files + 1 verify-only from refinement = 7)
    • [x] Acceptance Criteria (5 items)
    • [x] Test Expectations (3 items with real commands)
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    File Targets

    • [x] pal-e-docs-mcp/src/pal_e_docs_mcp/tools/boards.py — verified: 12 MCP tools present
    • [x] pal-e-docs-sdk/src/pal_e_docs_sdk/boards.py — verified: 12 methods (docstring says 11, needs fix)
    • [x] ~/.claude/hooks/boards-config.sh — verified: 6 board slugs, 21 lines
    • [x] ~/.claude/hooks/board-item-on-merge.sh — verified: sources boards-config.sh, searches boards via REST API
    • [x] ~/.claude/hooks/check-board-item.sh — verified: validates create_board_item tool calls
    • [x] ~/.claude/hooks/session-start-board-sync.sh — verified: syncs all boards via POST /boards/{slug}/sync
    • [x] ~/.claude/hooks/session-start-context.sh — verified (from refinement): verify-only target, reads .column/.title/.item_type at 3 locations

    Repo Placement

    OK. Issue filed on forgejo_admin/pal-e-docs (parent project repo) targeting three other repos: pal-e-docs-mcp, pal-e-docs-sdk, claude-custom. Repo field and Constraints correctly identify all three with one PR per repo.

    Dependencies

    • #195 (Add "board" to NoteType) — board item #314 in done. Forgejo issue closed. OK.
    • #196 (Add board_note_id FK + data migration) — board item #315 in done. Forgejo issue closed. OK.
    • #197 (Update board API for board notes) — board item #316 still in todo despite Forgejo issue being closed. Stale board state persists. Must be moved to done.
    • #199 (Drop legacy boards table) — board item #318 in backlog. Downstream. Correctly sequenced.
    • #208 (Refactor sync_board) — board item #327 in backlog. Related but independent.

    Acceptance Criteria

    All 5 acceptance criteria are testable. Test commands verified:

    • pytest tests/test_param_alignment.py -v — file exists in pal-e-docs-mcp
    • pytest tests/ -v — SDK has test_boards.py and integration/test_boards.py
    • Manual merge-and-verify appropriate for hook testing

    Note: No acceptance criterion covers session-start-context.sh compatibility. Consider adding one.

    Blast Radius

    • session-start-context.sh — now in scope as verify-only target (from refinement). Risk is mitigated if response fields are additive (they are).
    • pal-e-app — SvelteKit frontend consumes same REST API. Not in scope (correct). Contract stabilized by prerequisite #197.
    • Hook count — Context section says "4 hooks" but there are 4 hooks + 1 config file, plus the newly-scoped session-start-context.sh = 5 hooks + 1 config. Minor editorial inaccuracy in Context section.

    Recommendation

    One action remains before this ticket is READY:

    1. Move board item #316 to done — Forgejo issue #197 is closed but the board item is still in todo. The refinement claimed this was resolved, but current board state shows otherwise. Run: update_board_item(board-pal-e-docs, 316, column="done").

    Fixes 1 and 2 (session-start-context.sh as verify-only target, SDK method count correction) are verified and adequately documented in the refinement comment. Once #316 is moved to done, this ticket is READY.

  • Verdict: READY

    Re-review after architectural decisions posted in comment #6640. All three NEEDS_REFINEMENT blockers resolved.

    Template Completeness

    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    File Targets

    • [x] src/pal_e_docs/routes/boards.py — verified: exists, 636 lines, 14 @router endpoints. _board_to_out (line 26) and _item_to_out (line 46) are the core rewrite targets.
    • [x] src/pal_e_docs/schemas.py — verified: BoardOut at line 269, BoardItemOut at line 243. Additive fields (note_id on BoardOut, board_note_id on BoardItemOut) are straightforward schema additions.
    • [x] src/pal_e_docs/routes/notes.py — correctly listed as do-not-touch
    • [x] src/pal_e_docs/models.py — correctly listed as do-not-touch; BoardItem.board_note_id FK confirmed at line 259 with relationship at line 282
    • [x] scripts/migrate_boards_to_notes.py — verified: exists, prerequisite documented in Decision 2

    Repo Placement

    OK — issue filed on forgejo_admin/pal-e-docs, which owns boards.py and schemas.py. Single-repo change.

    Decisions Resolved

    # Blocker Decision Verified
    1 Unresolved architecture (keep routes vs. alias) Keep all 14 /boards/{slug} routes at same URLs, query notes WHERE note_type='board' internally. Boards table stays during this ticket; removal is #199. Sound — all 14 endpoints confirmed in boards.py, no route changes needed. Clean separation from #199.
    2 Migration script prerequisite undocumented Agent must document prerequisite in PR body. Code must handle NULL board_note_id gracefully (log warning, skip). Deploy order: run script then deploy. Sound — scripts/migrate_boards_to_notes.py exists. NULL handling is a safety net the agent can implement in _item_to_out and query filters.
    3 Response contract ambiguous (board_id/note_id) Additive: BoardOut gains note_id (board note ID), existing id stays (boards table ID). BoardItemOut keeps board_id, gains board_note_id. All existing fields preserved. Final cleanup deferred to #199. Sound — test suite (1010 lines) does not assert board_id on BoardItemOut responses. Additive fields won't break existing tests or consumers (SDK, MCP, frontend).

    Dependencies

    • #195 (Add board to NoteType) — board item #314, column: done. Prerequisite satisfied. Confirmed: "board" exists in schemas.py NoteType literal (line 23).
    • #196 (Add board_note_id FK) — board item #315, column: done. Prerequisite satisfied. Confirmed: FK at models.py line 259.
    • Migration scriptscripts/migrate_boards_to_notes.py exists, must run before deploy. Decision 2 documents this.
    • #198 (MCP/SDK/hooks update) — board item #317, backlog, position 3. Downstream of this ticket. Correctly sequenced.
    • #199 (Drop legacy boards table) — board item #318, backlog, position 4. Final cleanup. Correctly sequenced.
    • #208 (Refactor sync_board) — board item #327, backlog. Independent concern (plan discovery from notes vs. board items). No ordering conflict with this ticket.

    Acceptance Criteria

    Updated acceptance criteria from comment #6640 are sufficient for agent execution:

    • [x] GET /boards returns notes where note_type="board" internally, same response shape + note_id — testable
    • [x] GET /boards/{slug}/items queries via board_note_id, same response shape + board_note_id — testable
    • [x] All 14 endpoints work — now backed by the specific architecture decision (keep routes, query notes internally)
    • [x] sync_board works with board_note_id path — testable
    • [x] NULL board_note_id handled gracefully — testable (create board item without migration, verify warning not crash)
    • [x] PR documents migration prerequisite — verifiable in PR body

    Optional (non-blocking): Cross-board endpoints (GET /boards/backlog/items, GET /boards/activity) still join Board→Project. The agent should switch these joins to Note→Project via board_note_id. This is implicit in "all 14 endpoints work" but could be called out explicitly. Not a blocker — the agent will encounter these joins when rewriting.

    Blast Radius

    • Test suite — 1010 lines, 40+ test functions. No direct assertions on board_id in response payloads. Additive schema change (new fields, existing preserved) minimizes breakage risk. Tests create boards via POST /boards; the create endpoint must produce both a Note and a Board row during transition.
    • SDK/MCP/frontend — downstream consumers unaware of board_note_id. Additive response contract means zero breakage. Full consumer update deferred to #198.
    • sync_board / sync_issues — both currently query Board.id. Must switch to board_note_id-based lookups. Covered by "all 14 endpoints."
    • _board_to_out / _item_to_out helpers — core rewrite targets. Agent will need to map Note fields to BoardOut (note.id→note_id, board.id→id, note.slug→slug) and add board_note_id to BoardItemOut. Straightforward from Decision 3.

    Recommendation

    No action needed — ticket is READY for agent execution. All three blockers resolved with clear, verifiable decisions. The issue body should ideally be updated to incorporate the decisions from comment #6640, but the agent can read both issue body and comments, so this is not a blocker.

    One optional improvement: explicitly call out cross-board endpoints (backlog/items, activity) in acceptance criteria. The agent will discover these during implementation regardless.

  • Verdict: READY

    Re-review after NEEDS_REFINEMENT. Both issues from the first review have been addressed in refinement comment #6581.

    Template Completeness

    • [x] Type
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    File Targets

    • [x] src/pal_e_docs/models.py — verified: BoardItem model at line 252, board_id FK at line 256. No board_note_id column exists yet. Board model at line 234.
    • [x] alembic/versions/ — verified: directory exists with 19 migrations. Last migration is p6k7l8m9n0o1_add_project_updated_at.py.
    • [x] scripts/migrate_boards_to_notes.py — verified: scripts directory exists with 2 existing scripts. New script filename explicitly specified in refinement comment.
    • [x] src/pal_e_docs/routes/boards.py — verified: correctly excluded from scope. Has 16 references to board_id that will need updating in follow-up ticket (#197).

    Repo Placement

    OK. Forgejo issue filed on forgejo_admin/pal-e-docs, which is the correct repo for model and migration changes.

    Dependencies

    • Upstream (satisfied): Board item #314 / Forgejo issue #195 ("Add board to NoteType") — closed and merged. NoteType Literal in schemas.py line 23 includes "board". VALID_STATUSES in routes/notes.py line 62 includes board: ["active", "archived"].
    • Downstream: Board items #316 (#197, "Update board API"), #317 (#198, "Update MCP + SDK + hooks"), #318 (#199, "Drop legacy boards table") — all in backlog, correctly sequenced after this ticket.
    • No conflicts with in_progress items.

    Acceptance Criteria

    All 5 criteria are testable by an agent:

    • board_note_id column + FK — verifiable via alembic migration + model inspection
    • Board notes created — verifiable via SQL or API query after migration
    • board_note_id populated — verifiable via SQL query
    • Dual FK — verifiable by checking both columns exist and are populated
    • Slug/name/project_id match — verifiable via SQL join or API comparison

    Test command pytest tests/test_boards.py -v is valid — file exists at tests/test_boards.py.

    Blast Radius

    • board_id is referenced 16 times in routes/boards.py — correctly deferred to issue #197.
    • schemas.py line 245 has board_id: int in BoardItemResponse — will need a new board_note_id field eventually, but that is #197 scope.
    • SDK and MCP repos do not reference board_id directly — no blast radius there.
    • Note slug uniqueness: boards table has its own unique slug constraint (models.py line 238), notes table has its own (line 98). No existing notes have board-prefixed slugs (verified via search). Migration will create them without conflict.
    • Existing test file tests/test_boards.py does not reference board_id directly — no test breakage expected from adding the new column.

    Refinement Verification

    Both issues from the first review (NEEDS_REFINEMENT) have been addressed in comment #6581:

    1. Board count: Corrected from "6 active boards" to "13 existing boards". Verified: list_boards returns exactly 13 boards (IDs 1-14, no ID 9).
    2. Script filename: Explicitly specified as scripts/migrate_boards_to_notes.py.

    Note for Agent

    The issue body was NOT updated — refinements exist only in comment #6581. The executing agent must read the issue comments to get the corrected board count (13, not 6) and the explicit script filename (scripts/migrate_boards_to_notes.py).

    Recommendation

    No further action needed. Ticket is READY for execution. The agent should read both the issue body and comment #6581 for the corrected values.

  • Review: Add "board" to NoteType review-314-2026-03-24

    Verdict: READY

    Template Completeness

    • [x] Type
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets (includes both modify and do-not-touch lists)
    • [x] Acceptance Criteria (4 items)
    • [x] Test Expectations (4 items, references existing test file)
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    File Targets

    • [x] src/pal_e_docs/schemas.py — verified: NoteType Literal at lines 6-23, currently 15 types, no "board" present
    • [x] src/pal_e_docs/routes/notes.py — verified: VALID_STATUSES dict at lines 45-62, entries for all 15 current types, no "board" entry
    • [x] src/pal_e_docs/routes/boards.py — verified exists, correctly marked as do-not-touch
    • [x] src/pal_e_docs/models.py — verified exists, correctly marked as do-not-touch
    • [x] tests/test_note_type_enum.py — verified exists, follows parametrized pattern suitable for adding "board" type tests

    Repo Placement

    OK — Issue #195 is filed on forgejo_admin/pal-e-docs and all file targets are in that repo. Single-repo change, no cross-repo coordination needed.

    Dependencies

    • Board item #314 is in todo column, position 0 — first in the kanban-daily-review story sequence.
    • Downstream items with same story label (all in backlog, sequenced): #315 (board_note_id FK, 5pts, pos 1), #316 (board API update, 5pts, pos 2), #317 (MCP+SDK+hooks, 3pts, pos 3), #318 (drop legacy table, 2pts, pos 4).
    • No blockers — #314 has no upstream dependencies.
    • #294 (Remove sprint note type) is in_progress — no conflict. Removing a type and adding a type are independent changes to the same Literal, but no merge conflict risk since they touch different lines.

    Acceptance Criteria

    All four criteria are testable by an agent:

    • POST /notes with note_type="board" — directly testable via API call
    • Board notes accept statuses active/archived — testable via API call with status param
    • Existing board functionality unchanged — existing test suite covers this
    • No regression in existing note types — existing parametrized tests in test_note_type_enum.py cover this

    Test command pytest tests/test_note_type_enum.py -v is valid — file exists at that path.

    Blast Radius

    • SDK (pal-e-docs-sdk): Uses str for note_type, not a Literal. No SDK changes needed — "board" passes through as-is.
    • MCP (pal-e-docs-mcp): Field descriptions at lines 164, 228, 297 hardcode the note_type list. Already stale — missing reference, incident, journal, post, milestone. Pre-existing drift, not introduced by this ticket. Downstream ticket #317 covers MCP updates.
    • Board API: routes/boards.py and BoardItemType enum in models.py are unaffected — this ticket only adds to NoteType, not BoardItemType.
    • pal-e-app frontend: No frontend impact — the app renders note_type as a string, no hardcoded type list.

    Recommendation

    No action needed — scope is solid, all file targets verified, dependencies are documented via the story label sequence, and blast radius is contained. Ready for agent execution.

    Note

    Discovered: "review" is not a valid note_type. The skill-review-ticket spec calls for note_type: review but the NoteType Literal does not include it. This review note was created as doc type instead. Consider adding "review" to NoteType in a future ticket.

  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    All required template sections present. Well-structured issue.

    File Targets

    • [x] src/pal_e_docs/routes/boards.py — verified exists. However, update_board_item (line 611) and BoardItemUpdate schema (line 295 in schemas.py) already support title. AC #1 is already satisfied at the API layer.
    • [ ] src/pal_e_docs/services/board_sync.py — ISSUE: file does not exist. Sync logic lives in src/pal_e_docs/routes/boards.py (function sync_board at line 251). File target is wrong.
    • [x] src/pal_e_docs/routes/notes.py — verified exists; correctly marked as do-not-touch.

    Missing File Target: MCP Layer

    The actual gap is in pal-e-docs-mcp, not pal-e-docs. The MCP tool update_board_item in pal-e-docs-mcp/src/pal_e_docs_mcp/tools/boards.py (line 209) does not expose a title parameter, even though both the SDK (pal-e-docs-sdk/src/pal_e_docs_sdk/boards.py line 136) and the API already support it. This is the root cause of the reported inability to update titles programmatically.

    Repo Placement

    MISMATCH. The Forgejo issue is filed on forgejo_admin/pal-e-docs. The fix has two parts:

    • sync_board title drift — correct repo (pal-e-docs, in routes/boards.py)
    • MCP title param — wrong repo. Fix belongs in forgejo_admin/pal-e-docs-mcp

    This means the issue either needs to be split into two Forgejo issues (one per repo), or scoped down to only the sync_board fix (since the API already works). The MCP gap needs its own issue on pal-e-docs-mcp.

    Dependencies

    • No blocking dependencies found on the board.
    • Item #48 (MCP sprint tools cannot clear points/labels) is tangentially related to board API but not a blocker.
    • The SDK already supports title — no SDK change needed.

    Acceptance Criteria

    • AC #1 (PUT /boards/{slug}/items/{id} accepts optional title): Already implemented. The endpoint is PATCH, not PUT — minor inaccuracy. Verified: BoardItemUpdate.title exists at schemas.py:295, handler at boards.py:611.
    • AC #2 (sync_board detects title drift on phase items): Valid gap. sync_board lines 298-303 only check column drift. Note: sync_issues already handles title drift for issue-type items (lines 394-399), so the pattern exists and can be followed.
    • AC #3 (existing items without title changes are unaffected): Testable, valid criterion.

    AC #1 should be rewritten to target the MCP layer, or removed if that becomes a separate issue.

    Test Expectations

    • Test command pytest tests/ -k board is valid.
    • Existing test test_sync_sets_title_from_phase_note covers initial title population but not title drift on re-sync.
    • A test for title drift detection on phase re-sync is the correct test to add.
    • MCP-layer tests would need to live in pal-e-docs-mcp repo, not here.

    Blast Radius

    • sync_issues already handles title drift for issue-type items — no blast radius there.
    • sync_board only affects phase-type items linked to plan notes. Low blast radius.
    • The MCP gap affects all MCP consumers (Claude agents). Until the MCP tool exposes title, no agent can programmatically update board item titles despite the API supporting it.

    Recommendation

    Three specific actions before this ticket is READY:

    1. Fix file target: Replace src/pal_e_docs/services/board_sync.py with src/pal_e_docs/routes/boards.py (function sync_board at line 251).
    2. Split or rescope: AC #1 is already done at the API/SDK layer. The real gap is the MCP tool in pal-e-docs-mcp. Either (a) remove AC #1 and scope this issue to only the sync_board title drift fix, or (b) split into two issues — one for sync_board (pal-e-docs) and one for MCP title param (pal-e-docs-mcp).
    3. Minor fix: AC #1 says "PUT" but the endpoint is PATCH. Correct the HTTP method.
Plan 14
  • Vision

    Replace Jinja with SvelteKit so that note_type drives the renderer. Static HTML for docs, interactive components for boards. One frontend, one backend, one URL. The sprint board — one board per project — is the killer feature that justifies the migration and proves the pattern.

    Organizing principles:

    • One plan per project. All actionable work lives here. TODOs and bugs link to phases via parent_slug. Orphan TODOs = untriaged inbox.
    • One board per project. Boards replace sprints. Permanent kanban, not time-boxed. Column position = scoping depth (Backlog → Todo → Next Up → In Progress → Done).
    • note_type drives rendering. A board note renders as an interactive SvelteKit component. A doc note renders as static HTML. Same system, different renderers.
    • Playground-first. Design experiments in html-playground before committing to production repos.
    • Docker Compose for dev. Postgres + API + frontend containers. No cluster dependency during development.

    Projects & Repos Touched

    Repo Platform Role
    forgejo_admin/pal-e-docs (future: pal-e-api) Forgejo FastAPI backend — notes, blocks, projects, boards, search
    pal-e-app (new) Forgejo SvelteKit frontend — renders notes, interactive boards
    forgejo_admin/pal-e-docs-mcp Forgejo MCP server — board/sprint tool updates
    forgejo_admin/pal-e-docs-sdk Forgejo Python SDK — board/sprint method updates
    forgejo_admin/html-playground Forgejo Design experiments for board UI

    Context

    Act 1 (SQLite → Postgres) and Act 2 (Knowledge Engine — blocks, search, compiled pages) are complete. The platform has 262 notes, 32 MCP tools, full-text search, and a block content model. But the frontend is still Jinja server-rendered templates with zero interactivity. Sprint management exists as API + MCP tools but the multi-board/time-boxed sprint model proved wrong — projects need their own cadence and their own boards. This plan unifies the SvelteKit migration and the board redesign into one effort.

    Previous Plan

    plan-2026-02-26-tf-modularize-postgres — Act 1 + Act 2 (completed). plan-2026-03-01-pal-e-sprints — sprint backend (completed). plan-2026-03-03-sprint-workflow-automation — DORA instrumentation (completed).

    Depends On

    • Block content model (Phase 7) — COMPLETED
    • Sprint tables + API (Phase 1 of sprints backend) — COMPLETED
    • Full-text search (Phase 5) — COMPLETED

    Decisions Made

    Decision Rationale
    Boards replace sprints. One board per project. Projects have different cadences. A cross-project sprint doesn't map to how work actually gets done.
    Board columns are a scoping pipeline Backlog (fuzzy) → Todo (defined) → Next Up (scoped) → In Progress (issue exists, agent spawned) → Done.
    note_type drives rendering Unifies static docs and interactive components in one system. Sprint board is the first interactive renderer.
    pal-e-docs = project, not repo pal-e-api = backend repo. pal-e-app = frontend repo. Eliminates naming confusion.
    Sprint SDK/MCP rename deferred Build new stuff against current API first. Renames are high blast radius, low immediate value.
    Board data stays in pal-e-docs DB Unified backend. boards + board_items tables replace sprints + sprint_items.
    One plan per project, TODOs parent to phases Prevents orphan work. All actionables tracked under one plan. Orphan check = TODOs with null parent_slug.

    Phases

    See child phase notes: list_notes(parent_slug="plan-pal-e-docs")

    Key Files

    TBD — will be populated as phases complete.

    Verification

    • Board renders in SvelteKit with real data from pal-e-docs API
    • Drag-and-drop moves items between columns
    • note_type determines renderer (board vs static HTML)
    • Docker Compose dev environment works without cluster
    • All open TODOs have a parent_slug pointing to a phase

    Next Plan Seeds

    • DORA dashboard — once board tracks work, Grafana visualizes velocity
    • Semantic search UI — search bar with keyword/semantic/hybrid modes
    • In-browser editing — block-level CRUD UI for notes
    • Quick-jot + inbox — rapid note creation, triage workflow
    • project-pal-e-docs — project page (needs update)
    • plan-2026-03-01-pal-e-sprints — completed predecessor
    • plan-2026-03-03-sprint-workflow-automation — completed predecessor
    • plan-2026-02-26-tf-modularize-postgres — completed predecessor

    Epilogue

    • PR #166 nit: inline import patternfrom pal_e_docs.routes.boards import _status_to_column inside update_note. Consider shared module.
    • PR #166 nit: broad exception catchexcept Exception could be narrowed.
    • PR #166 nit: position=0 for all synced items — Consider using phase.position.
    • PR #166 nit: duplicated test helpers — Need shared conftest fixtures.
    • PR #166 nit: falsy check on status — Status comparison could be more explicit.
    • Frontend nit: $lib/constants.ts needed — RESOLVED. Extracted to $lib/columns.ts (commit 0c4019a).
    • PR #17 nit: URL sync effect cycles$effect fires redundantly. Consider debounce or guard.
    • PR #17 nit: SvelteURLSearchParams — Use plain URLSearchParams where reactivity not needed.
    • PR #20 nit: updated_at as column entry proxy — Known API limitation for stuck detection.
    • PR #20 nit: inline border-left style — Competes with Tailwind class.
    • PR #21 nit: duplicate listProjects() — Layout + home both fetch projects.
    • PR #21 nit: unsanitized body HTML — Quick-jot relies on backend sanitization only.
    • PR #21 nit: SELECT in isInput guard — Affects all keyboard shortcuts.
    • PR #24 nit: missing .env.example — Auth env vars undocumented for local dev.
    • PR #24 nit: hardcoded Tailscale URLs — Low priority given tailnet-only deployment.
    • PR #24 nit: @ts-expect-error — Should use proper type augmentation in app.d.ts.
    • PR #24 nit: unused accessToken — Stored in JWT callback but never consumed. Inflates cookie size.
    • PR #24 nit: missing plan slug in PR body — PR template compliance.
    • F12 nit: embedding worker NetworkPolicy gap — Worker pod not covered by app=pal-e-docs policy. Needs scoped policy in pal-e-deployments. (PR #90 QA)
    • F12 nit: PromQL float equalityembedding_total == 0 uses float equality. Consider threshold. (PR #90 QA)
    • F13b-1 nit: sequential board queries — Could parallelize with background jobs. (PR #114 QA)
    • F13b-1 nit: dirname resolution — Use resolved HOOK_DIR instead of dirname "$0". (PR #114 QA)
    • MCP labels nit: normalization duplication — Labels join+strip logic duplicated in create and update. Extract helper. (PR #42 QA)
    • SDK labels type mismatchpal-e-docs-sdk add_board_item types labels as list[str] | None instead of str | None. Issue #30. (PR #42 discovered scope)
    • DEFERRED: Phase 5b-2 deployforgejo-api-token k8s secret needed in pal-e-docs-secrets for Forgejo issue sync to work in prod. Code merged (PR #171).
    • DEFERRED: Phase 5b-3 stale detectionstale_at field, periodic sweep, stuck-item flagging. Issue TBD.
    • DEFERRED: Phase 6 — Token Metrics — Token usage tracking per sprint item. Needs observability foundation (DORA correlation).
    • DEFERRED: Phase 8 — Repo Renames — pal-e-docs → pal-e-api, pal-e-docs-mcp → pal-e-mcp, etc. High blast radius, low immediate value.
    • DEFERRED: Phase 9 — Jinja Sunset — Remove legacy Jinja templates. Low priority — SvelteKit frontend is primary.
    • DEFERRED: Phase 10 — Orchestration Automation — Auto-QA trigger, auto-dev-respawn. Auto-board-sync already absorbed by Agency Phase 11.
    • PR #185 nit: write endpoints lack auth gating — POST/PUT/DELETE on /projects and /boards have no auth check. Pre-existing, out of scope for #184. Should be tracked. (F14 QA)
    • PR #185 nit: /projects/{slug}/notes no project-level is_public gate — Endpoint returns public notes even if the project itself is private. Documented intentionally in test. (F14 QA)
    • PR #39 nit: deleteBoardItem DRY — duplicates header construction instead of using apiFetch. (F14 QA)
    • PR #39 nit: slugCache auth-state keying — Cache stores slugs from most-privileged request. Anonymous request within 60s TTL could see private slugs in cache (note-detail endpoint still filters). (F14 QA)
    • PR #39 nit: sign-in CSS duplication.contact-btn and .login-submit are 99% identical. Merge into single class. (F14 QA) → Addressed by F11a subphase.
    • PR #39 nit: hardcoded portfolio URLhttps://portfolio.tail5b443a.ts.net hardcoded in signin page. Consider env var. (F14 QA)
  • Plan: Knowledge Architecture plan-2026-03-16-knowledge-architecture

    Plan: Knowledge Architecture

    Vision

    Milestones become the structural boundary for plans. One active milestone per project, one plan per milestone. Completed milestones tier their children out of hot queries. Gapped positions eliminate cascading shifts. Projects scale indefinitely without degrading session performance.

    Projects & Repos Touched

    Project Repo What changes
    pal-e-docs pal-e-docs NoteType enum, VALID_STATUSES, list_notes tiering, position logic
    pal-e-docs pal-e-docs-sdk SDK methods for milestone + tier params
    pal-e-docs pal-e-docs-mcp MCP tools for milestone + tier params
    pal-e-agency claude-custom Session injection hook updates
    pal-e-agency Template + convention notes (docs only)

    Context

    Discovered during pal-e-agency audit on 2026-03-16. Token cost analysis showed list_notes returning 84K chars (~21K tokens) for pal-e-agency. Plan sprawl: plan-pal-e-docs at 26 phases, plan-wkq at 19 phases. Doc drift: 6 specialized agent notes active in docs but consolidated to 5-agent model in code. Root cause: no structural boundary for completed work. Milestones existed decoratively on project pages but had no note_type, no lifecycle, no hierarchy enforcement.

    Previous Plan

    plan-pal-e-docs — Interactive Knowledge Platform. Still active for F11 (Design System) and F13 (Context Intelligence). This plan does not replace it — it covers new scope that plan-pal-e-docs was never designed for. Once F11 and F13 complete, plan-pal-e-docs will be marked completed.

    Depends On

    • pal-e-docs semantic search (LIVE) — vectors enable cold-tier discoverability after notes leave hot queries
    • Block-first access pattern (LIVE) — makes large plans manageable even before tiering

    Decisions Made

    • Convention-first, enforce later. Milestone hierarchy is a convention before it's a hook. Manual enforcement until pattern is proven.
    • Pragmatic for history, clean going forward. Completed plans stay flat. New work uses milestone parents.
    • Don't reparent in-flight work. F11 and F13 finish under plan-pal-e-docs. New work goes under new milestones.
    • Gapped integers, not floats. Position spacing of 1000. Periodic rebalance when gaps collapse. No float precision issues.
    • Tiering derived from milestone status, not a separate column. Completed milestone → children are cold. No new DB column needed initially.
    • Dogfood before generalizing. pal-e-docs uses milestones first. Other projects adopt after the pattern is proven.

    Phases

    See child phase notes: list_notes(parent_slug="plan-2026-03-16-knowledge-architecture")

    Key Files

    File What
    pal-e-docs/src/pal_e_docs/routes/notes.py NoteType enum, VALID_STATUSES, list_notes filtering
    pal-e-docs/src/pal_e_docs/models.py Note.position, Block.position
    pal-e-docs/src/pal_e_docs/routes/blocks.py Block position/insert logic
    claude-custom/hooks/session-start-context.sh Session injection — milestone-aware filtering
    pal-e-docs-sdk/src/pal_e_docs_sdk/notes.py SDK list_notes tier param
    pal-e-docs-mcp/src/pal_e_docs_mcp/tools/notes.py MCP list_notes tier param

    Verification

    • list_notes(project="pal-e-agency") returns ~60 notes instead of ~131 (cold-tier excluded by default)
    • Session startup token injection drops by ~50% for mature projects
    • Block insert between positions 19000 and 20000 requires zero cascading shifts
    • agent-spawn-conventions and agent-workflow agree on agent count and model
    • New project creation starts with Milestone 1, not a bare plan

    Epilogue

    Phase 3 QA nits (PR #189):

    • Stale docstring in parser.py lines 33/38 — still references "0-based ordering" and "paragraph-3" examples. Should say "gapped" and "paragraph-1000".
    • _seed_note_with_blocks in test_blocks_api.py — add comment clarifying intentional use of legacy sequential positions for backward compat testing.

    Phase 4 QA nits (PR #191):

    • Missing note_type + cold composition test (e.g., list_notes(note_type=phase) with mix of cold/warm phases).
    • Inaccurate _seed_notes docstring — says it returns a dict but returns None.
    • resolved status not in COLD_STATUSES — consider adding if the status is used in practice.

    • milestone-2026-03-16-knowledge-architecture — parent milestone
    • plan-pal-e-docs — predecessor plan (still active for F11, F13)
    • plan-pal-e-agency — agency plan whose Phase 12 doc drift motivated this work
  • Plan: Private Notes & Browse Auth plan-2026-02-25-private-notes-auth

    Plan: Private Notes & Browse Auth

    Vision

    pal-e-docs is the single coordination hub for the pal-e AI agency. The browse frontend at https://pal-e-docs.tail5b443a.ts.net/browse/ is publicly accessible via Tailscale Funnel. Personal/private notes must be invisible to unauthenticated visitors and only visible to approved logged-in users.

    Projects & Repos Touched

    Project/RepoPlatformRole in this plan
    pal-e-docsForgejoAuth implementation, frontend filtering, migration
    pal-e-docs-mcpForgejoNo changes — API stays unauthenticated (Tailscale-only)

    Context

    The browse frontend is public via Tailscale Funnel. The Note model already has an is_public field, but it is not enforced — all notes are visible to all visitors regardless of this flag. There is no auth system. Forgejo issue #2 tracks auth integration.

    User wants a private project for personal notes (poems, reflections, private thoughts) with a simple workflow: "hey claude private note" → creates a numbered note in the private project.

    What's already done:

    • [x] is_public field on Note model
    • [x] is_public in NoteCreate, NoteUpdate, NoteOut schemas
    • [x] issue-xss-safe-filter already flagged as open issue
    • [x] Auth system (PR #27 merged)
    • [x] Browse frontend filters by is_public
    • [x] Users table with bcrypt hashing
    • [ ] Private project and convention not yet created

    Previous Plan

    plan-2026-02-24-docs-foundation

    Depends On

    None — can proceed independently.

    Decisions Made

    DecisionRationale
    SQLite for users table, not Postgrespal-e-docs already uses SQLite; single-user/small-group auth doesn't need Postgres
    Session cookies, not JWTBrowse frontend is server-rendered Jinja2; cookies are the natural fit. No SPA, no need for JWT.
    passlib + bcrypt for password hashingIndustry standard, arch-level security
    itsdangerous for signed cookiesAlready a Starlette dependency; simple and secure
    API stays unauthenticatedOnly accessible via Tailscale (MCP server, agents). Adding auth would break all MCP tooling for no benefit.
    priv- slug prefix for private notesClear namespace, easy to identify
    Private project slug: privateSimple, clear

    Phases

    Phase 1 — Auth system + browse filtering ✅

    • Slug: phase-2026-02-25-1-auth-and-filtering
    • Goal: Full auth flow — users table, login/logout, and is_public filtering on browse frontend. One deployable unit.
    • Owner: Agent (code change)
    • Status: COMPLETE
    • Deliverables:
      • PR #27 merged — feat: add browse frontend auth with is_public filtering
      • User model + Alembic migration for users table
      • auth.py — bcrypt password hashing, signed session cookies (https_only), get_current_user dependency
      • Login page at /browse/login, logout via POST, Login/Logout in nav bar
      • All browse routes filter is_public=False for unauthenticated visitors
      • Private notes redirect to login (not 404) with next param for post-login redirect
      • Linked notes filtered to prevent private title/slug leakage
      • Open redirect protection on next parameter
      • Red "PRIVATE" badge on private notes when logged in
      • User seeding via PALDOCS_SEED_EMAIL + PALDOCS_SEED_PASSWORD env vars
      • k8s deployment updated with PALDOCS_SECRET_KEY secret reference
      • 37 tests passing (23 auth + 14 existing), ruff clean
      • 3 QA review rounds, all passed

    Phase 2 — Private project & first note

    • Slug: phase-2026-02-25-2-private-project
    • Goal: Create the private project, first private note, and establish the convention
    • Owner: Main session (docs)
    • Steps:
      • Create project private in pal-e-docs
      • Create first note priv-1 with the user's poem, is_public=false
      • Create private project landing page (project-private) listing notes with number, date/time, link — also is_public=false
      • Create convention note for private note workflow: slug pattern (priv-{n}), always is_public=false, always in private project, auto-increment number, include timestamp
      • Update project-pal-e-docs roadmap with this plan

    Phase 3 — Audit existing notes

    • Slug: phase-2026-02-25-3-note-audit
    • Goal: Review all 40+ notes and confirm is_public is set correctly for each
    • Owner: Main session (docs)
    • Steps:
      • List all notes, review which should be public vs private
      • Update is_public on any notes that should not be publicly visible
      • Verify from unauthenticated browser that private notes are hidden

    Key Files

    PhaseFileRepoChange
    1src/pal_e_docs/models.pypal-e-docsAdd User model
    1src/pal_e_docs/auth.pypal-e-docsNew — auth helpers
    1src/pal_e_docs/config.pypal-e-docsAdd SECRET_KEY
    1alembic/versions/pal-e-docsNew migration for users table
    1pyproject.tomlpal-e-docsAdd passlib[bcrypt]
    1src/pal_e_docs/templates/login.htmlpal-e-docsNew — login form
    1src/pal_e_docs/templates/base.htmlpal-e-docsAdd login/logout nav button
    1src/pal_e_docs/routes/frontend.pypal-e-docsLogin/logout routes + is_public filtering

    Verification

    • [x] Phase 1: Visit /browse/ unauthenticated — private notes invisible. Log in at /browse/login — private notes appear. Log out — they disappear. Direct URL to private note redirects to login. Open redirect blocked. Linked notes filtered. 3 QA rounds passed.
    • [ ] Phase 2: priv-1 note exists with is_public=false, visible only when logged in. Convention note documents the workflow.
    • [ ] Phase 3: All 40+ notes reviewed, is_public set correctly, verified from unauthenticated browser.

    Next Plan Seeds

    • XSS sanitization (issue-xss-safe-filter) — must address before auth gives false sense of security on content injection
    • API auth — if API is ever exposed beyond Tailscale, it needs token auth too (Forgejo issue #2)
    • Note graph visualization — Obsidian-style interactive graph of note connections on landing page
    • Frontend redesign — separate presentation layer (pal-e-docs-server?), better mermaid rendering, professional styling
    • CSRF protection on login form

    Related

    • project-pal-e-docs — parent project
    • plan-2026-02-24-public-docs-and-templates — deferred plan that included public CSS/Funnel work
    • issue-xss-safe-filter — related security concern
  • Plan: Docs Foundation plan-2026-02-24-docs-foundation

    Plan: Docs Foundation

    Vision

    pal-e-docs is the development operating system. Documentation is king. Every hook event is a documentation check-in opportunity. Plans live as notes, link to each other, and agents find them automatically. Templates are enforced, not suggested. The note system is solid before we build on top of it. And the data is backed up — because 35+ notes of hard-won knowledge deserve durability.

    Projects & Repos Touched

    Project/RepoPlatformRole in this plan
    pal-e-docs appForgejoLanding page, browse frontend enhancements
    pal-e-docs-mcpForgejoMCP tool improvements
    claude-customForgejoSessionStart plan injection, PreToolUse hooks (Task, create_note), skills
    pal-e-platformGitHubMinIO deployment (see plan-2026-02-24-minio-object-storage)

    Context

    What's done:

    • 35+ notes, 5 projects, 8 repos, tag-based type system operational
    • Plan template with recursive structure documented
    • Hook events reference note created (see hook-events-reference)
    • Enforcement architecture documented (see enforcement-architecture)
    • Plan mode removed — plans are just notes, no special mode needed
    • Repo consolidation complete — all repos on Forgejo except bootstrap repos
    • Landing page shipped with architecture diagram
    • Phase 1 complete: note-conventions, agent-paradigm, html-style-guide created, note_links backfilled across 10 notes
    • Phase 2 complete: check-agent-spawn.sh hook deployed (PR #23 merged). "No plan, no agent" axiom enforced.
    • Phase 3 complete: SessionStart injects all active plans cross-project + convention refs always injected (PR #25 merged).
    • Phase 4 redefined: Markdown conversion killed. Template enforcement promoted to its own plan: plan-2026-02-25-template-enforcement.
    • Phase 5 complete: Doc check-in hooks deployed (PR #33 merged). PostToolUse on merge reminds to update project page. Stop hook reminds to update docs before session end.

    The gaps:

    • Templates exist but are advisory — no enforcement at hook level. See plan-2026-02-25-template-enforcement.
    • No backup strategy for SQLite database — data loss risk

    Previous Plan

    plan-2026-02-24-repo-consolidation (completed)

    Depends On

    Phase 6 depends on plan-2026-02-24-minio-object-storage (MinIO must be deployed before Litestream can replicate to it).

    Decisions Made

    DecisionRationale
    Kill plan mode entirelyPlans are notes in pal-e-docs. No special permission mode needed.
    Descriptive slugs over zettelkasten IDsSlugs are human-readable AND machine-guessable.
    Tags are the type systemNo NoteType column. Type determined by tags: plan,active vs sop,active vs issue,open.
    Templates enforced at hook level via PreToolUseHooks gate tool calls at point of action. Templates fetched from pal-e-docs dynamically.
    Dense linking over sparseEvery note should link to related notes via note_links.
    Phases can become plans with user approvalRecursive plan structure. Prevents scope creep.
    Events → Hooks → MCP → Skills → AgentsCorrected 5-layer paradigm with directional flow.
    SessionStart queries ALL active plans cross-projectPlans span repos/projects.
    No plan, no agent (the axiom)PreToolUse hook on Task tool. Simple plan- pattern check.
    Main session owns docs, agents own reposClean separation of concerns.
    Hook applies to ALL Task spawns including reviewsPR reviews should happen in context of a plan too.
    Convention references always injectedagent-spawn-conventions and agent-workflow injected even when pal-e-docs is unreachable.
    Issues tracked as notes, not Forgejo issuesAll knowledge in one system. Issues are notes tagged issue,open. Project pages have issues tables. User stories belong on issues, not project pages.
    One-way relationships (parent → child)Projects link to repos/plans/issues. Children don't need to point back. Keeps things flexible — repos/plans can serve multiple projects.
    Roadmaps are plan tables on project pagesNot separate entities. Project page IS the roadmap. Links to plan notes with status.
    Kill markdown conversion for pal-e-docsOnly PRs/issues need markdown, and those live on Forgejo already. No need to add markdown_content to notes. Templates are HTML in pal-e-docs, agents naturally write markdown for Forgejo.
    Stop hook is remind-only, not blockingBlocking a Stop feels aggressive. Reminder context is sufficient for main session to act on.
    Stop hook fires for main session onlyStop event is inherently main-session-only. SubagentStop is a separate event. Subagents don't touch docs per agent-workflow SOP.

    Phases

    Phase 1: Conventions & Paradigm Documentation — COMPLETE

    Completed 2026-02-24:

    • note-conventions, agent-paradigm, html-style-guide created
    • note_links backfilled on 10 notes

    Phase 2: Agent Spawn Quality Enforcement — COMPLETE

    Completed 2026-02-25:

    • check-agent-spawn.sh PreToolUse hook deployed — PR #23 merged on claude-custom

    Phase 3: SessionStart Plan Injection — COMPLETE

    Completed 2026-02-25:

    • SessionStart injects all active plans + convention refs — PR #25 merged on claude-custom

    Phase 4: Template Enforcement — PROMOTED TO PLAN

    Promoted 2026-02-25: Markdown conversion killed. Template enforcement promoted to plan-2026-02-25-template-enforcement.

    PreToolUse hooks on submit_pr and create_issue that validate bodies against templates fetched from pal-e-docs.

    Phase 5: Documentation Check-in Hooks — COMPLETE

    Completed 2026-02-26:

    • remind-update-docs.sh PostToolUse hook on mcp__forgejo__merge_approved_pr — PR #33 merged on claude-custom
    • stop-doc-checkin.sh Stop hook — remind-only, main session only
    • Issue: issue-doc-checkin-hooks (resolved)

    Phase 6: DB Backup via Litestream → MinIO

    Goal: Continuous SQLite replication to MinIO.

    Depends on: plan-2026-02-24-minio-object-storage

    Repo: pal-e-docs (Forgejo)

    1. Add Litestream sidecar to k8s deployment
    2. Configure replication to s3://litestream-backups/pal-e-docs/
    3. Test restore
    4. Document restore procedure as SOP

    Key Files

    PhaseFileRepoChange
    1(MCP operations only)pal-e-docs DBDONE
    2~/.claude/hooks/check-agent-spawn.shclaude-customDONE
    3~/.claude/hooks/session-start-context.shclaude-customDONE
    4Promoted to plan-2026-02-25-template-enforcement
    5~/.claude/hooks/remind-update-docs.sh, stop-doc-checkin.shclaude-customDONE
    6k8s/deployment.yamlpal-e-docsLitestream sidecar

    Verification

    • [x] note-conventions note exists
    • [x] agent-paradigm note exists
    • [x] html-style-guide note exists
    • [x] note_links backfilled
    • [x] agent-spawn-conventions note exists
    • [x] agent-workflow updated with 5 rules + separation of concerns
    • [x] PreToolUse hook on Task blocks prompts without plan slug
    • [x] PreToolUse hook on Task passes prompts with plan slug
    • [x] SessionStart injects all active plans cross-project
    • [x] Templates updated: project-page, plan, issue (new), pr-body
    • [x] First issue note created (issue-mermaid-newline-bug)
    • [x] Doc check-in hooks deployed — post-merge reminder + Stop session reminder (PR #33)
    • [ ] Template enforcement hooks deployed (see plan-2026-02-25-template-enforcement)
    • [ ] Litestream replicating to MinIO

    Next Plan Seeds

    • Public-facing CSS + Tailscale Funnel (see plan-2026-02-24-public-docs-and-templates)
    • Mermaid overflow/scroll fix for browse frontend
    • /new-issue and /new-project skills
    • Browse search bar
    • Asset upload API using MinIO
    • Update /review-pr skill to include plan context in review agent spawns

    Related

    • plan-2026-02-25-template-enforcement — promoted from Phase 4
    • plan-2026-02-24-minio-object-storage — Phase 6 depends on this
    • plan-2026-02-24-repo-consolidation — previous plan (completed)
    • plan-2026-02-24-public-docs-and-templates — next plan (deferred)
    • agent-spawn-conventions — the axiom
    • agent-workflow — the operating model
  • Plan: Repo Consolidation and Documentation Hub plan-2026-02-24-repo-consolidation

    Vision

    pal-e is the foundation for an AI agency. "I tell an agent what to do, and it already knows my platform, my SOPs, my active projects, and where I left off." pal-e-docs is the single coordination hub — both for AI agents (who query via MCP) and humans (who browse rendered docs with diagrams). All repos consolidated on Forgejo. All documentation accessible from a single landing page.

    Projects & Repos Touched

    Project/RepoPlatformRole in this plan
    forgejo-sdkGitHub → ForgejoMigrate to Forgejo
    forgejo-mcpGitHub → ForgejoMigrate to Forgejo
    pal-e-docs-mcpGitHub → ForgejoMigrate to Forgejo
    pal-e-docs (app)ForgejoLanding page + repos browse frontend
    pal-e-docs (database)MCPUpdate project pages with new URLs
    claude-configForgejoFix plan-skill enforcement gap

    Context

    The previous plan (mermaid diagrams + living documentation) is complete — all 5 phases executed, 3 PRs merged. But 3 repos (forgejo-sdk, forgejo-mcp, pal-e-docs-mcp) are still on GitHub for no good reason. The only repos that should be on GitHub are pal-e-platform and pal-e-services (chicken-and-egg: they create the Forgejo infra). Meanwhile, the browse frontend at /browse/ is a bare project list — not the documentation hub it should be.

    Previous Plan

    plan-2026-02-24-enforcement-unification (completed) → mermaid/living-docs (completed, not stored as note) → this plan

    Decisions Made

    DecisionRationale
    Keep pal-e-platform + pal-e-services on GitHubThey bootstrap Forgejo. If Forgejo dies, you need these to rebuild.
    Move forgejo-sdk, forgejo-mcp, pal-e-docs-mcp to ForgejoNo technical reason for GitHub. MCP tools run locally, remote doesn't affect runtime.
    Archive GitHub repos, don't deleteRead-only backup. No data loss.
    No Woodpecker CI initiallyLocal dev tools, not deployed services. CI can be added later.
    Landing page pulls from DBProjects, repos, SOPs all queryable via SQLAlchemy. Stays current automatically.

    Phases

    Phase 1: Move 3 Repos to Forgejo

    Goal: forgejo-sdk, forgejo-mcp, pal-e-docs-mcp live on Forgejo. GitHub copies archived.

    For each repo: create empty Forgejo repo, push all branches + tags, rename remotes, close GitHub issues, archive GitHub repo, update Repo entity via MCP.

    Phase 2: Enhance Browse Landing Page

    Goal: /browse/ becomes the documentation hub with projects, repos, SOPs, and architecture.

    Repo: pal-e-docs app (Forgejo) — issue + branch + PR

    • frontend.py — Add Repo import, rich landing route, /repos route
    • landing.html (new) — Platform overview + mermaid, projects, repos with badges, documentation links
    • repos.html (new) — Dedicated /browse/repos page
    • base.html — Nav update (Home, Repos, Tags) + badge CSS

    Phase 3: Update Project Pages + Repo Entities

    Goal: Project pages reflect new Forgejo URLs.

    Update project-claude-config and project-pal-e-docs via MCP.

    Phase 4: Fix Plan-Skill Enforcement Gap

    Goal: Prevent agents from skipping /plan skill steps 6-10 when in plan mode.

    Repo: claude-custom (Forgejo) — issue + branch + PR

    Reference: plan-skill-enforcement-gap note (details root cause and fix options)

    • PostToolUse hook on EnterPlanMode — remind MCP calls required
    • PreToolUse hook on ExitPlanMode — warn if no plan note created
    • Update /plan skill instructions — clarify MCP allowed in plan mode

    Verification

    • [ ] All 3 repos on Forgejo, GitHub archived
    • [ ] list_repos(project="claude-config") returns forgejo platform repos
    • [ ] pal-e-docs.tail5b443a.ts.net/browse/ shows rich landing page
    • [ ] pal-e-docs.tail5b443a.ts.net/browse/repos shows all repos
    • [ ] Plan-mode hooks fire correctly

    Next Plan Seeds

    • Woodpecker CI for MCP tools
    • PyPI publishing from Woodpecker
    • Browse search bar
    • Deprecate Project.repo_url
    • pal-e-auth
    • Litestream backup
    • Move pal-e-services to Forgejo
  • Plan: Knowledge System Consolidation plan-2026-02-28-knowledge-system-consolidation

    Vision

    The single coordination hub for the pal-e AI agency. Matures the knowledge system from "functional" to "properly indexed." Every note has a type, a status, and a project. Tags return to their proper role: topic classification.

    Projects & Repos Touched

    Project/RepoPlatformRole
    pal-e-docs (app)ForgejoSchema changes, API route updates, convention docs
    pal-e-docs-mcpForgejoExpose note_type/status query params
    claude-customForgejoSkills and hooks updated to use new query params
    All 22 reposForgejoREADME updates pointing to pal-e-docs page notes

    Phases

    See child phase notes: list_notes(parent_slug="plan-2026-02-28-knowledge-system-consolidation")

    Summary: Phases 1, 2, 4 complete. Phase 2 absorbed into plan-2026-03-01-note-decomposition. Phases 3, 5, 6 are low priority.

    Investigation Findings (2026-03-01)

    • Tag-based queries work fine functionally.
    • Session start hook has N+1 query problem (~16 HTTP calls).
    • Scope tags are vestigial (13 notes). Project FK already provides this.
    • Issues table is the highest-value schema change.
    • note_type/status columns correct but lower urgency.

    Decisions Made

    DecisionRationale
    Add note_type + status columns (Option B)Proper indexing without table-per-type.
    Issues earn their own tableNeed repo FK, number, priority.
    SOPs/plans/conventions stay as notesDocuments don't need fields beyond notes.
    Tags retire type/lifecycle/scope rolesReplaced by note_type, status, project FK.
    Merge bug into issueOnly 4 bugs. Slug convention distinguishes.
    Keep convention separate from sopRules vs procedures.
    README convention: point to pal-e-docsRepos contain code + PRs only.

    Related

    • plan-2026-03-01-note-decomposition -- successor plan, absorbs Phase 2
    • plan-2026-02-27-browse-ux-enhancements -- predecessor
    • note-conventions -- updated in Phase 1
    • project-pal-e-docs -- parent project
  • Plan: Note Decomposition plan-2026-03-01-note-decomposition

    Vision

    Evolve the knowledge system from monolithic HTML blobs to composable, structured notes. Plans become composed of independent phase records that can be read, updated, and queried individually. Token waste from surgical HTML editing is eliminated.

    Status

    COMPLETED — 2026-03-02. All 5 phases delivered. 4 active plans decomposed into 19 child phases. 98-99% token savings proven.

    Phases

    See child phase notes: list_notes(parent_slug="plan-2026-03-01-note-decomposition")

    Key Decisions

    • Phases are notes with parent_note_id, not a separate table
    • Composition rendering is server-side (Jinja2)
    • Backward compatible — monolithic plans render as-is
    • Issues live in Forgejo only, not pal-e-docs
    • MCP param names match model instincts (content not html_content)

    Results

    StoryBeforeAfterSavings
    Update phase status39KB, 2 calls0.5KB, 1 call98.7%
    Query in-progress phases193KB, 12 calls1KB, 1 call99.5%
    Read plan summary15KB2KB86.7%

    Incident

    Phase 2 deployment caused ~10 min outage (SQLite DDL auto-commit). See incident-2026-03-02-sqlite-migration-crash-pr61.

    Next Plan Seeds

    • Issues table — structured entity with repo FK, number, priority
    • Session start hook optimization — read phase summaries not full plans
    • Backfill note_type/status on existing notes
    • Tag retirement — strip type/lifecycle tags once columns exist

    Related

    • plan-2026-02-28-knowledge-system-consolidation — parent plan
    • note-conventions — the spec
    • todo-pal-e-docs-deployment-reliability — deployment hardening
  • Plan: Responsive Design & Mobile UX plan-2026-02-27-responsive-design-mobile-ux

    Vision

    pal-e-docs is on a resume and shown in interviews. The browse frontend must be properly responsive — readable, well-spaced, and user-friendly on any screen size. Not flashy, just correct. Nothing should overflow, nothing should feel cramped, and tables should be navigable on a phone.

    Projects & Repos Touched

    Project/RepoPlatformRole in this plan
    pal-e-docs (app)ForgejoTemplates, CSS, server-side HTML processing, playwright tests

    Context

    The Browse Frontend Polish plan shipped typography, table styling, and pre blocks (PR #43). But visual QA revealed the site is still broken on mobile. The root cause isn't missing CSS — it's structural:

    • Zero @media queries — one layout for all screen sizes
    • Tables in note content have no wrapper div — the CSS display: block; overflow-x: auto hack breaks table layout semantics
    • Nav overflows on mobile — brand + 5 links + login all compete for one flex row with no breakpoint
    • Doc lists on landing page dump tags inline with titles — chaotic wrapping on narrow screens
    • No automated mobile QA — the existing 3 playwright tests only cover mermaid/lightbox

    Key architectural insight: We don't need to touch the 75+ notes in the database. The rendering pipeline is:

    DB (raw HTML) → sanitize_html() → autolink_slugs() → wrap_tables() → Jinja2 template → browser

    We control three layers: server-side HTML processing (table wrapper step), Jinja2 templates (structural divs, nav), and CSS (breakpoints, mobile-first rules). All presentation concerns, zero content changes.

    Previous Plan

    plan-2026-02-26-browse-frontend-polish — completed. Shipped font, table CSS, pre blocks, XSS sanitization, auto-link slugs. This plan addresses the structural gaps that CSS alone can't fix.

    Depends On

    None.

    Decisions Made

    DecisionRationale
    Server-side table wrapping, not client-side JSClean approach. Same HTMLParser pattern as autolink_slugs(). No layout shift on page load. Consistent with existing pipeline.
    CSS-only responsive nav, no hamburger menu5 nav links + login is not enough to justify a hamburger. Flexbox + one @media breakpoint: brand on its own row on mobile, links wrap into a compact second row. Simple, no JS.
    Playwright tests define "done" before code changesTests written first, fail on current state, pass after fixes. CI catches regressions forever.
    New plan, not extending the old oneBrowse Frontend Polish was "make it look decent." This plan is "make the HTML structure correct for responsive design." Different problem, different scope.
    Pre-commit framework for ruff enforcementAgents repeatedly failed CI with unformatted code (PRs #37, #46). .pre-commit-config.yaml with ruff-format + ruff check catches this at git commit time.

    Phases

    Phase 1: Server-side table wrapper + playwright mobile test suite ✓ COMPLETE

    Slug: phase-2026-02-27-1-table-wrapper-tests
    Goal: Tables in note content are wrapped in scroll containers server-side. Playwright tests define the mobile responsiveness contract.
    Owner: Agent (worktree, pal-e-docs repo)
    Status: COMPLETE — PR #45 merged

    Delivered:

    • PR #45 — wrap_tables.py HTMLParser wrapping outermost <table> elements in <div class="table-scroll">. Nested table depth tracking (no double-wrap). Safety flush for unclosed tables. str | None type signature matching autolink_slugs().
    • Pipeline wired: sanitize → autolink → wrap_tables → template
    • CSS: .table-scroll with overflow-x: auto; -webkit-overflow-scrolling: touch; max-width: 100%. Removed display: block; overflow-x: auto hack from .note-content table.
    • 16 unit tests + 5 integration tests + 5 playwright mobile tests (151 total passing)
    • All 5 playwright mobile tests pass — no horizontal overflow on landing page, tables scrollable in wrapper, nav fits viewport, readable font sizes

    Review-fix loop: 2 rounds. Round 1: unclosed table safety flush (blocking), type signature consistency, entity-encoded table-in-pre test, import placement, max-width CSS. Round 2: all fixes verified, clean approval.

    Phase 2: Template restructuring + CSS overhaul ✓ COMPLETE

    Slug: phase-2026-02-27-2-template-css-overhaul
    Goal: All pages render correctly at 375px. No horizontal overflow. Nav is usable on mobile. Proper @media breakpoints.
    Owner: Agent (worktree, pal-e-docs repo)
    Status: COMPLETE — PR #48 merged

    Delivered:

    • PR #48 — Nav restructured with CSS classes (.nav-auth, .nav-links, .user-email, .logout-btn, .login-link). All inline styles removed from all 9 templates.
    • @media (max-width: 600px) breakpoint: brand on own row, nav links on own row, auth on own row right-aligned. Reduced main padding/margin. Card grid forced to single column. h1 size reduced.
    • landing.html: tags wrapped in <div class="tag-row"> below titles. Section headers moved to .section-header class.
    • login.html: all inline styles replaced with CSS classes.
    • tag-row class applied consistently across note.html, tag_notes.html, project_notes.html, landing.html.
    • Form elements inherit Atkinson Hyperlegible via font-family: inherit rule.
    • .pre-commit-config.yaml with ruff-format + ruff check (rev v0.15.2).
    • 151 tests passing, all 5 playwright mobile tests green.

    Review-fix loop: 3 rounds. Round 1: remaining inline style in landing.html (blocking), ruff version outdated (blocking), nav-links wrapper suggestion, tag-row consistency, font inheritance. Round 2: note.html tag-row, nav-links mobile gap. Round 3: clean approval.

    Phase 3: Visual QA + polish ✓ COMPLETE

    Slug: phase-2026-02-27-3-visual-qa-polish
    Goal: Final visual pass. Fix the nav wrap bug, improve mobile spacing. Everything looks intentional, not just "not broken."
    Owner: Agent (worktree, pal-e-docs repo)
    Status: COMPLETE — PR #50 merged

    Delivered:

    • PR #50 — flex-basis: 100%flex: 0 0 100% for .brand, .nav-links, .nav-auth in mobile breakpoint. Root cause: flex-shrink: 1 default allowed items to compress below 100% width, preventing proper row stacking.
    • Mobile section spacing: .section { margin-bottom: 3rem; } in mobile breakpoint (up from 2.5rem default).
    • New playwright test test_nav_three_row_layout — validates brand/nav-links/nav-auth have strictly increasing Y positions with 10px minimum gap assertions.
    • 152 total tests passing, all 9 playwright browser tests green.

    Review-fix loop: 2 rounds. Round 1: clean approval on CSS fix. Round 2: strengthened test assertions from simple > to > + 10 minimum gap (catches zero-height edge case). Clean approval.

    Key Files

    PhaseFileRepoChange
    1 ✓src/pal_e_docs/wrap_tables.pypal-e-docsNew — HTMLParser table wrapper
    1 ✓src/pal_e_docs/routes/frontend.pypal-e-docsWire wrap_tables into rendering pipeline
    1 ✓tests/test_wrap_tables.pypal-e-docsNew — 16 unit tests for table wrapping
    1 ✓tests/test_wrap_tables_integration.pypal-e-docsNew — 5 integration tests
    1 ✓tests/test_mobile_responsive.pypal-e-docsNew — 5 playwright mobile viewport tests
    1 ✓src/pal_e_docs/templates/base.htmlpal-e-docsCSS: .table-scroll styles, remove display:block hack
    2 ✓src/pal_e_docs/templates/base.htmlpal-e-docsNav restructure, @media breakpoints, CSS overhaul, form font inherit
    2 ✓src/pal_e_docs/templates/landing.htmlpal-e-docsDoc list tag-row layout, section-header class, view-all-link class
    2 ✓src/pal_e_docs/templates/login.htmlpal-e-docsAll inline styles moved to CSS classes
    2 ✓src/pal_e_docs/templates/note.htmlpal-e-docstag-row class consistency
    2 ✓src/pal_e_docs/templates/tag_notes.htmlpal-e-docstag-row class consistency
    2 ✓src/pal_e_docs/templates/project_notes.htmlpal-e-docstag-row class consistency
    2 ✓.pre-commit-config.yamlpal-e-docsNew — ruff-format + ruff check hooks (v0.15.2)
    3 ✓src/pal_e_docs/templates/base.htmlpal-e-docsNav wrap fix (flex: 0 0 100%), mobile section spacing
    3 ✓tests/test_mobile_responsive.pypal-e-docsNew test — test_nav_three_row_layout with 10px gap assertions

    Verification

    • [x] Phase 1: wrap_tables() unit tests pass. Table wrapper in pipeline. Playwright mobile tests exist and run in CI. 151 total tests passing. PR #45 merged.
    • [x] Phase 2: ALL playwright mobile tests pass. No horizontal overflow at 375px on any page. Nav usable on mobile. All inline styles removed. Pre-commit config added. PR #48 merged. 3-round review-fix loop.
    • [x] Phase 3: Nav renders as 3 rows on mobile. Mobile spacing improved. 152 total tests passing. PR #50 merged. 2-round review-fix loop.

    Next Plan Seeds

    • plan-2026-02-27-browse-ux-enhancements — sort by recency, project detail page redesign, mermaid diagram revision
    • Screenshot regression tests in CI
    • Dark mode (if there's ever demand)

    Related

    • project-pal-e-docs — parent project
    • plan-2026-02-26-browse-frontend-polish — predecessor (completed). This plan addresses structural gaps that CSS alone couldn't fix.
    • issue-pal-e-docs-table-wrapper-mobile-tests — resolved, Phase 1 (PR #45)
    • issue-pal-e-docs-template-css-overhaul — resolved, Phase 2 (PR #48)
    • issue-pal-e-docs-visual-qa-polish — resolved, Phase 3 (PR #50)
  • Plan: Browse Frontend Polish plan-2026-02-26-browse-frontend-polish

    Vision

    pal-e-docs is on a resume and shown in interviews. The browse frontend must look professional, render diagrams correctly, and give visitors a smooth experience on any screen size. Diagrams are a key value-add — they must be first-class citizens, not broken afterthoughts.

    Projects & Repos Touched

    Project/RepoPlatformRole in this plan
    pal-e-docs (app)ForgejoFrontend templates, CSS, mermaid rendering, playwright test infrastructure

    Context

    The site is publicly accessible and functional. Content is strong — 75+ notes covering SOPs, architecture, plans, project pages. But mermaid diagrams had a rendering bug, overflowed on mobile, and were untestable. This plan fixes all of that and adds professional polish.

    Architecture constraint: Server-side rendered Jinja2. Note HTML sanitized via nh3 then injected via | safe. Mermaid loads from CDN and transforms <pre class="mermaid"> into SVG client-side. All CSS inline in base.html. Playwright browser tests close the JS verification gap.

    Previous Plan

    plan-2026-02-25-private-notes-auth — auth is done, now polish the public experience.

    Depends On

    None.

    Decisions Made

    DecisionRationale
    Graph visualization (Obsidian-style) is out of scopeNot urgent, high complexity, low interview impact
    Search bar is deferredNice to have, not needed for interview readiness
    Mermaid interaction: scrollable container + click-to-expand lightboxSimple CSS gets 80% (scrolling), lightweight inline JS gets the rest (full-screen overlay). No new routes, no dependencies.
    QA approach: pytest + playwright baked into repoZero LLM token cost to run. Agents can verify their own mermaid work. Runs in CI.
    Playwright test infra scaffolded in Phase 1The agent needs to verify mermaid rendering — existing TestClient can't execute JS.
    CI uses official Microsoft playwright imagemcr.microsoft.com/playwright/python — has Python + playwright + Chromium pre-installed. No custom image needed.
    XSS sanitizer: nh3 (Rust-based), default safe tagsModern replacement for deprecated bleach. Uses nh3's built-in safe tag defaults — don't maintain a custom allowlist when the library already knows which tags are safe. Only customize attributes (class on pre/code for mermaid) and URL schemes (http/https/mailto).
    Sanitize at render time, not write timeAPI stays "raw" for agents — they might store HTML that looks suspicious but is valid. Only the browser rendering path needs protection. API is behind Tailscale anyway.
    Only note.html needs sanitizationLanding page mermaid diagram is hardcoded in landing.html (not from DB). Only note.html uses | safe on DB content. All other template variables use Jinja2 auto-escaping.
    Auto-link slugs server-side, not change authoring75+ existing notes reference slugs as <code>slug-name</code> text, not links. Server-side auto-linking fixes all existing notes retroactively. Changing how agents write content would only fix future notes.
    Font: Atkinson HyperlegibleFree (Google Fonts), designed for low-vision readability. Professional appearance — doesn't scream "accessibility font." Clean, highly legible sans-serif.

    Phases

    Phase 1: Playwright test infra + mermaid fix + diagram UX ✓ COMPLETE

    Slug: phase-2026-02-26-1-mermaid-fix
    Goal: Add browser-level test infrastructure. Fix mermaid rendering. Make diagrams responsive and expandable. Tests prove it works.
    Owner: Agent (worktree, pal-e-docs repo)
    Status: COMPLETE — deployed and verified live

    Delivered:

    • PR #31 — playwright test infra (live server fixture, 3 browser tests), mermaid newline fix, responsive CSS, click-to-expand lightbox
    • PR #33 — review follow-up: fixture scope mismatch fix, cloneNode(true) replacing innerHTML for XSS safety
    • PR #35 — CI: switched Woodpecker test step to official mcr.microsoft.com/playwright/python image so browser tests run in CI
    • PR #37 — CI: ruff formatting fix to unblock pipeline

    Verified: Pipeline #38 succeeded. Image 4093596 deployed via ArgoCD Image Updater. Lightbox, responsive containers, and mermaid rendering confirmed working on live site.

    Phase 2: XSS sanitization ✓ COMPLETE

    Slug: phase-2026-02-26-2-xss-fix
    Goal: Note HTML content is sanitized before rendering to prevent cross-site scripting attacks.
    Owner: Agent (worktree, pal-e-docs repo)
    Status: COMPLETE — PR #39 merged

    Delivered:

    • PR #39 — server-side HTML sanitization via nh3. Uses nh3 default safe tags (no custom tag allowlist). Custom attribute allowlist for class on pre/code (mermaid). URL schemes restricted to http/https/mailto. 26 unit tests + 5 integration tests (93 total passing).

    Key implementation:

    • src/pal_e_docs/sanitize.pysanitize_html() function, uses nh3 defaults for tags, only customizes attributes and URL schemes
    • browse_note route sanitizes content before passing sanitized_content to template
    • Template uses {{ sanitized_content | safe }}| safe still needed to prevent Jinja2 double-escaping

    Review-fix loop: 3 rounds. Round 1 found: type annotation mismatch, missing data: URI test, over-restrictive custom tag allowlist, weak assertion. Round 2 verified fixes, flagged allowlist design. Round 3 verified simplification to nh3 defaults — clean approval.

    Phase 3: Auto-link slug references ✓ COMPLETE

    Slug: phase-2026-02-26-3-auto-link-slugs
    Goal: Inline slug references in note content become clickable links to the referenced notes.
    Owner: Agent (worktree, pal-e-docs repo)
    Status: COMPLETE — PR #41 merged, deployed and verified live

    Delivered:

    • PR #41 — server-side auto-linking of slug references. HTMLParser-based state machine scans <code> elements, wraps known slugs in <a href="/browse/notes/{slug}" class="auto-link"> links. Thread-safe slug cache with 60s TTL. CSS for linked code (blue text, light blue background, hover underline). 26 unit tests + 5 integration tests (124 total passing).

    Key implementation:

    • src/pal_e_docs/autolink.pyautolink_slugs() function + get_known_slugs() with thread-safe TTL cache. Returns frozenset for immutability.
    • browse_note route calls autolink_slugs() after sanitization, before template rendering
    • Skips <code> inside <pre> blocks and already-linked <code>
    • Sanitizer allowlist updated: class added to <a> attributes for future-proofing
    • Shared create_test_note() helper extracted to conftest.py

    Review-fix loop: 3 rounds. Round 1 (agent internal): self-closing tag corruption, html_escape on slug href, attribute entity decoding. Round 2 (QA): thread safety (Lock), frozenset return, cache tests, sanitizer allowlist, conftest helper. Round 3: clean approval.

    Verified: Deployed via ArgoCD Image Updater. Live site confirmed: agent-workflow page renders 8 auto-linked slugs (agent-spawn-conventions, agent-paradigm, hook-events-reference, enforcement-architecture, pr-lifecycle) as clickable blue links with class="auto-link".

    Phase 4: Typography & CSS polish ✓ COMPLETE

    Slug: phase-2026-02-26-4-typography
    Goal: Professional, accessible typography. Readable tables. Polished mobile experience.
    Owner: Agent (worktree, pal-e-docs repo)
    Status: COMPLETE — PR #43 merged

    Delivered:

    • PR #43 — Atkinson Hyperlegible font (400, 400i, 700, 700i) from Google Fonts with preconnect and display=swap. Table styling (border-collapse, borders, cell padding, header backgrounds, alternating row colors, full width, mobile horizontal scroll via display: block; overflow-x: auto). Pre block styling (background, padding, border-radius, overflow-x). pre code reset to prevent double styling. pre.mermaid explicit overrides (padding: 0, border-radius: 0). .note-content h3 and h4 styles. Nav flex-wrap: wrap for mobile. +54/-3 lines, single file.

    Review: 1 round. QA reviewer approved — all acceptance criteria met, no regressions to mermaid/lightbox/auto-links. Two non-blocking suggestions noted (code comment on table display: block pattern, nth-child counting with thead).

    Key Files

    PhaseFileRepoChange
    1 ✓pyproject.tomlpal-e-docsAdd pytest-playwright to dev deps
    1 ✓tests/conftest.pypal-e-docsLive server fixture, browser marker, scope fix
    1 ✓tests/test_frontend_browser.pypal-e-docsPlaywright tests for mermaid rendering
    1 ✓src/pal_e_docs/templates/base.htmlpal-e-docsMermaid newline fix, responsive CSS, lightbox JS (cloneNode)
    1 ✓.woodpecker.yamlpal-e-docsSwitched to playwright CI image for browser tests
    2 ✓pyproject.tomlpal-e-docsAdd nh3 dependency
    2 ✓src/pal_e_docs/sanitize.pypal-e-docsNew — sanitize_html() with nh3 defaults
    2 ✓src/pal_e_docs/routes/frontend.pypal-e-docsSanitize html_content before rendering
    2 ✓src/pal_e_docs/templates/note.htmlpal-e-docsUse sanitized_content, updated security comment
    2 ✓tests/test_sanitize.pypal-e-docs26 unit tests for sanitization
    2 ✓tests/test_sanitize_integration.pypal-e-docs5 integration tests for browse_note route
    3 ✓src/pal_e_docs/autolink.pypal-e-docsNew — autolink_slugs() with HTMLParser state machine + thread-safe TTL cache
    3 ✓src/pal_e_docs/routes/frontend.pypal-e-docsAuto-link slug references after sanitization
    3 ✓src/pal_e_docs/sanitize.pypal-e-docsAdded class to <a> allowlist
    3 ✓src/pal_e_docs/templates/base.htmlpal-e-docsCSS for auto-linked code elements
    3 ✓tests/test_autolink.pypal-e-docs26 unit tests for auto-linking + caching
    3 ✓tests/test_autolink_integration.pypal-e-docs5 integration tests for browse_note route
    4 ✓src/pal_e_docs/templates/base.htmlpal-e-docsFont import, table styling, pre blocks, mobile, spacing

    Verification

    • [x] Phase 1: pytest -m browser passes in CI. Mermaid renders. Scrollable containers. Lightbox works. No regressions. Deployed and verified live.
    • [x] Phase 2: XSS payloads stripped. Mermaid still renders. Existing note content unaffected. 93 tests passing. 3-round review-fix loop completed. PR #39 merged.
    • [x] Phase 3: Slug references in note content are clickable links. Non-slug code unaffected. Pre blocks unaffected. 124 tests passing. 3-round review-fix loop completed. PR #41 merged. Verified live — 8 auto-linked slugs on agent-workflow page.
    • [x] Phase 4: Atkinson Hyperlegible font active. Tables styled with borders, padding, headers, alternating rows. Pre blocks styled. Mobile responsive. No regressions. PR #43 merged.

    Lessons Learned

    Phase 1

    • Always run review agent before merging. Self-review missed fixture scope mismatch and innerHTML XSS — the fresh review agent caught both.
    • CI must match dev deps. Adding playwright to dev deps without updating the CI image broke the pipeline for 4 PRs. The official Microsoft playwright Docker image is the right solution.
    • ArgoCD Image Updater has a ~2min polling interval. Don't panic if deploy doesn't happen immediately after image push.
    • Deploy strategy is Recreate — causes downtime on every deploy. Should move to RollingUpdate (tracked in todo-deployment-safety).

    Phase 2

    • Trust library defaults. Initial implementation defined a custom tag allowlist that was more restrictive than nh3's built-in safe defaults. This silently stripped valid HTML like <details>, <del>, <kbd>. Lesson: don't maintain a custom list when the library already knows which tags are safe. Only customize what you actually need to (attributes, URL schemes).
    • 3-round review-fix loop catches real issues. Round 1 found type annotation mismatch + test gap. Round 2 flagged over-restrictive design. Round 3 verified the simplified approach. Fresh eyes matter.

    Phase 3

    • HTMLParser needs careful handling of self-closing tags and entity encoding. Default handle_startendtag calls starttag+endtag which produces <br></br>. Must override to emit self-closing format. Also, HTMLParser decodes entities in attribute values — must re-encode when rebuilding tags.
    • Cache thread safety matters even for simple cases. A bare global variable cache works under CPython GIL but is a code smell. threading.Lock + frozenset return is cheap insurance.
    • Extract shared test helpers early. Duplicate _create_note helpers across integration test files were caught in review. Shared conftest helper is cleaner.

    Phase 4

    • CSS-only changes are low-risk, high-impact. +54 lines of CSS transformed the entire site's appearance. Single-file scope made review trivial.
    • Clean first-round approval is possible when the scope is tight and well-defined. Phase 4 had the clearest spec of all phases — no ambiguity in acceptance criteria.

    Next Plan Seeds

    • Browse search bar
    • CSRF protection on login form
    • Deployment strategy: switch from Recreate to RollingUpdate

    Related

    • project-pal-e-docs — parent project
    • plan-2026-02-25-private-notes-auth — predecessor (auth done, now polish)
    • issue-mermaid-newline-bug — resolved, fixed in Phase 1 (PR #31)
    • issue-xss-safe-filter — resolved, fixed in Phase 2 (PR #39)
    • issue-pal-e-docs-auto-link-slugs — resolved, fixed in Phase 3 (PR #41)
    • issue-pal-e-docs-playwright-mermaid-fix — resolved (PR #31)
    • issue-pal-e-docs-playwright-review-fixes — resolved (PR #33)
    • issue-pal-e-docs-typography-css-polish — resolved, fixed in Phase 4 (PR #43)
  • Plan: Browse UX Enhancements plan-2026-02-27-browse-ux-enhancements

    Vision

    pal-e-docs is on a resume and shown in interviews. The browse frontend must feel like a well-organized documentation hub — content surfaced by recency and relevance, project pages that tell a story, diagrams that are accurate, and access controls that let you share selectively with prospects. Not just "functional" but "this person knows how to build a knowledge system."

    Projects & Repos Touched

    Project/RepoPlatformRole in this plan
    pal-e-docs (app)ForgejoRoute queries, templates, landing page mermaid diagram

    Context

    The Responsive Design plan made the site structurally correct on all screen sizes. But the content presentation still has UX issues:

    • Sort order: Landing page lists projects, repos, and all doc sections alphabetically. Most recently updated content should surface first — that's what visitors care about.
    • Project detail page: Clicking a project shows a feed of notes sorted by updated_at (which is good), but the project's page note (page_note_id FK) isn't pinned at the top. The project's repos aren't shown. The page is a flat list when it should tell a story.
    • Mermaid diagram: The landing page architecture diagram is wide/horizontal, making it tiny on mobile. It may also be outdated — missing repos, inaccurate connections. Needs accuracy review and a more vertical layout.
    • Demo access: No way to give prospects a login to see private documentation. Currently only one seed user. Need a demo account with managed credentials.
    • Privacy gap: 30+ notes in pal-e-platform and pal-e-services are is_public: true but contain infrastructure details (Terraform assessments, host inventories, deployment strategies) that shouldn't be public. The Phase 3 audit from plan-2026-02-25-private-notes-auth was never completed.

    Previous Plan

    plan-2026-02-27-responsive-design-mobile-ux — completed. This plan picks up the UX enhancements that didn't belong in the responsive design scope.

    Depends On

    None — all phases are independently deployable.

    Decisions Made

    DecisionRationale
    Sort by recency EVERYWHERE, not just landing pageConsistency. Landing page, dedicated list pages (/browse/projects, /browse/repos), and project detail pages all sort by recency. No alphabetical anywhere — recency is the UX principle.
    Pin project page note at top of project detailThe page_note_id FK already exists on the projects table (PR #29). The project page note is the summary/overview — it should be the first thing you see, not buried in a recency-sorted feed.
    Mermaid diagram is hardcoded in landing.html, not from DBThe landing page diagram is a template element, not note content. Revision means editing the template directly.
    Diagram shows project-level architecture, not individual reposRepos section below the diagram already lists every repo. Diagram tells the platform story: Salt-managed host → pal-e-platform infra → pal-e-services onboarding → onboarded services.
    Add .tag-row CSS class and apply consistentlyTags displayed in flex-wrap row across all templates. Defined once in base.html, applied in project_notes.html, note.html, tag_notes.html.
    Add badge-planned CSS classRepo status can be "planned" but had no badge style. Pre-existing gap, fixed in scope.
    Fix test DB session leak_get_test_db() pattern across test files never closes the session generator. Harmless for in-memory SQLite but incorrect. Fixed while touching tests.
    Repos on landing page can leak private project namesPre-existing issue. Tracked for Phase 3 privacy audit — repos query needs project visibility filtering.

    Phases

    Phase 1: Sort by recency + project detail redesign ✓ COMPLETE

    Slug: phase-2026-02-27-1-recency-project-detail
    Goal: ALL list pages surface recent content. Project detail page pins the project page note at top and shows related repos.
    Owner: Agent (worktree, pal-e-docs repo)
    Status: COMPLETE — PR #52 merged

    Delivered:

    • PR #52 — Recency sort on ALL browse frontend list pages: landing (projects, repos, doc notes), /browse/projects, /browse/repos, project detail repos. Tags index stays alphabetical (correct for tags).
    • Project detail page redesign: page note content pinned at top (rendered through sanitize → autolink → wrap_tables pipeline), repos section with card grid below, notes feed excludes page note.
    • .tag-row CSS consolidated to single definition, applied consistently across note.html, tag_notes.html, project_notes.html.
    • .badge-planned CSS added (steel blue, between active green and archived gray).
    • Test DB session leak fixed — _get_test_db() generator pattern replaced with direct TestingSessionLocal() + try/finally in test_browse_ux.py, test_auth.py, test_project_schema.py.
    • SQLite timestamp format helper _sqlite_ts() for reliable sort-order testing.
    • 9 new tests in test_browse_ux.py. 161 total tests passing.

    Review-fix loop: 4 rounds. Round 1: clean initial approval. Round 2: tag-row CSS undefined, no tests, repos sort inconsistency — all fixed. Round 3: duplicate CSS selector, missing landing repos test, try/finally in tests — all fixed plus SQLite timestamp format bug discovered and fixed. Round 4: clean approval.

    Phase 2: Mermaid diagram revision ✓ COMPLETE

    Slug: phase-2026-02-27-2-mermaid-diagram-revision
    Goal: Landing page architecture diagram is accurate, vertical-oriented, and readable on mobile.
    Owner: Main session (content decision) + Agent (template edit)
    Status: COMPLETE — PR #57 merged

    Delivered:

    • PR #57 — Replaced outdated horizontal diagram with accurate vertical-oriented architecture diagram.
    • New diagram shows 4 subgraphs: Salt-managed host, pal-e-platform (OpenTofu infra: Tailscale, Forgejo, Woodpecker, Harbor, MinIO, Monitoring), pal-e-services (ArgoCD + var.services for_each), and onboarded services (pal-e-docs, basketball-api, Pal-E).
    • CI/CD pipeline flow: Forgejo → Woodpecker → Harbor → ArgoCD.
    • Claude Config shown reading from pal-e-docs.
    • Tailscale TLS funnels shown for all public-facing services.
    • Old "Bootstrap (GitHub)" grouping removed — everything is Forgejo now.

    Review-fix loop: 1 round. Clean approval — template-only change with no issues found.

    Phase 3: Privacy audit + demo user → SPUN OUT

    Slug: phase-2026-02-27-3-privacy-audit-demo-user
    Goal: Originally: infrastructure notes are private, demo account exists, repos query filters by project visibility.
    Owner: N/A
    Status: SPUN OUT into plan-2026-02-28-knowledge-system-consolidation

    During planning for Phase 3, scope expanded significantly beyond privacy audit into a comprehensive knowledge system consolidation: schema changes (note_type + status columns, issues table), tag taxonomy overhaul, MCP tool updates, claude-config skills migration, and repo page notes. This warranted its own plan. Privacy audit is Phase 4 of the consolidation plan. Demo user was dropped.

    Key Files

    PhaseFileRepoChange
    1 ✓src/pal_e_docs/routes/frontend.pypal-e-docsRecency sort on ALL list routes. Project detail: page_note + repos.
    1 ✓src/pal_e_docs/templates/project_notes.htmlpal-e-docsPage note content at top, repos section, then note feed.
    1 ✓src/pal_e_docs/templates/base.htmlpal-e-docs.tag-row consolidated, .badge-planned added.
    1 ✓src/pal_e_docs/templates/note.htmlpal-e-docstag-row class consistency.
    1 ✓src/pal_e_docs/templates/tag_notes.htmlpal-e-docstag-row class consistency.
    1 ✓tests/test_browse_ux.pypal-e-docsNew — 9 tests for sort order, page note, repos.
    1 ✓tests/test_auth.pypal-e-docsSession leak fix.
    1 ✓tests/test_project_schema.pypal-e-docsSession leak fix.
    2 ✓src/pal_e_docs/templates/landing.htmlpal-e-docsRevised mermaid diagram — vertical layout, accurate architecture.

    Verification

    • [x] Phase 1: ALL list pages sorted by recency. Project detail pages show page note at top + repos. .tag-row and .badge-planned CSS defined. Test sessions properly closed. 161 tests passing. PR #52 merged. 4-round review-fix loop.
    • [x] Phase 2: Mermaid diagram is accurate and vertical-oriented. Readable on mobile without lightbox. PR #57 merged. 1-round review-fix loop.
    • [x] Phase 3: Spun out into plan-2026-02-28-knowledge-system-consolidation. Privacy audit is Phase 4 there. Demo user dropped.

    Next Plan Seeds

    • Role-based access control — demo user sees curated content, admin sees everything
    • Browse search bar
    • CSRF protection on login form
    • Note graph visualization (Obsidian-style)

    Related

    • project-pal-e-docs — parent project
    • plan-2026-02-27-responsive-design-mobile-ux — predecessor (completed)
    • plan-2026-02-25-private-notes-auth — Phase 3 (privacy audit) absorbed into this plan's Phase 3, then spun out to consolidation plan
    • plan-2026-02-26-browse-frontend-polish — completed predecessor
    • plan-2026-02-28-knowledge-system-consolidation — successor (Phase 3 spun out)
    • issue-pal-e-docs-recency-project-detail — resolved, Phase 1 (PR #52)
    • issue-pal-e-docs-mermaid-diagram-revision — resolved, Phase 2 (PR #57)
  • Plan: Sprint Workflow Automation (DORA Instrumentation) plan-2026-03-03-sprint-workflow-automation

    Vision

    Sprint workflow automation is the DORA instrumentation layer for the pal-e platform. Without it, DORA metrics are manual estimates with "Low" confidence. With it, every agent action — setting a label, submitting a PR, passing QA — generates structured data that flows directly into DORA measurement.

    Agent ActionData GeneratedDORA Metric Fed
    Dev sets status:in-progress labelTimestamp: work startedLead Time (start)
    Dev submits PR, sets status:qaTimestamp: code complete + PR URLLead Time (code complete), Deployment Frequency
    QA sets status:approvedTimestamp: review passedLead Time (review complete), Change Failure Rate
    QA sets status:needs-fixRework iteration countChange Failure Rate (Agent CFR / Rework Rate)
    Betty Sue moves item to DoneTimestamp: shippedLead Time (end), Plan-to-Ship Time
    Betty Sue links PR to sprint itemDeployment count per sprintDeployment Frequency per sprint

    This plan implements the full agent-to-board loop: agents signal status via Forgejo labels, Betty Sue reads labels and syncs sprint boards, sprint boards accumulate the data that makes DORA real. The sprint system doesn't just organize work — it proves the platform thesis.

    Projects & Repos Touched

    Project/RepoPlatformRole in this plan
    All 29 Forgejo reposForgejoStandard labels created via API (Phase 1 — DONE)
    forgejo_admin/claude-customForgejoHooks, skills, agent profiles (Phases 3-4 — DONE, Phase 5 pending)
    pal-e-docs (notes)ForgejoSOP updates + skill notes (Phase 2 — DONE)

    Context

    Sprint 1 exists with 18 plans, 18 phases, and 10 issues across boards — but the sprint is still in "planning" status with no execution engine. The DORA baseline (2026-03-01) showed "Low-Medium" overall confidence. Agent DORA metrics (PRs/day, rework rate, plan-to-ship time, autonomy ratio) are entirely qualitative estimates marked "Low" confidence.

    The todo-sprint-workflow-automation note captured the design decisions and work areas during the 2026-03-02 session. This plan promoted that TODO to a proper phased plan.

    What's already done:

    • Sprint tables + API deployed (Phase 1 of pal-e-sprints backend)
    • Sprint MCP tools deployed (PR #9 merged on pal-e-docs-mcp)
    • Sprint 1 created with all 5 boards populated
    • Phases 1-4 of this plan DONE (see below)
    • Post-merge documentation SOP + /update-docs skill added (supplementary work)

    Previous Plan

    plan-2026-03-01-pal-e-sprints — the backend plan that delivered sprint tables, API, and MCP tools. This plan builds the agent behavior layer on top of that infrastructure.

    Depends On

    • plan-2026-03-01-pal-e-sprints Phase 1 (tables + API + MCP tools) — COMPLETED
    • Schema expansion (PR #67) — nice-to-have for repo/project boards, but NOT blocking.

    Decisions Made

    DecisionRationale
    Forgejo labels as status signalsAgents already interact with Forgejo. Labels are the lightest-weight signal mechanism.
    Hooks as enforcement, not promptsPostToolUse hooks fire automatically — agents can't forget to set labels. Hooks use curl via forgejo-helper.sh. No new MCP tools needed.
    Three enforcement layers: hooks > skills > promptsHooks enforce (can't skip), skills structure (recipe to follow), prompts inform (awareness only). Each layer serves a different purpose.
    Agents comment on PRs, hooks mirror to issuesQA has no Bash or comment_on_issue MCP tool. Hook on comment_on_pr parses verdict and mirrors to issue. TODO created for forgejo-mcp to add proper tools.
    Betty Sue syncs boards, agents don't touch pal-e-docsSeparation of concerns preserved. Agents own repos. Betty Sue owns docs.
    SOPs before implementationDocument the workflow before coding the workflow. Betty Sue's rule.
    Labels first, orchestration lastFoundation before automation. Each phase is independently valuable.
    Post-merge docs as gate before "done"Merged ≠ done. Docs must be current before sprint item moves to done. Formalized as sop-post-merge-docs + /update-docs skill.

    Phases

    See child phase notes: list_notes(parent_slug="plan-2026-03-03-sprint-workflow-automation")

    Five phases, each independently deployable:

    1. Forgejo Labels — DONE. 7 labels across 29 repos. (PR: API calls)
    2. SOP Updates — DONE. agent-workflow, pr-lifecycle, template-sprint-item.
    3. Agent Label Behavior — DONE. 3 PostToolUse hooks (label-on-branch, label-on-pr, label-on-verdict) + forgejo-helper.sh extensions + dev.md/qa.md awareness + skill-review-pr VERDICT format. (PR #52 on claude-custom)
    4. Betty Sue Sprint Skill — DONE. 4 main-session skills (/sprint-sync, /sprint-status, /sprint-add, /sprint-kickoff) + post-merge sprint reminder hook + 4 skill notes in pal-e-docs. (PR #54 on claude-custom)
    5. Orchestration Automation — NOT STARTED. Auto-QA trigger on PR submission. Auto-board sync on merge. Closes the loop for minimal human intervention.

    Supplementary: Post-merge documentation gate — sop-post-merge-docs + skill-update-docs + /update-docs SKILL.md + remind-update-docs.sh hook update. (PR #56 on claude-custom)

    Key Files

    PhaseFileRepo/LocationChange
    1N/A (API calls)ForgejoDONE — labels created
    2agent-workflow, pr-lifecycle, template-sprint-itempal-e-docsDONE — SOPs updated
    3hooks/forgejo-helper.shclaude-customDONE — forgejo_set_label, forgejo_comment_on_issue, forgejo_get_issue_number_from_branch
    3hooks/label-on-branch.shclaude-customDONE — PostToolUse: set status:in-progress
    3hooks/label-on-pr.shclaude-customDONE — PostToolUse: set status:qa + comment PR URL
    3hooks/label-on-verdict.shclaude-customDONE — PostToolUse: parse verdict, set status label
    3settings.json, agents/dev.md, agents/qa.mdclaude-customDONE — hook registration + awareness
    3skill-review-pr notepal-e-docsDONE — exact VERDICT format required
    4skills/sprint-*/SKILL.mdclaude-customDONE — 4 sprint management skills
    4hooks/remind-sprint-update.shclaude-customDONE — post-merge sprint reminder
    skills/update-docs/SKILL.md, hooks/remind-update-docs.shclaude-customDONE — post-merge docs gate
    5Existing hooksclaude-customPENDING — auto-QA trigger + auto-board sync

    Verification

    • Phase 1: DONE — labels visible on all 29 repos
    • Phase 2: DONE — SOPs reviewed and consistent
    • Phase 3: DONE — Dev agent submits PR → hook sets status:qa + comments PR URL on issue automatically. QA agent posts verdict → hook sets status:approved or status:needs-fix automatically.
    • Phase 4: DONE — /sprint-sync, /sprint-status, /sprint-add, /sprint-kickoff skills deployed.
    • Phase 5: PENDING — Dev submits PR → QA auto-spawned → verdict auto-labels → merge auto-syncs board. Full loop.
    • End-to-end: One issue through the full loop with all DORA timestamps captured.

    Discovered Scope

    • todo-fix-remind-mcp-review-loop-paldocs-ref — stale pal-e-docs reference in remind-mcp-review-loop.sh (Phase 3 QA)
    • todo-forgejo-mcp-label-comment-tools — MCP gap: no set_label or comment_on_issue tools (Phase 3 design)

    Next Plan Seeds

    • DORA dashboard automation — once timestamps are captured, build the Grafana dashboard (connects to plan-2026-02-25-platform-observability Phase 4)
    • Token metrics integration — layer token tracking (connects to todo-token-metrics-dora-correlation)
    • forgejo-mcp tool expansion — proper set_label + comment_on_issue MCP tools (see todo-forgejo-mcp-label-comment-tools)
    • Sprint auto-population — API-side auto-sync (connects to phase-sprints-2-issue-sync)

    Related

    • dora-framework — the axiom this plan serves
    • plan-2026-03-01-pal-e-sprints — parent backend plan
    • phase-sprints-2-issue-sync — API-side complement
    • todo-sprint-workflow-automation — the TODO this plan was promoted from
    • todo-token-metrics-dora-correlation — future token tracking
    • todo-forgejo-mcp-label-comment-tools — MCP gap discovered in Phase 3 design
    • sop-claude-config-development — SOP for Phases 3-5
    • sop-post-merge-docs — post-merge documentation gate (supplementary deliverable)
    • agent-workflow — label signaling protocol
    • pr-lifecycle — PR flow with label integration
  • Plan: pal-e-sprints Backend plan-2026-03-01-pal-e-sprints

    Vision

    Five-board sprint system in pal-e-docs. Plans, phases, issues, repos, and projects each get their own board with seven columns (Backlog, TODO, Next Up, In Progress, QA, Needs Approval, Done). Betty Sue manages sprints via MCP. Built on pal-e-docs' existing note decomposition — plans contain phases, phases generate issues.

    The Five Boards

    Board Tracks Source
    Projects Which projects are active this sprint pal-e-docs notes (note_type=project-page)
    Plans Which plans are committed to this sprint pal-e-docs notes (note_type=plan)
    Phases Which phases are moving this sprint pal-e-docs notes (note_type=phase, children of committed plans)
    Items (Issues) Which Forgejo issues are committed Forgejo issue URLs across repos
    Repos Which repos have active work Repo references

    Same table, same columns, filtered by item_type. The sprint board is the commitment layer — it says "these items are in this sprint, in this column."

    Projects & Repos Touched

    Project/Repo Platform Role
    forgejo_admin/pal-e-docs Forgejo Sprint tables, API endpoints
    forgejo_admin/pal-e-docs-mcp Forgejo MCP tools for sprint management

    Phases

    See child phase notes: list_notes(parent_slug="plan-2026-03-01-pal-e-sprints")

    Summary:

    1. Phase 1: Tables, API, MCP Tools — COMPLETED. PR #65 merged (pal-e-docs), PR #9 merged + deployed (pal-e-docs-mcp). 25 MCP tools live.
    2. Schema Expansion — COMPLETED. PR #67 (repo/project + needs_approval), PR #69 (points field), PR #32 (MCP tools update) all merged. QA nits tracked as pal-e-docs-mcp #33 and #34.
    3. Phase 2: Auto-Population and Sync — NOT STARTED. API-side auto-sync of sprint items.
    4. Phase 3: Token Metrics — NOT STARTED. Per-sprint token tracking.

    Decisions Made

    Decision Rationale
    Five boards (projects, plans, phases, items, repos) Expanded from three after Sprint 1 creation showed the need for project and repo tracking.
    Seven columns (added needs_approval) Gate between QA approval and Lucas's merge approval. Maps to status:approved label.
    Single SprintItem table, polymorphic item_type Same columns, same API, filtered by type. Simple.
    note_slug for plans/phases, forgejo_issue_url for issues Plans and phases already live in pal-e-docs. Issues live in Forgejo.
    Backend-first, no frontend Betty Sue manages via MCP. Frontend deferred.
    Sprint data lives in pal-e-docs DB Zero sync overhead. Isolated tables.
    • project-pal-e-sprints — project page
    • plan-2026-03-03-sprint-workflow-automation — the agent behavior plan built on top of this backend
    • todo-token-metrics-dora-correlation — Phase 3 feeds this
    • dora-framework — sprints + tokens measure planning-to-value
  • Vision

    pal-e is the foundation for an AI agency. Three pillars: pal-e-platform (done), pal-e-services (nearly done), pal-e-docs (this plan). Once all three are done, agents have context — they query for what they need and know the conventions.

    User story: "I tell an agent what to do, and it already knows my platform."

    Phases

    1. Phase 1: MCP Server — DONE. pal-e-docs-mcp on GitHub, 11 tools, registered in ~/.mcp.json.
    2. Phase 2: Seed Platform Fundamentals — DONE. 9 notes, 3 projects, cross-linked. Needs user review.
    3. Phase 3: Browser Frontend — DONE. PR #6 merged. Jinja2 SSR at /browse/.
    4. Phase 4: Dogfood — IN PROGRESS. /plan skill updated. CLAUDE.md points to pal-e-docs. This plan stored in pal-e-docs.

    Decisions Made

    • No auth for now — behind Tailscale, issue #2 tracks
    • Platform fundamentals first — platform knows itself before supporting projects
    • Small atomic notes, heavy tagging — Zettelkasten-like, query by tag intersection
    • MCP repo on GitHub — dev tool, same pattern as notion-mcp (user flagged: should this default to Forgejo?)
    • Jinja2 server-side frontend — no client JS, agent can open URLs for user review

    Next Plan Seeds

    • Session-start hook — auto-query active SOPs on session start, inject into context
    • Review seeded content — 9 notes written from agent memory, need user verification
    • Repo placement SOP — when does a repo go on GitHub vs Forgejo? Needs a documented convention.
    • Full-text search — SQLite FTS5 for content search beyond tags
    • Litestream backup — continuous SQLite replication
    • basketball-api seeding — project-specific knowledge after platform fundamentals verified
    • Hook false positive fix — remind-review-loop.sh fires on PR merges, not just PR creations
  • Plan: Public Docs and Template Enforcement plan-2026-02-24-public-docs-and-templates

    Plan: Public Docs and Template Enforcement

    Status: DEFERRED

    This plan depends on plan-2026-02-24-docs-foundation completing first. The foundation plan establishes naming conventions, linking conventions, plan continuity via SessionStart, and the recursive plan structure that this plan assumes.

    When the foundation plan completes, this plan's phases become executable. Each phase may be promoted to its own plan if it needs more detail.

    Vision

    Make pal-e-docs publicly accessible, professionally styled, and structurally enforced. "I tell an agent what to do, and it already knows my platform, my SOPs, my active projects, and where I left off." Everything lives in docs — projects, repos, issues, SOPs. Agents are trained to look at pal-e-docs first, always.

    Previous Plan

    plan-2026-02-24-docs-foundation (prerequisite — do that first)

    Depends On

    • plan-2026-02-24-docs-foundation — naming conventions, linking, plan continuity, template enforcement at MCP level

    Phases (each may become its own plan)

    Phase 1: Public-Facing + Professional CSS

    Goal: pal-e-docs accessible publicly with professional, readable, mobile-friendly styling.

    Candidate plan slug: plan-YYYY-MM-DD-public-css

    1. Enable Tailscale Funnel for pal-e-docs service
    2. Add HTML sanitization on note content rendering
    3. Overhaul base.html CSS: dyslexic-friendly font, responsive, professional typography
    4. Test on mobile and desktop

    Phase 2: Issue Tracking Migration

    Goal: All issues live in pal-e-docs. Agents look here first.

    Candidate plan slug: plan-YYYY-MM-DD-issue-tracking

    1. Migrate existing open issues into pal-e-docs notes
    2. Update SessionStart hook to inject open issues
    3. Add /browse/issues frontend page
    4. Create /new-issue and /close-issue skills

    Phase 3: Remaining Cleanup

    Goal: Close out deferred items.

    1. Merge pending claude-custom PRs (#15, #16, #17)
    2. Merge forgejo-mcp PR #3

    Next Plan Seeds

    • Woodpecker CI for MCP tools
    • Browse search bar
    • Litestream backup
    • Deprecate Project.repo_url
Doc 34
  • Verdict: APPROVED

    Round 2 re-review of board item #1015 (Forgejo issue forgejo_admin/pal-e-app#110). All 8 [BODY] recommendations from review-1015-2026-04-16 have been applied to the issue body and verified against ground truth (Forgejo API, repo state, current main HEAD). The 2 [SCOPE] items (missing story:app-definition entry on project-pal-e-docs, missing arch-pal-e-app note) are correctly captured in the issue's "Out of Scope" section as follow-ups and do not block this revert. Ticket is ready to advance from backlog to todo.

    Round 2 Fix Verification

    Each [BODY] item from round 1 verified against the live issue body fetched from the Forgejo API and against ground truth.

    Fix 1: Explicit 13-file list from PR #90

    • [x] Issue body section ### Scope — Files to Revert (verbatim from PR #90) contains a numbered list of exactly 13 files.
    • [x] Cross-checked against Forgejo API /repos/forgejo_admin/pal-e-app/pulls/90/files: PR #90 touched exactly 13 files. All 13 PR file paths match the issue list verbatim:
      • .env.example, .woodpecker.yaml, CLAUDE.md, README.md, e2e/auth.spec.ts, e2e/home.spec.ts, e2e/public-readiness.spec.ts, k8s/deployment.yaml, k8s/service.yaml, package-lock.json, package.json, playwright.config.ts, src/lib/keycloak.ts
    • [x] Each entry includes which token(s) to flip (e.g., .woodpecker.yaml calls out clone remote URL, Kaniko repo, OVERLAY, PLAYWRIGHT_BASE_URL).

    Fix 2: Forgejo no-rename callout

    • [x] Issue body ### Repo section contains a bolded callout: "Important — no Forgejo repo rename: PR #90's body states 'repo was renamed from pal-e-app to pal-e-docs-app' but this is false. The Forgejo repo is still forgejo_admin/pal-e-app (verified via API). Do not rename anything in Forgejo as part of this revert."
    • [x] Verified via API call to /repos/forgejo_admin/pal-e-app: full_name returns forgejo_admin/pal-e-app (no rename ever happened).

    Fix 3: Pin AC5 SHA

    • [x] AC for ArgoCD roll references 4454c8d10bb4e3f12044e3ba65646115c61a79ab with provenance: "PR #109 merge SHA at ticket creation; refresh via git -C ~/pal-e-app rev-parse origin/main before final verification if more PRs merge."
    • [x] Verified live via Forgejo API /repos/forgejo_admin/pal-e-app/branches/main: current main HEAD is 4454c8d10bb4e3f12044e3ba65646115c61a79ab, commit message feat: My Notes view + identity-aware dashboard + attribution (#109). SHA is real, current, and traceably documented.

    Fix 4: Verification recipe in AC6

    • [x] AC for manual verification spells out the recipe: open https://pal-e-app.tail5b443a.ts.net/notes in a logged-in browser session, confirm My Notes view renders (PR #109), confirm admin role lands on /dashboard and non-admin lands on /notes post-login (PR #105). Pre-step: kubectl rollout status deployment/pal-e-app -n pal-e-app.
    • [x] Recipe is concrete enough that a dev agent can execute without asking how.

    Fix 5: Keycloak orphan callout

    • [x] ### Environment section: "Keycloak: src/lib/keycloak.ts default clientId reverts to pal-e-app. Any orphan Keycloak client created under pal-e-docs-app is out of scope; flag as a follow-up."
    • [x] Mirrored in ### Out of Scope: "Cleanup of any orphan Keycloak client named pal-e-docs-app (if one was created)."

    Fix 6: Harbor orphan callout

    • [x] ### Environment section: "Harbor push target: harbor.tail5b443a.ts.net/pal-e-app/app — do not create a new Harbor project; reuse the existing one. Any orphan pal-e-docs-app Harbor project (if one was created) is out of scope; flag as a follow-up."
    • [x] Mirrored in ### Out of Scope: "Cleanup of any orphan Harbor project named pal-e-docs-app (if one was created)."
    • [x] Lines up with the feedback_harbor_project_naming.md 36-hour-outage lesson — defensive on Harbor.

    Fix 7: New AC — rg in pal-e-deployments

    • [x] AC present: "Cross-repo defensive check: rg pal-e-docs-app in ~/pal-e-deployments returns zero matches (currently passes — confirm no cross-contamination introduced)."
    • [x] Verified live: Grep for pal-e-docs-app in /home/ldraney/pal-e-deployments returns 0 matches (0 files). The defensive AC will pass cleanly today; it is here to catch any regression introduced by the revert agent.

    Fix 8: Issue #87 guidance

    • [x] AC present: "Issue #87 (the rename request that PR #90 closed): post a comment noting the rename was reverted and why; leave issue closed unless requestor reopens."
    • [x] Related section also references #87: "original rename request (verify whether it requested the rename; comment with revert rationale)."
    • [x] Guidance is unambiguous — dev agent will not accidentally reopen.

    [SCOPE] Items Tracked, Not Blocking

    • [x] Out of Scope: "Adding a story:app-definition row to the project-pal-e-docs user-stories table (label is in use but not yet documented on the project page)."
    • [x] Out of Scope: "Creating a missing arch-pal-e-app architecture note (referenced by arch:pal-e-app label, note does not yet exist)."
    • Both are correctly framed as documentation follow-ups. They do not block a regression revert. Ava can spawn separate backlog tickets for these.

    Template Completeness

    • [x] Type — Bug
    • [x] Lineage — standalone, regression from PR #90, references closed #87
    • [x] Repo — forgejo_admin/pal-e-app + Forgejo no-rename callout
    • [x] What Broke — clear, with cascade diagram and SHA evidence
    • [x] Repro Steps — pipelines #114-#123 named, command sequence concrete
    • [x] Expected Behavior — present
    • [x] Environment — cluster/namespace, deployed SHA, main SHA, ingress, Harbor target, Keycloak callout
    • [x] Scope (13 files, verbatim from PR #90)
    • [x] Acceptance Criteria — 7 items now (added cross-repo grep AC), all testable, with verification recipes
    • [x] Out of Scope — orphans + missing notes + PR #275 anti-pattern
    • [x] Related — full link set

    Traceability

    • [x] story:app-definition label — present on board item #1015 (story note still missing — flagged as Out of Scope follow-up)
    • [x] arch:pal-e-app label — present on board item #1015 (arch note still missing — flagged as Out of Scope follow-up)
    • [x] Forgejo issue — #110 open and well-formed
    • Story/arch notes are correctly tracked as separate follow-ups; do not block this revert (regression fix, not new architecture).

    File Targets

    13 files listed verbatim from PR #90, matched 1-to-1 against Forgejo API /pulls/90/files response. All paths exist in /home/ldraney/pal-e-app. Token-level scope (which strings to flip per file) is documented in the issue.

    Repo Placement

    Correct repo (forgejo_admin/pal-e-app). Forgejo no-rename callout prevents the dev agent from attempting a repo rename. Cross-repo defensive AC on pal-e-deployments guards against config drift.

    Dependencies

    • Blocks: PR #105 + PR #109 deploys (both merged, stuck on stale image 76f316b)
    • Blocks: every future pal-e-app PR (10 consecutive failed pipelines #114-#123)
    • Anti-pattern explicitly called out: do not apply pal-e-platform PR #275's decoupling pattern here
    • No competing in-flight work on the board (no other pal-e-app revert items in in_progress / next_up)

    Acceptance Criteria

    7 ACs, all testable, all with concrete verification commands or recipes. AC5 SHA verified live as current main HEAD. AC6 verification recipe is concrete (kubectl rollout + browser visit + role-based redirect check). AC7 (Issue #87 comment guidance) is unambiguous.

    Blast Radius

    Round 1's blast-radius callouts (Keycloak orphan client, Harbor orphan project, defensive grep on pal-e-deployments, Issue #87 guidance) all incorporated. Cross-repo grep on pal-e-deployments verified clean today.

    Decomposition Assessment

    13 files in 1 repo, mechanical sed-style revert, 7 ACs. Estimated agent time: ~10-15 min revert + commit + push, ~5 min CI wait. Tightly scoped, mechanical work — no decomposition needed. Single agent pass is correct sizing.

    Recommendation

    APPROVED. All 8 round-1 [BODY] fixes verified present and correct against ground truth (Forgejo API for PR #90 files + main HEAD SHA, repo grep for pal-e-deployments cleanliness, repo grep for token scope per file). The 2 [SCOPE] items are tracked as Out of Scope follow-ups and do not block this revert. Ticket is ready to advance from backlog → todo. Ava: feel free to flip the column.

    Side note for Ava: when this revert lands, also queue:

    • A small backlog item: "Add story:app-definition row to project-pal-e-docs user-stories"
    • A small backlog item: "Create arch-pal-e-app note"
    • A small follow-up: "Sweep Keycloak realm + Harbor catalog for orphan pal-e-docs-app entities; clean if present"
  • Verdict: NEEDS_REFINEMENT

    The diagnosis is correct and the fix direction is right, but scope is under-specified for a clean dev-agent execution. PR #90's body enumerates every file it touched — that list is the canonical revert checklist and the issue body should reuse it verbatim instead of saying "playwright.config.ts (and elsewhere)." Two values in that list (Keycloak clientId, Harbor image repo) are semantically loaded and need explicit guidance with infra evidence so the dev doesn't second-guess.

    Template Completeness

    • [x] Type — Bug
    • [x] Lineage — standalone, regression from PR #90
    • [x] Repo — forgejo_admin/pal-e-app
    • [x] What Broke — clear, with cascade diagram
    • [x] Repro Steps — pipelines #114-#123 named
    • [x] Expected Behavior — present
    • [x] Environment — cluster/namespace, deployed SHA, main SHA, ingress all named
    • [x] Acceptance Criteria — 6 items, mostly testable (see Acceptance Criteria section below)
    • [x] Related — links to dictionary + PR #275 deferral guidance

    Traceability

    • [x] story:app-definition label present on board item #1015
    • [ ] story note MISSING — get_section(slug="project-pal-e-docs", anchor_id="user-stories") returns 5 stories (superuser-query, superuser-maintain, agent-read, agent-write, reader-browse). app-definition is not listed. search_notes("story app-definition") returns empty. [SCOPE] Add story:app-definition entry to project-pal-e-docs user-stories table (or to a different project if the story lives elsewhere — clarify which).
    • [x] arch:pal-e-app label present on board item #1015
    • [ ] arch note MISSING — search_notes("arch-pal-e-app") and search_notes("arch pal-e-app frontend") return empty. [SCOPE] Create architecture note arch-pal-e-app describing the frontend component (SvelteKit + Keycloak + Tailscale ingress + Harbor image flow).
    • [x] Forgejo issue — #110 open and well-formed

    Note: missing story/arch notes do not block this revert (it's a regression fix, not new architecture). Flagging per skill convention so they get tracked.

    File Targets

    The issue says "playwright.config.ts (and elsewhere)" — but PR #90's body enumerates the exact file list. Repo-wide grep for pal-e-docs-app confirms PR #90's list matches the live repo state today. Verified files:

    • [x] /home/ldraney/pal-e-app/playwright.config.ts — line 4 (comment), line 20 (baseURL default) — 2 hits
    • [x] /home/ldraney/pal-e-app/.woodpecker.yaml — line 55 (PLAYWRIGHT_BASE_URL), line 69 (Kaniko repo), line 88 (OVERLAY) — 3 hits
    • [x] /home/ldraney/pal-e-app/k8s/deployment.yaml — lines 4, 14, 18, 23, 24 (image repo) — 5 hits
    • [x] /home/ldraney/pal-e-app/k8s/service.yaml — lines 4, 6, 9 — 3 hits
    • [x] /home/ldraney/pal-e-app/src/lib/keycloak.ts — lines 2, 6, 13 (default clientId) — 3 hits
    • [x] /home/ldraney/pal-e-app/.env.example — line 8 (VITE_KEYCLOAK_CLIENT_ID) — 1 hit
    • [x] /home/ldraney/pal-e-app/package.json — line 2 (name field)
    • [x] /home/ldraney/pal-e-app/package-lock.json — lines 2, 8 (name fields)
    • [x] /home/ldraney/pal-e-app/README.md — line 1 (title)
    • [x] /home/ldraney/pal-e-app/CLAUDE.md — lines 1, 72 (title + remote URL)
    • [x] /home/ldraney/pal-e-app/e2e/home.spec.ts — line 4 (comment)
    • [x] /home/ldraney/pal-e-app/e2e/auth.spec.ts — line 4 (comment)
    • [x] /home/ldraney/pal-e-app/e2e/public-readiness.spec.ts — line 8 (comment)

    13 files, 27+ hits total. Repo-wide grep for pal-e-app\.tail5b443a returns no matches (confirms no orphan correct refs to preserve).

    [BODY] Replace "(and elsewhere)" with the exact PR #90 file enumeration above. Dev shouldn't have to re-derive scope.

    Repo Placement

    Correct repo. PR #90 was filed and merged on forgejo_admin/pal-e-app (verified via curl to Forgejo API — repository.full_name returns forgejo_admin/pal-e-app). The Forgejo repo itself was NOT renamed; PR #90's premise ("Repo was renamed from pal-e-app to pal-e-docs-app") was wrong. [BODY] Add a one-liner: "PR #90 claimed the repo was renamed — it was not. Repo is and always has been forgejo_admin/pal-e-app. Do not attempt to rename anything in Forgejo as part of this revert."

    Dependencies

    • Blocks: PRs #105, #109 deploy (already merged to main, stuck on stale image)
    • Blocks: any future pal-e-app PR (every pipeline since #114 fails the same way)
    • Related but separate: pal-e-platform PR #275 (decouples update-kustomize-tag from test failures). Issue body explicitly says "don't apply here" — that guidance is unambiguous and correct.
    • No in-flight dependency on board (no other pal-e-app revert items in_progress / next_up).

    Acceptance Criteria

    Mostly testable. Two AC need a small tightening:

    • AC1 (baseURL revert) — testable via grep "pal-e-app.tail5b443a" playwright.config.ts
    • AC2 (repo-wide sweep) — testable via rg pal-e-docs-app returning zero matches. Strong as written.
    • AC3 (CI passes through update-kustomize-tag) — testable via Woodpecker pipeline status
    • AC4 (pal-e-deployments commit) — testable via git log on pal-e-deployments
    • AC5 (ArgoCD rolls to commit ≥ 4454c8d) — [BODY] verify 4454c8d is the actual main HEAD before the dev agent starts. The issue claims this is PR #109's commit; the dev agent should be told either to use the main HEAD at execution time, or this SHA needs to be pinned with provenance.
    • AC6 (manual /notes + role-based routing verification) — needs a how. Is this curl + auth, or browser? [BODY] add the verification recipe (e.g., "open https://pal-e-app.tail5b443a.ts.net/notes in browser after Keycloak login, confirm My Notes view renders").

    Blast Radius

    Important callouts the issue doesn't make:

    • Keycloak clientId: src/lib/keycloak.ts line 13 sets the default clientId to 'pal-e-docs-app'. .env.example sets VITE_KEYCLOAK_CLIENT_ID=pal-e-docs-app. The deployed pod likely uses an env override, but if not, the running app is asking Keycloak for a client ID that may or may not exist in the realm. [BODY] Add: "If a Keycloak client named pal-e-docs-app was created during PR #90, that's also stale config — the canonical client ID is pal-e-app. Reverting the code defaults is correct; check Keycloak realm for orphan clients as a follow-up (NOT part of this fix)."
    • Harbor image repo: k8s/deployment.yaml line 24 references harbor.tail5b443a.ts.net/pal-e-docs-app/app:e23a1d8c..., but the live overlay (pal-e-deployments/overlays/pal-e-app/prod/kustomization.yaml line 64) uses harbor.tail5b443a.ts.net/pal-e-app/app. The base manifest in this repo is overlay-overridden, so the wrong image path here doesn't break prod — but reverting it to pal-e-app/app is correct AND aligns with .woodpecker.yaml line 69 (Kaniko push target). [BODY] Add: "Harbor project for this image is pal-e-app. Verify by listing harbor.tail5b443a.ts.net/v2/_catalog — do NOT create a new Harbor project. If a pal-e-docs-app Harbor project was auto-created during failed pushes, leave it alone (cleanup is a separate ticket)."
    • Sibling services: grep -r "pal-e-docs-app" /home/ldraney/pal-e-deployments/ returns zero matches. grep -r "pal-e-docs-app" /home/ldraney/pal-e-services/ not checked but should be sweep-verified. [BODY] Add an AC: "Repo-wide grep on pal-e-deployments and pal-e-services returns zero pal-e-docs-app matches" (defensive — confirms no infra cross-contamination).
    • Original Issue #87: PR #90 closed Issue #87. The dev agent should NOT reopen #87 unless it explicitly described the (incorrect) rename request — if so, #87 needs a closing comment explaining the regression. [BODY] Add: "If PR #87 requested the rename, comment on it explaining the revert. Otherwise leave it closed."

    Decomposition Assessment

    13 files in 1 repo. AC count: 6 (with 2 needing tightening). Estimated agent work: ~10-15 min for the revert (mechanical sed + targeted manual review of keycloak.ts and deployment.yaml) + ~5 min waiting on CI. Total ~20 min agent time.

    This exceeds the 5-minute rule on raw time, but the work is mechanical and tightly scoped (one repo, one rename direction). No decomposition needed — splitting would create more coordination overhead than it saves. A single agent can handle the revert + commit + push + monitor CI in one pass.

    Recommendation

    • [BODY] Replace "(and elsewhere)" in "What Broke" with the explicit 13-file list from PR #90 (reproduced in File Targets section above).
    • [BODY] Add to "What Broke" or a new "Important context" section: "PR #90 claimed the Forgejo repo was renamed; it was not. Do not rename anything in Forgejo."
    • [BODY] Pin or refresh AC5's reference SHA (≥ 4454c8d) — either confirm it's main HEAD now or instruct dev to use git rev-parse origin/main at execution time.
    • [BODY] Add verification recipe to AC6 (how to manually verify /notes + role routing).
    • [BODY] Add Keycloak clientId callout: revert code defaults to pal-e-app, leave any orphan Keycloak client for follow-up cleanup.
    • [BODY] Add Harbor callout: image goes to pal-e-app/app, do not create new Harbor project, leave any orphan project alone.
    • [BODY] Add new AC: "rg pal-e-docs-app on pal-e-deployments returns zero matches."
    • [BODY] Add Issue #87 guidance (one line on whether to reopen / comment).
    • [SCOPE] Create user story entry on project-pal-e-docs user-stories section for story:app-definition (or clarify if story lives on a different project page).
    • [SCOPE] Create architecture note arch-pal-e-app for the frontend component.

    Once the [BODY] fixes land, this is READY. The [SCOPE] items are independent backlog work and should not block this revert.

  • Validation: #278 Hostname swap step 1 — additive api.pal-e-docs funnel

    Ticket

    Forgejo: forgejo_admin/pal-e-platform#278
    PR: forgejo_admin/pal-e-deployments#113 (merged as 8f34147)
    Board item: #972 on board-pal-e-docs
    What shipped: New kustomize overlay at overlays/pal-e-docs-api/prod/ containing an Ingress resource for api.pal-e-docs Tailscale funnel pointing to the existing pal-e-docs service on port 8000.

    Environment

    Prod cluster (k3s), namespace pal-e-docs, ArgoCD-managed deployments via pal-e-deployments repo.

    Checks

    # Criterion How to Verify Result Evidence
    1 ArgoCD synced PR #113 merge commit kubectl get application -n argocd pal-e-docs -o jsonpath='{.status.sync.revision}' PASS Revision = 8f34147264821a8e5c91ac68cb41c9e6c00c1ec2, status = Synced, health = Healthy
    2 New ingress pal-e-docs-api-funnel exists kubectl -n pal-e-docs get ingress BLOCKED Only pal-e-docs-funnel exists. The new overlay lives at overlays/pal-e-docs-api/prod/ — a separate path from the existing overlays/pal-e-docs/prod/ ArgoCD app. No ArgoCD application exists for pal-e-docs-api to deploy this overlay.
    3 New hostname resolves: api.pal-e-docs.tail5b443a.ts.net curl BLOCKED Ingress not created — no hostname to test.
    4 Regression: existing hostname still works curl -sI https://pal-e-docs.tail5b443a.ts.net/notes/arch-westside-emails PASS (implicit) Existing pal-e-docs-funnel ingress unchanged (47d age). No modifications to existing overlay.

    Verdict

    PARTIAL — The kustomize overlay is merged and the existing ArgoCD app (pal-e-docs) has synced the commit, but the new overlay at overlays/pal-e-docs-api/prod/ has no corresponding ArgoCD application to deploy it. The ingress resource is defined in code but not applied to the cluster.

    Next step required: Create an ArgoCD application (likely via var.services in pal-e-services, or a standalone ArgoCD Application resource) that points to overlays/pal-e-docs-api/prod/ in the pal-e-deployments repo. Once that app is created and synced, the pal-e-docs-api-funnel ingress will be deployed and the Tailscale funnel will provision api.pal-e-docs.tail5b443a.ts.net.

    Discovered Issues

    The PR merged a new overlay directory but did not include the ArgoCD application definition needed to deploy it. This is consistent with the ticket title ("step 1: additive") — the overlay is additive and safe, but a step 2 is needed to wire the ArgoCD app. This should be tracked as a follow-up ticket if not already scoped.

  • Ticket

    forgejo_admin/pal-e-api#255 — Audit and re-block legacy un-decomposed notes. PR #259 merged to main (commit 7737b91). Board item #969 on board-pal-e-docs.

    What was shipped: a one-time migration script that re-decomposes 34 legacy notes that had no block-level structure, converting monolithic html_content into typed blocks (heading, paragraph, table, code/mermaid, list).

    Environment

    Production cluster, pal-e-docs namespace. Validated via pal-e-docs MCP tools (get_note_toc, list_blocks) and kubectl.

    Checks

    This ticket has two validation phases: (A) script is deployed in image, (B) script has been executed against prod data.

    # Criterion How Verified Result Evidence
    A1 PR #259 merged to main git log --oneline main in ~/pal-e-docs PASS Commit 7737b91 fix: reblock 34 legacy un-decomposed notes (#259) is HEAD of main
    A2 CI pipeline green for merge commit Woodpecker pipeline #91 FAIL Pipeline #91 failed: stale Board import in alembic/env.py. This is a pre-existing CI issue unrelated to the reblock script. Running pod image is 89f663b (commit #244). Script is a one-time migration, not a runtime feature.
    B1 Canary note arch-secrets-pipeline returns headings via get_note_toc get_note_toc(slug="arch-secrets-pipeline") PASS Returns 8 headings: Architecture Secrets Pipeline, Domain Map, Data Flow, Deployment Map, Secret Inventory (15), GPG Key, Procedures, Open Gaps, Related
    B2 Canary note has multiple block types via list_blocks list_blocks(slug="arch-secrets-pipeline") PASS 20 blocks across 5 types: heading (8), paragraph (5), code/mermaid (3), table (1), list (3)
    B3 Other previously un-decomposed notes also have blocks get_note_toc on sop-secrets-management and convention-block-first-access PASS sop-secrets-management: 16 headings. convention-block-first-access: 9 headings. Both were previously un-decomposed.

    Verdict

    PASS — Both validation phases confirmed. The reblock migration script has been merged (Phase A) and executed against production data (Phase B). 34 legacy notes now have full block-level decomposition with correct block types. The get_note_toc and list_blocks APIs return rich structured data for previously monolithic notes.

    Discovered Issues

    • CI pipeline broken (pipeline #91, #92): Stale Board import in alembic/env.py causes ImportError during migration-test step. This blocks all future pal-e-api deployments. Running pod is 2 commits behind main (89f663b vs 7737b91). Needs a Forgejo issue + board item.
  • Ticket

    forgejo_admin/pal-e-api#252 — Add partial indexes for mermaid blocks and architecture notes
    Board item: #944 on board-pal-e-docs
    PR: #253 (merged)

    Shipped: Two partial indexes — ix_blocks_block_type_mermaid on blocks table and ix_notes_note_type_architecture on notes table — to speed up filtered queries.

    Environment

    Prod cluster (archbox), namespace pal-e-docs. Database: paledocs on pal-e-postgres-1 in namespace postgres.

    Checks

    # Criterion How to Verify Result Evidence
    1 PR #253 merged to main Forgejo PR list, state=closed PASS PR #253 state=closed, merged=true
    2 Pod running in pal-e-docs namespace kubectl get pods -n pal-e-docs PASS pal-e-docs-76b5f66c69-qxlg6 Running, 0 restarts, 14d age
    3 Index ix_blocks_block_type_mermaid exists on blocks table kubectl -n postgres exec pal-e-postgres-1 -- psql -U postgres -d paledocs -c "\di" FAIL Index not present in \di output. 27 indexes listed, neither partial index exists.
    4 Index ix_notes_note_type_architecture exists on notes table kubectl -n postgres exec pal-e-postgres-1 -- psql -U postgres -d paledocs -c "\di" FAIL Index not present. Current alembic_version: s9n0o1p2q3r4
    5 EXPLAIN uses mermaid partial index Blocked — index does not exist BLOCKED N/A

    Verdict

    PARTIAL — PR merged successfully, pod is healthy, but the alembic migration has not been applied to production. The two partial indexes (ix_blocks_block_type_mermaid, ix_notes_note_type_architecture) do not exist yet. Needs alembic upgrade head run against prod database.

    Discovered Issues

    • Pod image is 14 days old (tag 89f663bb...). The deployment may not have been updated after PR #253 merged. Verify CI built and pushed the new image, and that ArgoCD/kustomize picked up the new tag.
    • Migration alembic upgrade head needs to be run on prod — either via a job or manual exec into the pod.
  • Validation: pal-e-api #256 -- Add CORS Middleware

    Ticket

    Forgejo issue: forgejo_admin/pal-e-api#256 -- Add CORS middleware
    PR: #260 (merged to main)
    Board item: #971 on board-pal-e-docs
    What was shipped: CORS middleware for cross-origin frontend access to pal-e-api.

    Environment

    Production cluster, namespace pal-e-docs, Tailscale funnel URL https://pal-e-docs.tail5b443a.ts.net.

    Checks

    # Criterion How to Verify Result Evidence
    1 Pod redeployed with merged code kubectl -n pal-e-docs get pods -o wide FAIL Pod pal-e-docs-76b5f66c69-qxlg6 is 14d old, image tag 89f663bb.... No redeployment occurred.
    2 Woodpecker pipeline green for merge commit list_pipelines for pal-e-api main FAIL Pipeline #90 (push to main for PR #260) has status failure. Migration-test step fails with ImportError: cannot import name 'Board' from 'pal_e_docs.models' in alembic/env.py. This is an unrelated pre-existing import issue, not a CORS defect.
    3 CORS headers present for allowed origin curl -D- -H "Origin: https://pal-e-production.tail5b443a.ts.net" https://pal-e-docs.tail5b443a.ts.net/notes/arch-westside-emails FAIL Response headers show no access-control-allow-origin. Expected -- old image is still running.
    4 Preflight returns 200/204 with allow-methods curl -X OPTIONS with CORS preflight headers FAIL Returns HTTP/2 405 with allow: GET. No CORS preflight handling -- old image.
    5 Disallowed origin gets no CORS header curl -H "Origin: https://evil.example.com" N/A Cannot verify -- CORS middleware not deployed yet. No headers present on any origin.

    Verdict

    PARTIAL -- Awaiting deployment. PR #260 is merged but Woodpecker pipeline #90 failed due to an unrelated Board import error in alembic/env.py. The pod has not been redeployed. Pipeline #92 appears to be a fix attempt for this import issue. CORS checks cannot pass until the image is rebuilt and deployed.

    Blocker

    Woodpecker pipeline #90 failure: ImportError: cannot import name 'Board' from 'pal_e_docs.models' in the migration-test step. This blocks image build and deployment for all pal-e-api merges, not just this ticket.

    Discovered Issues

    • CI blocker: The alembic/env.py stale Board import is blocking all pal-e-api deployments. Pipeline #92 (PR for the fix) also shows status error. This needs immediate attention -- it is a cross-cutting blocker for all pal-e-api work.
  • Ticket

    forgejo_admin/claude-custom#239 — Harden check-note-template.sh: mermaid fence + facet keyword enforcement.
    PR #240 merged to main. Board item #945 on board-pal-e-docs.

    Environment

    Local workstation: ~/claude-custom (hardlinked to ~/.claude/hooks/). Forgejo remote: forgejo_admin/claude-custom.

    Checks

    # Criterion How to Verify Result Evidence
    1 Merge commit present on remote main git fetch origin; git log --oneline origin/main -1 PASS ceac036 feat(hooks): enforce mermaid fence + facet keyword for architecture notes (#239) (#240)
    2 Mermaid enforcement code present in merged file git show origin/main:hooks/check-note-template.sh | grep -c "mermaid" PASS 21 occurrences of "mermaid" in the merged hook file (vs 0 in pre-merge version)
    3 Local ~/claude-custom on main with merged changes cd ~/claude-custom; git log --oneline -1 FAIL Local main at 6bca6be (4 commits behind origin/main). Needs git pull.
    4 Hardlinks are live (matching inodes) ls -li ~/.claude/hooks/check-note-template.sh ~/claude-custom/hooks/check-note-template.sh PASS Both files share inode 4334436. Hardlink intact.
    5 Local hook has new mermaid logic grep -c "mermaid" ~/claude-custom/hooks/check-note-template.sh FAIL 0 occurrences — local file is stale (pre-merge version). Will resolve when git pull runs.

    Verdict

    PARTIAL — The merge commit is confirmed on remote main with correct content (21 mermaid references). Hardlinks are intact (same inode). However, ~/claude-custom local checkout is 4 commits behind origin/main. Once git pull is run, the hardlinked file will update in place and all checks will pass.

    Remediation

    cd ~/claude-custom && git pull origin main

    Discovered Issues

    None.

  • Purpose

    Prove visual design with raw HTML+CSS before committing to production repos. Zero framework overhead. Immediate feedback on any device.

    Philosophy

    • HTML+CSS first. New experiments start as index.html files. No npm, no build step, no SvelteKit. Open in browser, iterate, refresh.
    • Mobile-first. If it doesn't look right on your phone, it's not ready.
    • Prove the look, not the data. Use fake/hardcoded content. The API integration happens in the production repo (pal-e-app), not here.
    • SvelteKit only when needed. If an experiment requires interactivity (drag-and-drop, auth flows, live API data), SvelteKit is justified. Otherwise, raw HTML.
    • User in the loop. Lucas iterates on design personally. Agents don't ship frontend without visual approval.

    Infra

    Detail Value
    Repo forgejo_admin/playground
    Local path ~/html-playground
    Deployment k8s namespace playground, served on port 80
    Public URL Tailscale funnel: playground-funnel
    Access Any device on the tailnet

    Convention

    Experiments are numbered folders in the repo root:

    html-playground/
      index.html              ← landing page (raw HTML)
      1-first-repo/           ← SvelteKit (canvas experiment)
      2-svelte-hello/         ← SvelteKit (learning)
      3-westside-dashboard/   ← SvelteKit → promoted to westside-app
      4-sprint-board/         ← SvelteKit (kanban prototype)
      5-pal-e-docs/           ← SvelteKit (note browser prototype)
      6-next-experiment/      ← raw HTML+CSS (new paradigm)
    

    Going forward: new experiments should default to raw index.html unless interactivity is explicitly required.

    Promotion Path

    1. Playground — iterate on HTML+CSS until it looks right on phone
    2. Screenshot = spec — capture the approved look
    3. Agent ports to production — dev agent takes the proven CSS into pal-e-app (or whichever SvelteKit repo)
    4. User verifies — Lucas checks the production result on device before merge

    History

    • 3-westside-dashboard → successfully promoted to westside-app (production SvelteKit app)
    • 4-sprint-board → kanban concept that informed pal-e-app board implementation
    • 5-pal-e-docs → attempted live API integration in playground, hit complexity wall. Lesson: playground proves look, not data integration.
    • plan-pal-e-docs — playground-first is an organizing principle
    • feedback_playground_first — original feedback that established the convention
    • doc-network-traffic-map — shows playground namespace + funnel in cluster topology
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type — present but wrong value (says "Feature", should be "Bug"; ImagePullBackOff is broken behavior)
    • [x] Lineage — present (board, story, arch, blocking info)
    • [ ] Repo — present but inaccurate: says "pal-e-platform (Harbor config)" but Harbor config lives in pal-e-services (see Repo Placement)
    • [x] User Story — clear: platform operator needs pod running for portfolio demos
    • [x] Context — good background: pod name, image reference, namespace age, root cause hypothesis
    • [x] File Targets — 3 targets listed (Harbor UI, deployment overlay, woodpecker config)
    • [x] Acceptance Criteria — 4 testable conditions
    • [x] Test Expectations — explicit kubectl and curl commands
    • [x] Constraints — 3 constraints listed
    • [x] Checklist — 5 discrete steps
    • [x] Related — links to Harbor incident and live URL

    Missing bug-template sections: Repro Steps, Expected Behavior, Environment (structured). These are embedded loosely in Context but not per bug template format.

    Traceability

    • [x] story:reader-browse — pal-e-docs-app frontend serves the documentation browse experience
    • [x] arch:k8s-deploy — deployment/pod infrastructure
    • [x] Forgejo issue — forgejo_admin/pal-e-platform#234, open

    All three traceability legs present.

    File Targets

    • [x] pal-e-deployments/overlays/pal-e-docs-app/ — verified: directory exists with kustomization.yaml (image: harbor.tail5b443a.ts.net/pal-e-docs-app/app:e23a1d8c...), harbor-creds.enc.yaml, deployment-patch.yaml, ingress.yaml
    • [x] .woodpecker.yaml in app repo — verified: exists at ~/pal-e-app/.woodpecker.yaml, has build-and-push step pushing to pal-e-docs-app/app Harbor project with Kaniko + update-kustomize-tag step
    • [x] Harbor admin UI — operational check, not a file target (acceptable)

    Targets are specific enough for agent execution. The kustomize overlay image tag e23a1d8c... is the key artifact to verify against Harbor.

    Repo Placement

    ISSUE: The Forgejo issue is filed on forgejo_admin/pal-e-platform and says the repo is "pal-e-platform (Harbor config)". But pal-e-platform has zero references to pal-e-docs-app anywhere in the codebase. The Harbor project, robot accounts, namespace, pull secrets, and ArgoCD application for pal-e-docs-app are all managed by pal-e-services/terraform/k3s.tfvars (lines 137-144). The correct repo for Harbor config is forgejo_admin/pal-e-services, not forgejo_admin/pal-e-platform.

    The deployment overlay is correctly identified as forgejo_admin/pal-e-deployments. The Woodpecker pipeline lives in the app repo (forgejo_admin/pal-e-docs-app, checked out locally as ~/pal-e-app).

    The fix likely involves: (1) verifying the image was pushed to Harbor (operational), (2) possibly triggering a Woodpecker build if missing (pal-e-docs-app repo), (3) verifying pull secrets (pal-e-services terraform). None of these touch pal-e-platform.

    Dependencies

    • [ ] Item #510 "Rename pal-e-app to pal-e-docs-app" (backlog) — status: unknown/pending. If the rename has already happened, the old image SHA from pre-rename may not exist. This could be the root cause and should be investigated.
    • [x] Item #513 "Validate: pal-e-app (4 PRs, clone failure)" (backlog) — independent, same story:reader-browse but no blocking relationship.

    No hard blockers on the board. The rename item (#510) is a potential root cause contributor that should be investigated during execution.

    Acceptance Criteria

    4 criteria, all operationally verifiable by an agent:

    • [x] "Image exists in Harbor at the expected tag" — verifiable via Harbor API or kubectl describe pod events
    • [x] "Image pull secret exists in pal-e-docs-app namespace" — kubectl get secret -n pal-e-docs-app
    • [x] "Pod transitions from ImagePullBackOff to Running" — kubectl get pods -n pal-e-docs-app
    • [x] "curl returns 200" — curl -sI https://pal-e-docs-app.tail5b443a.ts.net

    All criteria are testable and specific. No ambiguous language. No missing criteria detected.

    Blast Radius

    All 10 services in pal-e-services use the same Harbor pull pattern (harbor.tail5b443a.ts.net/{project}/{image}:{sha}). If the root cause is a credential or Harbor project configuration issue, the same problem could affect other services. However, since the issue is specifically about a missing or wrong image tag for one service, blast radius is limited to pal-e-docs-app only. Rollback is straightforward — revert the kustomize image tag to a known-good SHA.

    Decomposition Assessment

    Applying three-thing limit and five-minute rule:

    • Discrete changes: 2-3 (diagnose root cause, fix image/secret/pipeline, verify). Under the 3-thing limit.
    • Repos touched: primarily operational (kubectl, Harbor API). Code changes likely limited to pal-e-deployments kustomize tag or triggering a Woodpecker build.
    • Estimated agent time: <5 minutes for diagnosis + fix.
    • No independent subtasks that need parallelization.

    No decomposition needed.

    Recommendation

    1. [BODY] Fix Type header: "Feature" to "Bug" (ImagePullBackOff is broken behavior, not new functionality). Board item label type:bug is correct.
    2. [BODY] Fix Repo line: replace forgejo_admin/pal-e-platform (Harbor config) with forgejo_admin/pal-e-services (Harbor config via terraform). pal-e-platform has zero references to pal-e-docs-app.
    3. [BODY] Add structured Environment section per bug template: Cluster: prod, Namespace: pal-e-docs-app, Image: harbor.tail5b443a.ts.net/pal-e-docs-app/app:e23a1d8c..., Kustomize overlay: pal-e-deployments/overlays/pal-e-docs-app/prod/kustomization.yaml
    4. [BODY] Add Repro Steps section: 1. kubectl get pods -n pal-e-docs-app 2. Observe ImagePullBackOff on pod 3. kubectl describe pod shows image pull failure
    5. [SCOPE] Investigate whether board item #510 (pal-e-app rename to pal-e-docs-app) has already executed — if so, the old SHA may reference a pre-rename build that doesn't exist in Harbor.
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- traced to plan-pal-e-docs, Westside docs sync
    • [x] Repo -- forgejo_admin/pal-e-docs (now pal-e-api)
    • [x] User Story -- clear: agent updating phase notes wants sync_board to propagate title drift
    • [x] Context -- sufficient background including discovery origin and prior review
    • [x] File Targets -- specific file and function identified
    • [x] Acceptance Criteria -- 4 testable conditions
    • [x] Test Expectations -- 3 unit tests with run command
    • [x] Constraints -- pattern to follow, count semantics, non-regression
    • [x] Checklist -- PR, tests, no unrelated changes
    • [x] Related -- project, SOP, phase note, prior review

    All required template sections present. Issue was previously reviewed (review-281-2026-03-22, NEEDS_REFINEMENT) and rescoped to sync_board title drift only. The rescoped body is well-structured.

    Traceability

    • [ ] story:X label -- MISSING. Board item labels are type:feature,scope:board-api,discovered-scope. The user story describes an agent updating phase notes. Suggest story:superuser-maintain or story:kanban-daily-review.
    • [ ] arch:X label -- MISSING. scope:board-api is present but the convention is arch:board-api. Should be added.
    • [x] Forgejo issue -- https://forgejo.tail5b443a.ts.net/forgejo_admin/pal-e-api/issues/192, open

    File Targets

    • [x] src/pal_e_docs/routes/boards.py -- verified exists. sync_board function confirmed. Title drift gap confirmed: lines 362-367 compare column only, no title comparison.
    • [ ] Line numbers stale -- Issue says "sync_board (line 251+)" but function is at line 324. Issue says "sync_issues pattern (lines 394-399)" but the actual drift detection code is at lines 458-462 (394-399 is the docstring). Code has changed since issue was written. Function names are correct so agent can still find the code, but line numbers should be updated for accuracy.
    • [x] sync_issues drift pattern verified at line 461: if existing.title != issue_title. Pattern is clear and directly applicable.
    • [x] BoardItemUpdate.title in schemas.py:294 -- confirmed exists (do-not-touch, correctly identified).

    Repo Placement

    OK after rescope. Issue filed on forgejo_admin/pal-e-api (formerly pal-e-docs). Fix is entirely within this repo's routes/boards.py. The MCP layer gap was split to a separate issue (board item #282, now done).

    Dependencies

    • [x] Board item #524 "Fix 12 failing board_sync tests" -- done. Tests are passing, so new tests can be added safely.
    • [x] Board item #282 (MCP title param) -- done. The MCP layer fix shipped independently.
    • [x] No unresolved blockers. No items depend on this ticket.

    Acceptance Criteria

    • AC 1: "sync_board detects when a phase board item title differs from its linked note title and updates it" -- Clear, testable. Verified the gap exists (lines 362-367 only check column).
    • AC 2: "Updated items increment the updated count in sync response" -- Clear, testable. The counter pattern already exists in the function.
    • AC 3: "Items with current titles are skipped (no unnecessary writes)" -- Clear, testable.
    • AC 4: "Non-phase items with manually set titles are not overwritten" -- Inherently true since sync_board only iterates phase children of plans. Still worth a test to confirm and prevent regression.

    All 4 ACs are agent-verifiable. Test command pytest tests/ -k board_sync or sync_board is valid.

    Blast Radius

    • sync_board only processes phase-type items linked to plan notes. Low blast radius.
    • sync_issues already handles title drift for issue-type items independently -- no interaction.
    • The change is additive: adds a title check alongside the existing column check. No destructive behavior.
    • Rollback is straightforward: revert the single commit.

    Decomposition Assessment

    No decomposition needed.

    • 1 file target in 1 repo -- under the 3-file limit
    • 4 acceptance criteria -- under the 5-AC limit
    • Estimated work: ~5 lines of production code + 3 unit tests -- well within the 5-minute rule
    • No independent subtasks to parallelize

    Recommendation

    1. [LABEL] Add story:superuser-maintain label to board item #281
    2. [LABEL] Add arch:board-api label to board item #281 (replace or supplement scope:board-api)
    3. [BODY] Update stale line numbers in issue body: sync_board is at line 324 (not "line 251+"), sync_issues drift pattern is at lines 458-462 (not "lines 394-399")
    4. [BODY] Update Repo field from forgejo_admin/pal-e-docs to forgejo_admin/pal-e-api (repo was renamed)
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type — Feature
    • [x] Lineage — Standalone
    • [x] Repo — present but STALE (says forgejo_admin/pal-e-app, repo renamed to forgejo_admin/pal-e-docs-app)
    • [x] User Story — present
    • [x] Context — present
    • [x] File Targets — present but contains stale reference (see below)
    • [x] Acceptance Criteria — present (4 criteria)
    • [x] Test Expectations — present (3 items)
    • [x] Constraints — present
    • [x] Checklist — present
    • [x] Related — present

    Traceability

    • [x] story:kanban-daily-review — present on board item #298
    • [ ] arch:X — MISSING. Should be arch:frontend (consistent with sibling board items #469, #471, #472, etc.)
    • [x] Forgejo issue — forgejo_admin/pal-e-docs-app/issues/47, open

    File Targets

    • [x] src/routes/boards/[slug]/+page.svelte — verified: exists, 1195 lines, already has drag-and-drop, optimistic updates, column rendering, type badges, auth gating
    • [x] src/app.css — verified: exists
    • [ ] src/routes/boards/[slug]/+page.server.ts (DO NOT TOUCH) — ISSUE: file does NOT exist. App was migrated to adapter-static (PR #57, issue #53). Data loading is client-side via onMount in the .svelte file. This "do not touch" reference is stale and will confuse the agent.

    Repo Placement

    Issue body says forgejo_admin/pal-e-app but the repo was renamed to forgejo_admin/pal-e-docs-app (issue #87, PR #90). The Forgejo redirect handles this transparently, but the issue body should be updated to avoid confusion. Local checkout at ~/pal-e-app still uses the old directory name.

    Dependencies

    • [ ] Board item #297 ("Playground: kanban prototype", issue #46) — PENDING. Still in_progress on board-pal-e-docs. This ticket's Context section explicitly states: "Depends on playground kanban prototype being approved by Lucas." The playground prototype (~/pal-e-docs-playground/note-board.html, 132 lines) exists but has not been approved. This ticket CANNOT move to next_up until #297 reaches done.
    • [x] Board item #318 ("Drop legacy boards table", issue #199) — in next_up, no conflict. Board page should not depend on the legacy table.
    • [x] Board item #522 ("Validate: alembic upgrade head") — in todo, API schema changes. No direct conflict for a CSS restyling ticket.

    Acceptance Criteria

    • "Board view matches playground-approved design" — testable by visual comparison, but playground must be approved first (dependency blocker)
    • "Real API data renders correctly" — ALREADY IMPLEMENTED. Current +page.svelte loads and renders real API data via client-side fetch.
    • "Drag-and-drop works with optimistic updates" — ALREADY IMPLEMENTED. Lines 67-87 show optimistic update logic with itemsByColumn state.
    • "Auth gating: board mutations require login" — ALREADY IMPLEMENTED. Line 32: isAuthenticated derived state controls mutation visibility.

    Assessment: AC 2, 3, and 4 describe functionality that ALREADY EXISTS in the 1195-line board page. The only real AC is #1 — visual restyling to match the playground design. The AC should be rewritten to describe the actual delta (CSS/layout changes) and add a non-regression criterion for existing functionality.

    Blast Radius

    • src/lib/columns.ts — shared column utilities used by the board page. Changes to column rendering could affect other consumers (e.g., ProjectLayout.svelte).
    • src/lib/api-client.ts — board API functions already integrated. No blast radius unless API contract changes.
    • src/lib/components/ProjectLayout.svelte — references boards in project view. If board card styling changes, project layout may need matching updates.
    • Rollback is straightforward — CSS-only changes to a single page.

    Decomposition Assessment

    2 file targets, 1 repo, 4 AC (though 3 already exist). Once scoped correctly to CSS/layout restyling only, the real work is: copy playground CSS classes into app.css and restyle the board page markup to match. Estimated agent time under 5 minutes. No decomposition needed — but AC must be refined first to reflect the actual delta.

    No independent subtasks to parallelize. Single-agent, single-PR work.

    Recommendation

    1. [BODY] Fix repo reference: forgejo_admin/pal-e-app to forgejo_admin/pal-e-docs-app
    2. [BODY] Remove stale "do not touch" reference to src/routes/boards/[slug]/+page.server.ts — file does not exist (app uses adapter-static with client-side loading)
    3. [BODY] Rewrite AC to reflect actual delta: AC 2/3/4 already exist. Real AC should be "board page CSS/layout matches playground-approved design" and "existing drag-drop/auth/API functionality preserved (no regressions)"
    4. [BODY] Add explicit dependency note: "Blocked by issue #46 (playground kanban prototype) — must reach done before this ticket moves to next_up"
    5. [LABEL] Add arch:frontend label to board item #298
    6. [SCOPE] Clarify: is this a full rewrite of +page.svelte (1195 lines) or a CSS-only restyling? The current page has extensive working logic. "Copy-paste from playground + data bindings" undersells the existing 1195-line implementation. The playground is 132 lines of static HTML; the app page is 1195 lines of working Svelte with drag-drop, filters, optimistic updates. The real task is likely "restyle the existing board page to match the playground CSS" — not a copy-paste replacement.
  • Verdict: READY

    Template Completeness

    • [x] Type -- Task
    • [ ] Lineage -- Missing (standalone task, acceptable for validation campaign work)
    • [ ] Repo -- Missing (implied by Forgejo issue location: forgejo_admin/pal-e-api)
    • [x] User Story -- Present, clear superuser motivation
    • [x] Context -- Present, references 3 merged PRs (#226, #230, #231) with specifics
    • [x] Scope -- Present (6 steps, appropriate for Task type replacing File Targets)
    • [x] File Targets -- Present ("No file changes" note, correct for operational task)
    • [x] Acceptance Criteria -- 4 items, all verifiable
    • [x] Test Expectations -- Present (healthcheck + board sync)
    • [ ] Constraints -- Missing (no special constraints needed for migration apply)
    • [ ] Checklist -- Missing (PR/test checklist not applicable for operational task)
    • [x] Related -- Present, references upstream PRs and downstream blockers

    Traceability

    • [x] story:superuser-maintain -- present on board item
    • [x] arch:note-system -- present on board item (NoteType enum changes)
    • [x] arch:board-api -- present on board item (validation BoardColumn addition)
    • [x] Forgejo issue -- forgejo_admin/pal-e-api#232, closed (work already completed)

    File Targets

    Task type -- no file targets to verify. This is an operational apply of already-merged migrations. Confirmed the code already contains validation column support in the codebase:

    • [x] src/pal_e_docs/models.py -- BoardColumn.validation enum value exists
    • [x] src/pal_e_docs/schemas.py -- validation in allowed columns list
    • [x] src/pal_e_docs/routes/notes.py -- validation type status map exists

    Targets are appropriate for an operational task -- no files to modify, only runtime state to change via alembic.

    Repo Placement

    OK. Forgejo issue filed on pal-e-api, which is the correct repo for alembic migrations. MCP restart is a secondary operational step on pal-e-mcp but does not require a separate issue -- it is a single kubectl command.

    Dependencies

    • [x] PR #226 (Add 4 new NoteTypes + validation BoardColumn) -- merged
    • [x] PR #230 (Data migration to retype doc notes) -- merged
    • [x] PR #231 (Remove 7 deprecated NoteTypes) -- merged
    • [x] Board item #524 ("Fix 12 failing board_sync tests blocking CI", parent:#232) -- done

    All dependencies satisfied. No unresolved blockers.

    Acceptance Criteria

    All 4 criteria are testable and specific:

    • alembic current shows head revision -- verifiable via kubectl exec
    • GET /boards/{slug} returns validation column -- verifiable via curl
    • list_notes(tags="sop") returns correct note_type -- verifiable via MCP tool
    • MCP tools reflect new NoteTypes -- verifiable via MCP tool calls

    No ambiguous language. Each criterion has a clear pass/fail signal.

    Blast Radius

    • 1 repo touched (pal-e-api runtime state via alembic)
    • 1 service restart needed (pal-e-mcp to pick up schema changes)
    • Data migration (PR #230) retypes existing notes -- this is the only destructive operation, but it was already code-reviewed in its own PR
    • Downstream consumers (pal-e-app, pal-e-mcp) read from the API and will pick up new enum values transparently
    • Rollback: alembic downgrade is available if needed, though the data migration would need a reverse migration

    Decomposition Assessment

    No decomposition needed.

    • 0 file changes (operational task)
    • 4 acceptance criteria (under 5 threshold)
    • Single repo target + 1 service restart
    • Estimated agent time: under 3 minutes
    • No independent subtasks that need parallelization -- the steps are sequential (apply migration, verify, restart MCP)

    Well within the three-thing limit and five-minute rule.

    Recommendation

    No action needed. Scope was solid and work is already completed (Forgejo issue closed 2026-03-28).

    Minor template nits for future reference (not blocking):

    1. [BODY] Add ### Repo section: forgejo_admin/pal-e-api
    2. [BODY] Add ### Lineage section: Standalone -- validation campaign for NoteType system migration.
    3. [BODY] Clarify Related references: "board-pal-e-agency #209, #210" should be "claude-custom#209, claude-custom#210 (on board-pal-e-agency)" to avoid ambiguity between board item IDs and Forgejo issue numbers.
  • Milestone: Knowledge Architecture milestone-2026-03-16-knowledge-architecture

    Milestone: Knowledge Architecture

    Evolve pal-e-docs from a flat knowledge system to a tiered, milestone-organized platform. Milestones become the structural boundary for plans — one active milestone per project, one plan per milestone. Completed milestones automatically tier their children out of hot queries, solving the unbounded token growth problem. Gapped integer positions eliminate cascading shifts on block/note inserts.

    Success Criteria

    • milestone is a first-class note_type with lifecycle statuses
    • Plans require a milestone parent (convention-enforced, not hook-enforced yet)
    • list_notes defaults to excluding notes under completed milestones
    • Session injection token cost drops by ~50% for mature projects
    • Block/note inserts no longer require position cascading
    • Doc drift from pal-e-agency Phase 12 consolidated-agent work is cleaned up

    Context

    Discovered 2026-03-16 during pal-e-agency audit. Token cost analysis: list_notes(project="pal-e-agency") returns 84K chars (~21K tokens). Plan sprawl: plan-pal-e-docs has 26 phases, plan-wkq has 19 phases. Doc drift: 6 specialized agent notes marked active in docs but consolidated to 5-agent model in code (4 configs in claude-custom). Root cause: no structural boundary for completed work, no mechanism to cool finished knowledge out of hot queries.

    Plan

    plan-knowledge-architecture

    • plan-pal-e-docs — predecessor plan (still active for F11, F13)
    • convention-block-first-access — prerequisite pattern that makes large plans manageable
    • convention-memory-scope — memory = behavioral, pal-e-docs = state
  • Milestone: Project Genesis (February 24, 2026) milestone-2026-02-24-project-genesis

    Project Genesis — February 24, 2026

    What shipped

    • FastAPI backend deployed on k3s
    • Postgres database (CloudNativePG)
    • First notes created
    • REST API for note CRUD
    • MCP server initial version

    Significance

    Day zero. The knowledge system exists. Notes can be created, queried, and managed through both API and MCP tools.

    • plan-2026-02-24-pal-e-docs-mcp — the founding plan
  • Milestone: Knowledge Engine (March 1, 2026) milestone-2026-03-01-knowledge-engine

    Knowledge Engine (Act 2) — March 1, 2026

    What shipped

    • Block parser — HTML content decomposed into typed blocks (heading, paragraph, list, table, code, mermaid) with anchor_ids for direct section access
    • Compiled pages — cached rendered HTML for fast retrieval
    • Semantic search — pgvector + Ollama (qwen3-embedding:4b), 6,054 blocks embedded
    • MCP server v0.3.0 — expanded to 33 tools including block-level operations and semantic search

    Significance

    The system went from "notes in a database" to "knowledge engine." Block decomposition enabled 91% token reduction via block-first access. Semantic search enabled meaning-based queries across all content. The foundation for every future capability.

    • plan-2026-02-26-tf-modularize-postgres — Act 1 (SQLite → Postgres) + Act 2 (blocks, search, compiled pages, MCP rewrite)
  • Milestone: Board System + SvelteKit Frontend (March 13, 2026) milestone-2026-03-13-board-system-frontend

    Board System + SvelteKit Frontend — March 13, 2026

    What shipped

    • Board data model — boards, board items, columns, item types, labels, points
    • Kanban component — drag-and-drop (desktop + mobile), column collapsing
    • Board auto-syncPOST /boards/{slug}/sync auto-populates phases from plans. update_note hook auto-moves board items on status change.
    • Forgejo issue sync — 27 issues across 9 boards, synced from Forgejo
    • SvelteKit frontend launch — pal-e-app scaffold, block renderer, note browsing, dark theme, deployed to k3s with ArgoCD
    • 7 boards populated across all active projects

    Significance

    Two capabilities in one day. The board system gave pal-e-docs project management capabilities — continuous kanban, not just documentation. The SvelteKit frontend gave humans a visual interface to the same data agents access via MCP. Two access paths to one knowledge base.

    • plan-pal-e-docs — phases 0-4 (board data model through note renderer)
  • Milestone: Frontend as Workbench (March 14, 2026) milestone-2026-03-14-frontend-workbench

    Frontend as Workbench — March 14, 2026

    Numbers

    Metric Value
    PRs merged 8 in one session
    Backend tests 513 pytest
    Frontend tests 33 Playwright E2E across 7 spec files

    What shipped

    • Frontend search — full-text, semantic, and hybrid search from the browser. Cmd+K shortcut, URL-persistent query params, mode toggle (PR #16)
    • Board filtering — type filter pills, hide-done toggle, collapsible columns, board summary card, project mini-board view (PR #17)
    • DORA Dashboard — cross-project board rollup at /dashboard, needs-attention section, per-project cards with column distribution, deployment frequency (PR #20)
    • Quick-Jot — FAB button + n shortcut opens modal for quick note creation. Auto-slug, project dropdown, note_type selector, success toast (PR #21)
    • Keycloak OIDC Auth — write operations require login, reads remain public. Auth.js + Keycloak provider. FAB hidden for unauthenticated users (PR #24)
    • 33 Playwright E2E tests — smoke tests across 7 spec files
    • CI pipeline fixed + fully green
    • $lib/columns.ts constants extraction

    Significance

    Frontend went from read-only display case to authenticated workbench. 8 PRs in one session. Every major feature (search, filtering, dashboard, note creation, auth) landed in a single day. The frontend became a tool for work, not just a window into data.

    • plan-pal-e-docs — phases F1-F7 (frontend feature phases)
  • Milestone: Knowledge Loop Closed (March 15-16, 2026) milestone-2026-03-15-knowledge-loop

    Knowledge Loop Closed — March 15-16, 2026

    Numbers

    Metric Before After
    Semantic search 503 (dead) Live, 0.72–0.88 similarity scores
    MEMORY.md 224 lines, 76% stale 60 lines, behavioral only
    Startup context ~4,000 tokens, 45% irrelevant Trimmed + vector-powered Dynamic Briefing
    Embedding errors 152 0
    PRs merged 6 across 4 repos

    What shipped

    • Ollama model persistence — hostPath volume replaced PVC, ensuring embedding model survives pod lifecycle events. PR #90 + #91 (pal-e-platform).
    • Embedding backfill — 152 error blocks reset and re-embedded. All blocks at completed/skipped.
    • Prometheus alerting — ServiceMonitor + embedding error rate alerts. 10-minute detection for future failures.
    • MEMORY.md diet — Audited 44 topic files. Removed 76% stale project state. Memory now carries only behavioral corrections.
    • Vector-powered Dynamic Briefing — Session startup queries semantic search using in-progress board items. Top results injected as enterprise context. Fail-open design. PR #116 (claude-custom).
    • Plan TOC trimming — Only inject TOCs for projects with in-progress board items. ~1,200 tokens saved. PR #114 (claude-custom).
    • convention-memory-scope — Decision gate: behavioral → memory, state → pal-e-docs. Prevents future bloat.
    • template-ticket — Kanban card definition with traceability triangle (User Story ↔ Architecture ↔ Phase).
    • MCP labels fix — SDK + MCP labels type fix. PR #42 (pal-e-docs-mcp), PR #31 (pal-e-docs-sdk).

    What we learned

    • Ollama PVC was the root cause, not Ollama itself — Pod was healthy (0 restarts, 6 days running). But the PVC was recreated and only chat models were pulled. The embedding model was missing. hostPath prevents this class of failure entirely.
    • embedding_queue_depth is misleading — Failed blocks get marked error after 3 retries, not pending. Queue depth reads 0 during complete failures. The correct alert is rate(embedding_errors_total[5m]) > 0.
    • MEMORY.md truncation was actively harmful — 200-line limit meant lines 201+ were invisible. Stale state ("262 notes" when there were 500+) created false confidence. The diet isn't just cleanup — it's a correctness fix.
    • 4 concurrent sessions share one Dynamic Briefing — Same cwd = same hook output. Briefing should be enterprise dashboard, not project deep-dive. Project depth comes from targeted queries on demand.

    The loop

    This milestone marks the point where pal-e-docs became self-reinforcing: docs get written → blocks get embedded → session startup queries vectors → relevant context surfaces → session work updates docs → new blocks get embedded. The knowledge loop closed.

    • phase-pal-e-docs-f12-semantic-search-recovery — Ollama fix + backfill + alerting
    • phase-pal-e-docs-f13-context-intelligence — MEMORY.md diet + Dynamic Briefing
    • convention-memory-scope — behavioral vs state decision gate
    • template-ticket — traceability triangle
  • What: pal-e-app CI pipeline built and pushed the image to Harbor, but the update-deployment-tag step was skipped because 16/61 E2E tests failed. The failures are expected — the tests validate new behavior (public-readiness checks, QuickJot FAB visibility) but the deployed site still has the old code.

    Problem: Chicken-and-egg — E2E tests run against the live deployment, but the deployment won't update until tests pass. This pattern always blocks the first deploy of behavior-changing PRs.

    Failures (16):

    • public-readiness tests (10): Check for "Contact Lucas" messaging and no private content, but deployed site still shows private content
    • quick-jot tests (6): FAB "Create new note" button now correctly hidden for anonymous visitors, but old tests expected it visible

    Fix options:

    • (a) Force deploy: Manually update the kustomize deployment tag to the built image SHA, let ArgoCD sync, then re-run E2E tests. Cleaner.
    • (b) Skip gate: Mark public-readiness tests as skip-in-CI until first deploy, then enable. More fragile.

    Where: pal-e-app repo. Image already built in Harbor. Need to update pal-e-deployments/overlays/pal-e-app/prod/kustomization.yaml with the new tag.

  • Bug: MCP board item labels sent as array instead of string

    Problem

    create_board_item and update_board_item MCP tools return 422 when labels are provided: "Input should be a valid string". The API receives an array like ["arch:deployment","track:backend"] instead of the string "arch:deployment,track:backend". Even single-value strings get wrapped: ["value"].

    Root Cause

    The pal-e-docs-mcp tool layer is wrapping the labels parameter value in a list before sending it to the pal-e-docs API. The API's Pydantic model expects a plain string. Likely a type coercion issue in the MCP tool's parameter handling or SDK serialization.

    Fix

    • Check pal-e-docs-mcp tool definitions for create_board_item and update_board_item
    • Ensure the labels parameter is passed as a string, not wrapped in an array
    • May also be an SDK issue in pal-e-docs-sdk — check how the SDK serializes the labels field

    Impact

    Blocks the template-ticket label conventions from being applied via MCP tools. Labels can only be set via direct API curl. First discovered during real-world ticket creation for Phase F12.

    Acceptance Criteria

    • create_board_item(labels="arch:deployment,track:backend,type:bug") succeeds
    • update_board_item(labels="type:feature,scope:planned") succeeds
    • Existing single-label items (e.g. "status:approved") still work
    • pal-e-docs — project
    • template-ticket — defines the label conventions this bug blocks
    • Repos: forgejo_admin/pal-e-docs-mcp, possibly forgejo_admin/pal-e-docs-sdk
  • pal-e-docs Database Schema doc-pal-e-docs-schema

    pal-e-docs Database Schema

    Current database schema for pal-e-docs. 8 tables on SQLite (migrating to Postgres). Source of truth: src/pal_e_docs/models.py.

    Entity-Relationship Diagram

    erDiagram
        projects ||--o{ notes : "has many"
        projects ||--o{ repos : "has many"
        projects |o--o| page_notes : "page_note_id"
        notes ||--o{ note_revisions : "has many"
        notes }o--o{ tags : "note_tags"
        notes ||--o{ linked_notes : "note_links"
    
        projects {
            int id PK
            string name
            string slug UK
            string platform "nullable"
            string repo_url "nullable"
            bool is_public "default true"
            int page_note_id FK "unique RESTRICT"
            datetime created_at
        }
    
        repos {
            int id PK
            string name
            string slug UK
            string platform "forgejo or github"
            string url
            string status "active or archived"
            string role "nullable"
            int project_id FK "nullable"
            datetime created_at
        }
    
        notes {
            int id PK
            int project_id FK "nullable"
            string title
            string slug UK
            text html_content "default empty"
            bool is_public "default true"
            datetime created_at
            datetime updated_at
        }
    
        tags {
            int id PK
            string name UK
        }
    
        note_tags {
            int note_id PK_FK "CASCADE"
            int tag_id PK_FK
        }
    
        linked_notes {
            int source_id PK_FK "CASCADE"
            int target_id PK_FK "CASCADE"
        }
    
        note_revisions {
            int id PK
            int note_id FK "CASCADE"
            text html_content
            string revised_by "nullable"
            datetime revised_at
            int revision_number
        }
    
        users {
            int id PK
            string email UK
            string hashed_password
            bool is_approved "default true"
            datetime created_at
        }
    
        page_notes {
            string note_ref "alias for notes"
        }
    

    Note: page_notes and linked_notes in the diagram are aliases for the notes table. Mermaid ERD does not support self-referential relationships or multiple relationships to the same entity, so aliases are used for visual clarity. The actual database has 8 tables, not 10.

    Table Summary

    Table Rows (approx) Purpose
    projects 11 Top-level organizational unit. Has many notes and repos. Optional page_note_id FK to a note for rich content.
    repos 24 Code repositories. Belongs to a project. Has platform, url, status, role.
    notes ~162 The core content unit. HTML fragments stored in html_content. Belongs to a project. Tagged via note_tags. Linked via note_links. Revision-tracked via note_revisions.
    tags ~47 Topic/domain labels. Many-to-many with notes via note_tags. Currently doing triple duty (type + lifecycle + topic) until note_type/status columns are added.
    note_tags ~300 Junction table. Composite PK (note_id, tag_id). CASCADE on note delete.
    note_links ~50 Directed relationships between notes (source to target). Composite PK. CASCADE on either note delete. Renders as Related Notes in browse frontend.
    note_revisions ~500 Revision history. Every update_note call creates a revision. Ordered by revision_number. CASCADE on note delete.
    users 1 Browse frontend auth. Email + hashed password. Session-based login.

    Key Relationships

    • projects to notes (1:N via project_id): every note belongs to a project
    • projects to notes (1:1 via page_note_id): a project can have one page note for rich content. RESTRICT on delete prevents orphaning.
    • projects to repos (1:N via project_id): repos belong to projects
    • notes to tags (M:N via note_tags): notes have topic tags
    • notes to notes (M:N via note_links): directed relationships (source to target)
    • notes to note_revisions (1:N): full revision history, cascade delete

    What the Note Decomposition Plan Adds

    See plan-2026-03-01-note-decomposition Phase 2. Four new columns on the notes table:

    erDiagram
        notes ||--o{ child_notes : "parent has children"
    
        notes {
            int id PK
            int project_id FK
            string title
            string slug UK
            text html_content
            bool is_public
            string note_type "NEW - plan phase sop etc"
            string status "NEW - active completed etc"
            int parent_note_id FK "NEW - self-ref nullable"
            int position "NEW - ordering in parent"
            datetime created_at
            datetime updated_at
        }
    
        child_notes {
            string note_ref "alias for notes"
        }
    

    What these enable:

    Column Type Purpose
    note_type varchar, nullable Replaces type tags. Values: plan, phase, sop, convention, issue, todo, template, project-page, skill, agent, doc. Enables list_notes(note_type="phase").
    status varchar, nullable Replaces lifecycle tags. Values depend on note_type (see note-conventions). Enables status-only updates without rewriting content.
    parent_note_id FK to notes.id, nullable Self-referential. Links a phase to its parent plan. Enables list_notes(parent_slug="plan-...") to get all phases of a plan.
    position integer, nullable Ordering of children within a parent. Phase 1 = position 1, Phase 2 = position 2, etc.

    Pydantic API Schemas

    The API uses Pydantic models in src/pal_e_docs/schemas.py. Key patterns:

    • Create schemas accept slugs for FK references (e.g., project_slug instead of project_id). The route resolves the slug to an ID.
    • Out schemas nest related objects (e.g., NoteOut.project is a full ProjectOut, not just an ID).
    • NoteSummary omits html_content to save tokens on list queries. Does not currently include project info (N+1 problem on session start hook).
    • Tags are passed as comma-separated strings on create/update, returned as TagOut objects on read.
    • entity-page-architecture — why page_note_id FK is on entity tables, not polymorphic
    • plan-2026-03-01-note-decomposition — the plan adding note_type, status, parent_note_id, position
    • note-conventions — defines the note_type enum and status-per-type values
    • Procedures: sop-db-migration-recovery — recovery SOP for database schema changes
  • Decision: Phase 6 Vector Search Architecture decision-phase6-vector-search-architecture

    Context

    Phase 6 adds semantic/vector search to pal-e-docs using pgvector. Before scoping the work, we researched embedding models, evaluated our hardware constraints, and made architectural decisions about what to embed and how.

    Hardware Profile (archbox — single-node k3s)

    Component Spec Availability
    CPU Intel i7-8700K, 6c/12t, 4.8GHz boost 11% utilized by cluster
    RAM 128GB DDR4 ~116GB available (8% used)
    GPU GTX 1070, 8GB VRAM, CUDA 13.0, compute 6.1 Effectively idle (54MB/8192MB, 0% compute). NVIDIA device plugin deployed.

    Embedding Model Research (March 2026)

    Models Evaluated

    Model Params Dims MTEB Rank Key Feature Fits GPU?
    Qwen3-Embedding-0.6B 600M 32-1024 configurable Competitive with 7B models Instruction-aware, 100+ languages Yes (1.2GB)
    Qwen3-Embedding-4B 4B 32-1024 configurable Near-8B quality Sweet spot for GTX 1070 Yes (fits 8GB VRAM)
    Qwen3-Embedding-8B 8B 32-1024 configurable #1 open-source on MTEB (70.58) Max quality No (16GB, CPU only)
    nomic-embed-text 137M 768 fixed Beats ada-002 Proven, small Yes
    mxbai-embed-large 335M 1024 fixed Beats text-embedding-3-large Strong quality Yes

    Why Not Qwen3.5?

    Qwen3.5 (Feb 2026) is a general LLM family only — chat/base models (9B, 35B-A3B MoE). No Qwen3.5 embedding model exists as of March 2026. Qwen3-Embedding (June 2025) remains the latest and best open-source embedding family. Only Google's proprietary Gemini-Embedding beats it on MTEB overall.

    Why Not External APIs?

    The self-hosted RAG vision (concept-phase5-self-hosted-rag) explicitly targets zero external dependencies — no Pinecone, no OpenAI embedding API, no data leaving the cluster. All inference runs on-cluster.

    Decisions Made

    # Decision Choice Rationale
    1 Embedding model Qwen3-Embedding-4B via Ollama Fits entirely in GTX 1070's 8GB VRAM for GPU-accelerated inference. Near-8B quality. Instruction-aware for domain tuning. Configurable dimensions (32-1024). Available on Ollama (qwen3-embedding:4b).
    2 Dimensions 768 Good balance of quality vs storage. Can re-embed at 1024 later if needed. Configurable at inference time — no model change required.
    3 What to embed Per-block (not per-note) Section-level semantic search. "Find the section about credentials" returns the specific block, not the whole note. Aligns with Phase 7's block content model. More embeddings but dramatically better retrieval precision.
    4 Embedding pipeline Async via PostgreSQL LISTEN/NOTIFY Writes don't block on embedding generation. Block create/update fires a Postgres trigger → NOTIFY embedding_queue → worker picks up immediately. Zero new infrastructure (no Redis/Celery). Event-driven, not polling. Sub-30-second staleness SLA.
    5 Dependency Phase 7 (blocks) before Phase 6 (vectors) Per-block embedding requires blocks to exist. Building blocks first avoids throwaway per-note embeddings and a migration. "Do it right, not fast."
    6 Ollama deployment Platform service Own namespace + deployment, like CNPG. Any app on the platform can generate embeddings. Reusable capability.
    7 Worker architecture Separate Kubernetes Deployment Independent from API pod. Owns the nvidia.com/gpu: 1 resource request. Independent failure domain — API restarts don't kill embedding jobs, API pod doesn't get a GPU it doesn't need. Clean GPU isolation.
    8 Instruction prefixes Asymmetric query/document Qwen3-Embedding is instruction-aware. Document side: "Represent this platform knowledge base section for retrieval: {block_text}". Query side: "Find the platform documentation about: {user_query}". Different prefixes for indexing vs querying improves retrieval quality.
    9 Block type filtering Embed semantic content, skip rendering artifacts Embed: paragraph, list, heading, table (flattened to text), code. Skip: mermaid, raw HTML, empty/structural blocks. Headings embedded with parent context for hierarchy.

    Dependency Chain Change

    Original plan had Phase 6 and Phase 7 as independent (both depend only on Phase 5). With per-block embedding, Phase 6 now depends on Phase 7. Phase 7 is the critical path.

    
    Phase 5 DONE → Phase 7 (blocks) DONE → Phase 7f (clean data) DONE → Phase 6 (vectors) → Epilogue
    

    Open Questions — RESOLVED (2026-03-08)

    All five open questions from original scoping have been resolved:

    Question Resolution Decision #
    Queue mechanism PostgreSQL LISTEN/NOTIFY + trigger. No new infra. #4
    Worker architecture Separate k8s Deployment with GPU resource request. #7
    Staleness window Event-driven, sub-30-second. LISTEN/NOTIFY is near-instant. #4
    Instruction prefix Asymmetric: different prefixes for document indexing vs query embedding. #8
    Block type filtering Embed semantic content (paragraph, list, heading, table, code). Skip mermaid, raw HTML. #9
    • phase-postgres-6-vector-search — the phase this decision supports
    • phase-postgres-7-block-content — prerequisite phase (blocks must exist before per-block embedding)
    • concept-phase5-self-hosted-rag — the RAG architecture vision
    • concept-phase5-database-side-intelligence — the database-side intelligence pattern
    • benchmark-phase5-knowledge-baseline — baseline measurements
  • Repo: pal-e-docs-sdk repo-pal-e-docs-sdk

    Purpose

    Typed Python SDK for the pal-e-docs REST API. Provides a PalEDocsClient class with httpx-based HTTP helpers, typed exceptions, Pydantic response models, and mixin-per-resource endpoint methods. The SDK sits between the FastAPI app and the MCP server -- integration tests run here, MCP tools wrap SDK methods.

    Value

    Eliminates raw httpx calls from MCP tools and scripts. Provides typed exceptions (NotFoundError, ValidationError, ServerError) instead of raw HTTP status codes. 15 typed methods across 6 mixins with Pydantic response models. Integration tests at this layer catch deployment failures automatically.

    Usage

    pip install pal-e-docs-sdk --index-url https://forgejo.tail5b443a.ts.net/api/packages/forgejo_admin/pypi/simple/
    
    from pal_e_docs_sdk import PalEDocsClient
    
    client = PalEDocsClient(base_url="https://pal-e-docs.tail5b443a.ts.net")
    note = client.get_note("plan-2026-02-26-tf-modularize-postgres")  # returns Note model
    results = client.search_notes("CNPG credentials")  # returns list[SearchResult]
    tags = client.list_tags()  # returns list[Tag]
    

    Status

    v0.2.0 -- 15 typed endpoint methods across 6 mixins (notes, search, tags, projects, links, repos). 58 tests. Blocks and sprints coming in 8c/8d.

    Architecture

    FastAPI app (pal-e-docs)
        ↑
    pal-e-docs-sdk (this repo)    ← integration tests
        ↑
    pal-e-docs-mcp (thin wrappers)
        ↑
    AI agents
    

    Plans

    Plan Phase Status Summary
    plan-2026-02-26-tf-modularize-postgres Phase 8a COMPLETED SDK core scaffold -- client, exceptions, CI, PyPI publish
    plan-2026-02-26-tf-modularize-postgres Phase 8b COMPLETED SDK: Notes, Search, Tags, Projects, Links, Repos -- 15 methods, 58 tests
    plan-2026-02-26-tf-modularize-postgres Phase 8c NOT STARTED SDK: Blocks, TOC, Sections
    plan-2026-02-26-tf-modularize-postgres Phase 8d NOT STARTED SDK: Sprints

    Issues

    None open.

    • phase-postgres-8-mcp-optimization -- parent phase
    • plan-2026-02-28-woodpecker-sdk-mcp -- the pattern this follows
  • QA Report: Phase 7c Backfill (2026-03-07) qa-phase7c-backfill-2026-03-07

    Summary

    Backfill script (scripts/backfill_blocks.py, merged in PR #101) executed against production via kubectl cp + kubectl exec. All 274 notes parsed into blocks and compiled pages populated. Zero data loss. Zero visual regressions.

    Database State After Backfill

    Metric Value
    Notes processed 274
    Total blocks created 5,197
    Compiled pages created 274 (100% coverage)
    Notes with TOC 247 (90%)
    Notes without TOC 27 (no headings)
    Avg blocks/note 19.0
    Max blocks/note 84 (plan-2026-02-28-woodpecker-mcp)
    Empty notes (0 blocks) 1 (convention-dockerfile-pypi-pattern — genuinely empty)

    Block Type Distribution

    Type Count What It Captures
    heading 2,087 Section headers — TOC entries + anchor IDs
    paragraph 1,723 Body text
    list 876 Bullet/numbered lists
    table 369 Structured data
    code 127 Code blocks, pre-formatted content
    mermaid 15 Dependency diagrams

    Round-Trip Fidelity Analysis

    Every note was parsed (HTML → blocks) then compiled (blocks → HTML) and compared against the original html_content.

    Category Count Verdict
    Identical (byte-for-byte) 15 Perfect match
    Anchor-only 3 Compiler adds id= to headings (expected, correct)
    Whitespace + entity + br normalization 228 Cosmetic only — zero visual impact
    True semantic diff 28 Investigated — all benign (see below)

    True Semantic Diff Root Causes

    All 28 "true semantic" diffs traced to exactly two causes:

    • Table formatting (22 notes): Compiler outputs <tr>\n<th> instead of <tr><th>. HTML renders identically in browsers — only source formatting differs.
    • Bare <pre><pre><code> (9 notes): Compiler wraps bare <pre> content in <code>. Browser rendering identical; actually more standards-compliant.

    Verdict: Zero visual regressions. Zero data loss. Zero broken notes.

    Cosmetic Diff Breakdown (the other 228)

    • &mdash; — HTML entity normalization to Unicode. Renders identically.
    • <br><br/> — Self-closing tag normalization. XHTML-compliant.
    • Blank line removal — Compiler doesn't insert blank lines between block-level elements. No visual impact.
    • Table cell whitespace — Newlines inside <tr>/<td> tags. HTML whitespace rules make these invisible.

    Token Reduction Preview (Phase 7d Payoff)

    With the blocks and TOC data now in the database, Phase 7d's get_note_toc and get_block MCP tools will deliver:

    Single Note (the main plan)

    Method Chars ~Tokens Reduction
    get_note (current) 9,668 ~2,417
    get_note_toc (Phase 7d) 710 ~177 92.7%
    get_block (Vision only) 1,275 ~318 86.8%

    Session Startup (4 active plans injected every conversation)

    Plan Current TOC Only Reduction
    plan-2026-02-26-tf-modularize-postgres 9,668 710 93%
    plan-2026-03-01-pal-e-sprints 3,693 400 89%
    plan-2026-03-03-sprint-workflow-automation 10,378 792 92%
    plan-2026-02-25-platform-observability 2,138 384 82%
    Total 25,877 2,286 91.2%

    ~5,900 tokens freed per session start. Compounds across every get_note call in a session (20-30 reads typical = tens of thousands of tokens saved).

    Execution Details

    • Script: scripts/backfill_blocks.py (merged PR #101, nit fixes PR #103)
    • Method: kubectl cp into pod, kubectl exec with DATABASE_URL=$PALDOCS_DATABASE_URL
    • Runtime: 2.0 seconds for 274 notes
    • Idempotent: Safe to re-run (deletes existing blocks/compiled_page per note before re-inserting)
    • Env var note: Pod uses PALDOCS_DATABASE_URL, not DATABASE_URL. Run with: sh -c 'DATABASE_URL="$PALDOCS_DATABASE_URL" python /tmp/backfill_blocks.py'

    Edge Cases

    • convention-dockerfile-pypi-pattern — 0 blocks, empty html_content. Compiled page exists with empty HTML and empty TOC. Correct behavior.
    • Notes with 1 block: phase-postgres-1-tf-modularize, todo-move-mcp-migration-plan-project — verified as simple single-paragraph notes.
  • Decision: Block-First Access Pattern (7e-3) decision-7e3-block-first-access

    Decision

    7e-3 is not just a hook optimization — it's establishing block-first as the default knowledge access pattern across all agents. The session hook change is one implementation of the pattern, not the whole deliverable.

    Key Insight

    TOC-based access doesn't reduce what agents read — it changes how they find what to read. The actual token savings come from reading sections instead of full notes. The TOC is navigation; get_section() is the optimization.

    This means the convention must be established platform-wide, not just in the session hook. Every agent personality, the agent workflow SOP, and a new convention note all need to encode this pattern.

    Decision Rules

    Scenario Tool Why
    Need to know what's in a note get_note_toc() Structure only, ~50 tokens
    Need one section get_section(slug, anchor) Targeted, ~200 tokens
    Need the full note (<1K chars) get_note() Overhead of TOC+section not worth it for small notes
    Update one section update_block(slug, anchor, content) Surgical, no full rewrite
    Rewrite most of a note update_note(content=...) Block tools inefficient for full rewrites
    Create a new note create_note() Blocks auto-generated from HTML (7e-1)

    The rule: start narrow, widen if needed. TOC first. Section if relevant. Full note only if you need most of it.

    Scope of 7e-3

    Four deliverables:

    1. Convention note (convention-block-first-access) — document the pattern and decision rules
    2. Session hook (session-start-context.sh) — inject plan TOCs, lazy loading instructions
    3. Agent personality updates (agent-betty-sue, agent-dottie) — encode block-first in operating instructions
    4. SOP update (agent-workflow) — add block-first as part of the operating model

    What Stays Unchanged

    • Personality injection (agent-betty-sue full text) — needs full content to define behavior, ~500 tokens, not the bottleneck
    • SOP list — already compact metadata (title + slug only)
    • Core SOP full reads (agent-spawn-conventions, agent-workflow) — small notes, ~1K each, frequently referenced
    • Small notes (<1K chars) — block overhead not worth it
    • phase-postgres-7e-compiled-pages — parent phase
    • benchmark-phase7-block-baseline — token measurements before blocks
  • Audit: Phase 7b Content Patterns audit-phase7b-content-patterns

    Audit: Phase 7b Content Patterns

    Content audit of all 256 notes in pal-e-docs, cataloging every HTML element and pattern the Phase 7b parser must handle. Captured 2026-03-07 by Dottie.

    Corpus Summary

    MetricValue
    Total notes256
    Note types present11: plan (38), todo (38), phase (34), doc (9), sop (13), convention (10), project-page (11), template (9), agent (5), skill (5), issue (1), plus 85 untyped
    Notes with headings229 (89%)
    Notes with no headings27 (11%)

    HTML Elements Found

    ElementPrevalenceParser Block TypeNotes
    <h2>~180 notesheadingUsed as note title echo AND as section dividers. See "Redundant h2" section below.
    <h3>~220 notesheadingPrimary section heading. Most common heading level.
    <h4>~40 notesheadingSub-subsections. Plans and phase notes use these for deliverable sub-items.
    <p>~256 notesparagraphUniversal. Contains inline <strong>, <em>, <code>, <a>.
    <ul>239 notes (93%)listUnordered lists. Most common structural element after paragraphs.
    <ol>~60 noteslistOrdered lists. SOPs, workflows, debugging stories.
    <table>118 notes (46%)tableAll use <tr><th> for headers, never <thead>/<tbody>. See table edge cases below.
    <pre><code>72 notes (28%)codeCode blocks. No language hints (no class="language-*"). Plain text content.
    <pre class="mermaid">37 notes (14%)mermaidMermaid diagrams. Whitespace-sensitive. No nested <code> wrapper.
    <code> (inline)~240 noteswithin paragraphInline code for slugs, commands, variable names. NOT a standalone block.
    <strong>~230 noteswithin paragraphBold emphasis. Inline element within paragraphs and list items.
    <em>~30 noteswithin paragraphItalic emphasis. Less common than strong.
    <a href="...">~15 noteswithin paragraphExternal links. Mostly in project-page repo tables and concept docs.

    Redundant h2 Title Pattern

    Many notes start with <h2>Note Title</h2> that exactly matches the title field. This is the html-style-guide standard pattern, but it means the title is stored twice (in the title column and in the HTML content).

    PatternEstimated CountExamples
    h2 matches title exactly~120 notes (47%)plan-skill-enforcement-gap, template-sprint-item, skill-sprint-sync, all plan stubs
    h2 present but differs from title~60 notes (23%)deployment-lessons (title: "Deployment Lessons Learned", h2: "Hard Shutdown Survival...")
    No h2, starts with h3~50 notes (20%)bug-grafana-crashloop, bug-cnpg-webhook-drift-wal-timeout, concept-argocd-ghost-override
    No headings at all27 notes (11%)phase-postgres-1-tf-modularize (single paragraph)

    Parser implication: The parser should not assume the first element is an h2 matching the title. It must handle all four patterns. The compiler should decide whether to emit the redundant h2 or suppress it (since base.html already renders the title as h1).

    Content Before First Heading

    Some notes have paragraph content before any heading element. This is common in phase notes and newer doc notes.

    PatternCountExamples
    Starts with <p> before any heading~45 notesphase-postgres-7b-parser-compiler (5 paragraphs before first h3), phase-postgres-1-tf-modularize (only content is a paragraph)
    Starts with <h2>~120 notesStandard html-style-guide pattern
    Starts with <h3>~65 notesBug/issue notes, concept docs
    Starts with <h2> then <p> then <h3>~25 notesStandard pattern with intro paragraph

    Parser implication: Content before the first heading must become standalone blocks (not orphaned). The flat-block model handles this naturally -- paragraphs before the first heading are just paragraph blocks with no preceding heading block.

    Table Edge Cases

    PatternPrevalenceExample
    Standard: first row is <th>, rest are <td>~110 notesMost tables follow this
    colspan attribute on cells~5 notesproject-pal-e-docs roadmap table uses colspan="3" for section headers
    No <thead>/<tbody>All 118 notesConsistent: flat <tr> rows only. Parser can assume first row with <th> = header.
    Bold text in cells (<strong>)~40 notesbenchmark-phase7-block-baseline uses bold for key values
    Code in cells (<code>)~80 notesSlug references, command names in table cells
    Links in cells (<a>)~10 notesProject pages with repo URLs

    List Edge Cases

    PatternPrevalenceExample
    Flat list items~230 notesStandard pattern
    Nested lists (<ul> inside <li>)~25 notesskill-sprint-sync (sub-steps within numbered items)
    Checklist pattern ([x] / [ ])~15 notesbug-grafana-crashloop acceptance criteria
    Rich content in items (<strong> + text)~150 notesDefinition-list style: <strong>Term:</strong> description
    Code blocks inside list items~5 notesSOPs with inline commands in steps

    Code Block Edge Cases

    PatternPrevalenceExample
    <pre><code>...</code></pre>72 notesStandard pattern. No language class attributes.
    <pre class="mermaid"> (no <code>)37 notesMermaid diagrams use <pre> directly, not nested <code>.
    Language hints0 notesNo notes use class="language-python" or similar. All code blocks are plain text.
    HTML entities in code~20 notesCode blocks containing &lt;, &gt;, &amp; for HTML examples.

    Parser implication: Distinguish <pre class="mermaid"> (mermaid block) from <pre><code> (code block). The class attribute on <pre> is the discriminator.

    Mermaid-Specific Patterns

    • Always <pre class="mermaid">, never <pre><code class="mermaid">
    • Whitespace inside the <pre> is significant -- diagram definitions are multi-line, indentation matters
    • Diagram types found: graph TD, graph LR, flowchart TD, sequenceDiagram
    • 37 notes total, concentrated in project-pages (11) and architecture docs

    Inline Element Patterns

    These are NOT standalone blocks but appear inside paragraphs, list items, and table cells:

    ElementContextParser Handling
    <strong>Everywhere: paragraphs, lists, tablesPreserve as inline HTML within block content
    <em>Paragraphs, occasional list itemsPreserve as inline HTML within block content
    <code> (inline)Everywhere. Slugs, commands, variables.Preserve as inline HTML. Auto-linked by frontend.
    <a href>Project pages, repo tables, some docsPreserve as inline HTML within block content

    Inconsistencies Found

    IssueCountImpact
    Multiple <h2> in one note (not title echo)~5 notesdeployment-lessons uses h2 for each lesson section. Parser must handle multiple h2s, not just one at the top.
    h2 title echo inconsistent with title field~3 notesproject-pal-e-docs has h2 "pal-e-docs" but title is "Project: pal-e-docs". Parser cannot assume h2 == title.
    HTML entities in titles1 noteplan-2026-02-28-agent-skill-frontmatter has &amp; in title field. Parser must handle entity-encoded content.
    Badge classes in note content0 notesBadge classes (.badge-github, etc.) are used only by Jinja2 templates, not in note html_content.
    Inline style attributes0 notesNo notes violate the html-style-guide prohibition on inline styles.
    <div> elements0 notesNo notes use divs. Clean semantic HTML throughout.

    Elements NOT Found (Parser Can Skip)

    • <h1> -- never used in note content (rendered by base.html from title)
    • <h5>, <h6> -- never used
    • <img> -- never used (mermaid replaces diagrams)
    • <div> -- never used
    • <span> -- never used in note content
    • <blockquote> -- never used
    • <hr> -- never used
    • <dl>/<dt>/<dd> -- never used (definition-style lists use <strong> in <li> instead)

    Summary for Parser Implementation

    The parser must handle exactly these top-level elements:

    1. <h2>, <h3>, <h4> -- heading blocks (3 levels)
    2. <p> -- paragraph blocks (with inline HTML preserved)
    3. <ul>, <ol> -- list blocks (with nested sub-lists possible)
    4. <table> -- table blocks (th-first-row convention, colspan possible)
    5. <pre><code> -- code blocks (no language hints)
    6. <pre class="mermaid"> -- mermaid blocks (whitespace-sensitive)

    That is the complete set. No other top-level elements exist in the corpus. Inline elements (<strong>, <em>, <code>, <a>) appear only inside the above block elements and should be preserved as raw HTML in block content.

    Related

    • benchmark-phase7-block-baseline -- quantitative baseline (sizes, distributions)
    • phase-postgres-7b-parser-compiler -- the phase this audit supports
    • html-style-guide -- the authoring convention (what SHOULD be used)
  • Benchmark: Phase 7 Block Content Baseline benchmark-phase7-block-baseline

    Phase 7 Baseline: Content Structure Before Blocks

    Captured 2026-03-07, before block-structured content model exists. All notes are monolithic HTML blobs.

    Corpus Overview

    MetricValue
    Total notes256
    Total content1,088,274 chars (~272K tokens)
    Average note size4,251 chars
    Median note size2,578 chars
    P90 note size9,706 chars
    Max note size28,769 chars (plan-2026-02-28-woodpecker-mcp)
    Average sections per note7.7 headings

    Size Distribution

    BucketCountAvg Size% of NotesBlock Impact
    < 500 chars123385%Low — too small for sections
    500-1K chars236849%Low — 1-2 sections
    1K-2K chars611,53224%Medium — 3-5 sections
    2K-5K chars943,14437%High — 5-8 sections, biggest cohort
    5K-10K chars416,68616%High — 8-15 sections
    10K+ chars2516,21710%Critical — 15-34 sections, most waste per read

    Key finding: 63% of notes (160/256) are over 2KB. These are the notes where block-level access delivers the most token savings. The 25 notes over 10KB average 16,217 chars (~4,054 tokens) — reading just one section instead of the full note would save ~90% per access.

    Section Distribution

    Headings per NoteCountAvg SizeBlock Benefit
    0 headings27764None — flat content, no sections to split
    1-3 headings15915Minimal — few sections
    4-7 headings1082,369Moderate — 4-7 addressable blocks
    8-15 headings865,401High — get_block saves ~85% per read
    16+ headings2016,683Critical — get_block saves ~94% per read

    Key finding: 214/256 notes (84%) have 4+ headings — meaning 84% of notes would benefit from block-level access. Only 27 notes (11%) are flat content with no sections.

    Content Type Distribution

    Content TypeCount% of NotesBlock Type
    Lists (ul/ol)23993%list
    Tables11846%table
    Code blocks (pre)7228%code
    Mermaid diagrams3714%mermaid

    Key finding: Rich, structured content is pervasive. 46% of notes have tables, 28% have code blocks. These are exactly the content types that benefit from typed blocks — a table block can be queried, updated, and rendered independently from surrounding text.

    Size by Note Type

    TypeCountAvg SizeMax SizeBlock Impact
    (untyped)853,56826,688High — legacy notes, many large
    todo382,2787,394Medium
    plan389,44128,769Critical — largest type, most sections, most read
    phase341,9266,948Medium
    sop134,3827,748High — procedural, section-level reads
    project-page117,61217,922High — large, multi-section
    convention103,83817,273High
    template92,8224,752Medium
    doc85,1217,426High — concept/benchmark/decision docs
    skill52,1522,873Medium
    agent44,1505,206High — personality definitions

    Key finding: Plans are the largest note type (avg 9,441 chars, ~2,360 tokens) and are the most frequently read notes (4 plans loaded at every session startup). Block-level access to plans alone would save thousands of tokens per session.

    Hierarchy: Current State

    MetricValue
    Notes with a parent34 (all phases)
    Notes without a parent222
    Note types that CAN have parentsOnly phase
    Orphaned docs (should have parents)6 concept/benchmark/incident/decision docs

    Orphaned Documents (logically belong under a phase)

    SlugLogical Parent
    concept-phase5-database-side-intelligencephase-postgres-5-fulltext-search
    concept-phase5-self-hosted-ragphase-postgres-5-fulltext-search
    benchmark-phase5-knowledge-baselinephase-postgres-5-fulltext-search
    concept-argocd-ghost-overridephase-postgres-5-fulltext-search
    incident-phase5-deployment-outage-2026-03-06phase-postgres-5-fulltext-search
    decision-phase6-vector-search-architecturephase-postgres-6-vector-search

    Key finding: 6 docs already exist that should nest under phases but can't due to the type restriction. This will grow as more concept/benchmark/decision docs are created for Phases 6, 7, and 8. The hierarchy relaxation in Phase 7 solves this.

    Token Cost Estimates: Before vs After Blocks

    OperationBefore (monolithic HTML)After (block-level)Savings
    Read one section of a plan~2,360 tokens (full note)~200 tokens (one block)~92%
    Read TOC of a plan~2,360 tokens (full note)~50 tokens (headings only)~98%
    Update one section~2,360 tokens (send full html_content back)~100 tokens (send one block)~96%
    Session startup (4 plans)~8,750 tokens (4 × full get_note)~400 tokens (4 × TOC + targeted sections)~95%
    Search result (find section)~640 tokens (note-level snippet)~200 tokens (block-level result)~69%

    Success Criteria for Phase 7

    • All 256 notes decomposed into blocks (backfill migration)
    • Block-level read/write API endpoints working
    • TOC generation for all notes with 4+ headings (214 notes)
    • 6 orphaned docs nested under their logical parent phases
    • Existing MCP tools (get_note, update_note) continue working unchanged
    • Benchmark re-test shows measurable token reduction vs this baseline

    Related

    • phase-postgres-7-block-content — the phase this benchmarks
    • benchmark-phase5-knowledge-baseline — Phase 5 before/after comparison (methodology reference)
    • decision-phase6-vector-search-architecture — per-block embedding depends on blocks existing
  • Incident: Phase 5 Deployment Outage (2026-03-06) incident-phase5-deployment-outage-2026-03-06

    Summary

    pal-e-docs was down for ~15 minutes on 2026-03-06. The pod entered ImagePullBackOff because the image tag in deployment.yaml referenced a commit SHA that never existed as a Harbor image tag. The Recreate deployment strategy killed the running pod before the new one could start.

    Timeline

    TimeEvent
    Prior sessionsPR #84 (tsvector search), PR #86 (ruff format), PR #88 (pin image tag), PR #91 (ghost override fix) all merged. CI built images for each merge commit.
    PR #88 mergedeployment.yaml pinned to c85a39da... — the squash commit SHA from PR #86's branch, NOT the merge commit SHA on main (c757d179...). This tag never existed in Harbor.
    Before this sessionPod was still running image e0654197... (PR #77) due to the ArgoCD ghost override. The wrong tag in deployment.yaml was masked.
    PR #91 mergeRemoved ghost override mechanism. ArgoCD now reads the actual deployment.yaml tag.
    ~T+0ArgoCD syncs, triggers Recreate rollout. Old pod killed. New pod fails to pull c85a39da... — image not found in Harbor.
    ~T+5 minOutage detected during next session. Pod in ImagePullBackOff.
    ~T+10 minEmergency rollback: disabled ArgoCD auto-sync, patched to cached e0654197... (IfNotPresent). Service restored.
    ~T+15 minUpdated deployment to 2eddd766... (latest merge commit, includes search code). Search endpoint verified live.

    Root Causes

    1. Wrong SHA in PR #88: Image tag was set to the squash/branch commit SHA (c85a39da) instead of the merge commit SHA (c757d179). Woodpecker CI tags images with ${CI_COMMIT_SHA}, which is the merge commit on main — not the branch commit.
    2. Ghost override masked the bug: .argocd-source-pal-e-docs.yaml was overriding the image tag to e0654197 (PR #77). The invalid tag in deployment.yaml was never used until PR #91 removed the override.
    3. Recreate strategy has no safety net: strategy: Recreate kills the old pod before the new one is ready. With RollingUpdate + readiness probes, the old pod would have continued serving.

    Contributing Factors

    • Woodpecker MCP logs broken (woodpecker-mcp #3) — couldn't inspect CI output to verify image tags.
    • Harbor auth confusion: Empty-password admin showed only 2 projects, hiding the fact that pal-e-docs images existed. Red herring investigation.
    • SHA confusion: Forgejo squash merges produce two SHAs — the squash commit (branch side) and the merge commit (main side). CI uses the merge commit. PR #88 used the wrong one.

    Resolution

    1. Disabled ArgoCD auto-sync temporarily
    2. Patched deployment to e0654197 (cached, working) with imagePullPolicy: IfNotPresent
    3. Upgraded to 2eddd766... (latest merge commit, includes tsvector search)
    4. Verified search endpoint returns results
    5. Updated deployment.yaml in Git to match, re-enabled ArgoCD auto-sync

    Action Items

    • Immediate: Update k8s/deployment.yaml image tag in Git, re-enable ArgoCD auto-sync
    • TODO: Change deployment strategy from Recreate to RollingUpdate with readiness probes
    • TODO: Add CI step or hook that validates image tag = merge commit SHA
    • TODO: Fix Woodpecker MCP logs (woodpecker-mcp #3)

    Lessons Learned

    • Squash commit SHA ≠ merge commit SHA. Woodpecker ${CI_COMMIT_SHA} is the merge commit. Never pin to a branch/squash SHA.
    • Ghost overrides mask broken configs. Fixing one bug (override) can expose another (wrong tag). Always verify the full deployment path end-to-end after removing workarounds.
    • Recreate strategy is dangerous without image pre-pull. Use RollingUpdate for zero-downtime deployments.
    • Harbor admin auth requires actual password. Empty password shows limited results.

    See Also

    • concept-argocd-ghost-override — what ghost overrides are and how to prevent them
    • phase-postgres-5-fulltext-search — the phase this incident occurred during
  • Concept: ArgoCD Ghost Override concept-argocd-ghost-override

    What Is a Ghost Override?

    ArgoCD Image Updater is a companion controller that watches container registries for new image tags and automatically updates running deployments. When it finds a new tag, it writes an override file.argocd-source-<app-name>.yaml — into the application's source directory. This file tells ArgoCD to use a different image tag than what's in the checked-in deployment.yaml.

    The ghost override happens when:

    1. Image Updater writes .argocd-source-pal-e-docs.yaml to override the image tag
    2. Image Updater is later disabled or removed (annotations stripped from the ArgoCD Application)
    3. But the override file persists — it was written to the repo checkout, not managed by the controller's lifecycle
    4. ArgoCD continues applying the stale override on every sync, silently ignoring the image tag in deployment.yaml

    The result: you update deployment.yaml with a new image tag, ArgoCD says "Synced", but the pod runs a completely different image. The override is invisible unless you know to look for .argocd-source-* files.

    Why It's Dangerous

    • Silent divergence: Git says one image, cluster runs another. GitOps promise is broken.
    • Debugging nightmare: kubectl get deploy -o yaml shows the overridden image, not what's in Git. The file causing it isn't even in Git — it's in ArgoCD's local repo clone.
    • Survives annotation removal: Removing Image Updater annotations from the Application doesn't clean up existing override files.

    How We Fixed It (PR #91)

    1. Removed Image Updater annotations from the ArgoCD Application (done previously)
    2. Added .argocd-source-* to .gitignore to prevent future write-backs from landing in Git
    3. ArgoCD picked up the gitignore change, stopped reading the override file

    Prevention

    • Always add k8s/.argocd-source-* to .gitignore in any repo using ArgoCD
    • When disabling Image Updater, manually verify no stale override files remain in ArgoCD's repo cache
    • Pin image tags explicitly in deployment manifests — don't rely on :latest or dynamic updaters until the full pipeline (registry auth, write-back) is proven

    See Also

    • bug-argocd-image-updater-ghost-override — the original bug report
    • bug-image-updater-harbor-auth — why Image Updater was broken in the first place
    • incident-phase5-deployment-outage-2026-03-06 — the outage caused when the override was removed
  • Benchmark: Phase 5 Knowledge Query Baseline benchmark-phase5-knowledge-baseline

    Phase 5 Baseline: MCP Knowledge Lookup Performance

    Captured 2026-03-06, before any search capability exists.

    Session Startup Cost (Betty Sue)

    Before a single user message, every Betty Sue session consumes:

    ComponentCharsEst. Tokens% of Total
    Session injection (personality, SOPs list, plans, instructions)~6,100~1,52512.5%
    CLAUDE.md (global)~900~2251.8%
    CLAUDE.md (project)~1,200~3002.5%
    MEMORY.md~5,500~1,37511.3%
    4 mandatory get_note calls (full HTML blobs)~35,000~8,75071.9%
    Total before first user message~48,700~12,175

    Key finding: 72% of startup cost is fetching 4 full HTML documents that the agent may not need. With search, startup could fetch summaries/snippets on demand instead of preloading full blobs.

    Ad-Hoc Query Cost (5 representative queries)

    BEFORE (pre-search, 2026-03-06)

    QueryDescriptionMCP CallsResponse CharsFound?
    Q1Secrets management SOP2~7,660YES
    Q2Postgres restore procedure2~7,374YES
    Q3Agent workflow operation2~8,954YES
    Q4Sprint workflow automation status2~9,670YES
    Q5Repos in postgres migration3~10,089YES
    Totals11~43,747
    Averages2.2~8,749

    AFTER (search_notes MCP tool, 2026-03-07)

    QueryDescriptionMCP CallsResponse CharsFound?
    Q1Secrets management SOP1~2,800YES (#1 result, rank 0.997)
    Q2Postgres restore procedure1~2,400YES (#1 result, rank 0.756)
    Q3ArgoCD deployment1~2,600YES (10 ranked results)
    Q4Sprint workflow automation1~2,600YES (#1 result, rank 1.0)
    Q5Woodpecker CI pipeline1~2,400YES (#1 result, rank 1.0)
    Totals5~12,800
    Averages1.0~2,560

    Comparison

    MetricBeforeAfterImprovement
    MCP calls (5 queries)11555% reduction
    Avg calls per query2.21.055% reduction
    Total response chars~43,747~12,80071% reduction
    Avg chars per query~8,749~2,56071% reduction
    Est. tokens per query~2,187~64071% reduction
    Requires tag/slug knowledgeYesNoNatural language queries
    Cross-cutting searchImpossibleEnabledNew capability

    Key Observations (After)

    1. 1 call per query, always. No more list→get loops. search_notes returns ranked results with snippets directly.
    2. 71% token reduction — slightly under the 80-90% estimate because each search returns 10 results with snippets. Using limit=3 for targeted queries would reduce further.
    3. Natural language works. "secrets management", "ArgoCD deployment", "woodpecker CI pipeline" all return the right notes as top results. No tag/slug knowledge needed.
    4. Ranking is accurate. Q1 returns SOP: Secrets Management at rank 0.997. Q4 returns the sprint workflow automation plan at rank 1.0. The weighted tsvector (title A, content B, slug C) is working as designed.
    5. Snippets provide context. Each result includes a headline snippet with **bold** match highlighting. Often enough to answer the question without fetching the full note.

    Remaining Optimization Opportunities

    • Session startup: 4 mandatory get_note calls still consume ~8,750 tokens. Could replace with search-on-demand, but requires session hook redesign.
    • Limit tuning: Default limit=10 returns more than needed for targeted queries. Agents should use limit=3-5 for specific lookups.
    • Phase 6 (pgvector): Semantic search will find related concepts even without keyword overlap — e.g., "how do we handle database credentials" → secrets management SOP.
    • Phase 7 (blocks): Section-level retrieval would return just the relevant section of a note, not the whole document or even a snippet.

    Related

    • phase-postgres-5-fulltext-search — the phase this benchmarks (COMPLETED)
    • concept-phase5-database-side-intelligence — architecture rationale
  • Are We Building a RAG?

    Yes — but not the typical one.

    Act 2 of the Postgres plan (Phases 5-8) is building a fully self-hosted RAG system that runs entirely inside Postgres + MCP tools. No external vector database, no managed embedding API dependency, no LangChain orchestration. The whole thing lives where the data already lives.

    How It Maps to RAG Components

    RAG ComponentTypical StackWhat We're Building
    Document StoreS3 + vector DB (Pinecone, Weaviate)Postgres (notes table, blocks table)
    ChunkingLangChain text splitters (arbitrary 512-token windows)Phase 7 blocks (natural semantic chunks — headings, paragraphs, tables, code)
    EmbeddingOpenAI ada-002 API callsPhase 6 pgvector (embeddings stored in DB)
    Keyword IndexElasticsearchPhase 5 tsvector (built into Postgres)
    RetrievalMulti-step orchestration codePhase 8 compound MCP queries
    Augmented GenerationPrompt stuffing into LLM contextAgent reads search results + snippets

    What Makes This Different

    1. Retrieval Is Invisible

    We're not building a retrieval pipeline outside the database that feeds into an LLM. We're building retrieval inside the database and exposing it through the same MCP tools agents already use. The agent doesn't know it's doing RAG — it just calls search_notes("postgres restore") and gets a ranked, snippet-enriched answer. The retrieval is invisible.

    This is the database-side intelligence pattern: Postgres does the heavy lifting, every other layer is thin.

    2. The Content Model IS the Chunking Strategy

    Most RAG systems have a chunking problem — how do you split documents into meaningful pieces? Arbitrary 512-token windows with 50-token overlap? Sentence splitting? Paragraph splitting? Every approach loses context at the boundaries.

    Phase 7 (blocks) solves this naturally. Notes decompose into typed blocks (headings, paragraphs, tables, code). Each block is a semantic unit with a known type. You embed blocks, not whole documents. No arbitrary splitting, no overlap windows. The content model is the chunking strategy.

    A table block stays a table. A code block stays a code block. A heading + its paragraphs stay together. The structure that humans created when writing the note is preserved in the retrieval.

    3. Hybrid Search From Day One

    Most RAG systems start with vector search and bolt on keyword search later when they realize embeddings miss exact terms. We're building both in the same database:

    • Phase 5 (tsvector) — deterministic keyword search. "Find notes containing 'kubectl port-forward'." Precise, fast, no false positives.
    • Phase 6 (pgvector) — semantic similarity search. "Find notes about recovering from infrastructure failures." Fuzzy, conceptual, handles synonyms.
    • Phase 8 — compound queries that combine both. "Find notes semantically related to 'disaster recovery' that also mention 'MinIO'." Best of both worlds in a single query.

    Because both indexes live in the same database, hybrid search is a JOIN, not an orchestration problem.

    4. Zero External Dependencies

    The entire system runs on the k3s cluster:

    • Postgres (CNPG operator) — already deployed
    • pal-e-docs API — already deployed
    • MCP tools — already deployed
    • Embeddings — TBD, but can run locally (e.g., sentence-transformers, or a self-hosted model)

    No Pinecone bills. No OpenAI embedding API rate limits. No data leaving the cluster. The knowledge stays where it lives.

    The Progression

    
    Phase 5 (NOW):    Agent asks "find notes with these words"     → keyword search
    Phase 6 (NEXT):   Agent asks "find notes about this concept"   → semantic search  
    Phase 7 (THEN):   Agent asks "give me this section of that note" → block retrieval
    Phase 8 (FINAL):  Agent asks a question, gets a precise answer   → full RAG
    

    Each phase adds a capability. By Phase 8, the agent can ask a natural language question and get back the specific blocks from the specific notes that answer it — ranked by relevance, with both keyword and semantic matching, chunked at natural content boundaries. That's RAG. But the agent just thinks it's calling an MCP tool.

    Related

    • phase-postgres-5-fulltext-search — Phase 5 (keyword search layer)
    • phase-postgres-6-vector-search — Phase 6 (semantic search layer)
    • phase-postgres-7-block-content — Phase 7 (chunking layer)
    • phase-postgres-8-mcp-optimization — Phase 8 (compound retrieval)
    • concept-phase5-database-side-intelligence — the underlying architecture pattern
    • plan-2026-02-26-tf-modularize-postgres — the parent plan (Act 2 vision)
  • Concept: Database-Side Intelligence (Why tsvector) concept-phase5-database-side-intelligence

    The Problem

    pal-e-docs stores 246 notes as HTML blobs. There is no search — not in the API, not in MCP tools, not in the frontend. When an AI agent needs to find knowledge, it does this:

    1. list_notes(tags="sop,active") — get a list of slugs
    2. get_note(slug=...) — fetch full HTML content (1-6KB per note)
    3. Repeat 5-12 times, reading each document to determine relevance
    4. Reason over all that content to answer the original question

    This is like searching a library by pulling every book off the shelf and reading the first chapter. It works at 50 notes. At 246 it's painful. At 1,000 it's unusable. Every query scales linearly — more notes, more tokens, more calls, more cost.

    The Insight: Let the Database Be Smart

    The traditional approach would be to build search in the application layer — add a Python text extraction pipeline, build an inverted index in memory or Redis, write sync logic to keep it updated. That's a lot of moving parts to maintain.

    Postgres already has a built-in full-text search engine. It's not a bolt-on — it's a first-class feature with 20+ years of refinement. The key components:

    ComponentWhat it doesWhy it matters
    tsvectorA column type that stores a pre-computed, stemmed, normalized representation of textSearch is instant — no parsing at query time
    tsqueryParses search terms into a structured query with AND/OR/NOT logic and stemming"managing secrets" matches "secrets management" because both stem to the same roots
    GIN indexGeneralized Inverted Index — maps each word to the rows that contain itO(1) lookup instead of scanning every row
    TriggerA function that fires automatically on INSERT/UPDATEThe app never has to think about search — write HTML, get searchability for free
    ts_rank()Scores results by relevance, respecting weights (title > content > slug)Best matches come first
    ts_headline()Extracts a snippet around the matching termsAgent sees why a note matched without fetching the whole document

    The Pattern: Database-Side Intelligence

    The architecture pattern here is: the database holds the intelligence, the application asks questions, the MCP tool exposes those questions to agents.

    
    ┌─────────────┐     ┌──────────────┐     ┌───────────────┐     ┌───────────┐
    │  AI Agent   │────▶│  MCP Tool    │────▶│  API Endpoint │────▶│ Postgres  │
    │             │     │ search_notes │     │ /notes/search │     │ tsvector  │
    │  1 call     │◀────│              │◀────│               │◀────│ GIN index │
    │  ~200 tokens│     │  httpx wrap  │     │  SQLAlchemy   │     │ trigger   │
    └─────────────┘     └──────────────┘     └───────────────┘     └───────────┘
    

    Each layer is thin:

    • Postgres — does the real work: tokenizes, stems, indexes, ranks, extracts snippets
    • API — translates HTTP params into a SQL query, returns JSON
    • MCP tool — translates tool params into an HTTP request
    • Agent — asks a question in natural language terms

    The app doesn't extract text from HTML. The app doesn't build indexes. The app doesn't rank results. Postgres does all of that inside a trigger that fires automatically. The app's only job is to ask: "what matches this query?"

    Why This Pattern Scales Into the Full Plan

    This isn't just a Phase 5 decision — it's the architectural foundation for the entire Act 2 knowledge engine:

    PhaseSame pattern, different intelligence
    Phase 5 — tsvectorDatabase builds a keyword index via trigger. Agent asks "find notes containing these words." Deterministic, precise.
    Phase 6 — pgvectorDatabase stores embedding vectors. Agent asks "find notes similar to this concept." Fuzzy, semantic. Same thin-layer architecture — embeddings computed on write, similarity search on read.
    Phase 7 — Block contentDatabase stores typed blocks instead of HTML blobs. Agent asks "give me the Decisions table from this plan" — not the whole 6KB document. Same pattern: structured data in Postgres, thin query layer on top.
    Phase 8 — MCP optimizationCompound queries that combine keyword search + semantic search + block-level retrieval in a single call. The intelligence compounds because it's all in one database.

    If we built search in Python, we'd have to rebuild for embeddings, rebuild again for blocks, and somehow coordinate across three separate systems. By putting the intelligence in Postgres, each phase adds to what's already there. One database, one trigger pipeline, one query engine — progressively smarter.

    The Token Economics

    The real payoff is what this does to AI agent efficiency:

    Before (brute force)After (search-first)
    Calls per lookup12+ (list + get loop)1 (search)
    Tokens per lookup~5,000-15,000 (full HTML blobs)~200-500 (summaries + snippets)
    ScalingLinear — more notes = more tokensConstant — more notes, same query cost
    RelevanceAgent decides (expensive reasoning)Database decides (ts_rank, free)

    This is why the plan's vision says "80-90% token reduction." It's not an optimization — it's a fundamentally different access pattern. The agent stops reading documents and starts asking questions.

    The Trigger Is the Key

    The most important design decision is the Postgres trigger. When a note is created or updated:

    1. The app writes html_content exactly as it does today — no code changes
    2. The trigger fires automatically (BEFORE INSERT OR UPDATE)
    3. The trigger strips HTML tags with regex, splits title/content/slug into weighted components
    4. The trigger builds the tsvector and stores it in the search_vector column

    The app never touches search_vector. It's server-managed. This means:

    • Zero application code to keep the index in sync
    • Zero risk of the index going stale
    • The existing create_note and update_note endpoints work unchanged
    • The existing MCP tools that write notes automatically make those notes searchable

    The trigger is the invisible bridge between "note storage app" and "knowledge engine." Every note that exists today — and every note written in the future — becomes searchable without any writer knowing or caring about search.

    Related

    • phase-postgres-5-fulltext-search — the implementation phase this concept supports
    • plan-2026-02-26-tf-modularize-postgres — the parent plan (Act 2 vision)
  • Incident: SQLite Migration Crash — PR #61 Deployment incident-2026-03-02-sqlite-migration-crash-pr61

    Incident: SQLite Migration Crash — PR #61 Deployment (2026-03-02)

    Summary

    pal-e-docs API went down for ~10 minutes after merging PR #61 (note decomposition schema). Same root cause as the PR #29 incident from 2026-02-26: SQLite auto-commits DDL statements, so Alembic migrations that add multiple columns leave the DB in a partial state when they fail.

    Timeline

    • T+0: PR #61 merged (squash). Woodpecker CI pipeline #81 succeeds. Image pushed to Harbor.
    • T+~2m: ArgoCD deploys new image. Pod starts, runs Alembic migration.
    • T+~2m: Migration adds 3 of 4 columns (note_type, status, parent_note_id) but crashes before adding position column. alembic_version NOT stamped. Pod enters CrashLoopBackOff.
    • T+~5m: Detected via MCP tool returning 502. Confirmed pod 1/2 Ready, CrashLoopBackOff.
    • T+~8m: Fixed manually: installed sqlite3 in litestream sidecar, added missing position column, created composite index, stamped alembic_version to d4e5f6a7b8c9.
    • T+~10m: Deleted pod. New pod started 2/2 Running. API returned 200.

    Root Cause

    SQLite auto-commits each DDL statement (ALTER TABLE, CREATE INDEX) individually. Alembic expects transactional DDL — either all migration steps succeed and the version is stamped, or none do. With SQLite, each ALTER TABLE commits immediately, but the alembic_version update only happens at the end. If any step fails mid-migration, the DB has partial schema changes with the old version stamp. Every subsequent restart tries the migration from the beginning and fails on the first already-applied DDL.

    This is the second time this has happened (first was PR #29, 2026-02-26). The same manual fix was required both times.

    Impact

    • pal-e-docs API down for ~10 minutes
    • All MCP tool calls failed (502) — affected all active Claude Code sessions using pal-e-docs tools
    • Browse frontend returned 502
    • No data loss

    What Failed

    • No migration testing in CI (known gap: todo-migration-testing-ci-pal-e-docs)
    • No health check or readiness probe that would prevent traffic routing to a crashing pod
    • No rollback mechanism — ArgoCD kept deploying the same crashing image
    • SQLite fundamentally cannot do transactional DDL — this WILL happen again with any multi-step migration

    Resolution

    Manual fix via litestream sidecar container: apk add sqlite, then ALTER TABLE + CREATE INDEX + UPDATE alembic_version.

    Prevention

    • Migrate to Postgres — Postgres supports transactional DDL. Alembic migrations are atomic. This class of bug is eliminated entirely. Plan: plan-2026-02-26-tf-modularize-postgres.
    • Migration testing in CI — run Alembic upgrade against a copy of production schema before deploying. Would catch column conflicts before they hit prod.
    • Readiness probes — prevent traffic routing to pods that haven't completed startup.

    Related

    • Previous incident: SQLite Migration Crash — PR #29 (2026-02-26)
    • plan-2026-02-26-tf-modularize-postgres — Postgres migration plan
    • todo-migration-testing-ci-pal-e-docs — CI migration testing TODO
    • deployment-lessons — deployment lessons learned
  • Entity-Page Architecture entity-page-architecture

    Entity-Page Architecture

    Core Principle

    Pages are for reading. Databases are for properties.

    Every entity in pal-e-docs (project, repo, issue) has two aspects:

    • Structured properties — queryable columns in an entity table (name, slug, status, platform, is_public). These are what the database searches, filters, and joins on.
    • Rich content — HTML rendered for humans (architecture diagrams, roadmaps, prose descriptions). This lives in a note, which also provides revision tracking, tags, and links.

    A foreign key (page_note_id) connects the entity to its page note. One query returns both.

    What is a Foreign Key?

    A foreign key (FK) is a column in one table that references the primary key (ID) of another table. It tells the database: "this value points to a row in that other table."

    Example with pal-e-docs data:

    projects table:
      id: 3, slug: "pal-e-docs", page_note_id: 24  → REFERENCES notes(id)
    
    notes table:
      id: 24, slug: "project-pal-e-docs", html_content: "<h2>pal-e-docs</h2>..."
    

    page_note_id = 24 means "this project's page is note 24." The database enforces that note 24 exists. The number 24 is called "foreign" because it belongs to the notes table, not the projects table — it's a reference to a foreign table's primary key.

    What the FK Enforces

    1. Can't point to nothing

    UPDATE projects SET page_note_id = 999 WHERE id = 3;
    -- DB ERROR: note 999 doesn't exist. Rejected.

    Without a FK, this would silently succeed and the project would reference a ghost.

    2. Can't accidentally delete a page

    With ON DELETE RESTRICT:

    DELETE FROM notes WHERE id = 24;
    -- DB ERROR: project 3 still references this note. Unlink it first.

    Without a FK, the note just disappears. The project's page is gone and nothing warned you.

    3. Can't share pages within a table

    With a UNIQUE constraint on page_note_id:

    -- Project "pal-e-docs" already has page_note_id = 24
    UPDATE projects SET page_note_id = 24 WHERE id = 7;  -- Private project
    -- DB ERROR: page_note_id 24 is already taken.

    4. One-query joins

    SELECT p.*, n.html_content, n.updated_at
    FROM projects p
    LEFT JOIN notes n ON p.page_note_id = n.id
    WHERE p.slug = 'pal-e-docs';
    -- Returns: structured data AND page content in one query.

    5. NULL means no page

    SELECT * FROM projects WHERE page_note_id IS NULL;
    -- Returns: entities that don't have a page yet. Explicit and queryable.

    Why Not Polymorphic Associations?

    When a note can be the page for a project OR a repo OR an issue, there are alternative designs. Both lose referential integrity.

    Polymorphic reverse relationship (Option 2)

    Put owner_type + owner_id on the notes table:

    notes table:
      id: 24, owner_type: "project", owner_id: 3

    Problem: owner_id = 3 could mean project 3, repo 3, or issue 3. The database cannot create a FK that points to multiple tables conditionally. So owner_id is just an integer with no enforcement. You can set owner_id = 999 even if project 999 doesn't exist. You've lost the whole point of using a database — enforcement without human discipline.

    This pattern is common in Rails and Django but it sacrifices referential integrity for convenience.

    Join table (Option 3)

    entity_pages table:
      entity_type: "project", entity_id: 3, note_id: 24

    The note_id FK is real. But entity_id has the same polymorphic problem — it can't be a real FK. Extra table, extra joins, same integrity gap on the entity side.

    FK on entity tables (Option 4 — what pal-e-docs uses)

    projects.page_note_id = 24  → REFERENCES notes(id)  ← REAL FK
    repos.page_note_id = 37     → REFERENCES notes(id)  ← REAL FK

    The FK from entity → note is real and enforced. The database guarantees the note exists. ON DELETE RESTRICT prevents deletion. UNIQUE prevents sharing within a table.

    One gap: A project and a repo could both point to the same note (cross-table). The database can't prevent this because UNIQUE is per-table. In practice, slug conventions (project-* vs repo-*) make accidental collisions nearly impossible, and ON DELETE RESTRICT would surface any conflict quickly.

    Comparison

    Entity→Note FK enforced?Note→Entity FK enforced?Cross-table uniqueness?
    Option 2 (polymorphic)NoN/AYes (natural)
    Option 3 (join table)NoYesYes (UNIQUE)
    Option 4 (FK on entity)YesNoNo (per-table only)

    Option 4 wins because the entity→note direction is the relationship you query most: "give me this project and its page." That FK being real and enforced matters more than the unlikely edge case of cross-table collision.

    Schema Pattern

    projects table:  id, name, slug, platform, repo_url, is_public, page_note_id → notes.id
    repos table:     id, name, slug, platform, url, status, role, page_note_id → notes.id
    issues table:    id, slug, status, repo_id → repos.id, plan_slug, page_note_id → notes.id
    notes table:     id, slug, title, html_content, is_public, project_id → projects.id
    

    Entity tables hold structured queryable properties. Notes hold rich HTML content with revision tracking, tags, and links. The page_note_id FK connects them. One query gets everything.

    Before and After

    Before (slug convention):

    -- Two calls, agent guesses the slug
    Call 1: list_projects() → {id: 3, slug: "pal-e-docs"}
    Call 2: get_note(slug="project-pal-e-docs") → {html_content: "..."}
    -- If someone named it "pal-e-docs-project" instead, it breaks.

    After (FK join):

    -- One call, database joins on FK
    Call 1: get_project(slug="pal-e-docs")
    → {id: 3, slug: "pal-e-docs", platform: "forgejo",
       page: {html_content: "...", tags: [...], updated_at: "..."}}

    Related

    • plan-2026-02-26-schema-entity-links — the plan implementing this architecture
    • project-pal-e-docs — the project this architecture serves
Board 1
  • Pal E Docs Board board-pal-e-docs

    pal-e-docs Board

    Parent

    Project-level board for project-pal-e-docs. Tracks all pal-e-docs work: API, SDK, MCP, frontend, content.

    User Stories

    Key Story Note Served by this board
    board-context-renderer story-pal-e-docs-board-context-renderer Yes
    superuser-query Inline (project page) Yes
    superuser-maintain Inline (project page) Yes
    agent-read Inline (project page) Yes
    agent-write Inline (project page) Yes
    reader-browse Inline (project page) Yes

    Architecture

    • arch-domain-pal-e-docs — Domain model: notes, blocks, boards, projects, tags

    Acceptance Criteria

    This is a permanent project board — no single completion criterion. See individual ticket ACs.

    Kanban

    Managed via mcp__pal-e-docs__* board tools. Columns follow sop-board-workflow.

User Story 1
  • Board Context Renderer story-pal-e-docs-board-context-renderer

    story: Board Context Renderer

    Role

    Superuser (Lucas)

    Key

    board-context-renderer

    Want

    As the Superuser, I want to open a board and see the architecture diagrams and user stories it serves, above the kanban columns

    So That

    So that I can orient to a project in one view without navigating between project page, architecture notes, and board separately — the board becomes the primary navigation surface where what I'm building (architecture), why I'm building it (stories), and where we are (kanban) live together

    Acceptance Criteria

    • [ ] Board page shows a collapsible context header above the kanban columns
    • [ ] Architecture section displays mermaid diagram thumbnails fetched from linked arch notes
    • [ ] User Stories section displays story cards with Role, Want, Key label, and AC completion progress
    • [ ] Slugs are extracted from the board note's Architecture and User Stories blocks — no schema changes
    • [ ] Missing or misspelled slugs degrade gracefully (placeholder card, not an error)
    • [ ] Click an architecture thumbnail to filter the kanban to matching arch: labeled tickets
    • [ ] Click a story card to filter the kanban to matching story: labeled tickets
    • [ ] Filters compose (arch + story = intersection)
    • [ ] Context header collapses to a toggle bar on viewports under 640px
    • [ ] Boards without User Stories / Architecture sections in their note render the kanban as today (no empty state, no error)
    • [ ] Dogfood: board-pal-e-docs displays arch-domain-pal-e-docs as a thumbnail and this story as a card

    Success Metric

    Lucas can open any board, see the architecture and stories it serves, and filter tickets by arch component or story — all without navigating away from the board page. Orientation time to a new project drops from "open 3-4 pages" to "open 1 board."

    • arch-domain-pal-e-docs — the blocks, notes, boards, and board_items entities this renderer queries
    • project-pal-e-docs — parent project
    • board-pal-e-docs — dogfood target board
    • template-board — the template that prescribes User Stories + Architecture sections
    • template-user-story — story note format rendered by StoryCard
    • template-architecture — arch note format rendered by ArchThumbnail
Validation 2
  • Validation: skill-review-ticket drift fix (#241)

    Ticket

    forgejo_admin/claude-custom#241 — Reconcile skill-review-ticket headings with template-review hook. Board item #970 on board-pal-e-docs.

    Environment

    pal-e-docs production API. Content task — single block update via mcp__pal-e-docs__update_block. No deployment, no pod restart.

    Checks

    • PASS — Block code-8000 in note skill-review-ticket now contains Decomposition Assessment (was Decomposition)
    • PASS — All 9 headings in the block match the hook's REQUIRED_HEADINGS exactly: Template Completeness, Traceability, File Targets, Repo Placement, Dependencies, Acceptance Criteria, Blast Radius, Decomposition Assessment, Recommendation
    • PASSsearch_notes(query="Decomposition") shows no other skill or convention note using the old wording as a prescribed heading. Prose usage of "decomposition" as a concept noun is correct and unaffected.
    • PASS — Historical review notes (review-364, review-970) use the old heading but are frozen artifacts, not prescriptive templates — no action needed.

    Verdict

    PASS — all 4 checks pass. The dogfooding loop is closed: future review agents following skill-review-ticket will create review notes that pass the check-note-template.sh hook on the first attempt.

    Discovered Issues

    None. No template-review drift detected — hook is canonical and skill now matches.

  • Validation: arch-generic-checkout migration (#254)

    Ticket

    forgejo_admin/pal-e-api#254 — Migrate arch-generic-checkout to template-conformant architecture note. Board item #968 on board-pal-e-docs.

    Environment

    pal-e-docs production API (https://pal-e-docs.tail5b443a.ts.net). Content task — no deployment, no pod restart, no migration. Changes applied via mcp__pal-e-docs__update_note.

    Checks

    • PASSget_note_toc(slug="arch-generic-checkout") returns all 4 required template-architecture headings in order: Diagram, Components, Key Decisions, Related
    • PASS — Diagram section contains a sequenceDiagram mermaid fence (correct facet keyword for a data-flow note)
    • PASS — Components table has 6 rows (Parent, Email, westside-app, basketball-api, PostgreSQL, Stripe) with Component | Purpose | Notes columns
    • PASS — Key Decisions has 6 WHY-focused bullets covering token auth, DB-driven products, JSONB fields, Stripe Checkout, opt-out pattern, category filtering
    • PASS — Original prose preserved: Database Schema tables, API Endpoints table, Adding a New Product Type guide all retained as additional sections between Key Decisions and Related (allowed by template)
    • PASSget_note_links(slug="arch-generic-checkout") returned zero inbound refs pre-migration — no link breakage risk. Slug unchanged.
    • PASS — Title updated to "Data Flow: Generic Checkout System" matching template naming convention for dataflow facet

    Verdict

    PASS — all 7 checks pass. The note is now template-conformant per template-architecture. The grandfather entry in check-note-template.sh can be removed in a separate follow-up PR (already scoped in the ticket's AC).

    Discovered Issues

    • Follow-up (already scoped): Remove arch-generic-checkout from the grandfather list in hooks/check-note-template.sh (claude-custom). This was AC item in the original ticket and should be a small follow-up PR once the hook from PR #240 is validated.
Architecture 1
  • Domain Model: pal-e-docs arch-domain-pal-e-docs

    Domain Model: pal-e-docs

    The knowledge platform's entity model. Notes hold metadata and legacy rendered HTML; blocks are the real content graph (heading/paragraph/list/table/code/mermaid). Tags, projects, boards, and board items provide cross-cutting classification and workflow. This is the what of pal-e-docs — the when (data flow) and where (deployment) live in sibling arch notes.

    Diagram

    erDiagram
        USER ||--o{ PROJECT : "owns"
        PROJECT ||--o{ NOTE : "contains"
        PROJECT ||--o{ REPO : "links"
        PROJECT ||--o| BOARD : "has-one"
        PROJECT ||--o| NOTE : "page_note"
    
        NOTE ||--o{ BLOCK : "decomposes-into"
        NOTE ||--o| COMPILED_PAGE : "caches-render"
        NOTE ||--o{ NOTE_REVISION : "history"
        NOTE }o--o{ TAG : "note_tags"
        NOTE }o--o{ NOTE : "note_links"
        NOTE ||--o{ NOTE : "parent-child"
    
        BOARD ||--o{ BOARD_ITEM : "has"
        BOARD_ITEM }o--o| NOTE : "board_note_id"
    
        BLOCK {
            int id PK
            int note_id FK
            int position
            string block_type "heading|paragraph|list|table|code|mermaid"
            json content
            string anchor_id
            vector embedding "halfvec 2560, HNSW indexed"
            string embedding_status "pending|indexed|skipped"
        }
    
        NOTE {
            int id PK
            int project_id FK
            string title
            string slug UK
            text html_content "legacy full-HTML, back-compat"
            string note_type "sop|convention|doc|architecture|template|project-page|board"
            string status
            int parent_note_id FK "self-ref for hierarchy"
        }
    
        PROJECT {
            int id PK
            string slug UK
            string platform "forgejo|github"
            int page_note_id FK "one project-page note"
        }
    
        TAG {
            int id PK
            string name UK
        }
    
        BOARD_ITEM {
            int id PK
            int board_id FK
            int board_note_id FK "nullable — issue items don't link"
            enum item_type "plan|phase|issue|repo|project|todo"
            enum column "backlog|todo|next_up|in_progress|qa|needs_approval|validation|done"
            string note_slug "for note-backed items"
            string forgejo_issue_url "for issue-backed items"
            string labels "comma-sep: story:X,arch:Y,type:Z"
        }
    

    Components

    Component Purpose Notes
    notes Page-level metadata + legacy rendered HTML SQLAlchemy model Note. html_content kept for back-compat; authoritative content lives in blocks. Indexes: PK, unique on slug. No index on note_type (opportunity for partial index — see ticket pal-e-api#252).
    blocks Atomic structural content units (heading, paragraph, list, table, code, mermaid) SQLAlchemy model Block. Embedding column is halfvec(2560) with HNSW index for semantic search. Mermaid blocks are intentionally skipped by the embedding worker (diagram source is not semantically meaningful). Unique (note_id, anchor_id) enables surgical get_section reads.
    projects Top-level grouping for notes, repos, and boards SQLAlchemy model Project. Every project optionally has a page_note_id pointing at its project-page note. One board per project.
    repos Forgejo/GitHub repository records associated with a project SQLAlchemy model Repo. Platform + URL + role metadata.
    tags Cross-cutting classification of notes (active, sop, template, convention, etc.) M:N via note_tags. Case-sensitive unique name.
    note_links Directed graph of note-to-note references Source → Target edges. Used by link maintenance and orphan detection.
    note_revisions Full-content history of every note edit Snapshots html_content per edit. Revision number monotonic per note. Enables rollback and audit.
    compiled_pages Rendered HTML cache with TOC JSON One row per note. Invalidated on block changes via content_hash. Serves the read-heavy public browsing path.
    boards Kanban board per project One-to-one with project. Slug-addressable.
    board_items Kanban tickets (issue-backed or note-backed) Dual-addressing: forgejo_issue_url for issue items, board_note_id/note_slug for note-backed items (plan/phase/repo/project). Labels carry story:X, arch:Y, type:Z traceability triangle.
    users Authenticated superusers of the API Email + hashed password + is_approved. Keycloak JWT is the actual runtime auth path; this table is the local user directory.

    Key Decisions

    • Blocks are the source of truth, not html_content. The legacy notes.html_content column is preserved for back-compat and for the compiled-page cache, but block-first access (get_note_tocget_section) is the cheap path that agents use. Rationale: token efficiency — one section is ~500 tokens, the full note is ~5000.
    • Embeddings live on blocks, not notes. Atomic semantic units give better retrieval (a single procedure paragraph inside a 10KB SOP is findable). Mermaid blocks are explicitly skipped — diagram syntax is not embedding-friendly, and the blocks_embedding_trigger queue filters them out.
    • board_items dual-addresses notes and issues. A board item is either note-backed (plan, phase, repo, project, todo) or issue-backed (forgejo_issue_url). This preserves the kanban-over-plans migration: old phase notes still work, new tickets flow through Forgejo. The item_type enum enforces which address field is required.
    • note_type is a varchar, not an enum. Extensibility: new note types (board, project-page, architecture, skill, agent) were added without migrations. The trade-off is no DB-level validation — hooks enforce correctness at write time (see check-note-template.sh).
    • Tags are M:N, labels are comma-separated strings. Tags are note-level classification (normalized, queryable). Labels are board-item-level traceability (denormalized for display speed). They serve different query patterns and should not be unified.
    • No index on block_type or note_type. Current scale (23K blocks, 900 notes) makes seq-scans <5ms so it hasn't mattered. Partial indexes on first-class query paths (block_type='mermaid', note_type='architecture') are scoped in ticket pal-e-api#252 — motivation is semantic self-documentation of query paths, not performance.
    • project-pal-e-docs — the project this diagram describes
    • template-architecture — the triplet model prescribing this note
    • arch-dataflow-pal-e-docsTODO, the when sibling (sequenceDiagram of MCP → API → DB round-trips)
    • arch-deployment-pal-e-docsTODO, the where sibling (graph TB of k3s pods, CNPG, ingress)
    • convention-architecture-ids — how arch: labels on board items map to Components table rows
Todo 9
  • TODO: Token Metrics -- Correlate Token Usage with DORA and Sprints

    Empirical Calibration (2026-03-10 basketball-api session)

    Key insight: Tokens are to AI agents what hours are to human developers — the unit of cost and effort. Sprint points traditionally map to time (1=half day, 3=2-3 days, 5=full week). For an AI workforce, they should map to tokens. First empirical data from a real session:

    Agent Task Tokens Tool Uses Wall Time
    Dev: single endpoint (Phase 2a) 71K 62 5 min
    Dev: fix 2 QA blockers (Phase 3a) 93K 59 6 min
    Dev: rebase + conflict resolve 53K 44 4 min
    QA: full PR review 38-73K 19-54 2-5 min
    QA: re-review after fix 48K 20 1.5 min
    Full issue cycle (dev + QA + fix + re-QA) ~150-250K ~15-20 min
    Points Human Time Token Equivalent What It Looks Like
    1 Half day ~100K Single fix, one agent cycle, no QA surprises
    2 Full day ~200K One feature PR, dev + QA + fix cycle
    3 2-3 days ~400K Feature with rebase/conflict, 2 QA rounds
    5 Full week ~800K Multiple PRs, plan updates, coordination
    8 Full sprint ~1.5M Multi-phase, multiple agents in parallel
    13 Multi-sprint 3M+ Cross-repo, architectural changes

    Next step: Instrument agent spawns with issue number tagging and token capture. Simplest path is a Claude Code hook that parses agent output files (they already report total_tokens, tool_uses, duration_ms) and pushes to pal-e-docs as sprint item metadata. No k8s infrastructure needed — just a post-session hook. Calibrate the fibonacci mapping over several sprints with real data.

    The Idea

    Measure token usage per issue, per sprint, per repo -- and correlate it with DORA metrics. Break token spend down by activity type: planning, development, review (QA), documentation. This gives us a cost-efficiency dimension that nobody else has because nobody else runs an AI agent workforce.

    Why This Matters

    Token usage alone is not directly valuable -- spending more tokens does not mean more value. But token usage correlated with outcomes is breakthrough:

    • What is the token cost per successful deployment?
    • Does more planning tokens reduce rework tokens? (proving that planning prevents waste)
    • What is the token efficiency curve per repo as it matures?
    • Which SOPs and claude config patterns reduce token waste?
    • Sprint planning: look at token spend breakdown and predict capacity

    Activity Categories

    • Planning -- Betty Sue sessions: plan creation, architecture decisions, sprint planning
    • Development -- Dev agent sessions: code writing, branch creation, PR submission
    • Review -- QA agent sessions: PR review, review-fix loops
    • Documentation -- Main session doc updates, pal-e-docs note creation/updates

    Data Sources

    • Claude API usage logs (if available) or Claude Code session metadata
    • Could instrument hooks to log token counts per agent spawn
    • Correlate with Forgejo issue URLs and sprint items

    Vision: Grafana Board

    One day: a Grafana dashboard showing per-repo DORA metrics alongside token metrics per repo. Sprint burndowns measured in tokens. Token cost per DORA band improvement. This is the economic proof that one human + AI agents operates at enterprise velocity at a fraction of the cost.

    • dora-framework -- the metrics this would extend
    • plan-2026-03-01-pal-e-sprints -- sprints provide the container for measuring token spend per iteration
    • plan-2026-03-01-dora-metrics-dashboard -- the Grafana dashboard this would feed into
  • TODO: MCP sprint tools cannot clear points/labels back to null

    Problem

    move_sprint_item maps None to _UNSET (don't send), so MCP callers cannot explicitly clear points or labels back to null once set. This is a gap in the MCP-to-SDK mapping pattern.

    Work Required

    Add a convention for "clear this field" in the MCP layer. Options:

    • Accept a special sentinel string like "clear" or 0 for points, "" for labels
    • Add a clear_points: bool parameter
    • Fix at SDK level: distinguish None (clear) from _UNSET (don't send) and expose both through MCP

    Forgejo Issues

    • pal-e-docs-mcp #33 — Clear points/labels to null (the main fix)
    • pal-e-docs-mcp #34 — Verify bulk_move_items backend supports points field

    References

    • forgejo_admin/pal-e-docs-mcp PR #32 — QA nits #1 and #2
    • pal-e-docs-sdk/src/pal_e_docs_sdk/sprints.py line 155 — _UNSET sentinel
    • pal-e-docs-mcp/src/pal_e_docs_mcp/tools/sprints.pymove_sprint_item
  • TODO: Add archived status to TODO note_type todo-archived-status-for-todos

    TODO note_type only allows status values open and done. Need an archived status for TODOs that have been absorbed into a plan but whose underlying work isn't complete yet.

    Why: When a TODO is scoped into a plan phase, the TODO's job (capture + triage) is done, but the work isn't. Marking it done is semantically wrong. We need archived (or absorbed) to distinguish "captured into plan" from "work completed."

    Also consider: Adding a plan_slug field to the note schema so TODOs can explicitly link to the plan that owns them. Enables orphan detection query: "show me all TODOs not assigned to a plan."

    Workaround for now: Using status=done + parent_slug=plan-* to indicate absorption.

  • TODO: pal-e-docs Deployment Reliability — Zero-Downtime Deploys

    Problem

    pal-e-docs is the single point of failure for the entire AI agency. Every Claude Code session depends on it (session-start hooks, plan slug enforcement, MCP tools). When it goes down, all agent workflows crash — not gracefully, just 502s everywhere.

    We have now had two production outages from the same root cause: SQLite auto-commits DDL, Alembic migrations crash mid-way, the DB is left in a partial state, and the pod enters CrashLoopBackOff. Both times required manual intervention via kubectl.

    • Incident 1: PR #29 (2026-02-26) — is_public + page_note_id migration
    • Incident 2: PR #61 (2026-03-02) — note_type + status + parent_note_id + position migration. See incident-2026-03-02-sqlite-migration-crash-pr61.

    The current deployment strategy is a simple rolling update via ArgoCD. The old pod is terminated before the new pod proves it can serve traffic. There is no readiness probe, no health check, no rollback mechanism. Any deployment failure = downtime.

    Three Things We Need

    1. Postgres Migration (eliminates the root cause)

    Postgres supports transactional DDL. Alembic migrations are atomic — all steps succeed or all roll back. The entire class of "partial DDL + unstamped version" bugs is eliminated.

    Existing plan: plan-2026-02-26-tf-modularize-postgres (currently DEFERRED). Phase 1 (TF modularization) was deferred, but Phases 2-4 (deploy Postgres, migrate pal-e-docs, backups) don't depend on it. The plan explicitly says "Postgres can be added directly to main.tf when needed." It's needed now — two incidents is the trigger.

    What to do: Un-defer the Postgres plan. Skip Phase 1 (modularization). Execute Phases 2-4 directly.

    2. Blue-Green Deployments with Readiness Probes (eliminates downtime from ANY deployment failure)

    Even with Postgres, a bad deployment can crash a pod. Blue-green ensures the old pod keeps serving until the new pod passes its readiness probe. Zero-downtime for any deployment, not just migrations.

    Implementation:

    • Add a readiness probe to pal-e-docs (HTTP GET on a health endpoint, only passes after migration completes and app is serving)
    • Switch from ArgoCD rolling update to Argo Rollouts blue-green strategy
    • Old ReplicaSet stays alive until new one is Ready
    • Traffic switches atomically

    Existing plans: plan-2026-02-26-kustomize-service-bases touches k8s manifests and deployment patterns. Blue-green could be a phase there, or a standalone effort under pal-e-platform. No existing plan covers Argo Rollouts specifically.

    3. Migration Testing in CI (catches the problem before it hits prod)

    Run alembic upgrade head against a copy of the production schema in CI before deploying. Would have caught "duplicate column name" before the image ever reached ArgoCD.

    Existing TODO: todo-migration-testing-ci-pal-e-docs (open). This has been a known gap since the first incident.

    Implementation: Add a Woodpecker CI step that: (a) starts a Postgres container (or SQLite for now), (b) applies all migrations up to current HEAD, (c) verifies clean state. Blocks deploy on failure.

    Priority Order

    1. Postgres — eliminates root cause. Two incidents from the same bug is unacceptable. Un-defer the plan.
    2. Blue-green + readiness probes — eliminates downtime from any future deployment failure. Defense in depth.
    3. Migration testing in CI — belt to blue-green's suspenders. Catches problems earlier in the pipeline.

    Relationship to Current Work

    The active decomposition plan (plan-2026-03-01-note-decomposition) has no more Alembic migrations in Phases 3-5. Phase 3 (MCP tools) and Phase 4 (browse rendering) are code-only changes. So the immediate SQLite risk is low for current work. But the next plan that touches the schema will hit this again.

    Related

    • incident-2026-03-02-sqlite-migration-crash-pr61 — the incident that prompted this TODO
    • plan-2026-02-26-tf-modularize-postgres — Postgres plan (deferred, needs un-deferring)
    • plan-2026-02-26-kustomize-service-bases — k8s deployment patterns
    • todo-migration-testing-ci-pal-e-docs — existing CI migration testing TODO
    • deployment-lessons — SQLite DDL danger documented
    • plan-2026-03-01-note-decomposition — current active work (no more migrations remaining)
  • TODO: Rewire ArgoCD to use pal-e-deployments repo todo-argocd-rewire-deployments-repo

    Problem

    ArgoCD for pal-e-docs points at pal-e-docs/k8s/ (the app repo) instead of the deployments repo with kustomize overlays. This means:

    • Image tags are hardcoded in the app repo's k8s/deployment.yaml
    • The deployments repo with proper kustomize bases/overlays is completely unused
    • The Image Updater (when fixed) would need to patch the app repo, not the deployments repo
    • GitOps separation of concerns is broken — app repo owns deployment config

    Current State

    WhatCurrentTarget
    ArgoCD sourceforgejo_admin/pal-e-docs path k8s/forgejo_admin/pal-e-deployments path overlays/pal-e-docs/prod/
    Image tag managementHardcoded in app repoUpdated in deployments repo by CI or Image Updater
    Repo namedeploymentspal-e-deployments (naming convention)

    Work Required

    1. Rename repo: forgejo_admin/deploymentsforgejo_admin/pal-e-deployments
    2. Clean up kustomization: Remove Litestream-era artifacts (litestream-configmap.yaml, pvc.yaml). Update deployment patch to match current Postgres-based deployment.
    3. Update ArgoCD Application: Change spec.source.repoURL and spec.source.path to point at the deployments repo overlay.
    4. Verify sync: Confirm ArgoCD syncs from the new source and deployment matches.
    5. Remove k8s/ from pal-e-docs: Once ArgoCD reads from deployments, the app repo no longer needs k8s/.
    6. Update CI: Woodpecker build-and-push should update the image tag in the deployments repo (or Image Updater does this when fixed).

    Acceptance Criteria

    • ArgoCD reads deployment config from pal-e-deployments repo
    • pal-e-docs repo has no k8s/ directory
    • Image tag updates happen in the deployments repo, not the app repo
    • Deployment works end-to-end: merge → CI builds → deployments repo updated → ArgoCD syncs

    Related

    • phase-postgres-5-fulltext-search — discovered during Phase 5 deployment
    • bug-image-updater-harbor-auth — Image Updater fix is complementary
    • service-onboarding-sop — needs updating once deployments repo is the standard
  • TODO: Migration Testing in CI for pal-e-docs todo-migration-testing-ci-pal-e-docs

    TODO: Migration Testing in CI for pal-e-docs

    Context

    The pal-e-docs Alembic migration crash (2026-02-26) deployed a migration that failed against production SQLite. The migration was never tested against a real database before hitting prod.

    What's needed

    Add a Woodpecker CI step that runs alembic upgrade head against a seeded database (not just empty) before merge. This catches migration failures in CI instead of production.

    Details

    • Woodpecker pipeline step: spin up a test database (SQLite file or Postgres container, depending on current DB), seed with representative data, run migrations
    • Must test against non-empty tables — empty-database migrations always succeed
    • Once pal-e-docs moves to Postgres (see tf-postgres-strategy), this should test against Postgres specifically
    • Consider: Woodpecker service container for Postgres, or use the CloudNativePG test pattern

    Origin

    Split from todo-deployment-safety — platform-level items (rollback, alerting, deployment protection) are covered by platform plans. This app-level CI item stays with pal-e-docs.

  • Problem

    The publish step in forgejo_admin/pal-e-docs-mcp Woodpecker pipeline fails on every push to main. Clone and lint pass. Publish exits with code 1.

    What We Know

    • Clone fix (PR #15) works — internal Forgejo URL resolves from pipeline pods
    • Lint step passes
    • PyPI URL was changed from from_secret to hardcoded internal URL in PR #17
    • Internal Forgejo PyPI endpoint responds correctly (405 on GET, allows POST) — verified via curl from cluster
    • Package version is 0.1.0 — never changes between builds
    • Woodpecker log streaming is broken (empty newlines) — cannot read actual error output

    What We Don't Know

    • The actual error message from twine — logs are empty due to Woodpecker 3.13.0 log streaming bug
    • Could be: DNS resolution failure in the publish container (different from clone container)
    • Could be: version conflict (0.1.0 already published, twine rejects duplicate)
    • Could be: auth failure (secrets not injected properly after URL change)
    • Could be: something else entirely

    Impact

    Low — pal-e-docs-mcp runs locally from source via uv run. The published PyPI package is not consumed by anything in production. But the pipeline should work.

    To Investigate

    1. Fix Woodpecker log streaming so we can actually read errors (may require Woodpecker DB cleanup or version upgrade)
    2. Or: add set -x / verbose output to the publish commands to force output to stdout before the log stream
    3. Or: run a test pod in the woodpecker namespace that exactly replicates the publish step (python:3.12-slim, pip install build twine, build, upload)
    4. Check if version 0.1.0 is already in the Forgejo PyPI registry
    5. Consider adding auto-version-bump (e.g. using git SHA or date) to avoid conflicts

    Related

    • todo-woodpecker-tls-clone-fix — same TLS root cause, clone portion fixed
    • Forgejo issues: pal-e-docs-mcp #14 (clone fix, merged), #16 (PyPI URL fix, merged)
  • Problem

    Woodpecker CI pipelines on pal-e-app failed at check/lint steps despite passing locally. Assumed to be K8s backend issue.

    Resolution

    Resolved as of pipeline 17 (2026-03-14). The push pipeline for PR #13 passes all 7 steps (clone, install, check, lint, build, build-and-push, update-deployment-tag). The original failures (pipelines 7-8) were likely transient K8s backend issues or workspace problems that resolved after subsequent deployments.

    • plan-pal-e-docs — pal-e-app is the frontend for this project
    • bug-woodpecker-smoke-test-empty-logs — related investigation that proved pod logs, not K8s backend, were the real issue
  • Problem

    Woodpecker CI pipelines on pal-e-docs showed all steps failing with empty logs. Assumed to be the Woodpecker K8s backend log streaming bug (#4409 upstream).

    Root Cause

    Three independent bugs, NOT a K8s backend issue:

    1. ruff format violationtests/test_blocks_compiled_pages.py had unformatted multi-arg constructors. ruff format --check exits 1, blocking all downstream steps.
    2. --index-url vs --extra-index-url — smoke-test used --index-url for Forgejo PyPI, which replaced public PyPI entirely. Transitive deps (httpx, pydantic) unreachable → ResolutionImpossible.
    3. Stale SDK v0.2.0 — published SDK had sprints module, not boards. Smoke test called list_boards() which didn't exist → 404 caught as connection error.

    The "empty logs" were caused by aggressive pod garbage collection in the K8s backend, not by a log streaming bug. Logs were captured by streaming in real-time with kubectl logs -f before pod cleanup.

    Fix

    • Commit 9fe5106: ruff format fix (test step now passes)
    • Commit b5032ba: --extra-index-url fix (SDK installs from both Forgejo + public PyPI)
    • Commit 385ecd6: replaced SDK smoke test with inline health check (no version skew dependency)

    Pipeline 209: first fully green pipeline. 5/5 steps SUCCESS.

    • plan-pal-e-docs — parent plan
    • phase-pal-e-docs-ci-infra — CI/Infra Hardening phase
    • todo-fix-mcp-pypi-publish — SDK needs republish with boards module
Project Page 1
  • Project: pal-e-docs project-pal-e-docs

    pal-e-docs

    Vision

    A DORA Elite AI Enterprise. pal-e-docs is an AI-native knowledge engine that doubles as an SRE command center and project management platform. It doesn't just store knowledge — it understands it. AI agents ask questions and get precise, ranked, section-level answers. The database holds the intelligence: full-text indexes, structured content blocks, compiled pages. Every note written automatically becomes searchable and navigable. The system gets smarter as it grows.

    Two access paths to the same data: AI agents use MCP tools, humans browse the rendered frontend. Both hit the same FastAPI REST API backed by Postgres (CloudNativePG on k3s). The frontend is SvelteKit — enabling interactive components like kanban boards alongside document rendering, all driven by note_type.

    User Stories

    Who uses the knowledge platform, what they need, and how we measure success. pal-e-docs serves three roles: the Superuser managing the knowledge base, agents querying it, and future external readers browsing public content.

    Role Story Success Metric story:X key
    Superuser (Lucas) I can query the knowledge base by meaning (semantic search) and find any SOP, plan, convention, or decision without remembering the exact slug. Semantic search returns relevant results in top 5. Zero "I know we documented this but can't find it" moments. story:superuser-query
    Superuser (Lucas) I can maintain the knowledge base — create notes, update blocks, manage boards — through MCP tools without touching the database directly. All CRUD operations available via MCP. Zero direct SQL needed for routine operations. story:superuser-maintain
    Agent (Betty Sue, Dottie, Dev, QA) I can read SOPs, plans, and conventions via MCP tools to inform my work. Block-first access keeps token costs low. get_section returns <500 tokens. Agents never need get_note for large notes. story:agent-read
    Agent (Betty Sue) I can create and update notes, phases, and board items via MCP tools to track work progress. Templates are enforced by hooks. All create/update operations template-validated. Zero malformed notes in production. story:agent-write
    Reader (future) I can browse public notes, plans, and project pages in a web UI without authentication. Public pages load in <2s. Navigation is intuitive (TOC, search, browse by project). story:reader-browse

    Plan

    Active: plan-pal-e-docs — Interactive Knowledge Platform

    24+ phases. Original 10 phases (0–4 board/frontend scaffold, plus infra/automation) all completed. Frontend feature phases F1–F10 completed. F11 (Design System Overhaul) and F13 (Context Intelligence) in progress. F12 (Semantic Search Recovery) completed.

    Completed plans:

    Plan Completed Summary
    plan-2026-02-26-tf-modularize-postgres 2026-03-13 Act 1 (SQLite → Postgres) + Act 2 (Knowledge Engine: blocks, search, compiled pages, MCP rewrite)
    plan-2026-03-01-pal-e-sprints 2026-03-13 Sprint schema, MCP tools, schema expansion. Absorbed into plan-pal-e-docs.
    plan-2026-03-03-sprint-workflow-automation 2026-03-13 Workflow automation phases 1-4. Phase 5 reparented into plan-pal-e-docs.
    plan-2026-03-01-note-decomposition 2026-03-02 note_type, status, parent_note_id, position columns
    plan-2026-02-28-knowledge-system-consolidation 2026-03-01 Schema maturity, tag cleanup, privacy audit
    plan-2026-02-27-browse-ux-enhancements 2026-02-28 Recency sort, project detail, mermaid revision
    plan-2026-02-27-responsive-design-mobile-ux 2026-02-28 Table wrapping, mobile breakpoints
    plan-2026-02-26-browse-frontend-polish 2026-02-27 Mermaid fix, XSS sanitization, auto-link slugs
    plan-2026-02-25-private-notes-auth 2026-02-26 Browse auth, private notes, is_public filtering
    plan-2026-02-24-docs-foundation 2026-02-25 Conventions, enforcement hooks, template system
    plan-2026-02-24-repo-consolidation 2026-02-25 Migrate all repos to Forgejo
    plan-2026-03-13-pal-e-frontend 2026-03-13 Killed and absorbed into plan-pal-e-docs during consolidation

    Board

    board-pal-e-docs — Pal E Docs Board. Continuous kanban. Columns: Backlog → Todo → Next Up → In Progress → Done. Auto-syncs plan phases via sync_board. Forgejo issues auto-sync via sync-issues endpoint.

    Status

    • API deployed on k3s, accessible via Tailscale Funnel
    • SvelteKit frontend LIVE at https://pal-e-app.tail5b443a.ts.net — kanban boards with drag-and-drop, dark theme
    • Keycloak OIDC Auth LIVE — write operations require login, reads remain public. Auth.js + Keycloak provider. FAB hidden for unauthenticated users. (PR #24)
    • Frontend search LIVE — full-text, semantic, and hybrid search from the browser. Cmd+K shortcut, URL-persistent query params, mode toggle (PR #16)
    • Board filtering LIVE — type filter pills, hide-done toggle, collapsible columns, board summary card, project mini-board view (PR #17)
    • DORA Dashboard LIVE — cross-project board rollup at /dashboard, needs-attention section, per-project cards with column distribution, deployment frequency (PR #20)
    • Quick-Jot LIVE — FAB button + n shortcut opens modal for quick note creation. Auto-slug, project dropdown, note_type selector, success toast (PR #21)
    • 500+ notes across 13 projects, 16 note types
    • 36 MCP tools — notes CRUD, search, semantic search, blocks, boards, board sync, links, repos, projects, tags, templates
    • 7,600+ content blocks — typed blocks with anchor_ids, compiled pages, block-first access (91% token reduction)
    • Semantic search LIVE — pgvector + Ollama (qwen3-embedding:4b), all blocks embedded, hybrid search (RRF fusion), Prometheus alerting for embedding failures
    • Vector-powered session startup — Dynamic Briefing injects semantically relevant context at session start. Fail-open design.
    • Board auto-syncPOST /boards/{slug}/sync auto-populates phases from plans. update_note hook auto-moves board items on status change.
    • Postgres (CloudNativePG) — WAL archiving to MinIO, daily base backups, PITR verified
    • CI FULLY GREEN — Woodpecker pipeline: check → lint → build → Harbor → deploy-tag → ArgoCD auto-deploy
    • 513 tests passing (backend) + 33 E2E tests (frontend, PR #25 pending)
    • XSS sanitization via nh3, auto-link slug references, Mermaid diagrams, Atkinson Hyperlegible font

    Milestones

    • milestone-2026-02-24-project-genesis — FastAPI + Postgres + REST API + first MCP server
    • milestone-2026-03-01-knowledge-engine — Block parser, compiled pages, semantic search (pgvector + Ollama), MCP v0.3.0
    • milestone-2026-03-13-board-system-frontend — Board data model, kanban drag-and-drop, board auto-sync, SvelteKit frontend launch
    • milestone-2026-03-14-frontend-workbench — 8 PRs in one session: search, board filtering, DORA dashboard, Quick-Jot, Keycloak auth, E2E tests, CI green
    • milestone-2026-03-15-knowledge-loop — Semantic search recovery, MEMORY.md diet (224 → 60 lines), vector-powered Dynamic Briefing, convention-memory-scope, template-ticket. The knowledge loop closed.
    • milestone-2026-03-16-knowledge-architecture (ACTIVE) — Milestone hierarchy, knowledge tiering (hot/warm/cold/frozen), gapped integer positions, doc drift cleanup. Plan: plan-2026-03-16-knowledge-architecture

    Architecture

    erDiagram
        Project ||--o{ Note : contains
        Project ||--o{ Repo : owns
        Project ||--|| Board : has
        Note ||--o{ Block : decomposes
        Note ||--o| CompiledPage : caches
        Note ||--o{ NoteRevision : tracks
        Note }o--o{ Tag : tagged
        Note }o--o{ Note : links
        Note |o--o{ Note : parent-children
        Board ||--o{ BoardItem : contains
    
        Project {
            string slug PK
            string name
            string platform
            int page_note_id FK
        }
        Note {
            string slug PK
            string note_type
            string status
            int project_id FK
            int parent_note_id FK
            int position
            text html_content
        }
        Block {
            int id PK
            int note_id FK
            string block_type
            string anchor_id UK
            json content
            vector embedding
        }
        Board {
            string slug PK
            int project_id FK
            string name
        }
        BoardItem {
            int id PK
            int board_id FK
            string item_type
            string column
            int position
            string note_slug
        }
        Tag {
            string name PK
        }
        Repo {
            string slug PK
            string platform
            string url
            int project_id FK
        }
    

    Domain Model. Notes are the universal document type — 16 note_type values (plan, phase, sop, convention, template, agent, skill, todo, doc, etc.) all stored in one table, differentiated by type. Notes decompose into typed Blocks (heading, paragraph, list, table, code, mermaid) with anchor_id for direct section access. CompiledPages cache rendered HTML. Parent-child relationships via parent_note_id create the plan → phase → subphase hierarchy. Boards provide kanban views per project, with BoardItems linking to notes via note_slug or Forgejo issues via forgejo_issue_url. Blocks carry 768-dim pgvector embeddings for semantic search.

    flowchart LR
        subgraph Consumers
            AGENT[AI Agent
    Claude Code] HUMAN[Human
    Browser] end subgraph Access Layer MCP[pal-e-docs-mcp
    36 MCP tools] APP[pal-e-app
    SvelteKit SSR] end subgraph Backend API[FastAPI
    REST API] SEARCH[Full-Text Search
    tsvector + GIN] SEMANTIC[Semantic Search
    pgvector + Ollama] end subgraph Storage DB[(Postgres 16
    CloudNativePG)] MINIO[(MinIO S3
    WAL archive)] end AGENT -->|MCP protocol| MCP HUMAN -->|HTTPS| APP MCP -->|HTTP| API APP -->|HTTP internal| API API --> DB API --> SEARCH API --> SEMANTIC SEARCH --> DB SEMANTIC --> DB DB -->|WAL streaming| MINIO

    Data Flow. Two access paths to the same data: AI agents use MCP tools (36 tools via pal-e-docs-mcp), humans use the SvelteKit frontend (pal-e-app). Both hit the same FastAPI REST API. The SvelteKit app uses server-side rendering — +page.server.ts loaders fetch from the API using the in-cluster service URL, then Svelte renders on the server. Client-side mutations (e.g. kanban drag-and-drop) go through a SvelteKit API proxy route that sanitizes errors and validates inputs before forwarding to the backend. Full-text search uses Postgres tsvector with GIN indexes. Semantic search uses pgvector with Ollama-generated embeddings (qwen3-embedding:4b, 768-dim). Session startup queries semantic search to inject a Dynamic Briefing — contextually relevant knowledge ranked by meaning.

    graph TD
        subgraph k3s Cluster
            subgraph ns-app["ns: pal-e-app"]
                APPOD[pal-e-app pod
    Node.js :3000] end subgraph ns-api["ns: pal-e-docs"] APIPOD[pal-e-docs pod
    FastAPI :8000] EMBED[embedding-worker
    :8001 metrics] end subgraph ns-pg["ns: postgres"] PG[pal-e-postgres
    CNPG Cluster] end subgraph ns-platform["Platform Services"] ARGOCD[ArgoCD] HARBOR[Harbor Registry] WOODPECKER[Woodpecker CI] FORGEJO[Forgejo] MINIO[MinIO S3] OLLAMA[Ollama GPU
    qwen3-embedding] end end subgraph External TS[Tailscale Funnel
    TLS termination] BROWSER[Browser] CLAUDE[Claude Code] end BROWSER -->|HTTPS| TS TS -->|pal-e-app.*| APPOD TS -->|pal-e-docs.*| APIPOD APPOD -->|HTTP internal| APIPOD APIPOD --> PG APIPOD --> OLLAMA EMBED --> OLLAMA EMBED --> PG PG -->|WAL archive| MINIO FORGEJO -->|webhook| WOODPECKER WOODPECKER -->|kaniko| HARBOR ARGOCD -->|sync k8s/| APPOD ARGOCD -->|sync k8s/| APIPOD CLAUDE -->|MCP| APIPOD

    Deployment. Single k3s node with Tailscale Funnels for ingress and TLS — no cert-manager, no Traefik. Each app gets its own namespace. Postgres runs as a CloudNativePG Cluster in the shared postgres namespace. GitOps pipeline: merge to main → Woodpecker CI builds via kaniko → pushes to Harbor → CI commits updated image tag back to repo → ArgoCD syncs k8s manifests from the app repo's k8s/ directory. Ollama runs on GPU (NVIDIA runtime, qwen3-embedding:4b on hostPath volume for persistence) for embedding generation. Embedding worker runs alongside the API pod, processing new/updated blocks and exposing Prometheus metrics on :8001. MinIO stores WAL archives for PITR backup.

    Repos

    Repo Platform Role Status
    pal-e-docs Forgejo FastAPI backend + Postgres (planned rename: pal-e-api) active
    pal-e-docs-mcp Forgejo MCP server — 36 tools (planned rename: pal-e-mcp) active
    pal-e-docs-sdk Forgejo Python SDK (planned rename: pal-e-sdk) active
    pal-e-app Forgejo SvelteKit frontend — board UI, k8s deployed, Woodpecker CI active

    Inbox

    Untriaged TODOs awaiting scoping into plan-pal-e-docs. See convention-todo-lifecycle.

    Query: list_notes(project="pal-e-docs", note_type="todo", status="open") to check for unparented items.

Convention 1
  • Tagging Conventions tagging-conventions

    Tag Categories

    DEPRECATED: This note has been superseded by note-conventions, which contains the canonical note_type enum, status values per type, revised tag taxonomy, and all naming conventions.

    Original Content (archived)

    • Type tags: architecture, sop, convention, plan
    • Project tags: pal-e-platform, pal-e-services, pal-e-docs, basketball-api
    • Domain tags: deployment, monitoring, ci-cd, onboarding, agent
    • Status tags: active (current, should be followed), deprecated (superseded)

    Query Patterns

    • ?tags=sop,active — all current SOPs
    • ?tags=architecture,pal-e-services — service layer architecture
    • ?tags=convention — all project conventions
    • ?tags=deployment,active — current deployment guidance

    Principles

    • Small atomic notes, heavy tagging. Don't create monoliths.
    • Query by tag intersection to find exactly what's needed.
    • Tags are created automatically when used in a note — no pre-registration needed.
Repos 5
  • pal-e-docs
    active
  • pal-e-frontend
    active
  • pal-e-docs-sdk
    active
  • pal-e-docs-mcp
    active
  • pal-e-docs app
    active