Commit Graph

965 Commits

Author SHA1 Message Date
bvandeusen 4c8044826f Merge pull request 'docs(mcp): encode rule-scope model in rulebook tool descriptions' (#67) from dev into main
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 47s
CI & Build / Build & push image (push) Successful in 13s
2026-06-10 10:18:50 -04:00
bvandeusen 50b6902fe2 docs(mcp): encode rule-scope model in rulebook tool descriptions
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 59s
Make the always-on / subscribed / project-rule distinction explicit at the
authoring surface so it can't silently regress (for this operator or other
users). Previously the tools said only 'cross-project rulebook rule' and a
bare 'subscribe a project' — nothing steered project-specific detail away
from shared rulebooks, which is how a Scribe-pinned rule ends up binding
every family project.

Principle encoded in 5 places: a rule's home is chosen by WHO it should bind,
and both rulebook tiers are SHARED so their rules stay general — they differ
in reach (all projects vs opt-in by theme), not generality. Project-specific
detail goes in create_project_rule.

- server.py MCP instructions: add the 3-tier authoring principle
- create_rule / create_rulebook / create_project_rule / subscribe_* docstrings
- using-scribe SKILL.md: a 'Where a new rule goes' note for the pull path

Refs #755

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 10:15:01 -04:00
bvandeusen 2a5f5fdbe1 Merge pull request 'feat(plugin): make using-scribe skill actively pull standing rules' (#66) from dev into main 2026-06-10 02:20:28 -04:00
bvandeusen 1983e8f4b1 feat(plugin): make using-scribe skill actively pull standing rules
The SessionStart push channel cannot reliably deliver a sensitive API
token to the hook subprocess (upstream Claude Code bug anthropics/
claude-code#62442 — sensitive plugin userConfig is not persisted and is
absent on a normal session). Stop depending on that push for standing
rules: make the using-scribe bootstrap skill own the load instead.

- description: name the FIRST ACTION (list_always_on_rules + enter_project
  when a repo/project is in scope) so it auto-surfaces at session start
- add a 'Do this first' block instructing an active pull; demote the
  SessionStart hook to a bonus, not a precondition (it fail-opens and may
  be absent)
- reflex step 2: rules come from list_always_on_rules(), not from an
  assumed SessionStart injection

The hook + hooks.json are left in place: they fail-open and resume adding
value automatically if #62442 is fixed or the token is made non-sensitive.

Refs #755 (Phase 1: push channel descoped to optional; pull is load-bearing)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 02:19:13 -04:00
bvandeusen 30826d250c chore: remove pre-plugin scribe_session_context.sh dogfood hook
Superseded by the plugin's own SessionStart hook (plugin/hooks/). This root
scripts/ copy read the now-deleted project .mcp.json (dead scribe-dev /
devassistant host), so it could never fire. Single Scribe environment now,
reached only via the Scribe plugin MCP.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 01:49:12 -04:00
bvandeusen 2c36249c15 Merge pull request 'feat(plugin): resolve session project from git remote, not a pinned project_id' (#65) from dev into main
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 38s
CI & Build / Python tests (push) Successful in 46s
CI & Build / Build & push image (push) Successful in 13s
2026-06-10 01:37:48 -04:00
bvandeusen 8fe571e175 feat(plugin): resolve session project from git remote, not a pinned project_id
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 51s
CI & Build / Build & push image (push) Successful in 1m0s
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>
2026-06-10 01:33:58 -04:00
bvandeusen 6cc47c7222 Merge pull request 'fix(plugin): session-start hook reads API token from CLAUDE_PLUGIN_OPTION env, not user_config placeholder' (#64) from dev into main 2026-06-10 00:52:02 -04:00
bvandeusen 651119cfb0 fix(plugin): read API token from CLAUDE_PLUGIN_OPTION env, not user_config placeholder
The SessionStart push-channel hook passed the key via
SCRIBE_TOKEN="${user_config.api_token}" in hooks.json, but api_token is
sensitive:true. Claude Code keeps sensitive userConfig in the keychain and
does not interpolate it into hook command strings (only into mcpServers
headers), so the hook received the literal placeholder, sent it as the Bearer
token, and the context endpoint 401'd -> fail-open -> no context injected.

Read the harness-exported CLAUDE_PLUGIN_OPTION_<key> env vars instead (SCRIBE_*
still override for the settings.json dogfooding path), and treat any unexpanded
${...} literal as unset so the hook fails open cleanly instead of 401-ing.
Bump 0.1.1 -> 0.1.2 so installs refresh the cache.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 00:43:40 -04:00
bvandeusen 3a5835b109 Merge pull request 'fix(plugin): load the bundled MCP server + admin-configurable marketplace URL' (#63) from dev into main
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 36s
CI & Build / Python tests (push) Successful in 58s
CI & Build / Build & push image (push) Successful in 12s
2026-06-10 00:25:22 -04:00
bvandeusen ff91948fa3 chore(plugin): bump to 0.1.1 so clients pick up the mcpServers fix
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 00:25:09 -04:00
bvandeusen 559f70eef0 fix(plugin): inline mcpServers in plugin.json so the MCP server actually loads
/reload-plugins reported '0 plugin MCP servers'. Root cause: plugin.json had
"mcpServers": "./.mcp.json" — a string path, which is neither a valid inline
object nor a recognized reference (per docs, plugin MCP servers are a root
.mcp.json OR an inline object in plugin.json), so it parsed to zero servers.

Inline the mcpServers object directly in plugin.json and remove the separate
.mcp.json. The user_config substitution syntax was already correct
(plugins-reference: values substitute as ${user_config.KEY} in MCP configs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 00:24:06 -04:00
bvandeusen 1d82e81527 feat(plugin): admin-configurable marketplace URL as the install default
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 46s
CI & Build / Build & push image (push) Successful in 59s
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>
2026-06-10 00:17:05 -04:00
bvandeusen b1674169a0 Merge pull request 'feat: Scribe-as-plugin foundation (push-channel endpoint, in-repo plugin, plugin-install Settings UI, backup v3)' (#62) from dev into main
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 15s
2026-06-10 00:02:53 -04:00
bvandeusen c0b3ec7d9b feat(settings): lead MCP Access with plugin install, demote raw MCP
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 47s
CI & Build / Build & push image (push) Successful in 57s
Per operator: the plugin install should supersede the bare MCP connection in
Settings, since the plugin incorporates the MCP and adds the session-start hook
+ skills. The Claude Code tab now leads with /plugin marketplace add + install
(with a persisted marketplace-URL field and the base-URL/key/project-id prompts
spelled out), and the old 'claude mcp add' command moves into a collapsed
'Advanced: connect the MCP only' disclosure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 23:52:15 -04:00
bvandeusen 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>
2026-06-09 23:52:14 -04:00
bvandeusen 9924f873b9 feat(plugin): ship the Scribe Claude Code plugin in-repo (marketplace + plugin)
Per operator: the plugin lives in the app repo so it ships and versions in
lockstep with the app and the /api/plugin/context contract it targets (same
co-location rationale as the former in-repo MCP). A git-cloned marketplace
supports relative plugin sources, so the FabledScribe repo IS the marketplace.

- .claude-plugin/marketplace.json — source ./plugin
- plugin/.claude-plugin/plugin.json — userConfig (base URL, api key, project id)
- plugin/.mcp.json — http scribe server, ${user_config.*} substitution
- plugin/hooks/ — SessionStart push-channel hook (fail-open)
- plugin/skills/using-scribe — bootstrap skill
- plugin/README.md — install via the FabledScribe repo marketplace

Phase 2 of plan #755. Install/userConfig-substitution test pending.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 23:04:09 -04:00
bvandeusen 3ab16fcbdb feat(plugin): add /api/plugin/context push-channel endpoint + dogfood hook
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 49s
CI & Build / Build & push image (push) Successful in 1m6s
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>
2026-06-09 22:43:08 -04:00
bvandeusen 8c1b19f49c chore(docs): retire dead fable-mcp wheel-distribution refs; untrack docs/superpowers
The old standalone fable-mcp wheel/download flow is gone from code (no route,
no Dockerfile build, no FABLE_MCP_DIST_DIR). Update api-keys-and-mcp,
api-reference, architecture, configuration, development to describe the
in-app HTTP MCP at /mcp (Bearer auth). Untrack the 18 committed
docs/superpowers/ files so the existing .gitignore takes effect.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 22:42:58 -04:00
bvandeusen 51feaddcd3 Merge pull request 'feat(mcp): make Scribe reflexively recall + scope reads to the active project' (#61) from dev into main
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 42s
CI & Build / Build & push image (push) Successful in 17s
2026-06-04 23:13:27 -04:00
bvandeusen e3c6124912 feat(mcp): make Scribe reflexively recall + scope reads to the active project
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Build & push image (push) Successful in 56s
The MCP surface advertised writing well but recall poorly, and project
scoping had no anchor that survived past enter_project's snapshot:

- search / list_notes dropped the project_id their services already
  support, so a scoped search was impossible — every query swept all
  projects and bled unrelated work into the session.
- The tool descriptions were mechanical ("Semantic search over the
  user's notes and tasks") with no trigger telling Claude WHEN to reach
  for them; the server instructions were all write-discipline and said
  nothing about searching before answering or starting work.

Changes:
- search, list_notes: add project_id param, wired to the service.
- search, list_notes, list_tasks: trigger-worded descriptions that push
  passing the active project's id and reserve project_id=0 for a
  deliberate cross-project sweep.
- _INSTRUCTIONS: add a 'Reach for Scribe to RECALL, not just to record'
  block — search before answering/starting, check for an existing ticket
  before create_task, scope reads to the active project (which does not
  stick on the server).

Paired with always-on rule #75 in the FabledSword-family rulebook.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 22:43:55 -04:00
bvandeusen 964c8005d5 Merge pull request 'Package rename (fabledassistant→scribe) + pivot cleanup' (#60) from dev into main
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Build & push image (push) Successful in 16s
2026-06-03 16:28:10 -04:00
bvandeusen 70ab3f38c6 chore: remove pre-pivot dead code + finish Scribe rebrand (#599 t1-3)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 51s
CI & Build / Build & push image (push) Successful in 1m1s
- 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>
2026-06-03 16:16:44 -04:00
bvandeusen b255a0f90e refactor: rename package fabledassistant -> scribe (code-only)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 54s
CI & Build / Build & push image (push) Successful in 1m14s
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>
2026-06-03 15:48:35 -04:00
bvandeusen 9ddc418f5f Merge pull request 'main publishes :latest + ACL _INSTRUCTIONS guard' (#59) from dev into main
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 49s
CI & Build / Build & push image (push) Successful in 15s
2026-06-03 14:41:15 -04:00
bvandeusen 1d4c206563 ci: main publishes :latest (main is the production line)
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 54s
CI & Build / Build & push image (push) Successful in 18s
main pushes now move :latest (in addition to the immutable :<sha>), so a
merge to main updates production's pointer directly — no separate release
needed just to refresh :latest. The v* release tag's distinct job becomes
the dated :<version> marker (it still refreshes :latest harmlessly). Still
no :main tag. Rules 47/46 + 10/4 updated to match on both instances.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:27:53 -04:00
bvandeusen 301352f628 Merge pull request 'MCP _INSTRUCTIONS: multi-user sharing ACL guard' (#58) from dev into main
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 55s
CI & Build / Build & push image (push) Successful in 25s
2026-06-03 14:18:01 -04:00
bvandeusen d4666bea7f feat(mcp): add multi-user sharing ACL guard to _INSTRUCTIONS
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 44s
CI & Build / Build & push image (push) Successful in 1m15s
The ACL constraint (scope every read/mutation by owner + shares via
services/access.py) is a security-correctness invariant that should
always be loaded, and it's FabledScribe-specific — so it belongs in
Scribe's own contained _INSTRUCTIONS, not the cross-project FabledSword
rulebook. The redundant rulebook rule will be retired once this ships
to prod.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 13:47:02 -04:00
bvandeusen 837489e4f2 Merge pull request 'CI: build on main (and drop the :main tag)' (#57) from dev into main
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
v26.06.03
2026-06-03 12:44:44 -04:00
bvandeusen 9a0d5f3109 ci: drop the :main tag — main builds publish only the immutable :<sha>
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 44s
CI & Build / Build & push image (push) Successful in 14s
:latest (release-only) is the single production pointer; a :main moving
tag just duplicated it. main pushes still gate + build (the :<sha> image
is the rollback point), but no longer publish a :main alias. The tag was
new and unreferenced, so nothing depends on it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:52:21 -04:00
bvandeusen 5a930319ba ci: gate and build main too (:main image); :latest stays release-only
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 45s
CI & Build / Build & push image (push) Successful in 15s
Previously main pushes were deliberately skipped — CI only ran on dev
and v* tags. This conflicted with the intended policy (CI on dev AND
main). Now main is a first-class gated, built line: dev->:dev, main->:main,
v* tag->:latest + :<version>, every build also tagged with the commit sha.
Per-ref concurrency already supersedes rapid pushes, so dev and main run
independently without stacking identical work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:27:09 -04:00
bvandeusen 266af7870d Merge pull request 'MCP instruction hardening + milestone-unset' (#56) from dev into main 2026-06-03 11:19:09 -04:00
bvandeusen f446573c3d feat(mcp): proactive project bootstrapping at session start
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 42s
CI & Build / Build & push image (push) Successful in 1m6s
Adds an always-on _INSTRUCTIONS directive: when work touches Scribe and
no project is in scope, search for a related project and propose
enter_project (confirm first), or offer to create one (confirm name/goal
first) — never silently adopt or create. Pairs with the enter_project
handshake and the host-memory pointer directive. Closes scribe task #585.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:01:55 -04:00
bvandeusen 82d6812c7f feat(mcp): milestone_id=-1 clears a task's milestone (update_task)
Optional FKs on update_task previously had no way to express 'remove' —
0 meant leave-unchanged and any positive int meant set, so a milestone
(or project) could only be cleared via the web UI. Now -1 clears the FK
(NULL); clearing project_id also clears milestone_id since a milestone
can't outlive its project. update_note already NULLs on None, so the
change is confined to the tool wrapper. Closes scribe task #586.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:59:47 -04:00
bvandeusen 8c9ca45479 feat(mcp): instruct agents to keep a Scribe-rules pointer in host memory
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 1m1s
CI & Build / Build & push image (push) Successful in 1m6s
When a project subscribes to a rulebook, the agent should ensure the
host's persistent memory carries a pointer that engineering/workflow
rules live in Scribe (loaded via list_always_on_rules / enter_project),
plus a one-line note of the current project's work. Pairs with the
existing 'don't duplicate rules into memory' directive: memory holds the
pointer + project context, Scribe holds the rules.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:42:23 -04:00
bvandeusen e023c21aa1 docs(mcp): instruct agents to drive task lifecycle + log work
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 1m48s
Adds a 'keep task state honest' directive to the MCP _INSTRUCTIONS: set
in_progress on start, log progress with add_task_log as you go, set done the
moment work completes (never leave finished work at todo), and write a dated
dev-log note on the project at significant landings. Reinforced in the
update_task status docstring. App-layer + always-loaded, no rule/config needed
— closes the gap where finished work (e.g. a shipped plan) sat open because the
lifecycle was available but never prescribed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:24:26 -04:00
bvandeusen e3d7007417 Merge pull request 'Drift-audit remediation + Stored Processes + Dashboard' (#55) from dev into main 2026-06-03 08:11:14 -04:00
bvandeusen 65c85bab15 feat(dashboard): DashboardView landing + route + nav
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Build & push image (push) Successful in 44s
Task 3 of #583. New DashboardView at /dashboard composes the approved layout:
done-recently strip, Active-now project panels (project -> active milestones ->
open tasks, in-progress flagged, + no-milestone group), and a rail with
upcoming events / week stats / quick-create (Task/Note/Process). '/' now
redirects to /dashboard; AppHeader gains a Dashboard link and relabels
Knowledge -> Browse (route unchanged). Empty + loading states included.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 23:24:32 -04:00
bvandeusen 7ef7d10b24 feat(dashboard): GET /api/dashboard endpoint
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 44s
CI & Build / Build & push image (push) Successful in 1m17s
Task 2 of #583. Minimal login-gated blueprint returning build_dashboard(uid);
registered in the app factory.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 23:22:08 -04:00
bvandeusen 6a3619555d feat(dashboard): aggregation service build_dashboard
Task 1 of #583. build_dashboard(user_id) assembles the /dashboard payload:
most-recently-active projects (ranked by max child updated_at) each broken
into active milestones -> open tasks (in_progress->priority->recency, capped 5),
recently-completed (7d/8), upcoming events (7d), week stats. Owner-scoped,
trashed excluded; each section isolated via _safe so one failure doesn't blank
the page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 23:21:23 -04:00
bvandeusen 8b3bd4804e feat(processes): monospace prompt editor for note_type=process
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 36s
CI & Build / Python tests (push) Successful in 56s
CI & Build / Build & push image (push) Successful in 41s
Task 6 of #582. NoteType gains 'process'. NoteEditorView branches to a plain
monospace textarea (labeled Prompt) for processes instead of the TipTap
rich-text editor — prompts are plain markdown and rich-text round-tripping
would mangle them. Title/tags/save path unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 22:33:23 -04:00
bvandeusen 74b337b587 feat(processes): surface processes in the Knowledge view
Task 5 of #582. Add 'process' to the KnowledgeItem/activeType/KnowledgeCounts
types, a Processes entry in the type-filter row, a Workflow-icon quick-create
button (createNew('process') -> /notes/new?type=process), and a Process card
badge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 22:33:23 -04:00
bvandeusen fb1ae915e4 feat(processes): expose process as a knowledge type
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 58s
CI & Build / Build & push image (push) Successful in 1m16s
Task 4 of #582. Add 'process' to the knowledge route _VALID_TYPES and to the
get_knowledge_counts facet + total. query_knowledge/_apply_type_filter already
handle arbitrary note_type, so listing by type=process works unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 22:29:08 -04:00
bvandeusen c2b2694ea3 docs(mcp): document Processes in server instructions
Task 3 of #582. Tells Claude that note_type=process notes are reusable saved
prompts and to fire them via list_processes/get_process on 'run the X process'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 22:29:08 -04:00
bvandeusen 7b5a75989a feat(processes): MCP create/list/get/update_process tools
Task 2 of #582. New mcp/tools/processes.py mirrors entities.py — tools wrap
notes_svc directly. get_process is the fire mechanism (returns the full prompt
via resolve_process; surfaces other_matches on an ambiguous name). Registered
in register_all.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 22:27:41 -04:00
bvandeusen 1babe59843 feat(processes): add resolve_process name/id resolver
Task 1 of the Stored Processes plan (#582). resolve_process(user_id, name_or_id)
resolves a note_type=process note owner-scoped + non-trashed, precedence
numeric id -> exact case-insensitive title -> substring; returns
(note, other_candidates) so an ambiguous fuzzy match can be disambiguated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 22:26:29 -04:00
bvandeusen 2c929a0435 feat(reminders): per-occurrence reminders for recurring events
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 57s
CI & Build / Build & push image (push) Successful in 1m6s
Drift-audit Group 8 (final item). _fire_reminders previously gated on the
base row (reminder_sent_at IS NULL AND start_dt > now), so a recurring event
reminded at most once ever — once the first occurrence passed, no future
occurrence qualified.

Now recurring events are evaluated every sweep against their next occurrence
(rrulestr.after(now)), and reminder_sent_at stores the start of the occurrence
last reminded about. Each new occurrence has a distinct marker, so it re-arms
and fires exactly once per occurrence. One-shot events keep the classic
NULL gate. Also adds the deleted_at filter so trashed events stop reminding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 19:50:45 -04:00
bvandeusen cf4962d7e8 chore(profile): drop dead curator columns from UserProfile
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 38s
CI & Build / Python tests (push) Successful in 1m1s
CI & Build / Build & push image (push) Successful in 58s
Drift-audit Group 7: learned_summary, observations_raw, and
observations_updated_at were populated by the curator/LLM-profile machinery
removed in the Phase-8 pivot. Nothing has written them since and the profile
API returned permanently-empty fields. Remove them from the model + to_dict
and drop the columns (migration 0062). Verified zero frontend/backend/test
consumers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 19:48:37 -04:00
bvandeusen 64bc50c788 chore(frontend): drop dead settings toggle, types, and store list-surface
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 56s
CI & Build / Build & push image (push) Has been cancelled
Drift-audit Group 7 frontend cleanup (no behavioral change):

- SettingsView: remove the 'auto-consolidate task bodies' toggle and its
  saveAutoConsolidate handler. The auto_consolidate_tasks setting has zero
  backend readers (curator removed in Phase 8); the control did nothing.
- AppSettings type: drop the dead assistant_name / default_model hints (kept
  the open string index signature the store actually uses). Delete the fully
  orphaned types/chat.ts (zero importers).
- notes/tasks Pinia stores: remove the list/filter/sort/pagination surface
  that backed the removed /notes and /tasks list views (verified no consumer
  uses the tasks/notes arrays, refresh, or any filter/sort/pagination method).
  Kept currentNote/currentTask, loading, fetch/create/update/delete, convert,
  patchStatus, startPlanning, backlinks, tags.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 19:47:28 -04:00
bvandeusen c39d7356ed chore(dead-code): fix prod image, drop orphaned code, correct delete_rule doc
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 39s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 1m40s
Drift-audit Group 7 (renamed/removed lingers) + Group 5 #9:

- docker-compose.prod.yml pulled fabledassistant:latest, a tag CI stopped
  publishing after the rename. Point it at fabledscribe:latest (the name CI
  and quickstart use). The internal DB name stays fabledassistant by design.
- Remove the unused hard-delete delete_note imports from the notes and tasks
  route modules (they delete via trash; the import was an attractive nuisance
  that bypassed soft-delete).
- delete_rule MCP tool: docstring/warning said 'permanently delete' but the
  body moves the rule to recoverable trash. Corrected to match.
- Delete services/calendar_sync.py: fully orphaned (zero importers) and it
  read Config attrs that no longer exist, so any re-wiring would crash.
- Remove dead services: notes.search_notes_for_context and logging.log_generation
  (zero callers; log_generation wrote a 'generation' category no stats/UI surface).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 19:28:36 -04:00