chore: remove pre-pivot dead code + finish Scribe rebrand (#599 t1-3)
- Header wordmark Fabled -> Scribe; fable:calendar-changed event -> scribe:calendar-changed; SettingsView CSS comment. - Drop dead Project.auto_summary + summary_updated_at columns (migration 0063) -- the Ollama-era summarizer is gone; model + 2 frontend types + projects test updated. - Remove pivot vestiges: diagnostics _curator_busy()/curator_busy heartbeat field, tz BRIEFING_DAY_START_HOUR/user_briefing_date dead aliases, the ignored 'model' param on get_embedding (+ its test). ruff src/ clean; CI is the gate. Part of scribe plan #599. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
"""drop dead Project.auto_summary columns
|
||||||
|
|
||||||
|
Revision ID: 0063
|
||||||
|
Revises: 0062
|
||||||
|
Create Date: 2026-06-03
|
||||||
|
|
||||||
|
auto_summary + summary_updated_at were written by generate_project_summary
|
||||||
|
(Ollama), removed in the MCP pivot. Nothing has populated them since; the
|
||||||
|
field was stale-or-NULL. Drop the columns.
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
revision = "0063"
|
||||||
|
down_revision = "0062"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.drop_column("projects", "auto_summary")
|
||||||
|
op.drop_column("projects", "summary_updated_at")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.add_column(
|
||||||
|
"projects",
|
||||||
|
sa.Column("summary_updated_at", sa.DateTime(timezone=True), nullable=True),
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"projects",
|
||||||
|
sa.Column("auto_summary", sa.Text(), nullable=True),
|
||||||
|
)
|
||||||
@@ -39,7 +39,7 @@ router.afterEach(() => {
|
|||||||
<!-- Left: brand -->
|
<!-- Left: brand -->
|
||||||
<router-link to="/" class="nav-brand">
|
<router-link to="/" class="nav-brand">
|
||||||
<AppLogo :size="34" />
|
<AppLogo :size="34" />
|
||||||
<span class="brand-text">Fabled</span>
|
<span class="brand-text">Scribe</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<!-- Center: primary navigation (desktop) -->
|
<!-- Center: primary navigation (desktop) -->
|
||||||
|
|||||||
@@ -168,11 +168,11 @@ function onCalendarChanged() {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener("mousedown", onDocClick);
|
document.addEventListener("mousedown", onDocClick);
|
||||||
document.addEventListener("fable:calendar-changed", onCalendarChanged);
|
document.addEventListener("scribe:calendar-changed", onCalendarChanged);
|
||||||
});
|
});
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener("mousedown", onDocClick);
|
document.removeEventListener("mousedown", onDocClick);
|
||||||
document.removeEventListener("fable:calendar-changed", onCalendarChanged);
|
document.removeEventListener("scribe:calendar-changed", onCalendarChanged);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Calendar callbacks ─────────────────────────────────────────────────────
|
// ── Calendar callbacks ─────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ interface Project {
|
|||||||
goal: string | null;
|
goal: string | null;
|
||||||
status: "active" | "paused" | "completed" | "archived";
|
status: "active" | "paused" | "completed" | "archived";
|
||||||
color: string | null;
|
color: string | null;
|
||||||
auto_summary: string | null;
|
|
||||||
permission?: string;
|
permission?: string;
|
||||||
is_shared?: boolean;
|
is_shared?: boolean;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ interface Project {
|
|||||||
goal: string | null;
|
goal: string | null;
|
||||||
status: "active" | "paused" | "completed" | "archived";
|
status: "active" | "paused" | "completed" | "archived";
|
||||||
color: string | null;
|
color: string | null;
|
||||||
auto_summary: string | null;
|
|
||||||
permission?: string;
|
permission?: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
|
|||||||
@@ -3076,7 +3076,7 @@ function formatUserDate(iso: string): string {
|
|||||||
.settings-empty { opacity: 0.5; margin-top: 1rem; }
|
.settings-empty { opacity: 0.5; margin-top: 1rem; }
|
||||||
.settings-description { opacity: 0.7; margin-bottom: 1rem; line-height: 1.5; }
|
.settings-description { opacity: 0.7; margin-bottom: 1rem; line-height: 1.5; }
|
||||||
|
|
||||||
/* Fable MCP section */
|
/* Scribe MCP section */
|
||||||
.mcp-status { opacity: 0.6; font-size: 0.9rem; }
|
.mcp-status { opacity: 0.6; font-size: 0.9rem; }
|
||||||
.mcp-unavailable p { opacity: 0.7; }
|
.mcp-unavailable p { opacity: 0.7; }
|
||||||
.mcp-available { display: flex; flex-direction: column; gap: 1.25rem; }
|
.mcp-available { display: flex; flex-direction: column; gap: 1.25rem; }
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import enum
|
import enum
|
||||||
from datetime import datetime
|
from sqlalchemy import ForeignKey, Integer, Text
|
||||||
from sqlalchemy import DateTime, ForeignKey, Integer, Text
|
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
from scribe.models import Base
|
from scribe.models import Base
|
||||||
from scribe.models.base import TimestampMixin, SoftDeleteMixin
|
from scribe.models.base import TimestampMixin, SoftDeleteMixin
|
||||||
@@ -22,10 +21,6 @@ class Project(Base, TimestampMixin, SoftDeleteMixin):
|
|||||||
goal: Mapped[str] = mapped_column(Text, default="")
|
goal: Mapped[str] = mapped_column(Text, default="")
|
||||||
status: Mapped[str] = mapped_column(Text, default="active")
|
status: Mapped[str] = mapped_column(Text, default="active")
|
||||||
color: Mapped[str | None] = mapped_column(Text, nullable=True) # hex color
|
color: Mapped[str | None] = mapped_column(Text, nullable=True) # hex color
|
||||||
auto_summary: Mapped[str | None] = mapped_column(Text, nullable=True, default=None)
|
|
||||||
summary_updated_at: Mapped[datetime | None] = mapped_column(
|
|
||||||
DateTime(timezone=True), nullable=True, default=None
|
|
||||||
)
|
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
return {
|
return {
|
||||||
@@ -36,7 +31,6 @@ class Project(Base, TimestampMixin, SoftDeleteMixin):
|
|||||||
"goal": self.goal,
|
"goal": self.goal,
|
||||||
"status": self.status,
|
"status": self.status,
|
||||||
"color": self.color,
|
"color": self.color,
|
||||||
"auto_summary": self.auto_summary,
|
|
||||||
"created_at": self.created_at.isoformat(),
|
"created_at": self.created_at.isoformat(),
|
||||||
"updated_at": self.updated_at.isoformat(),
|
"updated_at": self.updated_at.isoformat(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ clear cause in the logs. This module adds three things designed to make
|
|||||||
the crash class identifiable from logs alone:
|
the crash class identifiable from logs alone:
|
||||||
|
|
||||||
1. **Heartbeat** — once per minute, log a snapshot of process resources
|
1. **Heartbeat** — once per minute, log a snapshot of process resources
|
||||||
(RSS memory, asyncio task count, DB pool checked-in/out, curator
|
(RSS memory, asyncio task count, DB pool checked-in/out). A sudden
|
||||||
busy state). A sudden silence in heartbeats lets you bound the
|
silence in heartbeats lets you bound the crash time to within a
|
||||||
crash time to within a minute, and the last snapshot before silence
|
minute, and the last snapshot before silence usually rules in or out
|
||||||
usually rules in or out memory growth / pool exhaustion / hung
|
memory growth / pool exhaustion.
|
||||||
curator pass.
|
|
||||||
|
|
||||||
2. **Signal handler** — catches SIGTERM and SIGINT, logs them with the
|
2. **Signal handler** — catches SIGTERM and SIGINT, logs them with the
|
||||||
sender's intent ("docker stop", "swarm restart", "manual ctrl-C")
|
sender's intent ("docker stop", "swarm restart", "manual ctrl-C")
|
||||||
@@ -108,11 +107,6 @@ def _asyncio_task_count() -> int | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _curator_busy() -> bool | None:
|
|
||||||
# Curator was removed in Phase 8 of the MCP-first pivot. Always False.
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _uptime_secs() -> float | None:
|
def _uptime_secs() -> float | None:
|
||||||
if _started_at is None:
|
if _started_at is None:
|
||||||
return None
|
return None
|
||||||
@@ -134,7 +128,6 @@ def _snapshot_dict(reason: str = "heartbeat") -> dict[str, Any]:
|
|||||||
"rss_mb": _process_rss_mb(),
|
"rss_mb": _process_rss_mb(),
|
||||||
"asyncio_tasks": _asyncio_task_count(),
|
"asyncio_tasks": _asyncio_task_count(),
|
||||||
"db_pool": _db_pool_stats(),
|
"db_pool": _db_pool_stats(),
|
||||||
"curator_busy": _curator_busy(),
|
|
||||||
"pid": os.getpid(),
|
"pid": os.getpid(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,9 +277,9 @@ async def _heartbeat_loop() -> None:
|
|||||||
_persistent_log(
|
_persistent_log(
|
||||||
logging.INFO,
|
logging.INFO,
|
||||||
"diag heartbeat: uptime=%ss rss=%sMB asyncio_tasks=%s "
|
"diag heartbeat: uptime=%ss rss=%sMB asyncio_tasks=%s "
|
||||||
"db_pool=%s curator_busy=%s",
|
"db_pool=%s",
|
||||||
snap["uptime_secs"], snap["rss_mb"], snap["asyncio_tasks"],
|
snap["uptime_secs"], snap["rss_mb"], snap["asyncio_tasks"],
|
||||||
snap["db_pool"], snap["curator_busy"],
|
snap["db_pool"],
|
||||||
)
|
)
|
||||||
_write_state_atomic(_CURRENT_STATE_PATH, snap)
|
_write_state_atomic(_CURRENT_STATE_PATH, snap)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -53,12 +53,9 @@ async def _get_model():
|
|||||||
return _model
|
return _model
|
||||||
|
|
||||||
|
|
||||||
async def get_embedding(text: str, model: str | None = None) -> list[float]:
|
async def get_embedding(text: str) -> list[float]:
|
||||||
"""Get an embedding vector for the given text.
|
"""Get an embedding vector for the given text.
|
||||||
|
|
||||||
The ``model`` parameter is preserved for backward compatibility with the
|
|
||||||
Ollama era but is now ignored — fastembed uses a single fixed model.
|
|
||||||
|
|
||||||
Raises if the fastembed model fails to load. Callers should catch and
|
Raises if the fastembed model fails to load. Callers should catch and
|
||||||
degrade to keyword search.
|
degrade to keyword search.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -13,14 +13,11 @@ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|||||||
|
|
||||||
from scribe.services.settings import get_setting
|
from scribe.services.settings import get_setting
|
||||||
|
|
||||||
# Day-rollover boundary for journal/day-anchored views. The "day" flips at
|
# Day-rollover boundary for day-anchored "today" logic. The "day" flips at
|
||||||
# this local hour (not midnight) so the 00:00–04:00 local window still
|
# this local hour (not midnight) so the 00:00–04:00 local window still
|
||||||
# shows yesterday's content until the user "starts" the new day.
|
# shows yesterday's content until the user "starts" the new day.
|
||||||
DAY_ROLLOVER_HOUR = 4
|
DAY_ROLLOVER_HOUR = 4
|
||||||
|
|
||||||
# Backwards-compat alias for code paths still referencing the old name.
|
|
||||||
BRIEFING_DAY_START_HOUR = DAY_ROLLOVER_HOUR
|
|
||||||
|
|
||||||
|
|
||||||
async def get_user_tz(user_id: int) -> ZoneInfo:
|
async def get_user_tz(user_id: int) -> ZoneInfo:
|
||||||
"""Return the user's IANA ``ZoneInfo``, falling back to UTC."""
|
"""Return the user's IANA ``ZoneInfo``, falling back to UTC."""
|
||||||
@@ -41,12 +38,8 @@ async def user_day_date(user_id: int) -> date:
|
|||||||
"""Return the current "day" in the user's local timezone.
|
"""Return the current "day" in the user's local timezone.
|
||||||
|
|
||||||
The day flips at ``DAY_ROLLOVER_HOUR`` (4am local) rather than midnight,
|
The day flips at ``DAY_ROLLOVER_HOUR`` (4am local) rather than midnight,
|
||||||
so the 00:00–04:00 local window still returns *yesterday* — the user's
|
so the 00:00–04:00 local window still returns *yesterday* — a day-anchored
|
||||||
journal stays anchored to yesterday until they cross the rollover.
|
view stays on yesterday until the user crosses the rollover.
|
||||||
"""
|
"""
|
||||||
tz = await get_user_tz(user_id)
|
tz = await get_user_tz(user_id)
|
||||||
return (datetime.now(tz) - timedelta(hours=DAY_ROLLOVER_HOUR)).date()
|
return (datetime.now(tz) - timedelta(hours=DAY_ROLLOVER_HOUR)).date()
|
||||||
|
|
||||||
|
|
||||||
# Backwards-compat alias used by briefing-only modules being torn down in Stage B.
|
|
||||||
user_briefing_date = user_day_date
|
|
||||||
|
|||||||
@@ -62,22 +62,6 @@ async def test_get_embedding_returns_list_of_floats():
|
|||||||
fake_embedder.embed.assert_called_once_with(["hello world"])
|
fake_embedder.embed.assert_called_once_with(["hello world"])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_get_embedding_ignores_legacy_model_param():
|
|
||||||
"""The `model` kwarg is preserved for backward-compat but should not affect
|
|
||||||
the fastembed call."""
|
|
||||||
fake_vec = MagicMock()
|
|
||||||
fake_vec.tolist.return_value = [0.0]
|
|
||||||
fake_embedder = MagicMock()
|
|
||||||
fake_embedder.embed = MagicMock(return_value=iter([fake_vec]))
|
|
||||||
with patch(
|
|
||||||
"scribe.services.embeddings._get_model",
|
|
||||||
AsyncMock(return_value=fake_embedder),
|
|
||||||
):
|
|
||||||
out = await get_embedding("x", model="ignored-model-name")
|
|
||||||
assert out == [0.0]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_embedding_propagates_model_load_failures():
|
async def test_get_embedding_propagates_model_load_failures():
|
||||||
"""If fastembed can't initialize, the error propagates — callers catch
|
"""If fastembed can't initialize, the error propagates — callers catch
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ def _bind_user():
|
|||||||
def _fake_project(**overrides) -> MagicMock:
|
def _fake_project(**overrides) -> MagicMock:
|
||||||
p = MagicMock()
|
p = MagicMock()
|
||||||
base = {"id": 1, "title": "P", "description": "", "goal": "",
|
base = {"id": 1, "title": "P", "description": "", "goal": "",
|
||||||
"status": "active", "color": None, "auto_summary": None}
|
"status": "active", "color": None}
|
||||||
base.update(overrides)
|
base.update(overrides)
|
||||||
p.to_dict.return_value = base
|
p.to_dict.return_value = base
|
||||||
return p
|
return p
|
||||||
|
|||||||
Reference in New Issue
Block a user