Commit Graph

1020 Commits

Author SHA1 Message Date
bvandeusen 71d65442d3 Merge pull request 'Narrow Scribe to a work system-of-record — remove calendar + person/place/list surfaces' (#77) from dev into main
CI & Build / Python lint (push) Successful in 3s
CI & Build / integration (push) Successful in 24s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 57s
CI & Build / Build & push image (push) Successful in 12s
2026-07-19 18:24:40 -04:00
bvandeusen d2f08d6113 docs(scribe): rewrite features/api-reference/README to the current product
Resolves issue #1778 — the docs still described the pre-pivot product (in-app
LLM chat, journal, weather, web-research, push, model management) plus the
just-removed calendar/entities.

- features.md: full rewrite to the actual surfaces — notes, tasks & issues,
  projects/milestones (kanban), systems, rules & rulebooks, stored processes,
  search + knowledge-injection, graph, MCP + the Claude Code plugin, sharing,
  export/backup (v4), OIDC. Dropped chat/journal/weather/web-research/calendar/
  push/workspace/model-management; fixed the shortcuts + settings tables.
- api-reference.md: rewrite to the real endpoint surface (verified from the
  route decorators) — added Knowledge/Rulebooks/Systems/Plugin/Trash/Dashboard
  and the milestone/system sub-routes; removed Chat/Journal/Push/Quick-Capture/
  Images/assist/models.
- README.md: Quick Start no longer tells users to pull an Ollama model or size
  RAM/GPU "for LLM inference" — points at the API key + plugin instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPtbSzA4JLMAKgFZ8VTg7Q
2026-07-19 18:23:28 -04:00
bvandeusen ab9b5b647c docs(scribe): true up plugin + MCP docs after calendar/entity removal
Milestone #194 plugin/docs slice.

- plugin.json: "second brain" framing -> "system-of-record"; bump
  0.1.11 -> 0.1.12 (any plugin/ change must bump the manifest, issue #1040)
- plugin/README.md + using-scribe SKILL + scribe_static_context: drop
  events/typed-entities from the surface lists; "second brain" -> "system
  of record" in the SessionStart context Claude reads each session
- docs/api-keys-and-mcp.md: drop the Typed-entities + Events MCP tool rows,
  add the Systems row
- README.md: rewrite the stale front-matter (chat/RAG/calendar/weather/push
  described a pre-pivot product) to the current Claude-driven work store

Deeper pre-pivot doc-rot in docs/features.md + docs/api-reference.md
(chat/journal/weather/web-research) is tracked separately as an issue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPtbSzA4JLMAKgFZ8VTg7Q
2026-07-19 16:14:46 -04:00
bvandeusen 12f71fabdf refactor(scribe): remove calendar + entity surfaces from web UI (frontend)
CI & Build / Python lint (push) Successful in 4s
CI & Build / integration (push) Successful in 28s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 46s
CI & Build / Build & push image (push) Successful in 44s
Frontend half of the narrowing (milestone #194); matches backend b49efdc.

- delete CalendarView, EventSlideOver, WeatherCard (orphan)
- drop /calendar route + nav link + the g→l keyboard shortcut
- strip the calendar-events API client + event/metadata bits from note
  types and the notes store
- KnowledgeView: remove People/Places/Lists tabs, entity cards, create
  buttons and the upcoming-events widget; keep notes/tasks/plans/processes
  + the overdue-task badge
- NoteEditorView: remove person/place/list forms + list-builder + entity
  metadata; keep note + process editors (type select = Note/Process)
- DashboardView: drop the "Upcoming · 7 days" events rail card
- SettingsView: remove the CalDAV integration card + save/test (its
  endpoints were deleted backend-side)
- prune the now-dead entity/event CSS

RecurrenceEditor + task recurrence rules are kept (task machinery, not
calendar). Verified by a full dangler sweep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPtbSzA4JLMAKgFZ8VTg7Q
2026-07-19 16:10:24 -04:00
bvandeusen dd60244429 test(trash): update model-count assertions after Event removal
CI & Build / Python lint (push) Successful in 3s
CI & Build / integration (push) Successful in 13s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 53s
CI & Build / Build & push image (push) Successful in 1m12s
restore() / purge_expired() now iterate 6 soft-deletable models (Note,
Project, Milestone, Rulebook, RulebookTopic, Rule) — Event was removed
with the calendar surface. Adjust the two count-coupled assertions
(execute.await_count 7→6; the rowcount list drops a zero so the sum
stays 4). Caught by CI run #2580.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPtbSzA4JLMAKgFZ8VTg7Q
2026-07-19 13:32:30 -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 05f0cc2c4b Merge pull request 'fix(plugin): keep always-on rules alive across compaction (0.1.11)' (#76) from dev into main 2026-06-30 23:01:28 -04:00
bvandeusen f6629d4bcf fix(plugin): keep always-on rules alive across compaction (0.1.10 → 0.1.11)
Always-on rules were on-demand, not always-present: Tier-1 static context only
tells the agent to call list_always_on_rules(), and Tier-2 dynamic fetch is dark
(token doesn't reach the hook subprocess). On compaction the fetched rules get
summarized away while the harness's own built-in git instruction ("branch first")
survives in the base prompt — so post-compact the generic git instinct wins and
rule #1 ("dev is home") is missed.

- scribe_static_context.md: new "Operator rules govern consequential actions"
  bullet — before any git branch/commit/push or hard-to-reverse action, loaded
  rules beat generic harness/default habits; re-pull rules if not loaded or
  summarized by a compaction. Tier 1 = always fires, keyless, re-fires on compact.
- scribe_session_context.sh: compaction banner now re-pulls list_always_on_rules(),
  not just enter_project().
- plugin.json: 0.1.10 → 0.1.11 so autoUpdate ships the plugin/ change (#1040).

Generic and instance-agnostic per rules #115/#119 — no operator-specific rule
text hardcoded. Refs issue #1197.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4bNefPFAz7esmMZMZmkzL
2026-06-30 12:41:45 -04:00
bvandeusen 03772ff424 Merge pull request 'fix(plugin): bump to 0.1.10 to ship the auto-inject hook' (#75) from dev into main 2026-06-22 22:08:25 -04:00
bvandeusen 2bc054d7ef fix(plugin): bump version 0.1.9 → 0.1.10 to ship auto-inject hook
Path A's UserPromptSubmit hook (scribe_autoinject.sh) + hooks.json were
merged to main in PR #74 but the plugin version was never bumped, so the
autoUpdate marketplace (keyed by version string) never re-pulled the
snapshot — the hook was stranded, uninstallable, and not running in any
session. Bumping the version is what makes installs detect and pull it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xz4j1H7pjYSjKsEpgcNH5E
2026-06-22 22:07:19 -04:00
bvandeusen 058e8794af Merge pull request 'KB injection tuning: pgvector substrate + retrieval telemetry + title-first auto-inject' (#74) from dev into main
CI & Build / Python lint (push) Successful in 2s
CI & Build / integration (push) Successful in 20s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 44s
CI & Build / Build & push image (push) Successful in 13s
2026-06-22 20:56:15 -04:00
bvandeusen eec241d3c0 feat(plugin): sharpen the recall-before-acting reflex in static context
Turn the SessionStart static guidance into a concrete recall trigger — search
Scribe before answering about the operator projects/people/places/decisions or
starting a task, and pass the active project id to scope results — instead of a
vague "search for related work". Step 4 (pull-path sharpening); the
cross-encoder rerank half is deferred until auto_inject telemetry shows
precision is the bottleneck.

Scribe: project 2, milestone 93, task 1034.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xz4j1H7pjYSjKsEpgcNH5E
2026-06-22 20:39:54 -04:00
bvandeusen 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
2026-06-22 20:31:07 -04:00
bvandeusen 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
2026-06-22 20:10:15 -04:00
bvandeusen 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
2026-06-22 20:10:15 -04:00
bvandeusen f8c58a7f0f Merge pull request 'feat(mcp): S5 — issue-kind guidance across all instruction surfaces' (#73) from dev into main
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 47s
CI & Build / Build & push image (push) Successful in 14s
2026-06-14 23:32:04 -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 6cdac307af Merge pull request 'DB maintenance + health observability + Postgres integration CI lane' (#72) from dev into main
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / integration (push) Successful in 35s
CI & Build / Python tests (push) Successful in 51s
CI & Build / Build & push image (push) Successful in 14s
2026-06-14 20:54:34 -04:00
bvandeusen 4f31890bde test(ci): dispose engine between integration tests (per-loop pool)
CI & Build / Python lint (push) Successful in 2s
CI & Build / integration (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Build & push image (push) Successful in 13s
First integration run proved the lane works (run_maintenance test passed against
real Postgres), but the health test failed with 'Future attached to a different
loop': pytest-asyncio uses a fresh loop per test while the app's module-level
engine pools a connection from the prior test's loop. Dispose the engine in each
test's teardown so the next test starts with an empty pool on its own loop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:24:09 -04:00
bvandeusen 2ad2e943f3 test(ci): add Postgres integration lane + real run_maintenance guard
CI & Build / Python lint (push) Successful in 2s
CI & Build / integration (push) Failing after 19s
CI & Build / TypeScript typecheck (push) Successful in 20s
CI & Build / Python tests (push) Successful in 45s
CI & Build / Build & push image (push) Successful in 1m1s
The unit suite can't catch sync/async API mismatches against SQLAlchemy (an
un-awaited execution_options passed green CI but failed at runtime: VACUUM 0/6).
Add a real-Postgres integration lane modelled on the family pattern (rules
6/79-82): a new CI 'integration' job with a postgres:16 service, bridge-IP
discovery, busybox-safe readiness wait, and 'alembic upgrade head', running
pytest -m integration. Non-gating, like the unit lane.

- tests/test_integration_db_maintenance.py: runs run_maintenance() and
  get_table_health() against real Postgres; asserts all allowlisted tables
  vacuum OK (the await regression makes this fail) and health reports real stats.
- pyproject: register the 'integration' marker.
- conftest: integration-marked tests use the real DATABASE_URL, not the stub.
- ci.yml: unit 'test' job now runs -m 'not integration'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:19:18 -04:00
bvandeusen e6c89f6b88 fix(db): await AsyncConnection.execution_options in run_maintenance
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 20s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 1m0s
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>
2026-06-14 18:31:30 -04:00
bvandeusen 96079d5b77 feat(db): table-health readout — per-table bloat metrics in admin card
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 52s
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>
2026-06-14 17:53:24 -04:00
bvandeusen c4553d937c feat(db): scheduled DB maintenance — daily targeted VACUUM (ANALYZE)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 54s
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>
2026-06-14 16:42:04 -04:00
bvandeusen d324205450 Merge pull request 'Release: Issues+Systems, milestone-as-plan, plugin reliability/skills/dedup, compaction hygiene' (#71) 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 49s
CI & Build / Build & push image (push) Successful in 14s
2026-06-14 15:51:44 -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 dd1fc2d506 feat(mcp): extend dedup gate to create_rule / create_project_rule
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 53s
CI & Build / Build & push image (push) Successful in 52s
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>
2026-06-14 13:43:17 -04:00
bvandeusen 5102ffb558 fix(dedup): fail open when the duplicate check can't run
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 56s
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>
2026-06-14 13:24:20 -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 33f9a0a4d4 feat(plugin): Phase 4 — Scribe Processes auto-surface as local skills
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 52s
CI & Build / Build & push image (push) Successful in 1m3s
#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>
2026-06-14 13:00:32 -04:00
bvandeusen e8d6de287b test: fix obsolete create_task kind=plan passthrough test
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 1m8s
test_create_task_passes_kind asserted create_task forwards kind=plan; the
hard-retire guard now rejects that. Exercise passthrough with kind=issue
instead. (Service-level create_note still accepts task_kind=plan by design —
the guard lives at the user-facing tool/route layer, not the primitive.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 12:34:30 -04:00
bvandeusen f7742173aa chore(plans): make kind=plan retirement consistent across MCP, REST, UI, skills
CI & Build / Python lint (push) Successful in 3s
CI & Build / Python tests (push) Failing after 30s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Build & push image (push) Has been skipped
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>
2026-06-14 12:31:51 -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 c972af2690 ci: make npm cache step non-fatal (fixes recurring typecheck flake #828)
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 14s
The TypeScript-typecheck job intermittently failed at 'Cache npm download
cache' (transient cache-backend hiccup), which skipped install + type check and
marked the run red — 3x during the issues+systems build, all on pushes the
cache step had no bearing on. continue-on-error: true degrades a cache failure
to 'install without cache' instead of failing the job.

Closes the rerun churn from task #828.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 11:57:20 -04:00
bvandeusen b6d01686d8 feat(issues): S4b editor controls — Kind selector + Systems multi-select
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 49s
CI & Build / Build & push image (push) Successful in 36s
In the full task editor (TaskEditorView) sidebar:
- Kind selector (Work / Plan / Issue), mirroring the Status/Priority selects.
- Systems multi-select (checkboxes of the project's systems, fetched via the
  systems store), shown when a project is set.
Both wired through load (prefill from task.task_kind / task.systems), dirty
tracking, and save (kind + system_ids via the store's IssueFields). No new
colors — existing sb-field/sb-select tokens.

Deferred: the arose-from (provenance) picker — least-critical control and the
riskiest (task-search UI); the field is already supported by API/store/route for
a later add. NEEDS operator browser verification (CI typechecks only).

Refs plan 825 (S4b editor).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 10:56:53 -04:00
bvandeusen 94d32c524a feat(issues): S4b frontend — open-issues lists + issue plumbing
CI & Build / Python lint (push) Successful in 5s
CI & Build / TypeScript typecheck (push) Successful in 40s
CI & Build / Python tests (push) Successful in 1m36s
CI & Build / Build & push image (push) Successful in 59s
- types/note.ts: Note gains systems? + arose_from_id?; TaskKind includes 'issue'.
- stores/tasks.ts: create/update accept IssueFields (kind/system_ids/arose_from_id).
- api/systems.ts: getProjectIssues + TaskLike.
- DashboardView.vue: 'Open issues' rail section from dashboard.open_issues
  (links to /tasks/<id>, project + status).
- SystemsSection.vue (project Systems tab): 'Open issues' list via getProjectIssues,
  with system chips, links to /tasks/<id>. Both reuse existing CSS tokens.

Issue-editor controls (kind selector / system multi-select / arose-from picker
in WorkspaceTaskPanel) are the remaining S4b piece. NEEDS operator browser
verification (CI typechecks only).

Refs plan 825 (S4b frontend — lists).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 10:50:41 -04:00
bvandeusen 79040fe5db feat(issues): S4b backend — REST task issue fields + dashboard open-issues
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Build & push image (push) Successful in 59s
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>
2026-06-14 10:42:24 -04:00
bvandeusen 9293a9b198 fix(issues): S4a typecheck — allow null color in updateSystem param
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 37s
vue-tsc TS2345: System.color is string|null, but updateSystem's data param
typed color as string, so the store's Partial<Pick<System,...>> wasn't
assignable. Widen the param's color to string|null (clearing a color is valid).

Refs plan 825 (S4a).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 23:46:40 -04:00
bvandeusen 4da29562bd feat(issues): S4a UI — Systems management section in project view
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Failing after 23s
CI & Build / Python tests (push) Successful in 51s
CI & Build / Build & push image (push) Has been skipped
Frontend foundation for Issues + Systems (spec #825, S4a).

- frontend/src/api/systems.ts: typed client (System + list/create/update/delete)
  over /api/projects/<id>/systems, matching the rulebooks api style.
- frontend/src/stores/systems.ts: Pinia store keyed by project (fetch/create/
  update/archive/unarchive/delete), toast-on-error.
- frontend/src/components/SystemsSection.vue: a Systems management section —
  cards (color swatch, name, description, 'N open' issue-count badge) with
  inline create/edit, archive (hidden behind a 'show archived' toggle), and a
  delete-confirm modal. v1 quality: loading skeleton, empty state, error toasts,
  keyboard a11y, focus rings; reuses existing CSS tokens (no new colors).
- ProjectView.vue: new 'Systems' tab (between Notes and Rules), rendering
  <SystemsSection :project-id>, wired like the existing rules tab.

S4b (next) adds issue-editor controls (kind=issue/system multi-select/arose-from),
open-issues lists, and the dashboard surface. NEEDS operator browser verification
(CI typechecks but can't render).

Refs plan 825 (S4a).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 23:42:40 -04:00
bvandeusen 4f22646c88 feat(issues): S3 REST routes — systems CRUD + project open-issues
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 48s
CI & Build / Build & push image (push) Successful in 56s
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>
2026-06-13 23:14:54 -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 b91c447b0b feat(issues): S1 schema — issue task_kind, System entity, associations
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 49s
CI & Build / Build & push image (push) Successful in 1m5s
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>
2026-06-13 22:53:51 -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 d99c4e3c15 feat(plugin): compaction re-grounding in SessionStart hook (A7)
Deliver 'don't silently lose work at compaction' via the mechanism that
actually works. Verified contract: a PreCompact hook CANNOT make the model
flush to Scribe (host hooks can't trigger model tool calls, and can't know the
in-flight task ids), and its additionalContext only shapes the one-shot summary.
The correct tool is SessionStart scoped to source=compact, which fires AFTER
compaction and injects context the model reads.

Our SessionStart hook is matcher-less, so it already fires on compact — it just
said nothing compaction-specific. Now it reads the stdin event  and,
when source==compact, leads with a banner telling the model to reload the active
project + in-flight tasks from Scribe and reconcile half-remembered state.
Durable path = record-as-you-go (A4/B8) + this post-compaction reload.

Refs plan 812 (A7); supersedes the literal 'PreCompact hook' idea.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 16:49:10 -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 700cfc664b feat(plugin): surface dynamic-tier failures in SessionStart hook (A2)
Fail-open but no longer silent. When the dynamic context fetch yields nothing,
append a short status line to the injected context so a session can tell
'couldn't load live context' apart from 'Scribe had nothing to say':
- endpoint+token present but fetch empty/failed -> 'instance unreachable / request failed'
- endpoint present but token absent -> fingerprints the known Claude Code
  userConfig export gap ('API token did not reach this hook')
A fully unconfigured install (no url AND no token) stays quiet — static-only is
the intended mode there. Static Tier 1 still always carries the mandate.

Refs plan 812 item A2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 16:14:27 -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 f2ab02ba2b ref, plugin: neutral, concrete triggers in static SessionStart mandate
Plain-language 'related prior work' instead of 'prior art'; replace the
dev-shaped 'meaningful landing (a merge, a shipped feature, a finished plan)'
with concrete neutral triggers — log on task completion and when a problem is
found, so direction pivots are captured, not just successes. Keeps the static
mandate domain-neutral (pre-empts B9 drift in plan 812).

Refs task 809 / plan 812 item A1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 15:20:18 -04:00
bvandeusen d11eb9145b feat(plugin): two-tier SessionStart hook — static keyless floor + dynamic enrichment
The SessionStart push channel was single-tier: it curled /api/plugin/context
with a Bearer token and, on any failure (missing/unexported token, network
error, missing curl), injected nothing and exited 0 — silently. A known
upstream Claude Code gap (sensitive userConfig not reliably exported to hook
subprocesses) trips this routinely, so a fresh session gets no signal to reach
for Scribe and falls back to local file-memory (root cause of unlogged work on
remote/rc sessions).

Split into two tiers:
- Tier 1 (static, keyless, networkless, always fires): inject bundled
  scribe_static_context.md — the load-bearing behavioral mandate. Cannot be
  suppressed by the upstream key bug.
- Tier 2 (dynamic, best-effort, fails open): existing curl for live rules +
  active-project context, appended below the static block. Lights up as
  enrichment once the key reaches the hook.

Only jq is now required (JSON envelope); curl/token gate the dynamic tier only.
Bump plugin 0.1.5 -> 0.1.6 so clients pick up the change.

Refs milestone 55; task 809; decision note 810.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 15:01:35 -04:00
bvandeusen e631a4e615 Merge pull request 'feat(plugin): Scribe replaces native memory by instruction; tighten project-scope discipline' (#70) from dev into main
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 49s
CI & Build / Build & push image (push) Successful in 15s
2026-06-10 13:35:28 -04:00