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:
32
.claude/rules/architecture.md
Normal file
32
.claude/rules/architecture.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Architecture rule (always loaded)
|
||||
|
||||
EverOS is a DDD-layered framework. The dependency direction is **single, downward only**:
|
||||
|
||||
```
|
||||
entrypoints → service → memory → infra
|
||||
↓
|
||||
component / core / config
|
||||
```
|
||||
|
||||
- `entrypoints/` — CLI + HTTP API (presentation). No business logic.
|
||||
- `service/` — use-case orchestration (memorize / retrieve / evolve / manage).
|
||||
- `memory/` — domain (extract / search / cascade / prompt_slots / models).
|
||||
- `infra/` — storage adapters (markdown + sqlite + lancedb) and the OME subsystem.
|
||||
- `component/` — injectable providers (llm / embedding / config / utils).
|
||||
- `core/` — runtime base (observability / lifespan / context / persistence primitives).
|
||||
- `config/` — configuration data (Settings + default TOML).
|
||||
|
||||
## Hard constraints (enforced by `import-linter`, run in `make lint`)
|
||||
|
||||
1. **Layering**: an outer layer may import an inner layer, never the reverse.
|
||||
`entrypoints → service → memory → infra`.
|
||||
2. **Private internals**: `service`, `memory`, and `entrypoints` must not import
|
||||
`infra.persistence.{markdown,lancedb,sqlite}.**` internals — go through the
|
||||
package facade (`from everos.infra.persistence.markdown import ...`).
|
||||
3. **OME isolation**: `infra.ome` must not import `persistence`, `memory`,
|
||||
`service`, or `entrypoints`. It is a low-level scheduler with no domain knowledge.
|
||||
|
||||
If a change needs to cross a boundary the wrong way, the design is wrong — refactor,
|
||||
don't add an exception.
|
||||
|
||||
Full rationale: [docs/architecture.md](../../docs/architecture.md).
|
||||
21
.claude/rules/async-programming.md
Normal file
21
.claude/rules/async-programming.md
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
paths:
|
||||
- "src/**/*.py"
|
||||
- "tests/**/*.py"
|
||||
---
|
||||
|
||||
# Async programming rule
|
||||
|
||||
The write/read paths are async end-to-end. Keep them non-blocking.
|
||||
|
||||
- **No blocking calls in async functions** — no synchronous file I/O, no `time.sleep`,
|
||||
no blocking DB/network calls inside `async def`. Ruff `ASYNC` flags the common cases.
|
||||
- **Offload CPU/blocking work** with `anyio.to_thread.run_sync` (or the established
|
||||
helper) rather than blocking the event loop.
|
||||
- **Concurrency** via `asyncio.gather` / `asyncio.TaskGroup` for independent awaits;
|
||||
don't `await` in a loop when the calls are independent.
|
||||
- **Tests**: `pytest-asyncio` is in `auto` mode — an `async def test_*` just works,
|
||||
no `@pytest.mark.asyncio` needed.
|
||||
- **Don't fire-and-forget** without holding a reference (`asyncio.create_task` results
|
||||
must be tracked, or you lose exceptions). The OME subsystem owns the long-running
|
||||
background loops — application code shouldn't spawn its own.
|
||||
17
.claude/rules/code-style.md
Normal file
17
.claude/rules/code-style.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Code style rule (always loaded)
|
||||
|
||||
- **Formatter & linter**: `ruff` is the single tool (replaces black / isort / flake8).
|
||||
Line length 88, target `py312`. Run `make format` to auto-fix; `make lint` checks.
|
||||
- **Active ruff rule sets**: `E F I N UP B SIM ASYNC`. Don't disable a rule inline
|
||||
unless there's a genuine reason — prefer fixing the code.
|
||||
- **Type hints**: annotate every public function signature (params + return). The
|
||||
codebase is ~100% typed; keep it that way.
|
||||
- **`from __future__ import annotations`** at the top of every module — annotations
|
||||
are strings, so forward refs and `X | None` unions are free.
|
||||
- **Prefer `collections.abc`** (`Sequence`, `Mapping`) over concrete `list`/`dict`
|
||||
in signatures; use `Protocol` for structural interfaces.
|
||||
- **No dead code**: no commented-out blocks, no unused imports, no speculative
|
||||
abstractions. Delete rather than comment out.
|
||||
- **Naming**: `*Manager` (orchestrators), `*Provider` (injectable services),
|
||||
`*Reader`/`*Writer` (persistence), `*Recaller` (search routes). Follow the
|
||||
established suffix when adding a sibling.
|
||||
33
.claude/rules/datetime-handling.md
Normal file
33
.claude/rules/datetime-handling.md
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
paths:
|
||||
- "src/**/*.py"
|
||||
- "tests/**/*.py"
|
||||
---
|
||||
|
||||
# Datetime handling rule (two-zone discipline)
|
||||
|
||||
**Never** construct or read "now" directly. All datetime flows through
|
||||
`everos.component.utils.datetime`. This is a **hard CI gate**
|
||||
(`make check-datetime`, wired into `make lint`).
|
||||
|
||||
## Banned (the checker fails the build on these)
|
||||
|
||||
- `datetime.now()`, `datetime.utcnow()`, `datetime.today()`
|
||||
- `time.time()`, `time.time_ns()`
|
||||
- `datetime(YYYY, ...)` without `tzinfo=`
|
||||
- `.astimezone(...)` / `.replace(tzinfo=...)` outside the helper module
|
||||
|
||||
## Use instead
|
||||
|
||||
| Need | Helper |
|
||||
|---|---|
|
||||
| "now" for **storage** (UTC) | `get_utc_now()` |
|
||||
| "now" for **display** (configured TZ) | `get_now_with_timezone()` |
|
||||
| today's date, display TZ | `today_with_timezone()` |
|
||||
| normalize a value to UTC | `ensure_utc(d)` |
|
||||
| render to display TZ | `to_display_tz(d)` |
|
||||
| parse ISO / epoch / str | `from_iso_format(v)`, `from_timestamp(ts)` |
|
||||
| serialize | `to_iso_format(d)`, `to_date_str(d)`, `to_timestamp_ms(d)` |
|
||||
|
||||
**Two zones**: persist in UTC, present in the configured display TZ. Crossing them
|
||||
goes through the helpers — never ad-hoc. See [docs/datetime.md](../../docs/datetime.md).
|
||||
22
.claude/rules/imports.md
Normal file
22
.claude/rules/imports.md
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
paths:
|
||||
- "src/**/*.py"
|
||||
- "tests/**/*.py"
|
||||
---
|
||||
|
||||
# Imports rule
|
||||
|
||||
- **`from __future__ import annotations`** is the first import in every module.
|
||||
- **Import order** (ruff `I` enforces, `make format` fixes): stdlib → third-party
|
||||
→ first-party (`everalgo`, then `everos`). One group per blank-line-separated block.
|
||||
- **Absolute imports** for cross-package references (`from everos.memory import ...`).
|
||||
Relative imports (`from .models import ...`) only **within** a package, typically
|
||||
in its `__init__.py`.
|
||||
- **`TYPE_CHECKING` guard** for import cycles and type-only imports:
|
||||
```python
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from everos.config import Settings
|
||||
```
|
||||
- Never import a private internal across a package boundary — respect the
|
||||
`import-linter` contracts (see [architecture.md](architecture.md)).
|
||||
37
.claude/rules/init-py-and-reexport.md
Normal file
37
.claude/rules/init-py-and-reexport.md
Normal file
@ -0,0 +1,37 @@
|
||||
---
|
||||
paths:
|
||||
- "src/**/__init__.py"
|
||||
- "src/**/*.py"
|
||||
---
|
||||
|
||||
# `__init__.py` and re-export rule
|
||||
|
||||
A package's `__init__.py` is its **public facade**. Consumers import from the
|
||||
package, never from its internal modules.
|
||||
|
||||
## Pattern
|
||||
|
||||
```python
|
||||
"""One-paragraph module docstring: what this package is and how to use it."""
|
||||
|
||||
from .models import Episode as Episode
|
||||
from .models import MemCell as MemCell
|
||||
|
||||
__all__ = [
|
||||
"Episode",
|
||||
"MemCell",
|
||||
]
|
||||
```
|
||||
|
||||
- **Explicit `X as X` redundant-alias form** on each re-export. This is intentional:
|
||||
it marks the name as a deliberate public re-export (ruff `F401` / `PLC0414` aware)
|
||||
rather than an accidental unused import.
|
||||
- **`__all__`** lists every public name, alphabetically sorted, matching the
|
||||
re-exports. It is the contract; keep it in sync.
|
||||
- **Internal modules stay private** — don't re-export helpers that aren't part of
|
||||
the public API.
|
||||
- New subpackage? Add an `__init__.py` with a docstring + `__all__` even if it
|
||||
starts small. Empty-but-documented beats missing.
|
||||
|
||||
This facade discipline is what lets `import-linter` forbid deep imports across
|
||||
package boundaries (see [architecture.md](architecture.md)).
|
||||
12
.claude/rules/language-policy.md
Normal file
12
.claude/rules/language-policy.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Language policy rule (always loaded)
|
||||
|
||||
The project targets a global audience and is **English-first**.
|
||||
|
||||
- **Code, comments, docstrings, docs, commit messages, identifiers**: English only.
|
||||
- **CJK characters are allowed only in**:
|
||||
- test fixtures under `tests/` and `data/` (multilingual input is the point), and
|
||||
- locale-suffixed mirror files (e.g. `*_zh.json`).
|
||||
- Do **not** introduce CJK into `src/`, `docs/`, or config.
|
||||
|
||||
Enforcement: `make check-cjk` scans for stray CJK outside the allowlist (advisory).
|
||||
Keep user-facing strings and error messages in English.
|
||||
23
.claude/rules/logging-observability.md
Normal file
23
.claude/rules/logging-observability.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
paths:
|
||||
- "src/**/*.py"
|
||||
---
|
||||
|
||||
# Logging & observability rule
|
||||
|
||||
- **Use the project logger**, never `print` or the stdlib `logging` directly:
|
||||
```python
|
||||
from everos.core.observability.logging import get_logger
|
||||
logger = get_logger(__name__)
|
||||
```
|
||||
- **Structured logging** (`structlog`): pass context as keyword fields, not f-strings.
|
||||
```python
|
||||
logger.info("memory.search.completed", owner_type=owner, n_results=len(items))
|
||||
```
|
||||
Event name first (dotted, stable), structured kwargs after. This keeps logs
|
||||
queryable and avoids leaking interpolated PII into the message string.
|
||||
- **Levels**: `debug` for developer detail, `info` for lifecycle milestones,
|
||||
`warning` for recoverable anomalies, `error` for failures with a stack/context.
|
||||
- **Metrics** go through `core.observability.metrics` (Prometheus); don't invent
|
||||
ad-hoc counters. Histograms/counters/gauges have registry helpers.
|
||||
- Don't log secrets, API keys, or full memory content at `info`/above.
|
||||
35
.claude/rules/module-docstring.md
Normal file
35
.claude/rules/module-docstring.md
Normal file
@ -0,0 +1,35 @@
|
||||
---
|
||||
paths:
|
||||
- "src/everos/infra/**/*.py"
|
||||
- "src/everos/memory/**/*.py"
|
||||
- "src/everos/service/**/*.py"
|
||||
- "src/everos/component/**/*.py"
|
||||
- "src/everos/core/**/*.py"
|
||||
---
|
||||
|
||||
# Module docstring rule
|
||||
|
||||
Every non-trivial module in the domain/infra layers opens with a docstring that
|
||||
explains **intent and contract**, not just a one-line label.
|
||||
|
||||
A good module docstring states:
|
||||
|
||||
- **What** the module is responsible for (one sentence).
|
||||
- **The load-bearing invariants** — the rules a reader must know to change it
|
||||
safely (partition keys, what is/isn't written, defaults, ignored flags).
|
||||
- **External usage** when the module is a package facade (a short import example).
|
||||
|
||||
Example (abbreviated, from `memory/search/manager.py`):
|
||||
|
||||
```python
|
||||
"""SearchManager — top-level orchestrator for POST /api/v1/memory/search.
|
||||
|
||||
Hard partition by owner_type: user → episodes (+ profiles), agent →
|
||||
agent_cases + agent_skills. The manager never writes to storage; it only
|
||||
reads LanceDB + markdown.
|
||||
"""
|
||||
```
|
||||
|
||||
Prefer prose that would save the next engineer a debugging session over
|
||||
boilerplate. If a module is genuinely trivial (a 3-line constant), a one-liner
|
||||
is fine — but most modules here are not.
|
||||
27
.claude/rules/testing.md
Normal file
27
.claude/rules/testing.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
paths:
|
||||
- "tests/**/*.py"
|
||||
---
|
||||
|
||||
# Testing rule
|
||||
|
||||
Tests mirror the source layout: `tests/unit/test_<layer>/...`,
|
||||
`tests/integration/...`, `tests/e2e/...`.
|
||||
|
||||
- **Structure**: `tests/unit/` mirrors `src/everos/` package-for-package. Put a test
|
||||
next to where its subject lives in the mirror.
|
||||
- **Async**: `pytest-asyncio` is in `auto` mode — write `async def test_*` directly,
|
||||
no marker needed.
|
||||
- **Markers** (default run excludes both — `-m "not slow and not live_llm"`):
|
||||
- `@pytest.mark.slow` — tests ≥ ~10s.
|
||||
- `@pytest.mark.live_llm` — needs real LLM/embedder credentials.
|
||||
Keep unit tests fast and credential-free; push anything needing real services
|
||||
behind a marker or into `integration`/`e2e`.
|
||||
- **Fixtures**: shared fixtures live in the nearest `conftest.py`. The root conftest
|
||||
resets module caches (settings/logging/datetime) per test — rely on that for
|
||||
isolation rather than mutating globals.
|
||||
- **Module docstring** on each test file stating what contract it pins (see existing
|
||||
tests for the style).
|
||||
- **Coverage gate**: `make cov` enforces 80% (`--cov-fail-under=80`). New code should
|
||||
not drop coverage below the gate.
|
||||
- Run `make test` (unit) and `make integration` before pushing; both run in CI.
|
||||
19
.claude/settings.json
Normal file
19
.claude/settings.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(make:*)",
|
||||
"Bash(uv sync:*)",
|
||||
"Bash(uv run:*)",
|
||||
"Bash(uv pip:*)",
|
||||
"Bash(ruff:*)",
|
||||
"Bash(pytest:*)",
|
||||
"Bash(git status:*)",
|
||||
"Bash(git diff:*)",
|
||||
"Bash(git log:*)",
|
||||
"Bash(git branch:*)",
|
||||
"Bash(git show:*)",
|
||||
"Bash(gh pr view:*)",
|
||||
"Bash(gh pr list:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
54
.claude/skills/commit/SKILL.md
Normal file
54
.claude/skills/commit/SKILL.md
Normal file
@ -0,0 +1,54 @@
|
||||
---
|
||||
name: commit
|
||||
description: Stage and create a Conventional Commits message for the current change
|
||||
---
|
||||
|
||||
# /commit
|
||||
|
||||
Create a well-formed commit following the [Conventional Commits](https://www.conventionalcommits.org)
|
||||
standard. The format is enforced by `gitlint` in the `commit-msg` hook.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Run `git status` and `git diff` (and `git diff --staged`) to see what changed.
|
||||
2. Review recent history for style: `git log --oneline -10`.
|
||||
3. Group the change into a single focused commit. If the working tree mixes
|
||||
unrelated changes, stage selectively (`git add -p` / specific paths) rather
|
||||
than committing everything at once.
|
||||
4. Write the message in **Conventional Commits** form:
|
||||
|
||||
```
|
||||
<type>[(scope)][!]: <imperative summary, ≤72 chars>
|
||||
|
||||
<optional body: what & why, wrapped at 72>
|
||||
|
||||
<optional footer: BREAKING CHANGE: …, Refs: #123>
|
||||
```
|
||||
|
||||
5. Never use `--no-verify`. If pre-commit hooks fail, fix the cause and re-commit.
|
||||
6. Do not commit secrets, generated artifacts, or work-in-progress to a
|
||||
protected branch (`main` / `dev` / `master`).
|
||||
|
||||
## Types
|
||||
|
||||
| Type | Use for |
|
||||
|---|---|
|
||||
| `feat` | new feature |
|
||||
| `fix` | bug fix |
|
||||
| `refactor` | behavior-preserving restructure |
|
||||
| `test` | add / update tests |
|
||||
| `docs` | documentation |
|
||||
| `style` | formatting only |
|
||||
| `perf` | performance |
|
||||
| `chore` | config / build / tooling |
|
||||
| `build` | build system or dependencies |
|
||||
| `ci` | CI configuration |
|
||||
| `revert` | revert a previous commit |
|
||||
|
||||
## Notes
|
||||
|
||||
- No emoji — the title must start with the `type`.
|
||||
- One logical change per commit; keep the history bisectable.
|
||||
- The summary is imperative mood: "add", not "added" / "adds".
|
||||
- `scope` is optional: `fix(search): …`. A `!` before the colon (or a
|
||||
`BREAKING CHANGE:` footer) marks a breaking change.
|
||||
43
.claude/skills/new-branch/SKILL.md
Normal file
43
.claude/skills/new-branch/SKILL.md
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
name: new-branch
|
||||
description: Create a branch following the project's GitFlow Lite model
|
||||
---
|
||||
|
||||
# /new-branch
|
||||
|
||||
Cut a new branch under the GitFlow Lite model.
|
||||
|
||||
## Branch model
|
||||
|
||||
```
|
||||
master = released / stable (tagged on release; protected)
|
||||
dev = integration branch (protected)
|
||||
feat/* = cut from dev → PR → merge into dev
|
||||
fix/* = cut from dev → PR → merge into dev
|
||||
hotfix/* = cut from master → merge into master AND synced into dev (double merge)
|
||||
release = dev → master + tag on master (no separate release branch)
|
||||
```
|
||||
|
||||
## Steps
|
||||
|
||||
1. Ask (or infer) the change type: `feat`, `fix`, or `hotfix`.
|
||||
2. Pick the parent:
|
||||
- `feat/*`, `fix/*` → branch from **`dev`**.
|
||||
- `hotfix/*` → branch from **`master`**.
|
||||
3. Update the parent first:
|
||||
```bash
|
||||
git checkout <parent>
|
||||
git pull --ff-only
|
||||
```
|
||||
4. Create the branch with a kebab-case slug:
|
||||
```bash
|
||||
git checkout -b feat/<short-slug>
|
||||
```
|
||||
5. For a `hotfix`, remember it must later merge into **both** `master` and `dev`.
|
||||
|
||||
## Naming
|
||||
|
||||
- `feat/add-agentic-rerank`, `fix/empty-profile-crash`, `hotfix/lancedb-conn-leak`.
|
||||
- Lowercase, hyphen-separated, no spaces, concise.
|
||||
|
||||
Never commit directly to `master` or `dev` — always via a branch + PR.
|
||||
41
.claude/skills/pr/SKILL.md
Normal file
41
.claude/skills/pr/SKILL.md
Normal file
@ -0,0 +1,41 @@
|
||||
---
|
||||
name: pr
|
||||
description: Open a GitHub PR targeting the correct branch with the project template
|
||||
---
|
||||
|
||||
# /pr
|
||||
|
||||
Open a pull request on GitHub using the `gh` CLI and the repo's PR template.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Confirm the branch and target:
|
||||
- `feat/*`, `fix/*` → base **`dev`**.
|
||||
- `hotfix/*` → base **`master`** (then a follow-up PR/sync into `dev`).
|
||||
2. Ensure local checks pass first:
|
||||
```bash
|
||||
make ci
|
||||
```
|
||||
Do not open a PR with failing lint/tests.
|
||||
3. Push the branch:
|
||||
```bash
|
||||
git push -u origin HEAD
|
||||
```
|
||||
4. Create the PR, filling the template
|
||||
([.github/PULL_REQUEST_TEMPLATE.md](../../../.github/PULL_REQUEST_TEMPLATE.md)):
|
||||
```bash
|
||||
gh pr create --base dev --fill-first
|
||||
```
|
||||
Then edit the body to complete each section:
|
||||
- **Summary** — what changed and why.
|
||||
- **Area** — tick the relevant box (architecture / benchmark / use case /
|
||||
docs / DX / CI-build-release).
|
||||
- **Verification** — paste the commands you ran (`make ci`, manual checks).
|
||||
- **Checklist** — tick honestly; don't tick boxes you didn't satisfy.
|
||||
- **Notes for Reviewers** — anything subtle.
|
||||
|
||||
## Notes
|
||||
|
||||
- Keep the PR scoped to one area. Split unrelated changes.
|
||||
- If `make ci` was not fully run, say so in Verification rather than implying it passed.
|
||||
- A `hotfix` is not done until it has landed on **both** `master` and `dev`.
|
||||
Reference in New Issue
Block a user