dfcf4df2e99718d9def3692ccdb3ff270a948784
328
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7a81b7333e |
feat(scribe): snippet merge UI — multi-select merge, repeatable locations
CI & Build / Python lint (push) Successful in 4s
CI & Build / integration (push) Successful in 29s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 53s
CI & Build / Build & push image (push) Successful in 1m2s
Step 3 of the snippet-merge milestone (#231): the human surfaces for merge + multi-location, at v1 quality. Frontend: - SnippetListView: a Select mode (checkbox on each card) → a sticky action bar → a merge modal that lets you pick which selected snippet is the canonical (the others fold in and go to trash). Accent border on selected cards, Moss action buttons (Hybrid rule). - SnippetEditorView: the single Location fieldset becomes a repeatable locations list (add/remove rows), so editing a merged snippet no longer collapses its call sites — no data loss. Sends `locations`. - SnippetDetailView: renders every location (Location vs Locations label). - api/snippets.ts: SnippetLocation type, `locations` on fields/input, mergeSnippets(). Backend (editor enablement): - create_snippet service + POST route accept an optional `locations` list; PATCH route forwards `locations` — so the editor's location list works uniformly on create and edit. Single repo/path/symbol remain the one-location shorthand (MCP create contract unchanged). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pa2EsuB54BuWQ8GfJq9c7t |
||
|
|
85625de394 |
feat(scribe): merge_snippets — unify found one-off snippets into one canonical
CI & Build / Python lint (push) Successful in 4s
CI & Build / integration (push) Successful in 38s
CI & Build / Build & push image (push) Successful in 1m10s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 59s
Step 2 of the snippet-merge milestone (#231). The dedup gate only PREVENTS new near-duplicates; merge is the CURE for the ones already scattered. - services/snippets.py: merge_snippets(user_id, target_id, source_ids) — keep the target's scalar fields (name/when_to_use/signature/language/ code), union the sources' locations + extra tags onto it (so the survivor carries every call site as a location), trash the sources (recoverable), re-embed the survivor. Pure merge_snippet_fields() factored out for unit testing. Returns (survivor_note, merged_ids). - mcp/tools/snippets.py: merge_snippets(target_id, source_ids) tool (5th), and a create_snippet dedup-path nudge toward merge over a forced copy. - routes/snippets.py: POST /api/snippets/<id>/merge {source_ids} — share- aware (can_write target + every source, rule #78) with a same-owner guard (cross-owner merge is out of scope). - plugin reusing-code skill + MCP _INSTRUCTIONS: point found-duplicates at merge as the cure (rule #119 surfaces, not a Scribe rule). plugin.json 0.1.13 -> 0.1.14 in the same change (the #1040 marketplace-ship lesson). - Tests: pure merge-helper union/dedup; MCP tool (requires a source, survivor+merged_ids, not-found); route handler + 5-tool registration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pa2EsuB54BuWQ8GfJq9c7t |
||
|
|
eb400a521b |
feat(scribe): multi-location snippet body convention (backward compatible)
CI & Build / Python lint (push) Successful in 3s
CI & Build / integration (push) Successful in 29s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 51s
CI & Build / Build & push image (push) Successful in 1m7s
Step 1 of the snippet-merge milestone (#231). A snippet that unifies N found one-offs carries N locations (one per call site), so `location` becomes a list. Ships on the body-convention — no migration, swappable to the deferred `data` JSONB later (as #227 decision 5 anticipated). - compose_body: renders `**Location:**` for a single location, a `**Locations:**` bullet list for several. Accepts a `locations` list; the single repo/path/symbol params remain as a one-location shorthand (create path + existing callers/tests unchanged). - parse_snippet_fields: reads BOTH the new `**Locations:**` list block AND the legacy single `**Location:**` line (tolerant, never raises); returns a `locations` list and mirrors the first into repo/path/symbol for back-compat (rule #33). - update_snippet: gains a `locations` param — replaces the whole set; else a legacy single triple overlays onto the first location; else kept. - Tests: multi-location round-trip, singular-vs-plural label, legacy single-line parse, normalize dedup/empty. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pa2EsuB54BuWQ8GfJq9c7t |
||
|
|
d257c0fd67 |
feat(scribe): snippet management UI + REST routes; embed snippets on create
CI & Build / Python lint (push) Successful in 3s
CI & Build / integration (push) Successful in 23s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 1m4s
Step 6 of the Drafter recall milestone (#227): a human-facing surface for the reusable-code snippets that agents record via MCP, plus the REST API behind it. Backend embeds snippets inline on create/update so they're recallable immediately, not only after a restart. Backend: - routes/snippets.py: GET/POST /api/snippets, GET/PATCH/DELETE /api/snippets/<id>. Share-aware per rule #78 (get_note_for_user + can_write_note), writes performed as the owner; list owner-scoped — mirrors routes/notes.py. Registered in app.py. - services/snippets.py: embed on create/update via a _embed_snippet fire-and-forget helper, covering BOTH the MCP tool and the REST route by construction. A snippet's value is immediate recall, so it can't wait for the startup-only backfill (see issue: MCP create path doesn't embed inline for notes/tasks generally). - tests/test_routes_snippets.py: structural registration + handler/service contract + PATCH-field ↔ update_snippet-kwarg parity (rule #33). Frontend (Vue 3 + TS): - api/snippets.ts: typed client, modeled on api/systems.ts. - views: SnippetListView (search, skeleton/empty/error states), SnippetDetailView (read + copy-to-clipboard, ConfirmDialog delete), SnippetEditorView (create/edit all fields, Ctrl/Cmd+S, Esc, autofocus, validation). v1 quality per rules #24/#27. - router: /snippets, /snippets/new, /snippets/:id, /snippets/:id/edit. - NoteType union widened to include 'snippet'; Snippets nav link added to AppHeader (desktop pill bar + mobile menu). - Design system: Moss --color-action-primary for action buttons, accent --color-primary reserved for tags/brand (Hybrid rule); focus rings; JetBrains Mono for code/name/signature/location. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pa2EsuB54BuWQ8GfJq9c7t |
||
|
|
1942913366 |
feat(scribe): add snippet recall — note_type='snippet' service + MCP tools
CI & Build / integration (push) Successful in 20s
CI & Build / Python tests (push) Successful in 50s
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Build & push image (push) Successful in 1m7s
Record reusable functions/components once so they surface via the existing semantic search + title-first auto-inject, instead of re-solving as one-offs. A snippet is a Note with note_type='snippet' (no schema change): note_type is free-text, and semantic_search_notes never filters by type, so snippets join the recall/auto-inject pool the moment they're embedded. Structured fields (name/language/signature/location/when_to_use/code) are stored via a body convention — title = "name — when to use" (what auto-inject surfaces), language + "snippet" as tags, templated markdown body — keeping storage swappable later without changing the tool/UI contract. - services/snippets.py: compose/parse helpers + create/get/list/update wrappers over notes_svc (dedup + System association reused). - mcp/tools/snippets.py: list_snippets / create_snippet / get_snippet / update_snippet, registered in tools/__init__.py. - unit tests for the serialize/parse round-trip and the MCP tool surface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pa2EsuB54BuWQ8GfJq9c7t |
||
|
|
b49efdcb11 |
refactor(scribe): retire calendar/events + person/place/list entities (backend)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 11s
CI & Build / integration (push) Successful in 30s
CI & Build / Python tests (push) Failing after 31s
CI & Build / Build & push image (push) Has been skipped
Narrow Scribe to a Claude-Code work system-of-record (milestone #194, decision note #1759). Wholesale removal per rule #22 — backend + schema half. Calendar/events + CalDAV: delete models/event, services/{events,caldav, caldav_sync}, routes/events, mcp/tools/events; strip event branches from backup (bump v3->v4), dashboard (upcoming_events), trash, recent, and the mcp server read-only allowlist + instructions. Typed entities (person/place/list): delete mcp/tools/entities; drop the notes.metadata (entity_meta) column from model/service/routes and the knowledge browse service. note_type STAYS — it also marks 'process' notes. Scheduler: event_scheduler -> recurrence_scheduler, keeping only the recurring-task spawn job (drops event reminders + CalDAV sync). Schema: migration 0069 drops the events table + notes.metadata column + orphan caldav settings rows (faithful downgrade recreates them). KEEP: recurrence.py (task recurrence), notifications task reminders, graph view, and every work surface. Frontend + plugin/docs true-up follow next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPtbSzA4JLMAKgFZ8VTg7Q |
||
|
|
8126db3203 |
feat(plugin): knowledge auto-inject (Path A) — title-first per-turn awareness
CI & Build / integration (push) Successful in 12s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 21s
CI & Build / Build & push image (push) Successful in 55s
New UserPromptSubmit hook (scribe_autoinject.sh) + GET /api/plugin/retrieve that surface the TITLES (never bodies) of the few notes clearing four anti-bloat gates: a per-user confidence threshold (stricter than pull search), a margin gate, per-session dedup (exclude_ids), and a top-k ceiling. Each retrieval is logged to retrieval_logs as source=auto_inject so the threshold can be tuned from data. Per-user config (enable / threshold / top-k) is DB-backed via /api/settings with a Settings UI card; defaults enabled, threshold 0.55, top-k 3 (conservative — tune once auto_inject telemetry accrues). Scribe: project 2, milestone 93, task 1033. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xz4j1H7pjYSjKsEpgcNH5E |
||
|
|
807f478cac |
feat(search): retrieval telemetry — log every semantic retrieval
CI & Build / Python lint (push) Successful in 2s
CI & Build / integration (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 44s
CI & Build / Build & push image (push) Successful in 58s
Add retrieval_logs (migration 0068) + services/retrieval_telemetry with a fire-and-forget record_retrieval(), wired into the MCP search tool (source=mcp_search) and the REST search route (source=rest_search). Captures query, effective params, and the per-result score distribution so KB-injection thresholds can be tuned from data rather than guessed. Scribe: project 2, milestone 93, task 1032. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xz4j1H7pjYSjKsEpgcNH5E |
||
|
|
513019786e |
feat(search): pgvector substrate — vector(384) + HNSW for semantic search
Move semantic_search_notes off the full-table Python cosine scan onto a native pgvector column: indexed ORDER BY embedding <=> :q LIMIT k (HNSW, cosine). Migration 0067 enables the extension, converts the JSONB embedding column to vector(384) (stale-dim rows dropped and regenerated by the startup backfill), and builds the HNSW cosine index. Postgres image moves postgres:16-alpine -> pgvector/pgvector:pg17 across prod, quickstart, and CI. Scribe: project 2, milestone 93, task 1031. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xz4j1H7pjYSjKsEpgcNH5E |
||
|
|
e6c89f6b88 |
fix(db): await AsyncConnection.execution_options in run_maintenance
execution_options() is a coroutine on AsyncConnection and must be awaited; the un-awaited call returned a coroutine, so exec_driver_sql() blew up with AttributeError and every table's VACUUM was skipped (Run-now reported 0/6). A prior change had wrongly dropped the await. Fix it and make the test mock execution_options async so this call shape is actually exercised. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
96079d5b77 |
feat(db): table-health readout — per-table bloat metrics in admin card
You can't decide what to maintain without seeing what's bloating. Adds a read-only health panel driven by Postgres' own statistics views. - services/db_maintenance.py: get_table_health() queries pg_stat_user_tables + pg_total_relation_size + pg_database_size — per-table size, live/dead tuples, dead-tuple ratio (the bloat signal), and last (auto)vacuum/(auto)analyze. - routes/admin.py: admin-only GET /api/admin/db-maintenance/health. - SettingsView.vue: 'Table health' table in the maintenance card, all tables sorted by dead tuples, rows >=20% dead-ratio flagged; total DB size shown; refreshes after a Run-now so the dead-tuple drop is visible. - Tests: health row/size shaping + null-timestamp passthrough; route + service surface. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
c4553d937c |
feat(db): scheduled DB maintenance — daily targeted VACUUM (ANALYZE)
Adds a daily off-hours VACUUM (ANALYZE) over the high-churn tables the retention/purge sweeps churn (app_logs, notifications, token tables, notes, note_versions), on top of Postgres autovacuum, to reclaim bloat left by the nightly bulk DELETEs and keep planner stats fresh. - services/db_maintenance.py: run_maintenance() over a closed table allowlist via an AUTOCOMMIT connection (VACUUM can't run in a txn); per-table summary persisted as the db_maintenance_last_run admin setting. - services/db_maintenance_scheduler.py: BackgroundScheduler cron (default 04:00 UTC, after the 03:30 trash purge); enabled-gate checked at fire time; live reschedule on hour change. Wired into app.py start/stop. - routes/admin.py: admin-only GET/PUT /api/admin/db-maintenance + POST /run. - settings.py: set_admin_setting() (write-side of get_admin_setting) for out-of-request writes. - SettingsView.vue: admin 'Database maintenance' card — enable toggle, run-hour (UTC), Run-now, last-run summary. - Tests: allowlist is closed, VACUUM issued per table, one failure doesn't abort the rest, summary persisted; route/scheduler/service surface. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
dd1fc2d506 |
feat(mcp): extend dedup gate to create_rule / create_project_rule
Completes the Phase 5 follow-up: rules now get the same update-over-create gate. Title-based only (rules aren't a semantic-retrieval/RAG surface), scoped to the same topic (rulebook rule) or same project (project rule). force=true overrides; fail-open like the note/task gate. Deferred-item decisions (operator): REST/web gating SKIPPED (kept MCP-only — humans rarely double-create and a hard block needs UI affordance); orphan scope kept orphan↔orphan (no change). So this rule gate is the only remaining build. - services/dedup.py: find_duplicate_rule(title, topic_id|project_id). - create_rule + create_project_rule: force param + gate. - tests: rule title match, scope-required guard, tool gate (block + force). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5102ffb558 |
fix(dedup): fail open when the duplicate check can't run
The Phase 5 gate added a DB query before every create_note/create_task. When that query fails (DB unreachable, etc.) the create must NOT error — a dedup check is advisory infrastructure, not a correctness gate. Wrap the title query so any failure degrades to "no duplicate found" and the create proceeds. Also fixes 7 existing create tests that don't mock the DB: they now exercise the fail-open path (no Postgres in the unit-test job) instead of erroring. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
322cbc3b5e |
feat(mcp): Phase 5 — write-time near-duplicate gate (update-over-create)
#755 Phase 5. create_note / create_task now BLOCK a near-duplicate instead of silently inserting: they return {"duplicate": true, "existing_id", message} pointing at the record to UPDATE. Fights store bloat and stale competing copies that semantic search (RAG) would otherwise resurface for reconciliation. A force=true override creates anyway for genuinely-distinct records. - services/dedup.py: find_duplicate_note — two signals, scoped to owner + same project + same kind: (1) normalized-title exact match (cheap, always); (2) semantic cosine ≥ 0.90 but ONLY when body ≥ 200 chars (short/title-only embeddings false-positive — the pre-pivot lesson). Project-less (orphan) records compare only to other orphans on BOTH signals (orphan_only on the semantic call) — they're not matched across every project. - Gate wired into the MCP create_note/create_task tools (the LLM write path) with force override; _INSTRUCTIONS documents the duplicate response + force. - Opt-in by design: the service helper is only called from the interactive create tools. Internal/programmatic creates (recurrence spawn, imports) go straight through services.create_note and are NOT gated — a recurring task spawning its next same-titled instance must not be blocked. - Scope v1: MCP tools only. REST/web (human CRUD, needs a UI affordance) and create_rule (not a RAG surface; _INSTRUCTIONS already steer it) are follow-ups. - tests: dedup service (title/semantic/body-gate/type-filter) + tool gate (blocks, force bypasses) for notes and tasks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
33f9a0a4d4 |
feat(plugin): Phase 4 — Scribe Processes auto-surface as local skills
#755 Phase 4. Saved Scribe Processes (DRY pass, Drift Audit, …) now surface as auto-triggered Claude Code skills instead of pull-only get_process calls. Design correction vs the plan: stubs live in the USER's ~/.claude/skills/, NOT plugin/skills/_instance/. The plugin is git-cloned and identical per install, so instance-specific generated files can't ride in it; personal skills are live-detected within the session (verified via claude-code-guide). MCP prompts were the alternative but are pull-only (no relevance auto-surface), so skills are the right primitive. - backend: GET /api/plugin/processes manifest (services/plugin_context. build_process_manifest) — {name, slug, description} per Process; description is the auto-surface trigger (title + preview); slugs deduped, blanks skipped. - plugin: scribe_sync_processes.sh writes ~/.claude/skills/scribe-proc-<slug>/ SKILL.md (body = "call get_process(name), follow verbatim") and PRUNES stale scribe-proc-* stubs. Fail-open + silent; a transient fetch failure never wipes existing stubs. Runs as a 2nd SessionStart hook + via the /scribe:sync command. - plugin.json 0.1.7 → 0.1.8; README updated. - tests: build_process_manifest (render, slug dedupe, blank-title skip, preview truncation). Sync script's write+prune validated in isolation (plugin/** is not CI-covered): correct stubs created, stale pruned, unrelated skills untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1f6c592226 |
feat(plans): milestone-as-plan-container; retire kind=plan (T3)
The milestone becomes the plan container: a new nullable milestones.body holds the design/intent (Goal/Approach/Verification) and individual steps live as first-class child tasks (milestone_id) instead of checkboxes crammed into one kind=plan task body. start_planning now creates a MILESTONE seeded with the body template (not a kind=plan task) and returns it with applicable rules; a new get_milestone MCP tool reads the plan back (body + steps + rules). kind=plan is hard-retired going forward — start_planning never creates one. The 'plan' task_kind enum value stays valid so the 11 historical plan-tasks remain readable in place; no body-shredding backfill (corpus review showed auto-splitting their checklists into tasks would be lossy: embedded code blocks, a non-binary [~] state, tables, ID-encoded hierarchy). - migration 0066: add milestones.body - model/service/route/MCP: body passthrough on create+update; get_milestone - server _INSTRUCTIONS: "plan" = milestone w/ body + child step-tasks - UI: ProjectView shows/edits a milestone's plan body; start_planning expands the new milestone and opens its plan editor - tests updated to the milestone contract + new body/get_milestone coverage Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
79040fe5db |
feat(issues): S4b backend — REST task issue fields + dashboard open-issues
Closes the REST gap S4b's UI needs (S2 only extended MCP tools): - routes/tasks.py: create/update accept system_ids (set-semantics) + arose_from_id; GET/create/update return the task's associated systems. kind=issue already flowed via task_kind. Associations set via services/systems (ACL-checked; can_write_note already gated). - services/dashboard.py: _open_issues section (owner-scoped, ranked like other task lists, capped) added to build_dashboard. Dashboard test updated for the new key. Refs plan 825 (S4b, backend half). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4f22646c88 |
feat(issues): S3 REST routes — systems CRUD + project open-issues
Third slice of Issues + Systems (spec #825). routes/systems.py (nested /api/projects/<id>/...): GET/POST systems (list adds per-system open_issue_count via one grouped query), GET/PATCH/DELETE a system (GET returns records split into issues/tasks/notes), GET .../systems/<id>/records (kind/open_only filters), GET .../issues (project's open issues for the project view + dashboard roll-up). login_required; project access via get_project_for_user; writes gated by can_write_project (clean 403); system.project_id verified to match the path. Blueprint registered in app.py. services/systems.py: + open_issue_counts_by_system (one grouped query) and list_issues (project issues, open by default). Tests: structural (blueprint registered + in app, handlers callable, service contracts take user_id) — matches the house route-test pattern. Refs plan 825 (S3). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
85e0501705 |
feat(issues): S2 MCP tools — system CRUD + issue/system wiring
Second slice of Issues + Systems (spec #825). New mcp/tools/systems.py: create_system, list_systems, get_system (records split into issues/tasks/notes), update_system (incl. archive via status), list_system_records (kind/open_only filters), delete_system. Registered in register_all; read tools (get_system, list_systems, list_system_records) added to the read-only-key allowlist (write tools default-deny). create_task/update_task: kind now accepts 'issue'; new system_ids (set-semantics associations) and arose_from_id (provenance, 0=unchanged/-1=clear) args. create_note/update_note: new system_ids arg (notes associate with systems too). services/notes.create_note: arose_from_id passthrough (update_note already handles it via setattr). Tests: MCP system tools + create_task issue-wiring (kind/provenance/systems), service layer mocked. Refs plan 825 (S2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b91c447b0b |
feat(issues): S1 schema — issue task_kind, System entity, associations
First slice of the Issues + Systems feature (spec #825, plan #819 T2). Schema (migration 0065): - task_kind CHECK expands work|plan -> work|plan|issue (same-change, rule 36) - notes.arose_from_id: optional self-FK for issue->originating-task provenance (distinct from parent_id sub-task hierarchy) - systems: per-project, self-describing (name + description) subsystem/area - record_systems: M2M join linking any note/task/issue to systems (mutable) Models: System + RecordSystem; note.py gains arose_from_id (+ index, to_dict). Service services/systems.py: CRUD, archive, soft-delete, set/list associations, records-for-system, open-issue count — all gated via services/access.py project permissions (rule 78, no bare-owner filters). Unit tests lock the ACL gating; the migration is exercised by CI's integration lane (alembic upgrade head). is_task stays a derived property (status is not None) — unchanged. T1 (typing- axis rationalization) intentionally NOT bundled; this only adds the enum value. Refs plan 825 (S1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
da511fcc9f |
feat(ui): declutter dashboard done-recently + MCP-access, add per-project stats
Dashboard: - 'Done recently' chip-cloud -> compact uniform list (Active-now row style), showing 5 with inline expand to the rest (backend already returns up to 8). - New 'Projects' rail card: each active project with 'N open · M done'. Backend already computed done_count (dashboard.py) — now surfaced in the /api/dashboard payload per active project. MCP Access (Connect Claude / Claude Code): - Progressive disclosure: lead with the pre-filled plugin-install snippet; fold server name, scope, marketplace URL, and the MCP-only path into a single 'Customize' expander. Desktop tab keeps its own server-name field. - Marketplace URL now defaults to this instance's own repo via config.PLUGIN_MARKETPLACE_URL (env-overridable); /api/plugin/marketplace-url falls back to it, so the field + install snippet are pre-filled out of the box instead of showing a generic placeholder. Refs #761 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8fe571e175 |
feat(plugin): resolve session project from git remote, not a pinned project_id
The SessionStart hook asked for a project_id via plugin userConfig, which pins one install to a single project — wrong for an operator working across many repos/projects. Resolve the active project server-side from the working repo's git remote instead (a stable identifier, not a dir-name guess). - repo_bindings table (migration 0064) + RepoBinding model: (user, repo_key) -> project, FKs CASCADE. - services/repo_bindings: normalize_repo_key collapses ssh/https/scp/creds/port/ .git to host/owner/repo; resolve/set/list/delete. - GET /api/plugin/context takes ?repo=<remote>; unbound repo -> a "bind this repo" hint with a ready bind_repo() call. project_id kept as manual override. - MCP tools: bind_repo / list_repo_bindings / unbind_repo. - Hook sends ?repo=$(git remote get-url origin) URL-encoded; all project_id handling removed. plugin.json drops the project_id userConfig (0.1.2 -> 0.1.3). - Tests: normalize equivalence classes + unbound-hint rendering. Refs task 755 (Scribe-as-plugin push channel). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1d82e81527 |
feat(plugin): admin-configurable marketplace URL as the install default
The Settings install command had a <your-scribe-repo> placeholder — not copyable. Add an instance-global 'plugin_marketplace_url' setting (admin sets it to the app's own repo) that every user's MCP Access reads, so the /plugin marketplace add command is copyable out of the box. Keeps it universal (each deployment configures its own repo) rather than hardcoding one. - services/settings.get_admin_setting(key): admin-scoped global read. - routes/plugin: GET /api/plugin/marketplace-url (any user) + PUT (admin). - SettingsView: Admin → 'Plugin marketplace' field to set it; MCP Access marketplace field falls back to the configured value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d6c8470ab2 |
feat(backup): v3 backup covers rulebooks, rules, events + join tables
The v2 backup silently dropped the entire rulebook system (rulebooks, topics, rules), the project subscription/suppression join tables, and events — so a 'full' backup wasn't. v3 adds all of them with FK re-mapping on restore, and a _not_included field that names the still-deferred tables (ACL groups/shares, api_keys, embeddings, transient/operational) so the gap is explicit, not silent. restore_full_backup routes v2 and v3 through one path; v3-only sections are guarded by data.get so a v2 payload still restores cleanly. Tests: version/coverage constants, pure join-table row helpers, and the export contract via a mocked session (CI has no DB; full round-trip is a manual check). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3ab16fcbdb |
feat(plugin): add /api/plugin/context push-channel endpoint + dogfood hook
Phase 1 of plan #755 (Scribe-as-plugin). Gives Scribe its own session-start push channel so always-on rules + active-project context surface without being asked — the gap behind 'I have to prompt for everything'. - services/plugin_context.build_session_context: renders always-on rule titles grouped by topic (under the 10k additionalContext cap; full text stays one list_always_on_rules/get_rule call away) + optional project goal/open-task count + a recall/update-over-create reflex line. Capped at 9000 chars. - routes/plugin GET /api/plugin/context (login_required already accepts Bearer fmcp_ keys; read scope suffices). - tests: titles-not-statements, project scoping, length cap (pure mocks). - scripts/scribe_session_context.sh: dogfood SessionStart hook, fail-open, reads url+token from .mcp.json. Superseded in Phase 2 by the plugin-bundled hook using userConfig. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
70ab3f38c6 |
chore: remove pre-pivot dead code + finish Scribe rebrand (#599 t1-3)
- Header wordmark Fabled -> Scribe; fable:calendar-changed event -> scribe:calendar-changed; SettingsView CSS comment. - Drop dead Project.auto_summary + summary_updated_at columns (migration 0063) -- the Ollama-era summarizer is gone; model + 2 frontend types + projects test updated. - Remove pivot vestiges: diagnostics _curator_busy()/curator_busy heartbeat field, tz BRIEFING_DAY_START_HOUR/user_briefing_date dead aliases, the ignored 'model' param on get_embedding (+ its test). ruff src/ clean; CI is the gate. Part of scribe plan #599. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b255a0f90e |
refactor: rename package fabledassistant -> scribe (code-only)
Renames src/fabledassistant -> src/scribe and all imports, plus the default DB name and DB user/password (fabled -> scribe) in config + compose. 952 refs / 154 files. Reverses the old 'internal name stays fabledassistant' convention. Code-only: live databases are still physically named 'fabledassistant'. Deployed environments must set POSTGRES_DB / POSTGRES_USER (or rename the DB) since the defaults now resolve to 'scribe'. Repo (FabledScribe), git host (fabledsword), MCP (fabled-git) and the image name (fabledscribe) are intentionally unchanged. ruff check src/ clean locally; CI (typecheck + pytest) is the gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |