Commit Graph

17 Commits

Author SHA1 Message Date
bvandeusen b33e2a79c6 fix(snippets): close the recall-surface gaps found reviewing the Drafter
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 11s
CI & Build / integration (push) Successful in 19s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Build & push image (push) Successful in 1m7s
Four defects from the 2026-07-25 review of the recall (#227) and merge (#231)
milestones. The theme: a snippet could be recorded but not fully corrected, and
the agent and web surfaces had drifted apart.

- #2076 language was mis-derived from the first caller tag, so a snippet created
  with tags and no language read that tag back as its language — corrupting the
  tag set and the code fence on the next update. Only the FIRST tag can carry
  the language, since compose_tags emits [language, "snippet", *caller].
- #2077 MCP update_snippet mapped "" to "unchanged", so no field could ever be
  cleared and no snippet detached from its project. Now an omitted field is left
  alone, an empty string clears, and project_id follows the -1 = detach
  convention. A service-level UNSET sentinel keeps None available as the clear.
- #2078 surface parity: adds delete_snippet (MCP had none, so a wrong snippet
  could not be retired by the agent that recorded it), locations on MCP create
  and update, system_ids through the REST routes and the editor, and the
  near-duplicate gate on REST create with a "record it anyway" escape.
- #2079 project scoping: list_snippets takes project_id through the service, the
  MCP tool and the REST route, defaulting to every project — reaching across
  projects is the point when the helper you need was written elsewhere.

Sharing the list across owners is deliberately NOT in here: query_knowledge is
shared with the Knowledge browse surface, so widening it changes behaviour well
beyond snippets. Left open on #2079 for a scope decision.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLwAaV4DQEmVyn496HnEvt
2026-07-25 19:00:05 -04:00
bvandeusen 85625de394 feat(scribe): merge_snippets — unify found one-off snippets into one canonical
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / integration (push) Successful in 38s
CI & Build / Python tests (push) Successful in 59s
CI & Build / Build & push image (push) Successful in 1m10s
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
2026-07-25 17:11:47 -04:00
bvandeusen 0ea3bff797 feat(scribe): snippet recording nudge — reusing-code skill + MCP/SessionStart guidance
CI & Build / Python lint (push) Successful in 3s
CI & Build / integration (push) Successful in 31s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 55s
CI & Build / Build & push image (push) Successful in 1m10s
Step 5 of the Drafter recall milestone (#227): teach agents the two
snippet reflexes — search recorded snippets before writing a new
helper/util/component, and record something reusable the moment it's
built — via the app's own instruction surfaces, not a Scribe rule
(project rule #119). All instance-agnostic (rule #115).

- plugin/skills/reusing-code/SKILL.md: new auto-surfacing process-skill
  covering both reflexes (recall-before-rebuild + record-when-reusable).
- src/scribe/mcp/server.py: a Snippets paragraph in the MCP _INSTRUCTIONS.
- plugin/hooks/scribe_static_context.md: a "reuse before rebuilding"
  bullet in the SessionStart static context.
- plugin/.claude-plugin/plugin.json: version 0.1.12 -> 0.1.13 in the same
  change so the autoUpdate marketplace ships it (the #1040 lesson);
  description skill list updated.
- plugin/README.md: trued the process-skill list to what actually ships.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pa2EsuB54BuWQ8GfJq9c7t
2026-07-25 14:00:07 -04:00
bvandeusen 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
2026-07-19 13:29:14 -04:00
bvandeusen 5fbee18a94 feat(mcp): S5 — issue-kind guidance across all instruction surfaces
CI & Build / Python lint (push) Successful in 3s
CI & Build / integration (push) Successful in 15s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 46s
CI & Build / Build & push image (push) Successful in 1m3s
Plan #825 (T2 — Issues task_kind) shipped S1–S4 but its S5 docs slice
never landed, so every behavioral surface the plugin pushes to the agent
still described the pre-kind convention ("tag `issue`" on a create_note).
Result: agents fixed bugs without reaching for kind=issue and dumped the
work as logs on unrelated open tasks.

- _INSTRUCTIONS: rewrite the "record a problem" bullet to
  create_task(kind="issue") with symptom→cause→fix + arose_from_id /
  system_ids, and an explicit "not a work-log on an unrelated task"; add
  Issue + System to the hierarchy section.
- skills/systematic-debugging, verification: drop "tag `issue`" /
  create_note-issue, point at create_task(kind="issue").
- skills/using-scribe: add issues/systems to the entity list + reflex #6.
- hooks/scribe_static_context: fix → its own issue on the keyless floor.

Instance-agnostic, prose-only; no schema or tool-behavior change.
Pairs with always-on rule #118. Issue: #855.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 23:22:17 -04:00
bvandeusen ee02ed37c1 feat(plugin): compaction-hygiene guidance — recommend safe compaction at seams
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 59s
#834. The pre-compaction complement to the shipped post-compaction re-grounding
banner. Because Scribe records progress as you go (task status, work-logs,
decision notes), a compaction at a clean work-seam is lossless — so guide the
model to recommend it proactively rather than letting auto-compact fire mid-task.

Placed in the ALWAYS-loaded channels (operator wants it consistently in context,
not relevance-gated like a skill): MCP _INSTRUCTIONS (every handshake) + the
static SessionStart floor (every session, MCP-independent). Behavior: at the end
of a block of work in a long session, ensure in-flight state is logged, then tell
the operator it's a safe moment to /compact (naming what was logged); recommend
at seams, not every turn; the model can't run /compact itself.

plugin.json 0.1.8 → 0.1.9 so clients re-pull the static-context change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 15:41:24 -04:00
bvandeusen 322cbc3b5e feat(mcp): Phase 5 — write-time near-duplicate gate (update-over-create)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Failing after 42s
CI & Build / Build & push image (push) Has been skipped
#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>
2026-06-14 13:21:35 -04:00
bvandeusen 1f6c592226 feat(plans): milestone-as-plan-container; retire kind=plan (T3)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 22s
CI & Build / Python tests (push) Successful in 42s
CI & Build / Build & push image (push) Successful in 59s
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>
2026-06-14 12:22:22 -04:00
bvandeusen 85e0501705 feat(issues): S2 MCP tools — system CRUD + issue/system wiring
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 50s
CI & Build / Build & push image (push) Successful in 1m3s
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>
2026-06-13 23:07:37 -04:00
bvandeusen 88106309f4 feat(plugin): add 4 Scribe-native process-skills (restore superpowers gap)
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 49s
CI & Build / Build & push image (push) Successful in 1m12s
Superpowers was uninstalled but its replacements were never built (only
using-scribe shipped) — a live functional hole. Author the 4 the operator
wants back, each integrated with Scribe's toolset rather than generic copies:
- writing-plans     -> start_planning / kind=plan task, not local .md
- systematic-debugging -> capture issue (symptom->cause->fix, tag issue) on resolve
- verification      -> log results to the task work-log; honest done
- brainstorming     -> recall prior thinking first; capture the decision note

Skipped TDD + receiving-code-review per operator (well-covered by Claude/them).
Manifest + using-scribe list now advertise only the 4 that ship. Remove the
stale docs/superpowers/*.md reference in _INSTRUCTIONS (superpowers is gone).
Plugin 0.1.6 -> 0.1.7.

Refs plan 821 (Phase 3 of 755).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 20:52:34 -04:00
bvandeusen c0b9831b0f feat(mcp): issue-capture convention in _INSTRUCTIONS (B8)
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 59s
Finish the breakfix/issue-logging gap as a lightweight convention: when
recording a solved problem, capture symptom -> root cause -> fix and tag it
'issue' so it's findable instead of re-diagnosed. Pairs with the B9 trigger
('log when a problem is found'). No schema change — a structured note_type/
task_kind=issue is deferred to a joint schema pass with B7.

Refs plan 812 (B8 convention; B7 deferred).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 16:27:33 -04:00
bvandeusen f125f86e16 ref(mcp): make the dev-ACL instruction self-contained (no instance coupling)
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 51s
CI & Build / Build & push image (push) Successful in 56s
Drop the '(This instance's rules carry the specifics.)' pointer — universal
_INSTRUCTIONS must not assume this install has a particular rulebook. State the
ACL principle on its own so it holds for any Scribe install/fork.

Refs plan 812 (instance-agnostic product principle).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 16:01:17 -04:00
bvandeusen 95e1d47ceb ref(mcp): neutralize dev-shaped vocabulary in _INSTRUCTIONS + add write-mandate (B9/A4)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 50s
CI & Build / Build & push image (push) Successful in 1m8s
The MCP instructions are domain-neutral except a thin layer of dev vocabulary
and one project-specific paragraph (B10 audit, task 812). Make the data store's
own instructions serve any domain, and add the missing positive write-mandate.

B9 (neutralize):
- 'before writing code' -> 'before you dive in'
- Note examples 'dev-logs' -> 'logs of what happened'
- record trigger 'a merge, a shipped feature, a finished plan' + 'dev-log note'
  -> 'finishing a task, or hitting/discovering a problem that changes direction'
  (folds in B8: log pivots, not just wins; mirrors the static-tier wording)
- recall examples 'ticket/dev-log' -> 'task/prior note' (server + SKILL.md)
- 'Engineering and workflow rules' -> 'Workflow and standards rules'
- slim the 'developing Scribe itself' ACL paragraph to a neutral one-liner
  (project-specific specifics already live in rules #47/#78)

A4 (write-mandate): state up front that Scribe is the system of record — record
work here, recall before acting, don't keep project work in local files.

Refs plan 812 (B9, A4, B8-trigger); B10 audit work-log.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 15:57:22 -04:00
bvandeusen 9eddb8497c feat(plugin): Scribe replaces native memory by instruction; tighten project-scope discipline
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 57s
Direction change (operator, see plan task #755 work-log): the plugin must
NOT depend on disabling a native Claude function to work. It earns its place
by steering behavior, not by toggling autoMemoryEnabled.

Memory doctrine (no dual-write):
- using-scribe SKILL.md gains "Scribe holds these functions — don't keep a
  second copy": route rules/recall/planning to Scribe, don't also write them
  to native auto-memory, never instruct disabling a native function, and
  accept a "Scribe-shaped hole" if the plugin is removed (recover over time).
- mcp/server.py _INSTRUCTIONS: drop the paragraph that told the model to
  create/refresh a "rules live in Scribe" pointer in CLAUDE.md / ~/.claude
  memory. That was an active dual-write instruction; the SessionStart hook is
  the bridge now. Replaced with the no-dual-write / no-settings-dependency
  doctrine. Supersedes plan #755 Phase 6 ("set autoMemoryEnabled:false").

Project-scope discipline (stop cross-project bleed):
- using-scribe SKILL.md gains "Stay inside the active project's scope": pass
  project_id to every read, only reference/offer work on the in-scope project,
  ask before switching.
- _INSTRUCTIONS scope bullet extended from reads to referencing/offering, and
  flags get_recent as cross-project.
- get_recent docstring gains a scope note steering to scoped list_* when a
  project is active.

plugin.json 0.1.4 -> 0.1.5 so clients' caches actually refresh (re-shipping
under the same version does not bust the cache).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:08:17 -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 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 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