chore: initialize EverOS 1.0.0

md-first memory extraction framework for AI agents.

Markdown is the single source of truth; SQLite holds state and LanceDB
provides the rebuildable vector + BM25 + scalar index. The codebase follows
a single-direction DDD layering (entrypoints -> service -> memory -> infra,
with component / core / config cross-cutting) enforced by import-linter.

Engineering surface:
- Coding conventions in .claude/rules/ (path-scoped) and workflows in
  .claude/skills/ (/commit, /new-branch, /pr).
- GitHub Actions CI runs make lint + test + integration; pre-commit mirrors
  the gates locally (ruff, hygiene hooks, gitlint commit-msg).
- Commit messages follow Conventional Commits, enforced by gitlint.
- make lint also enforces datetime two-zone discipline and OpenAPI drift.
This commit is contained in:
Elliot Chen
2026-06-05 22:35:51 +08:00
commit 518b8eca85
636 changed files with 160553 additions and 0 deletions

View File

@ -0,0 +1,177 @@
"""Base business reader for daily-log markdown files.
Symmetric to :class:`BaseDailyWriter`: reads the daily-log file for
a given ``(scope_id, date)``, locates entries by id within it, and
optionally upgrades them to :class:`StructuredEntry` so service-layer
callers don't have to re-do that plumbing each time.
Subclass usage::
class _MemcellReader(BaseDailyReader):
schema = UserMemcellDailyFrontmatter
reader = _MemcellReader(root)
parsed = reader.read_for("u_jason") # today's file
entry = reader.find_entry("u_jason", "umc_20260422_0001")
structured = reader.find_structured("u_jason", entry.id)
The reader does **not** typed-parse the file's frontmatter dict — the
schema is used only for path resolution (matching what the appender
writes). Frontmatter validation belongs to higher-level callers that
know the business rules.
Path resolution is identical to :class:`BaseDailyWriter` (same
``SCOPE_DIR`` / ``DIR_NAME`` / ``FILE_PREFIX`` ClassVars), so a
reader and writer bound to the same schema agree on every path.
"""
from __future__ import annotations
import datetime as _dt
from pathlib import Path
from typing import ClassVar
import anyio
from everos.component.utils.datetime import today_with_timezone
from everos.core.persistence import (
BaseFrontmatter,
Entry,
EntryId,
MarkdownReader,
MemoryRoot,
ParsedMarkdown,
StructuredEntry,
find_entry,
)
class BaseDailyReader:
"""Single-record reader for daily-log markdown files.
Subclasses bind a :class:`BaseFrontmatter` subclass via the
``schema`` ClassVar. The schema must declare ``SCOPE_DIR``,
``DIR_NAME``, and ``FILE_PREFIX`` (same set the appender uses); no
``ENTRY_ID_PREFIX`` requirement here because the reader takes the
entry id from the caller, not the schema.
"""
schema: ClassVar[type[BaseFrontmatter]] # subclass must declare
def __init__(self, root: MemoryRoot) -> None:
schema = getattr(type(self), "schema", None)
if schema is None:
raise TypeError(
f"{type(self).__name__} must declare a class-level ``schema`` attribute"
)
for attr in ("SCOPE_DIR", "DIR_NAME", "FILE_PREFIX"):
if not getattr(schema, attr, None):
raise TypeError(f"{schema.__name__} missing ClassVar {attr!r}")
self._root = root
# ── Public API ────────────────────────────────────────────────────────
async def read_for(
self,
scope_id: str,
date: _dt.date | None = None,
*,
app_id: str = "default",
project_id: str = "default",
) -> ParsedMarkdown | None:
"""Read the daily-log file for ``(scope_id, date)``.
Args:
scope_id: ``user_id`` or ``agent_id``.
date: Date bucket — defaults to today in the configured TZ.
app_id: App scope segment (defaults to the ``"default"`` space).
project_id: Project scope segment (defaults to ``"default"``).
Returns:
:class:`ParsedMarkdown` (frontmatter dict + body + entries),
or ``None`` when the file does not exist on disk. ``None``
avoids forcing every caller to wrap reads in try/except —
"no file yet" is a normal early state.
"""
path = self._resolve_path(
scope_id, date or today_with_timezone(), app_id, project_id
)
if not await anyio.Path(path).is_file():
return None
return await MarkdownReader.read(path)
async def find_entry(
self,
scope_id: str,
entry_id: str | EntryId,
*,
app_id: str = "default",
project_id: str = "default",
) -> Entry | None:
"""Locate the entry with ``entry_id`` inside its daily-log file.
The date bucket is taken from the entry id (an :class:`EntryId`
encodes its own date), so the caller doesn't pass a date.
Returns ``None`` if either the file or the entry is missing.
"""
eid = entry_id if isinstance(entry_id, EntryId) else EntryId.parse(entry_id)
eid_str = eid.format()
parsed = await self.read_for(
scope_id, eid.date, app_id=app_id, project_id=project_id
)
if parsed is None:
return None
return find_entry(parsed.body, eid_str)
async def find_structured(
self,
scope_id: str,
entry_id: str | EntryId,
*,
app_id: str = "default",
project_id: str = "default",
) -> StructuredEntry | None:
"""Locate the entry and parse its body as audit-form data.
Sugar over :meth:`find_entry` + :meth:`Entry.as_structured`.
Returns ``None`` if the entry is missing.
"""
entry = await self.find_entry(
scope_id, entry_id, app_id=app_id, project_id=project_id
)
if entry is None:
return None
return entry.as_structured()
def path_for(
self,
scope_id: str,
date: _dt.date | None = None,
*,
app_id: str = "default",
project_id: str = "default",
) -> Path:
"""Return the daily-log path for ``scope_id`` on ``date`` (today default).
Public counterpart of :meth:`_resolve_path` — symmetric with
:meth:`BaseDailyWriter.path_for`. Does not check existence.
"""
return self._resolve_path(
scope_id, date or today_with_timezone(), app_id, project_id
)
# ── Internals ─────────────────────────────────────────────────────────
def _resolve_path(
self, scope_id: str, date: _dt.date, app_id: str, project_id: str
) -> Path:
"""Build the daily-log path for ``scope_id`` on ``date``."""
# SCOPE_DIR ("users" / "agents") names the matching MemoryRoot method,
# which prepends the <app>/<project> business prefix.
scope_dir = getattr(self._root, f"{self.schema.SCOPE_DIR}_dir")
return (
scope_dir(app_id, project_id)
/ scope_id
/ self.schema.DIR_NAME
/ f"{self.schema.FILE_PREFIX}-{date.isoformat()}.md"
)