diff --git a/reme4/config/default.yaml b/reme4/config/default.yaml index 09ce7a7f..4ae80805 100644 --- a/reme4/config/default.yaml +++ b/reme4/config/default.yaml @@ -109,19 +109,19 @@ jobs: daily_create: backend: base - description: "Provision a note slug under a daily folder: daily//.md" + description: "Provision a session note under a daily folder: daily//.md" parameters: type: object properties: - slug: + session_id: type: string - description: "the file stem: event name or topic name" + description: "the session identifier (also the file stem)" date: type: string description: "YYYY-MM-DD; empty = today" default: "" required: - - slug + - session_id steps: - backend: daily_create_step diff --git a/reme4/config/qwenpaw.yaml b/reme4/config/qwenpaw.yaml index 5c61b8ba..e69de29b 100644 --- a/reme4/config/qwenpaw.yaml +++ b/reme4/config/qwenpaw.yaml @@ -1,474 +0,0 @@ -service: - backend: http - -daily_dir: daily -digest_dir: digest -resource_dir: "" - -jobs: - version: - backend: base - description: "return reme package version" - parameters: - type: object - properties: {} - steps: - - backend: version_step - - health_check: - backend: base - description: "return a concise health-check snapshot of reme components" - parameters: - type: object - properties: {} - steps: - - backend: health_check_step - - help: - backend: base - description: "list all registered jobs with their metadata" - parameters: - type: object - properties: {} - steps: - - backend: help_step - - traverse: - backend: base - description: "Walk the wikilink graph from a path." - parameters: - type: object - properties: - path: - type: string - description: "path" - depth: - type: integer - description: "hop limit" - default: 1 - direction: - type: string - enum: - - forward - - backward - - both - default: both - required: - - path - steps: - - backend: traverse_step - - reindex: - backend: base - description: "wipe the file store and rebuild it from the watcher's tracked files" - parameters: - type: object - properties: {} - steps: - - backend: clear_and_scan_step - - backend: update_index_step - persist: true - - index_changes: - backend: base - description: "apply a batch of file changes (added/modified/deleted) into file_store" - parameters: - type: object - properties: - changes: - type: array - description: "list of change items" - items: - type: object - properties: - change: - type: string - enum: [added, modified, deleted] - description: "type of file change" - path: - type: string - description: "absolute file path" - required: - - change - - path - required: - - changes - steps: - - backend: index_changes_step - - # ════════════════════════════════════════════════════════════════════ - # ATOMIC TOOLS — same surface plugins/reme-{service,expert} expose - # ════════════════════════════════════════════════════════════════════ - - # ── Retrieve ─────────────────────────────────────────────────────── - search: - backend: base - description: "Hybrid vault search (vector + BM25, RRF-fused)." - parameters: - type: object - properties: - query: - type: string - description: "search query" - limit: - type: integer - description: "max results" - default: 5 - min_score: - type: number - description: "min fused score" - default: 0.0 - required: - - query - steps: - - backend: search_step - vector_weight: 0.7 - candidate_multiplier: 3.0 - expand_links: true - max_links_per_direction: 10 - - - - # ── Read Operations ─────────────────────────────────────────────────────────── - list: - backend: base - description: "List files under a vault path." - parameters: - type: object - properties: - path: - type: string - description: "vault-relative dir; empty = root" - default: "" - recursive: - type: boolean - description: "recurse" - default: false - limit: - type: integer - description: "max results" - default: 100 - steps: - - backend: list_step - - read: - backend: base - description: "Read a markdown file under the vault." - parameters: - type: object - properties: - path: - type: string - description: "vault-relative path; markdown only" - start_line: - type: integer - description: "first line (1-based, inclusive)" - end_line: - type: integer - description: "last line (1-based, inclusive)" - required: - - path - steps: - - backend: read_step - - stat: - backend: base - description: "Stat a vault file (size, mtime, exists, is_dir, is_file)." - parameters: - type: object - properties: - path: - type: string - description: "vault-relative path" - required: - - path - steps: - - backend: stat_step - - frontmatter_read: - backend: base - description: "Read a file's YAML frontmatter as a dict." - parameters: - type: object - properties: - path: - type: string - description: "vault-relative path" - required: - - path - steps: - - backend: frontmatter_read_step - - # ── Write Operations────────────────────────────────────────────────────────── - write: - backend: base - description: "Write a markdown file (create or overwrite) with name/description frontmatter." - parameters: - type: object - properties: - path: - type: string - description: "vault-relative path; markdown only" - name: - type: string - description: "frontmatter name" - description: - type: string - description: "frontmatter description" - content: - type: string - description: "body" - required: - - path - - name - - description - - content - steps: - - backend: write_step - - edit: - backend: base - description: "Find-and-replace in a markdown file (all occurrences)." - parameters: - type: object - properties: - path: - type: string - description: "vault-relative path" - old: - type: string - description: "text to find" - new: - type: string - description: "replacement" - default: "" - required: - - path - - old - - new - steps: - - backend: edit_step - - append: - backend: base - description: "Append content to a markdown file." - parameters: - type: object - properties: - path: - type: string - description: "vault-relative path" - content: - type: string - description: "content to append" - required: - - path - - content - steps: - - backend: append_step - - frontmatter_update: - backend: base - description: "Merge keys into a file's YAML frontmatter." - parameters: - type: object - properties: - path: - type: string - description: "vault-relative path" - metadata: - type: object - description: "keys to merge" - additionalProperties: true - required: - - path - - metadata - steps: - - backend: frontmatter_update_step - - frontmatter_delete: - backend: base - description: "Drop keys from a file's YAML frontmatter." - parameters: - type: object - properties: - path: - type: string - description: "vault-relative path" - keys: - type: array - description: "keys to remove" - items: - type: string - required: - - path - - keys - steps: - - backend: frontmatter_delete_step - - delete: - backend: base - description: "Delete a vault file or folder; returns surviving inbound wikilinks." - parameters: - type: object - properties: - path: - type: string - description: "vault-relative path" - required: - - path - steps: - - backend: delete_step - - upload_resource: - backend: base - description: "Ingest an external-channel asset into resource// with provenance." - parameters: - type: object - properties: - path: - type: string - description: "host source path" - channel: - type: string - description: "channel id (wechat / email / browser / api / ...)" - description: - type: string - description: "what the asset is and how to interpret it" - metadata: - type: object - description: "extra provenance keys (e.g. source)" - default: {} - required: - - path - - channel - - description - steps: - - backend: upload_resource_step - - download: - backend: base - description: "Copy a vault file out to the host filesystem." - parameters: - type: object - properties: - src_path: - type: string - description: "vault-relative source" - dst_path: - type: string - description: "host absolute dest; empty = temp file" - default: "" - overwrite: - type: boolean - description: "overwrite if dst exists" - default: false - required: - - src_path - steps: - - backend: download_step - - # ── Daily Operations (slug provisioning + day-index rollup) ────────── - daily_create: - backend: base - description: "Provision daily//.md (empty body, frontmatter {name: slug}); idempotent; refreshes the day index." - parameters: - type: object - properties: - slug: - type: string - description: "note slug" - date: - type: string - description: "ISO date; empty = today" - default: "" - required: - - slug - steps: - - backend: daily_create_step - - daily_list: - backend: base - description: "List notes under a single day." - parameters: - type: object - properties: - date: - type: string - description: "ISO date; empty = today" - default: "" - steps: - - backend: daily_list_step - - daily_reindex: - backend: base - description: "Rebuild the day-index page daily/.md." - parameters: - type: object - properties: - date: - type: string - description: "ISO date; empty = today" - default: "" - steps: - - backend: daily_reindex_step - - watch_file: - backend: background - watch_paths: - - MEMORY.md - - memory - suffix_filters: - - md - steps: - - backend: scan_changes_step - - backend: update_index_step - persist: true - - backend: watch_changes_step - dispatch_step: update_index_step - -components: - tokenizer: - default: - backend: regex - - embedding_model: - default: - backend: ${EMBEDDING_BACKEND:-openai} - api_key: ${EMBEDDING_API_KEY} - base_url: ${EMBEDDING_BASE_URL:-https://api.openai.com/v1} - model_name: ${EMBEDDING_MODEL_NAME:-text-embedding-v4} - dimensions: 1024 - - file_graph: - default: - backend: local - - file_parser: - linked: - backend: linked - supported_extensions: - - md - chunked: - backend: chunked - supported_extensions: - - txt - - html - - json - - yaml - - py - default: - backend: default - - keyword_index: - default: - backend: bm25 - tokenizer: default - - file_store: - default: - backend: local - store_name: local - embedding_model: default - keyword_index: default - file_graph: default diff --git a/reme4/steps/file_io/_file_io.py b/reme4/steps/file_io/_file_io.py index 4b7a6022..46c91be5 100644 --- a/reme4/steps/file_io/_file_io.py +++ b/reme4/steps/file_io/_file_io.py @@ -4,10 +4,10 @@ Two related concerns, both private to the ``crud`` package: 1. **Generic file IO** — path gating, encoding-aware read/write, output truncation (used by every CRUD step that touches the filesystem). -2. **Daily-note helpers** — slug validation + ``daily/.md`` index - rebuild (used by the ``daily_*`` steps). The day index is a derived - rollup page auto-managed in marker-delimited sections; user-edited - manual sections are preserved verbatim across refreshes. +2. **Daily-note helpers** — session_id validation + ``daily/.md`` + index rebuild (used by the ``daily_*`` steps). The day index is a + derived rollup page auto-managed in marker-delimited sections; + user-edited manual sections are preserved verbatim across refreshes. """ import asyncio @@ -140,7 +140,7 @@ def validate_filename_component(name: str, *, kind: str = "filename") -> str | N for callers that validate per component) ``kind`` is the human-readable label inserted into error messages - (e.g. ``"slug"``, ``"path component"``). + (e.g. ``"session_id"``, ``"path component"``). """ if not name: return f"{kind} is required" @@ -382,16 +382,16 @@ def truncate_text_output( # --------------------------------------------------------------------------- -# Daily-note helpers: slug validation + day-index rebuild +# Daily-note helpers: session_id validation + day-index rebuild # --------------------------------------------------------------------------- -# Slug validation -# --------------- +# Session ID validation +# --------------------- -def validate_slug(slug: str) -> str | None: - """Validate a daily-note slug. Thin wrapper over :func:`validate_filename_component`.""" - return validate_filename_component(slug, kind="slug") +def validate_session_id(session_id: str) -> str | None: + """Validate a daily-note session_id. Thin wrapper over :func:`validate_filename_component`.""" + return validate_filename_component(session_id, kind="session_id") # Day-index rebuild @@ -447,26 +447,26 @@ def scan_notes(vault_dir: Path, date: str, daily_dir: str) -> list[dict]: Returns one dict per note:: - {"slug": str, "path": str, "metadata": dict} + {"session_id": str, "path": str, "metadata": dict} ``metadata`` is the raw frontmatter dict (insertion-ordered); consumers decide which keys to surface. Each ``.md`` directly - under the day folder is a note; the file's stem is the slug. + under the day folder is a note; the file's stem is the session_id. """ date_dir = vault_dir / daily_dir / date if not date_dir.is_dir(): return [] out: list[dict] = [] for md_path in sorted(p for p in date_dir.iterdir() if p.is_file() and p.suffix == ".md"): - slug = md_path.stem + session_id = md_path.stem try: post = frontmatter.loads(md_path.read_text(encoding="utf-8")) except Exception: # pylint: disable=broad-except continue out.append( { - "slug": slug, - "path": f"{daily_dir}/{date}/{slug}.md", + "session_id": session_id, + "path": f"{daily_dir}/{date}/{session_id}.md", "metadata": dict(post.metadata or {}), }, ) @@ -492,7 +492,7 @@ async def refresh_day_index(file_store, date: str, daily_dir: str) -> dict: index_abs = vault_dir / index_rel notes = scan_notes(vault_dir, date, daily_dir) - notes_payload = [{"path": n["path"], "slug": n["slug"], "metadata": n["metadata"]} for n in notes] + notes_payload = [{"path": n["path"], "session_id": n["session_id"], "metadata": n["metadata"]} for n in notes] if not notes and not index_abs.is_file(): return { diff --git a/reme4/steps/file_io/daily_create.py b/reme4/steps/file_io/daily_create.py index 0cb15386..800e0156 100644 --- a/reme4/steps/file_io/daily_create.py +++ b/reme4/steps/file_io/daily_create.py @@ -1,9 +1,9 @@ -"""``daily_create`` — provision a note slug under a daily folder: ``daily//.md``. +"""``daily_create`` — provision a session note under a daily folder: ``daily//.md``. -Minimal slug provisioner. Validates the slug, mkdirs the day folder, -writes an empty-body note with frontmatter ``{name: slug}`` if (and -only if) the file does not already exist, refreshes the day index, -and returns the vault-relative path. +Validates the session_id, mkdirs the day folder, writes an empty-body +note with frontmatter ``{name: session_id}`` if (and only if) the file +does not already exist, refreshes the day index, and returns the +vault-relative path. Idempotent: when the note already exists this is a no-op write (the day index still refreshes — siblings may have changed; cheap @@ -12,12 +12,12 @@ self-healing). The caller fills the body via ``file_write`` / deliberately does not accept a body. Inputs: - slug (required, validated) — the note's name (also the file stem) - date (optional, ``YYYY-MM-DD``; empty = today) + session_id (required, validated) — the note's session identifier (also the file stem) + date (optional, ``YYYY-MM-DD``; empty = today) Outputs: answer = one-line human-readable status - metadata = {date, slug, path, created, index?} + metadata = {date, session_id, path, created, index?} """ from datetime import date as _date @@ -25,14 +25,14 @@ from pathlib import Path import frontmatter -from ._file_io import refresh_day_index, validate_slug, write_file_safe +from ._file_io import refresh_day_index, validate_session_id, write_file_safe from ..base_step import BaseStep from ...components import R @R.register("daily_create_step") class DailyCreateStep(BaseStep): - """Provision ``daily//.md`` (idempotent); refresh day index.""" + """Provision ``daily//.md`` (idempotent); refresh day index.""" def _fail(self, message: str, **meta) -> None: """Mark response failed; copy ``meta`` into ``response.metadata``.""" @@ -43,24 +43,24 @@ class DailyCreateStep(BaseStep): self.context.response.metadata.update(meta) def _collect_params(self) -> tuple[str, str, str]: - """Read ``slug`` + ``date`` from context; default ``date`` today, ``daily_dir`` from app config.""" + """Read ``session_id`` + ``date`` from context; default ``date`` today, ``daily_dir`` from app config.""" assert self.context is not None - slug = self.context.get("slug", "") + session_id = self.context.get("session_id", "") day = self.context.get("date", "") or _date.today().strftime("%Y-%m-%d") daily_dir = self.app_context.app_config.daily_dir if self.app_context is not None else "daily" - return slug, day, daily_dir + return session_id, day, daily_dir @staticmethod - def _empty_note_text(slug: str) -> str: - """Serialize an empty-body markdown note with frontmatter ``{name: slug}``; trailing newline guaranteed.""" - text = frontmatter.dumps(frontmatter.Post("", name=slug)) + def _empty_note_text(session_id: str) -> str: + """Serialize an empty-body markdown note with frontmatter ``{name, description}``; trailing newline.""" + text = frontmatter.dumps(frontmatter.Post("", name=session_id, description="")) return text if text.endswith("\n") else text + "\n" - async def _create_if_missing(self, path_abs: Path, slug: str) -> bool: + async def _create_if_missing(self, path_abs: Path, session_id: str) -> bool: """Write the empty note only when the file is absent. Returns ``True`` iff a new file was created.""" if path_abs.is_file(): return False - await write_file_safe(path_abs, self._empty_note_text(slug), encoding="utf-8") + await write_file_safe(path_abs, self._empty_note_text(session_id), encoding="utf-8") return True def _set_success(self, payload: dict, created: bool) -> None: @@ -71,24 +71,27 @@ class DailyCreateStep(BaseStep): self.context.response.metadata.update(payload) async def execute(self): - """Validate the slug, provision the note file, refresh the day index, stamp the response.""" + """Validate the session_id, provision the note file, refresh the day index, stamp the response.""" assert self.context is not None - slug, day, daily_dir = self._collect_params() + session_id, day, daily_dir = self._collect_params() - err = validate_slug(slug) + err = validate_session_id(session_id) if err: self._fail(err) return None - path_rel = f"{daily_dir}/{day}/{slug}.md" + path_rel = f"{daily_dir}/{day}/{session_id}.md" path_abs = (self.vault_path / path_rel).resolve() try: - created = await self._create_if_missing(path_abs, slug) + created = await self._create_if_missing(path_abs, session_id) except Exception as e: # pylint: disable=broad-except - self._fail(f"create failed: {e}", date=day, slug=slug, path=path_rel) + self._fail(f"create failed: {e}", date=day, session_id=session_id, path=path_rel) return None index = await refresh_day_index(self.file_store, day, daily_dir) - self._set_success({"date": day, "slug": slug, "path": path_rel, "created": created, "index": index}, created) + self._set_success( + {"date": day, "session_id": session_id, "path": path_rel, "created": created, "index": index}, + created, + ) self.logger.info(f"[{self.name}] {'created' if created else 'reused'} path={path_rel}") return self.context.response diff --git a/reme4/steps/file_io/daily_list.py b/reme4/steps/file_io/daily_list.py index 05d9bedc..084cc47b 100644 --- a/reme4/steps/file_io/daily_list.py +++ b/reme4/steps/file_io/daily_list.py @@ -1,8 +1,8 @@ """``daily_list`` — list the notes under a single day (pure read, no side effects). -Returns one row per ``daily//.md`` note file with its -vault-relative ``path``, ``slug``, and the raw ``metadata`` dict -(full frontmatter). Sorted by slug for stable output. +Returns one row per ``daily//.md`` note file with its +vault-relative ``path``, ``session_id``, and the raw ``metadata`` dict +(full frontmatter). Sorted by session_id for stable output. **Does NOT refresh** ``daily/.md`` — call ``daily_reindex`` explicitly when the index page needs to be rebuilt. Decoupling @@ -35,7 +35,7 @@ class DailyListStep(BaseStep): @staticmethod def _project(note: dict) -> dict: """Keep only the user-facing keys (drop internal scan_notes fields, if any).""" - return {"path": note["path"], "slug": note["slug"], "metadata": note["metadata"]} + return {"path": note["path"], "session_id": note["session_id"], "metadata": note["metadata"]} @staticmethod def _format_note_line(note: dict) -> str: diff --git a/reme4/steps/file_io/daily_reindex.py b/reme4/steps/file_io/daily_reindex.py index 71cefb5b..d01b1e5b 100644 --- a/reme4/steps/file_io/daily_reindex.py +++ b/reme4/steps/file_io/daily_reindex.py @@ -1,8 +1,8 @@ -"""``daily_reindex_step`` — rebuild ``daily/.md`` from its notes. +"""``daily_reindex_step`` — rebuild ``daily/.md`` from its session notes. The day index ``daily/.md`` is a derived artifact whose job is to -list and describe every note file under ``daily//``. It is auto- -refreshed by ``daily_create``. Generic ops like ``file_write`` / +list and describe every session note file under ``daily//``. It is +auto-refreshed by ``daily_create``. Generic ops like ``file_write`` / ``file_append`` / ``frontmatter_update`` leave it stale — this step is the standalone writer to call after batch flows (historical backfill, drift recovery, end-of-batch consolidation, or a diff --git a/tests4/unit/test_daily_steps.py b/tests4/unit/test_daily_steps.py index 7909d971..8036ca39 100644 --- a/tests4/unit/test_daily_steps.py +++ b/tests4/unit/test_daily_steps.py @@ -5,10 +5,10 @@ provision / listing / index-rebuild operations. Body authoring and frontmatter mutations are generic CRUD (covered in test_crud_steps and test_property_steps). -A daily note is the single file ``daily//.md`` (no -folder, no sibling materials). ``daily_create`` is a minimal slug -provisioner: it validates the slug, writes an empty-body note with -default ``{name: slug}`` frontmatter when the file is absent, and +A daily note is the single file ``daily//.md`` +(no folder, no sibling materials). ``daily_create`` validates the +session_id, writes an empty-body note with default +``{name: session_id}`` frontmatter when the file is absent, and refreshes the day index. When the file already exists it is a no-op write (``created=False``) — the body is filled in afterwards via ``file_write`` / ``file_edit`` / ``frontmatter_update`` or a native @@ -69,17 +69,17 @@ def _today() -> str: async def _make_store_with_dailies(entries: list[tuple[str, str, str]]) -> LocalFileStore: """Seed the vault with daily notes. - entries: list of (date, slug, body). Each tuple creates - ``daily//.md`` with a minimal ``name``-only + entries: list of (date, session_id, body). Each tuple creates + ``daily//.md`` with a minimal ``name``-only frontmatter — no opinionated status / lifecycle axes. """ store = LocalFileStore(name="t", embedding_model="") await store.start() - for day, slug, body in entries: + for day, session_id, body in entries: day_dir = Path.cwd() / "daily" / day day_dir.mkdir(parents=True, exist_ok=True) - text = f"---\nname: {slug}\n---\n{body}\n" - (day_dir / f"{slug}.md").write_text(text, encoding="utf-8") + text = f"---\nname: {session_id}\n---\n{body}\n" + (day_dir / f"{session_id}.md").write_text(text, encoding="utf-8") return store @@ -87,15 +87,15 @@ def _metadata(step) -> dict: return step.context.response.metadata -async def _seed_note(date: str, slug: str, name: str = "", description: str = "") -> None: - """Write ``daily//.md`` with optional frontmatter.""" +async def _seed_note(date: str, session_id: str, name: str = "", description: str = "") -> None: + """Write ``daily//.md`` with optional frontmatter.""" day_dir = Path.cwd() / "daily" / date day_dir.mkdir(parents=True, exist_ok=True) - fm_lines = [f"name: {name or slug}"] + fm_lines = [f"name: {name or session_id}"] if description: fm_lines.append(f"description: {description}") text = "---\n" + "\n".join(fm_lines) + "\n---\nbody\n" - (day_dir / f"{slug}.md").write_text(text, encoding="utf-8") + (day_dir / f"{session_id}.md").write_text(text, encoding="utf-8") # -- daily_list_step ---------------------------------------------------------- @@ -151,8 +151,8 @@ def test_daily_list_filters_by_date(): asyncio.run(run()) -def test_daily_list_returns_path_slug_metadata(): - """Each note row exposes path / slug / metadata (full frontmatter dict).""" +def test_daily_list_returns_path_session_id_metadata(): + """Each note row exposes path / session_id / metadata (full frontmatter dict).""" async def run(): with tempfile.TemporaryDirectory() as tmp, temp_chdir(tmp): @@ -173,7 +173,7 @@ def test_daily_list_returns_path_slug_metadata(): assert "Alpha Project" in answer assert "JWT auth migration" in answer await store.close() - print("✓ test_daily_list_returns_path_slug_metadata passed") + print("✓ test_daily_list_returns_path_session_id_metadata passed") asyncio.run(run()) @@ -272,19 +272,19 @@ def test_daily_list_response_shape(): def test_daily_create_provisions_note_and_refreshes_index(): - """Fresh slug ⇒ empty-body note with ``{name: slug}`` + day index refreshed.""" + """Fresh session_id ⇒ empty-body note with ``{name: session_id}`` + day index refreshed.""" async def run(): with tempfile.TemporaryDirectory() as tmp, temp_chdir(tmp): store = await _make_store_with_dailies([]) step = daily_create_step.DailyCreateStep(file_store=store) - await step(slug="kickoff", date="2026-05-18") + await step(session_id="kickoff", date="2026-05-18") payload = _metadata(step) assert step.context.response.success is True assert payload["created"] is True assert payload["date"] == "2026-05-18" - assert payload["slug"] == "kickoff" + assert payload["session_id"] == "kickoff" assert payload["path"] == "daily/2026-05-18/kickoff.md" note = Path(tmp) / "daily" / "2026-05-18" / "kickoff.md" @@ -314,7 +314,7 @@ def test_daily_create_is_idempotent_on_existing(): before = file_path.read_text(encoding="utf-8") step = daily_create_step.DailyCreateStep(file_store=store) - await step(slug="ongoing", date="2026-05-18") + await step(session_id="ongoing", date="2026-05-18") payload = _metadata(step) assert step.context.response.success is True @@ -335,7 +335,7 @@ def test_daily_create_default_date_is_today(): with tempfile.TemporaryDirectory() as tmp, temp_chdir(tmp): store = await _make_store_with_dailies([]) step = daily_create_step.DailyCreateStep(file_store=store) - await step(slug="today-task") + await step(session_id="today-task") payload = _metadata(step) assert payload["date"] == _today() assert payload["path"] == f"daily/{_today()}/today-task.md" @@ -346,55 +346,54 @@ def test_daily_create_default_date_is_today(): asyncio.run(run()) -def test_daily_create_default_frontmatter_uses_slug_as_name(): - """The provisioned note's frontmatter is ``{name: slug}`` (no body).""" +def test_daily_create_default_frontmatter_uses_session_id_as_name(): + """The provisioned note's frontmatter is ``{name: session_id, description: ''}`` (no body).""" async def run(): with tempfile.TemporaryDirectory() as tmp, temp_chdir(tmp): store = await _make_store_with_dailies([]) step = daily_create_step.DailyCreateStep(file_store=store) - await step(slug="auth-refactor", date="2026-05-18") + await step(session_id="auth-refactor", date="2026-05-18") note = Path(tmp) / "daily" / "2026-05-18" / "auth-refactor.md" text = note.read_text(encoding="utf-8") assert "name: auth-refactor" in text - # No description in default frontmatter. - assert "description:" not in text + assert "description:" in text await store.close() - print("✓ test_daily_create_default_frontmatter_uses_slug_as_name passed") + print("✓ test_daily_create_default_frontmatter_uses_session_id_as_name passed") asyncio.run(run()) -def test_daily_create_rejects_invalid_slug(): - """Slug validation runs before any IO; no day folder is created on reject.""" +def test_daily_create_rejects_invalid_session_id(): + """session_id validation runs before any IO; no day folder is created on reject.""" async def run(): with tempfile.TemporaryDirectory() as tmp, temp_chdir(tmp): store = await _make_store_with_dailies([]) step = daily_create_step.DailyCreateStep(file_store=store) for bad in ("foo/bar", "foo:bar", "CON", "lpt9", "foo.", " bar"): - await step(slug=bad, date="2026-05-18") + await step(session_id=bad, date="2026-05-18") assert step.context.response.success is False, f"expected reject for {bad!r}" assert not (Path(tmp) / "daily" / "2026-05-18").exists() await store.close() - print("✓ test_daily_create_rejects_invalid_slug passed") + print("✓ test_daily_create_rejects_invalid_session_id passed") asyncio.run(run()) -def test_daily_create_rejects_empty_slug(): - """Empty / missing slug is rejected with a clear message.""" +def test_daily_create_rejects_empty_session_id(): + """Empty / missing session_id is rejected with a clear message.""" async def run(): with tempfile.TemporaryDirectory() as tmp, temp_chdir(tmp): store = await _make_store_with_dailies([]) step = daily_create_step.DailyCreateStep(file_store=store) - await step(slug="", date="2026-05-18") + await step(session_id="", date="2026-05-18") assert step.context.response.success is False - assert "slug" in (step.context.response.answer or "").lower() + assert "session_id" in (step.context.response.answer or "").lower() await store.close() - print("✓ test_daily_create_rejects_empty_slug passed") + print("✓ test_daily_create_rejects_empty_session_id passed") asyncio.run(run()) @@ -407,14 +406,14 @@ def test_daily_create_then_skip_round_trip(): store = await _make_store_with_dailies([]) step = daily_create_step.DailyCreateStep(file_store=store) - await step(slug="probe", date="2026-05-18") + await step(session_id="probe", date="2026-05-18") first = _metadata(step) assert first["created"] is True note = Path(tmp) / "daily" / "2026-05-18" / "probe.md" before = note.read_text(encoding="utf-8") - await step(slug="probe", date="2026-05-18") + await step(session_id="probe", date="2026-05-18") second = _metadata(step) assert second["created"] is False assert note.read_text(encoding="utf-8") == before @@ -465,8 +464,8 @@ def test_day_index_includes_note_descriptions(): ("beta", "beta", "调研增值税新政对 SaaS 的影响"), ("gamma", "Gamma", ""), ] - for slug, name, description in cases: - await _seed_note("2026-05-18", slug, name=name, description=description) + for sid, name, description in cases: + await _seed_note("2026-05-18", sid, name=name, description=description) await daily_reindex_step.DailyReindexStep(file_store=store)(date="2026-05-18") text = _day_index_text(tmp, "2026-05-18") @@ -601,7 +600,7 @@ if __name__ == "__main__": print("\n=== Daily step tests ===") test_daily_list_default_date_is_today() test_daily_list_filters_by_date() - test_daily_list_returns_path_slug_metadata() + test_daily_list_returns_path_session_id_metadata() test_daily_list_ignores_subdirectories() test_daily_list_empty_when_no_daily_dir() test_daily_list_does_not_refresh_index() @@ -609,9 +608,9 @@ if __name__ == "__main__": test_daily_create_provisions_note_and_refreshes_index() test_daily_create_is_idempotent_on_existing() test_daily_create_default_date_is_today() - test_daily_create_default_frontmatter_uses_slug_as_name() - test_daily_create_rejects_invalid_slug() - test_daily_create_rejects_empty_slug() + test_daily_create_default_frontmatter_uses_session_id_as_name() + test_daily_create_rejects_invalid_session_id() + test_daily_create_rejects_empty_session_id() test_daily_create_then_skip_round_trip() test_day_index_lists_each_note() test_day_index_includes_note_descriptions()