mirror of
https://github.com/himanshudongre/smriti.git
synced 2026-08-28 05:14:59 +00:00
419 lines
14 KiB
Python
419 lines
14 KiB
Python
"""Formatter tests for multi-branch state rendering + MCP wiring tests
|
|
for `smriti_state` main_only and default paths.
|
|
|
|
These tests cover the digestibility guarantees the build rests on:
|
|
|
|
- Passing `space_state=None` to `format_state_brief` produces output
|
|
byte-for-byte identical to the pre-multi-branch version.
|
|
- Passing a space_state with empty active_branches and no divergence
|
|
produces no "Active branches" or "Divergence signal" sections.
|
|
- Active branches render as a concise one-liner per branch.
|
|
- Divergence signal renders only when pairs are present and names the
|
|
specific conflicting decisions.
|
|
- MCP `smriti_state` calls `get_space_state` by default and the legacy
|
|
`get_head` + `get_commit` path only when `main_only=True`.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
import pytest
|
|
|
|
from smriti_cli import mcp_server
|
|
from smriti_cli.formatters import (
|
|
_format_active_branches_section,
|
|
_format_divergence_signal_section,
|
|
format_state_brief,
|
|
)
|
|
|
|
|
|
# ── Fixtures: canonical space / head / commit dicts ─────────────────────────
|
|
|
|
|
|
def _base_space():
|
|
return {
|
|
"id": "space-uuid",
|
|
"name": "my-project",
|
|
"description": "Test project.",
|
|
}
|
|
|
|
|
|
def _base_head(commit_id="commit-uuid"):
|
|
return {
|
|
"repo_id": "space-uuid",
|
|
"commit_hash": "abcdef1234567890",
|
|
"commit_id": commit_id,
|
|
"summary": "Current state summary.",
|
|
"objective": "Build the skill pack.",
|
|
"latest_session_id": "session-uuid",
|
|
"latest_session_title": "work session",
|
|
}
|
|
|
|
|
|
def _base_commit():
|
|
return {
|
|
"id": "commit-uuid",
|
|
"repo_id": "space-uuid",
|
|
"commit_hash": "abcdef1234567890",
|
|
"branch_name": "main",
|
|
"author_agent": "claude-code",
|
|
"project_root": "/tmp/test-project",
|
|
"message": "Base",
|
|
"summary": "Main summary here.",
|
|
"objective": "Build something",
|
|
"decisions": ["Decision A"],
|
|
"assumptions": ["Assumption X"],
|
|
"tasks": ["Task 1"],
|
|
"open_questions": [],
|
|
"entities": ["Pydantic"],
|
|
"artifacts": [],
|
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
}
|
|
|
|
|
|
# ── Formatter-level tests ────────────────────────────────────────────────────
|
|
|
|
|
|
def test_format_state_brief_no_space_state_matches_pre_build():
|
|
"""With space_state=None, output must not contain any multi-branch sections.
|
|
This is the regression guard for existing CLI callers."""
|
|
space = _base_space()
|
|
head = _base_head()
|
|
commit = _base_commit()
|
|
|
|
out = format_state_brief(space, head, commit, space_state=None)
|
|
|
|
assert "## Active branches" not in out
|
|
assert "## Divergence signal" not in out
|
|
# Core sections still present.
|
|
assert "# my-project" in out
|
|
assert "## Current objective" in out
|
|
assert "## Decisions" in out
|
|
|
|
|
|
def test_format_state_brief_empty_branches_and_no_divergence_elided():
|
|
"""space_state present but empty → same output as space_state=None."""
|
|
space = _base_space()
|
|
head = _base_head()
|
|
commit = _base_commit()
|
|
|
|
empty_state = {"active_branches": [], "divergence": None}
|
|
out = format_state_brief(space, head, commit, space_state=empty_state)
|
|
|
|
assert "## Active branches" not in out
|
|
assert "## Divergence signal" not in out
|
|
|
|
|
|
def test_format_state_brief_active_branches_only():
|
|
"""Non-empty active_branches → Active branches section appears,
|
|
Divergence signal section absent."""
|
|
space = _base_space()
|
|
head = _base_head()
|
|
commit = _base_commit()
|
|
|
|
state = {
|
|
"active_branches": [
|
|
{
|
|
"branch_name": "experiment-a",
|
|
"commit_id": "branch-uuid-1",
|
|
"commit_hash": "11112222" * 5,
|
|
"message": "Trying stdlib",
|
|
"author_agent": "codex-local",
|
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
"summary": "Fork summary.",
|
|
}
|
|
],
|
|
"divergence": None,
|
|
}
|
|
out = format_state_brief(space, head, commit, space_state=state)
|
|
|
|
assert "## Active branches" in out
|
|
assert "`experiment-a`" in out
|
|
assert "`codex-local`" in out
|
|
assert "Trying stdlib" in out
|
|
assert "## Divergence signal" not in out
|
|
|
|
|
|
def test_format_state_brief_with_divergence():
|
|
"""Active branches + divergence → both sections rendered, specific
|
|
conflicting decisions named."""
|
|
space = _base_space()
|
|
head = _base_head()
|
|
commit = _base_commit()
|
|
|
|
state = {
|
|
"active_branches": [
|
|
{
|
|
"branch_name": "stdlib-fork",
|
|
"commit_id": "branch-uuid",
|
|
"commit_hash": "deadbeef" * 5,
|
|
"message": "Stdlib attempt",
|
|
"author_agent": "codex-local",
|
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
"summary": "Fork summary.",
|
|
}
|
|
],
|
|
"divergence": {
|
|
"pairs": [
|
|
{
|
|
"branch_name": "stdlib-fork",
|
|
"branch_commit_hash": "deadbeef" * 5,
|
|
"main_only_decisions": ["Use Pydantic with extra=forbid"],
|
|
"branch_only_decisions": [
|
|
"Use stdlib dataclasses",
|
|
"Reject third-party deps",
|
|
],
|
|
}
|
|
]
|
|
},
|
|
}
|
|
out = format_state_brief(space, head, commit, space_state=state)
|
|
|
|
assert "## Active branches" in out
|
|
assert "## Divergence signal" in out
|
|
assert "Use Pydantic with extra=forbid" in out
|
|
assert "Use stdlib dataclasses" in out
|
|
assert "Reject third-party deps" in out
|
|
# Pointer to compare command is in the section header text.
|
|
assert "smriti compare" in out
|
|
|
|
|
|
def test_format_divergence_signal_empty_pairs_elided():
|
|
"""divergence with empty pairs array → helper returns empty string."""
|
|
assert _format_divergence_signal_section(None) == ""
|
|
assert _format_divergence_signal_section({"pairs": []}) == ""
|
|
assert _format_divergence_signal_section({}) == ""
|
|
|
|
|
|
def test_format_active_branches_empty_returns_empty():
|
|
assert _format_active_branches_section([]) == ""
|
|
|
|
|
|
def test_format_active_branches_multiple_branches_one_line_each():
|
|
branches = [
|
|
{
|
|
"branch_name": "b1",
|
|
"commit_hash": "aaaa" * 8,
|
|
"message": "First",
|
|
"author_agent": "claude-code",
|
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
},
|
|
{
|
|
"branch_name": "b2",
|
|
"commit_hash": "bbbb" * 8,
|
|
"message": "Second",
|
|
"author_agent": "codex-local",
|
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
},
|
|
]
|
|
out = _format_active_branches_section(branches)
|
|
# One bullet per branch.
|
|
assert out.count("- `") == 2
|
|
assert "First" in out
|
|
assert "Second" in out
|
|
assert "`claude-code`" in out
|
|
assert "`codex-local`" in out
|
|
|
|
|
|
# ── MCP tool wiring tests ───────────────────────────────────────────────────
|
|
|
|
|
|
def _space_dict():
|
|
return {"id": "space-uuid", "name": "my-project", "description": ""}
|
|
|
|
|
|
def _space_state_dict(with_divergence=False):
|
|
base = {
|
|
"space": _space_dict(),
|
|
"head": _base_head(),
|
|
"commit": _base_commit(),
|
|
"active_branches": [],
|
|
"divergence": None,
|
|
}
|
|
if with_divergence:
|
|
base["active_branches"] = [
|
|
{
|
|
"branch_name": "fork-a",
|
|
"commit_id": "fork-uuid",
|
|
"commit_hash": "abcd" * 8,
|
|
"message": "Fork alt",
|
|
"author_agent": "codex-local",
|
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
"summary": "Fork summary.",
|
|
}
|
|
]
|
|
base["divergence"] = {
|
|
"pairs": [
|
|
{
|
|
"branch_name": "fork-a",
|
|
"branch_commit_hash": "abcd" * 8,
|
|
"main_only_decisions": ["Decision A"],
|
|
"branch_only_decisions": ["Decision B"],
|
|
}
|
|
]
|
|
}
|
|
return base
|
|
|
|
|
|
def test_mcp_smriti_state_default_calls_get_space_state(mock_client):
|
|
"""Default path: smriti_state → resolve_space → get_space_state (one call)."""
|
|
mock_client.resolve_space.return_value = _space_dict()
|
|
mock_client.get_space_state.return_value = _space_state_dict()
|
|
|
|
out = mcp_server.smriti_state(space="my-project")
|
|
|
|
mock_client.get_space_state.assert_called_once_with("space-uuid")
|
|
mock_client.get_head.assert_not_called()
|
|
mock_client.get_commit.assert_not_called()
|
|
assert "# my-project" in out
|
|
|
|
|
|
def test_mcp_smriti_state_main_only_uses_legacy_path(mock_client):
|
|
"""main_only=True falls back to the old get_head + get_commit dance."""
|
|
mock_client.resolve_space.return_value = _space_dict()
|
|
mock_client.get_head.return_value = _base_head()
|
|
mock_client.get_commit.return_value = _base_commit()
|
|
|
|
out = mcp_server.smriti_state(space="my-project", main_only=True)
|
|
|
|
mock_client.get_head.assert_called_once_with("space-uuid")
|
|
mock_client.get_commit.assert_called_once_with("commit-uuid")
|
|
mock_client.get_space_state.assert_not_called()
|
|
assert "# my-project" in out
|
|
|
|
|
|
def test_mcp_smriti_state_default_renders_divergence_signal(mock_client):
|
|
"""End-to-end: default path + a divergent space_state → divergence
|
|
section visible in rendered output."""
|
|
mock_client.resolve_space.return_value = _space_dict()
|
|
mock_client.get_space_state.return_value = _space_state_dict(with_divergence=True)
|
|
|
|
out = mcp_server.smriti_state(space="my-project")
|
|
|
|
assert "## Active branches" in out
|
|
assert "## Divergence signal" in out
|
|
assert "Decision A" in out
|
|
assert "Decision B" in out
|
|
|
|
|
|
def test_mcp_smriti_state_no_checkpoints_short_circuit(mock_client):
|
|
"""Space exists but has no main commit: default path returns the
|
|
empty-space message without calling format_state_brief."""
|
|
empty_state = _space_state_dict()
|
|
empty_state["head"] = {
|
|
"repo_id": "space-uuid",
|
|
"commit_hash": None,
|
|
"commit_id": None,
|
|
"summary": None,
|
|
"objective": None,
|
|
"latest_session_id": None,
|
|
"latest_session_title": None,
|
|
}
|
|
empty_state["commit"] = None
|
|
mock_client.resolve_space.return_value = _space_dict()
|
|
mock_client.get_space_state.return_value = empty_state
|
|
|
|
out = mcp_server.smriti_state(space="my-project")
|
|
|
|
assert "No checkpoints yet" in out
|
|
|
|
|
|
# ── Compact mode tests ──────────────────────────────────────────────────────
|
|
|
|
|
|
def _commit_with_artifacts():
|
|
"""Commit with realistic artifacts for compact mode testing."""
|
|
base = _base_commit()
|
|
base["id"] = "abc12345-6789-0abc-def0-123456789abc"
|
|
base["artifacts"] = [
|
|
{
|
|
"id": "a1",
|
|
"type": "python",
|
|
"label": "Draft implementation",
|
|
"content": "def hello():\n return 'world'\n" * 50,
|
|
},
|
|
{
|
|
"id": "a2",
|
|
"type": "markdown",
|
|
"label": "Test plan",
|
|
"content": "# Test Plan\n\n- Unit tests for X\n- Integration tests for Y\n" * 30,
|
|
},
|
|
]
|
|
return base
|
|
|
|
|
|
def test_compact_omits_artifact_content():
|
|
"""Compact mode shows artifact labels but not content."""
|
|
commit = _commit_with_artifacts()
|
|
out = format_state_brief(
|
|
_base_space(), _base_head(), commit, compact=True,
|
|
)
|
|
|
|
# Labels present
|
|
assert "Draft implementation" in out
|
|
assert "Test plan" in out
|
|
# Content absent
|
|
assert "def hello():" not in out
|
|
assert "Unit tests for X" not in out
|
|
# Recovery instruction present
|
|
assert "compact — content omitted" in out
|
|
assert "smriti checkpoint show" in out
|
|
assert "abc12345-6789-0abc-def0-123456789abc" in out
|
|
|
|
|
|
def test_compact_is_smaller_than_full():
|
|
"""Compact output must be materially smaller than full output."""
|
|
commit = _commit_with_artifacts()
|
|
full = format_state_brief(
|
|
_base_space(), _base_head(), commit, full_artifacts=True,
|
|
)
|
|
compact = format_state_brief(
|
|
_base_space(), _base_head(), commit, compact=True,
|
|
)
|
|
|
|
# Compact should be at least 50% smaller when artifacts dominate
|
|
assert len(compact) < len(full) * 0.5, (
|
|
f"Compact ({len(compact)} chars) should be less than 50% of "
|
|
f"full ({len(full)} chars)"
|
|
)
|
|
|
|
|
|
def test_compact_preserves_decisions_and_tasks():
|
|
"""Compact mode must not touch non-artifact sections."""
|
|
commit = _commit_with_artifacts()
|
|
out = format_state_brief(
|
|
_base_space(), _base_head(), commit, compact=True,
|
|
)
|
|
|
|
assert "## Decisions" in out
|
|
assert "Decision A" in out
|
|
assert "## Assumptions we are relying on" in out
|
|
assert "Assumption X" in out
|
|
assert "## In progress" in out
|
|
assert "Task 1" in out
|
|
|
|
|
|
def test_compact_with_no_artifacts_is_clean():
|
|
"""Compact mode on a commit with no artifacts should not show the section."""
|
|
commit = _base_commit() # no artifacts
|
|
out = format_state_brief(
|
|
_base_space(), _base_head(), commit, compact=True,
|
|
)
|
|
|
|
assert "Attached artifacts" not in out
|
|
assert "compact" not in out.lower() or "compact" in out.lower() # no section = no mention
|
|
|
|
|
|
def test_mcp_smriti_state_compact_mode(mock_client):
|
|
"""MCP tool with compact=True should pass compact to formatter."""
|
|
mock_client.resolve_space.return_value = _space_dict()
|
|
state = _space_state_dict()
|
|
state["commit"] = _commit_with_artifacts()
|
|
mock_client.get_space_state.return_value = state
|
|
|
|
out = mcp_server.smriti_state(space="my-project", compact=True)
|
|
|
|
# Labels present, content absent
|
|
assert "Draft implementation" in out
|
|
assert "def hello():" not in out
|
|
assert "compact — content omitted" in out
|