d257c0fd67d96a073c92b84884eccb39c40ac7e9
16 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |
||
|
|
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 / Python lint (push) Successful in 2s
CI & Build / integration (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 21s
CI & Build / Python tests (push) Successful in 43s
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 |
||
|
|
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> |
||
|
|
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> |
||
|
|
f7742173aa |
chore(plans): make kind=plan retirement consistent across MCP, REST, UI, skills
Audit of the plugin + MCP surface after milestone-as-plan (T3): every path that could still create a kind=plan task or describe the old plan-task model is now aligned with the hard-retire decision. - create_task (MCP + REST POST /api/tasks): reject kind=plan with a message pointing to start_planning. The 'plan' enum value stays valid so legacy plan-tasks remain readable; update paths never touch kind, so they round-trip. - create_task / get_task docstrings: 'plan' dropped from creatable kinds; get_task's rules-augmentation noted as legacy-only (get_milestone for new plans). - skills/writing-plans: rewritten for milestone-as-plan (body = design, steps = child tasks, get_milestone to read back). - skills/using-scribe: "plans live in milestones via start_planning", not kind=plan. - TaskEditorView Kind selector: offers Work/Issue; "Plan (legacy)" shown only when the loaded task is already kind=plan (display round-trip). - test: create_task rejects kind=plan. 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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |