• v26.06.03
    CI & Build / Python lint (push) Successful in 2s
    CI & Build / TypeScript typecheck (push) Successful in 32s
    CI & Build / Python tests (push) Successful in 45s
    CI & Build / Build & push image (push) Successful in 14s
    Stable

    bvandeusen released this 2026-06-03 12:51:04 -04:00 | 77 commits to main since this release

    First release under the no-suffix CalVer scheme (rule 47). Covers everything merged to main since v26.06.01.2.

    Features

    • Stored Processes — reusable saved prompts (note_type=process) with create/list/get/update + resolve_process; Knowledge-view editor (PR #55).
    • Dashboard — new /dashboard landing ("what to work on") with / redirect; Knowledge becomes the Browse surface (PR #55).
    • MCP _INSTRUCTIONS hardening — agents now drive task lifecycle (in_progress → log → done + landing dev-logs), keep a Scribe-rules pointer in host memory when a project subscribes to a rulebook, and proactively bootstrap project context at session start (PR #56).

    Changes

    • update_task gains milestone_id=-1 (and project_id=-1) to CLEAR an FK; 0 still leaves unchanged (PR #56).
    • CI now gates + builds main as well as dev; main publishes the immutable :<sha> image, :latest + :<version> are release-only (PRs #56–#57).

    🤖 Generated with Claude Code

    Downloads
  • v26.06.01.2 — Rules consolidation + project rule suppressions
    CI & Build / Python lint (push) Successful in 3s
    CI & Build / TypeScript typecheck (push) Successful in 33s
    CI & Build / Python tests (push) Successful in 48s
    CI & Build / Build & push image (push) Successful in 22s
    Stable

    bvandeusen released this 2026-06-01 08:11:45 -04:00 | 109 commits to main since this release

    Rules subsystem matures from "additive cross-project rulebooks only" into a full first-class system: always-on rulebooks, project-scoped rules, an active-project handshake, and per-project suppression of inherited rules. Two PRs since v26.06.01.1.

    PR #53 — Rules consolidation (4 slices)

    Problem. Rules in Scribe were pull-only; CLAUDE.md and ~/.claude/.../memory/feedback_*.md were session-start auto-loaded. The MCP prompt never insisted that Claude fetch Scribe rules eagerly or codify new rules in Scribe rather than the competing stores. Result: Scribe rules silently lost.

    • S1 + S2 — always_on rulebook flag (commit 658348f)

      • Migration 0058: rulebooks.always_on boolean. FabledSword family rulebook seeded always_on=true at migrate-time.
      • New MCP tool list_always_on_rules() returns the union of standing rules for the user in one round-trip, no active-project needed.
      • _INSTRUCTIONS rewritten: at session start, call list_always_on_rules; treat as binding. Rules codified in Scribe via create_rule, never in CLAUDE.md or feedback files. Those stores are now scoped to user-facts and codebase onboarding respectively.
      • Frontend: badge on always-on rulebooks in RulebookListPane; toggle in RulebookDetailPane header.
    • S3 — Project-scoped rules (commit 43a860c)

      • Migration 0059: rules.project_id (nullable FK, CASCADE), rules.topic_id made nullable, CHECK ck_rule_topic_xor_project enforces exactly-one.
      • New MCP tool create_project_rule(project_id, statement, title?, why?, how_to_apply?) — no rulebook ceremony for project-only rules.
      • get_applicable_rules adds a project_rules field surfaced through get_project, get_task (plan), start_planning.
      • Trash cascade: deleting a project soft-deletes its project-scoped rules along with the rest.
      • REST: POST /api/projects/<id>/rules; frontend Rules-tab gains a "Project rules" section with inline create form.
    • S4 — enter_project handshake (commit c546921)

      • New MCP tool enter_project(project_id) composes get_project + get_applicable_rules + milestone-summary + open-tasks + recent-notes into one round-trip. Intended for once-per-session use when work is project-scoped.
      • _INSTRUCTIONS directs Claude to call it before any project-scoped mutation.
      • No schema change; pure composition over existing services.

    PR #54 — Project rule + topic suppressions (commit 7861607)

    Lets a project mute individual rules or whole topics from rulebooks it subscribes to, without unsubscribing the rulebook. Use case: subscribe to a broad rulebook (e.g. a future "FabledSword design system") but opt out of one or two rules — or a whole topic — that doesn't apply to a non-frontend project.

    • Schema (migration 0060): two new pure m2m association tables — project_rule_suppressions and project_topic_suppressions. PKs on both columns; FKs CASCADE. No deleted_at — pure associations, hard-deleted on project trash.
    • MCP tools (4): suppress_rule_for_project, unsuppress_rule_for_project, suppress_topic_for_project, unsuppress_topic_for_project. All idempotent; verify project + rule/topic ownership. Project-scoped rules (Rule.project_id) are deliberately not suppressible — delete them instead.
    • REST: POST/DELETE /api/projects/<pid>/suppressions/{rules,topics}/<id>.
    • Service projection: get_applicable_rules runs two extra queries and emits suppressed_rules + suppressed_topics as detail objects (id + title + rulebook context, not bare IDs — so the UI doesn't need a follow-up lookup). The main rules list is filtered SQL-side so truncated reflects post-suppression count. Rules now carry topic_id + rulebook_id columns for the UI affordance.
    • Propagation: suppressed_rules and suppressed_topics surface through get_project, enter_project, get_task (plan), and start_planning — Claude sees what's muted from any standard handshake.
    • Frontend: "× skip" affordance reveals on hover for each rule row and each topic header in ProjectRulesTab.vue; collapsed "Suppressed (N)" section at the bottom with per-item "↻ re-enable" buttons. groupByRulebookAndTopic refactored to id-bearing structs.
    • Trash cascade: project deletion hard-deletes the project's suppression rows so a later restore starts fresh.

    Migrations

    • 0058rulebooks.always_on boolean, default false; seeds FabledSword family to true.
    • 0059rules.project_id (FK CASCADE), rules.topic_id nullable, CHECK ck_rule_topic_xor_project. Down-migration requires migrating/deleting project-scoped rules first.
    • 0060project_rule_suppressions and project_topic_suppressions association tables.

    All three run automatically on first boot.

    Verification

    • CI green on c546921 (S4 — after rebuild; original run flaked on npm-cache action), 43a860c (S3), 658348f (S1+S2), and 7861607 (suppressions).
    • Migrations 0058/0059/0060 applied cleanly to the dev environment; operator confirmed the suppression UI and MCP tools work end-to-end on dev (migration in dev worked, 2026-06-01).
    • Tests cover: list_always_on_rules, update_rulebook(always_on=…), create_project_rule (required-fields + title-derivation + explicit-title), enter_project composed shape + 404, get_applicable_rules with project_rules + suppressed_rules + suppressed_topics, project-cascade trash hitting 7 tables (was 4 before S3, 5 after S3, 7 with this release).

    Deploy notes

    • Three migrations run automatically on container start.
    • After deploy, MCP reconnects pick up the new _INSTRUCTIONS; on the next project-scoped turn Claude should call list_always_on_rules and enter_project. Eyeball the tool-call transcript on first use.
    • No content migration: rule-shaped entries in CLAUDE.md and feedback memory files stay where they are. The prompt reroutes NEW rules to Scribe going forward.

    What this release does NOT do

    • No supersede semantics — a project rule can't yet replace a rulebook rule for one project (only mute it). Pure suppression covers the stated use case; replacement can land later.
    • No write-scope enforcement on bearer tokens — mutating tools still ignore the read/write scope on api_keys. M1 work, tracked separately.
    • No share-aware filtering on list_trash / restore / purge — same M1 carve-out.
    Downloads
  • v26.06.01.1 — MCP plan-prompt tune + Flutter docs removal
    CI & Build / Python lint (push) Successful in 3s
    CI & Build / TypeScript typecheck (push) Successful in 33s
    CI & Build / Python tests (push) Successful in 45s
    CI & Build / Build & push image (push) Successful in 24s
    Stable

    bvandeusen released this 2026-06-01 00:13:58 -04:00 | 115 commits to main since this release

    Small follow-up release on top of v26.05.30.1. Two commits, docs/prompt only — no schema, no behavior changes to running services.

    MCP plan-instruction tightening

    The Plans paragraph in the MCP server's _INSTRUCTIONS previously ended with a quiet footer ("not in local .md files") that lost to the much louder superpowers brainstorming / writing-plans skill flow, whose terminal steps save to docs/superpowers/specs/*.md and docs/superpowers/plans/*.md. Reordered so start_planning is named as the FIRST action when beginning non-trivial work, with an explicit override of those .md paths and coverage extended to specs as well as plans (matches rulebook rule 27).

    Effect: when Claude reconnects after the new image deploys, it will reach for start_planning before any markdown plan file by default.

    Flutter companion-app docs removed

    The Flutter app no longer adds value over web/PWA access. In-repo surface stripped:

    • deleted docs/android-app.md
    • dropped Android App row from README docs table and from the README feature paragraph
    • dropped the two Flutter-related roadmap bullets from docs/features.md
    • removed the "Flutter app port" subsection from docs/design-system.md

    The standalone fabled_app repo is untouched here; archival of that repo is a separate operation on that repo.

    Deploy notes

    No migration. No data changes. Pull the new :latest image; restart. Done.

    Verification

    CI green on commit fd20b67 (typecheck / lint / tests / image build all ) before merge.

    Downloads
  • bvandeusen released this 2026-05-29 22:51:28 -04:00 | 118 commits to main since this release

    118 commits since v26.05.13.1. Headline: the MCP-first pivot lands in production, and three new feature arcs land on top of it.

    MCP-first pivot (Phases 7-10)

    Scribe is now a pure data store. Claude via the in-app /mcp endpoint is the only LLM. ~30,000 lines deleted across backend, frontend, and infrastructure.

    • New endpoint/mcp mounted alongside /api/* via FastMCP's streamable-HTTP transport. ASGI middleware authenticates via bearer tokens from the existing api_keys table; rejects with 401 before FastMCP sees the request. Per-request user_id ContextVar for tool handlers.
    • 34 MCP tools covering notes, tasks, projects, milestones, events, typed entities (person/place/list), search.
    • Embeddings: Ollama → fastembed. In-process ONNX (BAAI/bge-small-en-v1.5, 384d). First boot downloads the model to /data/fastembed-cache once. Migration 0052 cleared the existing 768d note_embeddings; startup re-embeds existing notes.
    • Phase 7 — frontend strip: removed /chat, /journal, /workspace/:projectId, voice overlay, the chat-style home view. Surfaces that survived: /knowledge, /notes, /tasks, /projects, /calendar, /graph, /settings, /rules, /trash.
    • Phase 8 — backend deletion: /api/chat/*, /api/journal/*, /api/voice/*, /api/push/*, /api/quick-capture, /api/fable-mcp/* and their backing services. The standalone fable-mcp stdio wheel removed.
    • Phase 9 (migration 0053): dropped conversations, messages, generation_tool_log, moments + junction tables, moment_embeddings, pending_curator_actions, push_subscriptions, weather_cache, rss_item_embeddings.
    • Phase 10 (migration 0054): dropped image_cache; removed Ollama service config and frontend orphans.
    • Rebrand: "Scribe" user-facing; fabledassistant internal package name kept stable.
    • Settings → MCP Access tab: create write or read-only tokens (fmcp_<32-byte-base64url> format), shown once, sent as Authorization: Bearer <token>. Configurable MCP server name + scope.

    Rulebook system (May 2026 milestone)

    Cross-project standards: Rulebook → Topic → Rule, sibling to Project, with project subscriptions.

    • Migration 0055rulebooks, rulebook_topics, rules, project_rulebook_subscriptions. Each rule is a statement + why + how_to_apply triple.
    • 16 MCP tools for rulebook/topic/rule CRUD plus subscribe/unsubscribe. get_project(id) returns applicable_rules + subscribed_rulebooks (capped at 50).
    • /rules three-pane UI (rulebooks → topics → rules) with rule editor; project Rules tab grouped by rulebook → topic.
    • Seeded: the external FabledRulebook (12 topic .md files) imported into the "FabledSword family" rulebook — 12 topics, 43 rules. Fabled Assistant project subscribed.

    Plans-as-tasks (May 2026 milestone)

    Plans are first-class without a new entity: a Task with task_kind="plan".

    • Migration 0056 — additive notes.task_kind column (work | plan, default work, CHECK-constrained).
    • MCP: start_planning(project_id, title) seeds a plan template + returns applicable_rules. get_task surfaces applicable_rules for plan-tasks only.
    • UI: "Start planning" button on /projects/:id; Applicable Rules panel in the task editor; Knowledge view's Plans facet + plan badge.
    • Rulebook hook: rule 27 reframed ("Planning artifacts live in Scribe, never in git"); ritual rule 45 added ("Build plans via start_planning, not .md") — the rulebook itself points planning at Scribe.

    Soft-delete / Trash / Retention (May 2026 milestone)

    Deletes become recoverable.

    • Migration 0057deleted_at TIMESTAMPTZ NULL + deleted_batch_id TEXT NULL (+ ix_<t>_deleted_at) on 7 tables (notes, events, projects, milestones, rulebooks, rulebook_topics, rules). SoftDeleteMixin in models/base.py.
    • Cascade: project → milestones + tasks/notes; rulebook → topics → rules; restore brings the whole batch back together (reverses the prior FK SET-NULL behavior so a restored project comes back with its children).
    • MCP surface: existing delete_* flipped to soft (return {deleted_batch_id, message}); added delete_task, delete_project, delete_milestone (closes the CRUD-asymmetry gap); added list_trash, restore, purge_trash (confirmed-gated hard delete).
    • REST: /api/trash (GET list, POST /<batch>/restore, DELETE /<batch> for purge). All entity DELETE handlers flipped to call trash.delete.
    • Retention: trash_retention_days setting (default 90, 0 = keep forever). Daily 03:30 UTC APScheduler purge cron.
    • UI: /trash view (batches summary, Restore, Delete-permanently, Empty trash, empty state) + Settings → General → "Trash retention" field. g+x keyboard shortcut.

    MCP transport — stateless HTTP

    mcp/server.py sets stateless_http=True on the FastMCP instance. The default stateful streamable-HTTP transport handed Claude Code an Mcp-Session-Id; after a container redeploy the server rejected the now-unknown id with 404, and Claude Code doesn't re-initialize on 404 (issue #60949), so the connection stranded until manual /mcp retry. Stateless makes every request self-contained (bearer-auth only); the next message after a redeploy reconnects automatically — no manual intervention.

    Pre-pivot work in the diff (removed by the pivot)

    The diff includes substantial work that landed on dev before the MCP pivot and was subsequently removed by it: curator phases C1-C5 (additive-only curator tools, authority routing, Needs Review panel), journal closeout + captures panel, TTS migration (kokoro → piper) + voice admin UI, diagnostics for crash investigation, model-bench tooling (bench_ollama.py), user-controlled think mode. These commits remain in history but the surfaces they touched no longer exist in production.

    Deploy notes

    • Destructive migrations. 0052/0053/0054 drop large chunks of pre-pivot data (chat conversations, journal moments, voice settings, weather cache, image cache, push subscriptions, rss item embeddings). Back up before deploy if anything in those tables matters.
    • Migration sequence: 0044 → 0057 run automatically on first boot.
    • Compose stack reduces to two services: app + db. No Ollama, no Redis, no background worker.
    • MCP access: create a bearer token via Settings → MCP Access after deploy. Configure in Claude Code via claude mcp add --transport http scribe <BASE_URL>/mcp with the token in the bearer header.
    • First-boot fastembed download: ~150 MB ONNX model goes to /data/fastembed-cache; persists across container restarts.

    Verification

    • All backend pytest tests pass (CI green on commit 2f577fe).
    • /trash UI + Settings retention field device-checked by operator 2026-05-29.
    • MCP reconnect verified after the stateless-HTTP fix (Portainer redeploy → next message reconnected without manual /mcp retry).
    • Trash dogfood (via MCP this session): throwaway project cascade-delete → list_trashrestore → re-delete → purge_trash round-trip; single delete_tasklist_trashrestore round-trip. Both passed.

    Known follow-ups

    Captured as plan-task 183 on project 3, milestone M1 "Authorization & share-correctness (May 2026)":

    • Share-debt in soft-delete: list_trash / restore / purge ignore user_id (cross-user leak under multi-user); retention reads user 1's setting globally rather than a per-instance admin value; collaborator-delete semantics undefined.
    • Cosmetic papercuts: purge_trash rowcount is off-by-one when the batch holds parent + children whose DB-level FK has ON DELETE CASCADE (end state correct, count off by one); trash items show type: "note" for is_task=True tasks.
    • MCP write-scope enforcement — mutating tools currently ignore the read/write token scope.

    These feed the M1 foundation work. See project 3 milestones for the full M1→M4 roadmap (authz → audit → review surface → retrieval quality).

    Downloads
  • bvandeusen released this 2026-05-13 19:28:34 -04:00 | 237 commits to main since this release

    Forty-five commits across four feature groups: the journal closeout work from earlier in the day, the LLM tool-use fixes from the 2026-05-08 prod conversation investigation, and two new fully-specced projects (task-as-durable-record and note version pinning).

    Project A — Task as durable record

    The task body becomes the canonical record of what happened, not just what was initially asked.

    • Schema: new description field on Note (user-stated goal). New consolidated_at timestamp. Migration 0044 copies existing body content into description for existing tasks.
    • Body semantics shift for tasks: body is now LLM-maintained, written by a background consolidation pipeline from the task's accumulated work logs. Description is user-owned and never edited by the pipeline. Embeddings continue to index title + body, so semantic search now hits the work-done summary rather than the initial goal text — tasks become discoverable by what was actually done.
    • Consolidation pipeline: debounced after 3 new log_work entries, OR fired immediately on task status transition to done/cancelled. Uses background_model so the chat model's KV cache stays warm. Per-task asyncio lock prevents concurrent runs. Failures leave body untouched; next trigger retries.
    • Tool layer enforcement: create_note tool drops the body argument when status is set (i.e., creating a task); update_note tool rejects body writes on tasks with an error that nudges toward log_work. HTTP routes still accept body so the frontend editor works pre-consolidation.
    • Manual control: POST /api/tasks/:id/consolidate endpoint + auto_consolidate_tasks user setting (General tab toggle). Manual endpoint bypasses the toggle.
    • Frontend: "Goal" textarea added above the body in TaskEditorView; TaskViewerView renders the Goal block subordinate to the body. TipTap body editor is gated to read-only once consolidated_at is set; a "Re-consolidate" button calls the manual endpoint.

    Project B — Note version pinning

    The existing rolling 50-version snapshot system grows two retention tiers above the baseline.

    • Schema: pin_kind (NULL = rolling, 'auto', 'manual') and pin_label columns on note_versions. Migration 0045 leaves all existing rows as rolling.
    • Three FIFO buckets:
      • Rolling autosaves: cap 50 per note (unchanged from before, but the prune query now filters pin_kind IS NULL so pinned rows are not counted).
      • Auto-pinned: cap 25 per note. A daily 03:00 UTC APScheduler job walks each note's version chain and pins versions that are followed by another version ≥2 days later (or the latest version if it's ≥2 days old with no successor). Auto-label is generated from the stability window.
      • Manually declared: unlimited. Never pruned.
    • API: POST /api/notes/:id/versions/:vid/pin accepts an optional label; DELETE /api/notes/:id/versions/:vid/pin clears pin_kind/pin_label (downgrades back to rolling — does NOT delete the row).
    • Backup export/import roundtrip: both new fields persist through /api/export and restore. .get() on import preserves backward compatibility with older backup files.
    • Frontend: HistoryPanel.vue extended with kind-aware badges (filled circle = manual pin, half-filled = auto-pin), label rendering, and a pin/unpin/edit-label control row above the diff view.

    LLM tool-use fixes (2026-05-08 investigation)

    Five fixes shipped together because they share a root failure mode in colloquial project/task references.

    • search_projects_tool now returns success: True so the dispatcher doesn't mislabel valid results as errors.
    • list_notes strips filler type-nouns (task, tasks, note, notes, project, projects) from q before the ILIKE AND-filter; search_notes tool description nudges the model to use type/project parameters instead of stuffing those words into query.
    • New shared score_project_match helper unifies ranking between search_projects_tool and resolve_project. Tiered scoring (1.0 exact / 0.85 substring either way / 0.70 query-in-description / SequenceMatcher fallback against the title). score_project_match also strips project / projects filler from queries so "famous supply project" substring-matches "Famous-Supply Work topics" at 0.85 instead of 0.228.
    • record_moment tool description tightens task_titles / note_titles to require values from a prior search_notes call in the same turn.
    • Journal calibration prompt + chat tool_lines static block both gain a "search-first when user references existing work" clause.

    Journal closeout

    Nightly per-user closeout extracts profile observations from the day's journal.

    • services/journal_closeout.py runs once per user per day at the user's day_rollover_hour. Reads yesterday's /journal conversation, filters out daily_prep-metadata messages at the SQL layer (hard data-exclusion guard against the briefing-era feedback-loop pathology), and asks the background model for 2–5 bullet observations. Appends to observations_raw.
    • closeout_enabled config key (default ON) wired to Settings → Profile.
    • GET /api/profile/observations + collapsible "Recent observations (14)" panel for transparency.
    • Catch-up on startup if today's day_rollover_hour already passed and yesterday's entry is missing.

    Deploy notes

    • Two schema migrations (0044, 0045) — both additive and safe.
    • No frontend build required beyond standard CI.
    • The auto-pin scan first fires at 03:00 UTC after deploy. Existing notes won't see auto-pinned versions until then (or earlier if you manually pin via the new UI).
    • The consolidation pipeline only fires for tasks that get new log_work entries OR status transitions after deploy. Existing tasks retain their current body until they accumulate enough new logs to trigger consolidation.

    Verification

    • All backend pytest tests pass (CI green on PR #50)
    • Migrations apply cleanly on the dev environment
    • End-to-end on the dev environment: task description roundtrip, consolidation trigger after 3 work logs, body editor gating, version pinning + auto-pin scan, journal closeout settings UI

    Known follow-ups

    • Project D (fable-dev #167) — journal prompt simplification + WRITE_FALSE_CLAIM reconciler + tool description tightening + model-swap safety check. Recommended next; high priority. M effort.
    • Project E (fable-dev #168) — pipeline decoupling to enable journal model swap. Deferred.
    Downloads
  • bvandeusen released this 2026-05-12 21:32:01 -04:00 | 260 commits to main since this release

    Two threads — one rebuilds a half-deleted feature, the other patches an LLM-misuse failure mode caught in prod.

    Journal closeout for profile observations

    The briefing tear-down on 2026-04-25 deleted the closeout job but left the read path: build_profile_context still injects learned_summary into journal-prep and chat system prompts. Result was a stale frozen summary plus no path to refresh it. This release reinstates the write side with a hard guard against the briefing-era feedback loop.

    • services/journal_closeout.py (new) — runs once per user per day at the configured day_rollover_hour. Reads yesterday's /journal conversation, filters out messages with msg_metadata.kind="daily_prep" at the SQL layer, asks the background LLM for 2–5 bullet observations focused on user-side patterns (NOT structured fields like name/job/expertise — those are still the update_profile tool's lane), and appends to observations_raw via the existing append_observations.
    • Hard data-exclusion guard — the daily-prep block is structurally invisible to the closeout LLM. The previous briefing-era closeout sent all messages with role labels and relied on the prompt to say "user revealed"; that's the shape that caused the feedback loop. Instruction-only role separation isn't reliable on local Ollama models against long transcripts.
    • background_model for the LLM call — keeps chat-model KV cache warm.
    • closeout_enabled config key (default ON), wired to a Settings → Profile toggle. New GET /api/profile/observations endpoint and collapsible "Recent observations (14)" panel for transparency.
    • Catch-up on startup — if today's day_rollover_hour already passed and observations_raw has no entry for yesterday, runs once.

    LLM tool-use fixes from 2026-05-08 journal session

    Prod conversation #282 exposed three concrete code bugs and two behavioral patterns. All five fixes shipped together because they share a root failure mode: when the user names a project/task colloquially, the model fails to find it, fails to trust valid matches, or skips searching entirely and fabricates a moment.

    • search_projects returns success: True — the dispatcher reads this key to set status on the conversation history entry. Missing key → result labeled status: error even when data was returned → model second-guessed valid matches. One-line fix.
    • list_notes strips type-nouns from qtask, tasks, note, notes, project, projects are filtered out of the keyword-search tokens before the AND-ILIKE filter is built. Closed the case where "find the sebring secondary task" returned 7 unrelated tasks because task #95's title and body don't contain the word "task". search_notes tool description also tells the LLM to use type / project parameters instead of stuffing those words into query.
    • score_project_match shared helper (new in tools/_helpers.py) — tiered scoring (1.0 exact title / 0.85 substring either way / 0.70 query-in-description / SequenceMatcher-against-title fallback). Strips project / projects filler from the query so "famous supply project" substring-matches "Famous-Supply Work topics" at 0.85 instead of 0.228 SequenceMatcher floor. Both search_projects_tool and resolve_project use the helper — single source of truth, no drift between the LLM-facing surface and the create-task resolver.
    • record_moment descriptiontask_titles / note_titles must come from a prior search_notes call in the same turn. Prevents the project-name-as-task-title misuse that triggered the original investigation.
    • Search-first heuristic added to both JOURNAL_CALIBRATION and the chat tool_lines static block: when the user references existing work, call search_notes first; create or record only after no match surfaces and the user confirms.

    Verification

    • 178+ backend pytest tests pass (CI)
    • tests/test_journal_closeout.py — filter, run_for_user happy + skip paths, scheduler register/disable, catch-up
    • tests/test_tool_use_fixes.py_strip_type_nouns, score_project_match tiers, search_projects_tool ranks substring above SequenceMatcher, resolve_project finds colloquial match
    • Tool-use fixes verified end-to-end against fable-dev MCP: search_projects shows status: success, score 1.0 for exact-match substring; search_notes correctly split query="tool-use" / type="task" and returned the right task
    • Search-first heuristic verified in journal UI

    Deploy notes

    • No schema migrations; deploy-only
    • After deploy, optionally click Reset Learned Data in Settings → Profile to wipe the stale briefing-era learned_summary. The new closeout will rebuild from clean observations starting tomorrow's prep cycle.

    Out of scope (follow-ups)

    • Manual verification of the closeout toggle / observations panel (PR test plan checkbox left unchecked — covered separately)
    • Hybrid BM25 + embedding search (replaces semantic-only)
    • "Did you mean X?" UI surface for project matches in the 0.40–0.85 score band
    Downloads
  • bvandeusen released this 2026-04-29 22:08:38 -04:00 | 284 commits to main since this release

    Highlights

    EventSlideOver recurrence UI

    The calendar event modal now exposes the existing Event.recurrence RRULE column. A "Repeat" select offers None / Daily / Weekly / Monthly / Yearly presets that map to/from canonical RRULE strings.

    CalDAV-imported rules with extra parts (e.g. FREQ=WEEKLY;BYDAY=MO,WE,FR) are preserved — they surface as a disabled "Custom" option with the raw rule shown read-only, so they remain visible in the UI without us silently destroying them.

    Journal weather wiring fixes

    Closes two real gaps that caused empty weather sites in the journal panel:

    1. Saving locations now triggers a fetch. PUT /api/journal/config kicks off a background refresh_location_cache for every saved location with valid lat/lon. Previously, newly-entered sites had no cache row (or a stale one) until the user manually clicked the panel's refresh button.

    2. Orphaned cache rows are filtered. Migration 0040 deleted the briefing-era briefing_config setting but left weather_cache rows untouched. Those briefing-era rows showed up as ghost tabs in the new journal weather panel. get_cached_weather_rows now accepts a valid_keys filter; the journal weather routes and prep pipeline pass currently-configured location keys, so only cache rows that match an actively-configured location surface in the UI or the daily prep.

    Commits

    • 36cd08c feat(events): expose recurrence presets in EventSlideOver
    • c33cab7 fix(journal): wire weather refresh on config save; drop orphaned cache rows

    Verification

    • 153 backend pytest tests pass
    • Frontend vue-tsc --noEmit clean
    • CI green on dev before merge
    Downloads
  • bvandeusen released this 2026-04-29 16:04:58 -04:00 | 287 commits to main since this release

    Closes the 4/29 dentist-appointment investigation. Two complementary structural fixes plus the held-back EventSlideOver UX refactor.

    Event-store data integrity

    #160 — Duration replaces end_dt at the storage layer. Schema migration 0043 adds duration_minutes (with >= 0 CHECK constraint) and drops end_dt. Wire format unchanged — end_dt is now a derived @property. End-before-start is structurally inexpressible. The corrupt prod row that triggered this work backfills to duration_minutes=NULL automatically.

    Behavioral upgrade: moving an event's start now slides the effective end forward (preserving duration), instead of corrupting or hard-rejecting.

    #161 — Tool layer disambiguates multi-match. When update_event or delete_event get a query that matches 2+ events, the tool returns success: false with a candidates array. The model picks one by passing event_id on the next call. Pre-fix: both tools silently took matches[0], which is exactly how the dentist event got applied to the wrong row.

    Calendar UX

    EventSlideOver redesigned as a centered modal. Auto-save on close (X / Esc / backdrop click / Enter in form). Trash icon in the header (edit mode only). Invalid form in edit mode → toast + discard; in create mode → silent discard. Removes the right-edge floor band where the destructive Delete button was visually invisible.

    Reverted in-flight

    94b169f (A+B hotfix) is reverted by b7e7073 — superseded by #160. The hotfix made bad data filterable; #160 makes it inexpressible.


    46 calendar/events tests pass. Ruff clean. Schema migration runs automatically on container restart. Tag push triggers the build.

    Downloads
  • bvandeusen released this 2026-04-29 12:11:12 -04:00 | 293 commits to main since this release

    Hotfix-style follow-up to v26.04.29.1.

    Anti-placeholder rule on create_event

    The model was creating events with stand-in titles ("Appointment") and "details TBD" descriptions when the user mentioned a future event without concrete specifics, instead of asking for the missing pieces first. The placeholder title then never got updated when the user supplied real details.

    The create_event tool description now names the failure mode explicitly and instructs: record a moment, ask for the missing pieces, and call create_event once with the actual title, time, and location. Pairs with the capture-first persona changes from v26.04.29.1.

    Pure prompt change. 18 calendar-tool tests pass; ruff clean. No schema or migration.

    Downloads
  • bvandeusen released this 2026-04-29 10:38:03 -04:00 | 295 commits to main since this release

    Calendar tool — date/time handling made model-agnostic

    A "schedule for this Friday at 8am" request landed on Thursday. Two distinct fault lines fixed:

    • Split start into start_date + start_time — a YYYY-MM-DD string carries no TZ for the model to mis-tag, so the calendar day cannot drift across the local→UTC boundary. Strict regex rejects TZ suffixes on either field. Legacy combined start/end kept as backcompat.
    • Today's weekday anchored in system prompts; expected_weekday validator on create_event/update_event. Prompts now read "Today is Wednesday, 2026-04-29" instead of forcing the model to derive the weekday. The validator rejects mismatches with a self-correcting error and respects the user's local TZ (Tokyo Friday 23:00 stays Friday).

    Journal — three rough edges from real usage

    • Persona tuning — capture-first; helpdesk-style follow-ups banned; emoji ban; first-person moment phrasing required (no more "The user mentioned…").
    • record_moment data hygiene — server-side keyword-overlap guard drops moment→task links that share no meaningful keyword; placeholder filter drops generic single-word place names (work, home, office).
    • Prep filtering — tasks bucketed into due-today / upcoming / overdue (overdue items surface with staleness duration, not as "due today"); events use a TZ-aware day window + proximity filter, fixing recurring events surfacing 5 months out.

    fable-mcp 0.3.0

    Briefing tools removed, journal tools shipped. Reinstall with pipx install --force after deploy.


    53 backend tests pass; ruff clean. No schema or migration changes — deploy-only.

    Downloads