mirror of
https://github.com/himanshudongre/smriti.git
synced 2026-08-28 05:14:59 +00:00
Add Project Current State CLI surface
This commit is contained in:
parent
7c8f259342
commit
704f28570c
5 changed files with 654 additions and 0 deletions
|
|
@ -136,6 +136,8 @@ smriti state <space> # multi-branch continua
|
|||
smriti state <space> --preview # truncate artifacts to a short preview
|
||||
smriti state <space> --main-only # legacy single-HEAD path (pre-V4 behaviour)
|
||||
smriti state <space> --json # structured output
|
||||
smriti current <space> # compact Project Current State surface
|
||||
smriti current <space> --json # structured current-state payload
|
||||
|
||||
smriti claim create <space> --agent <name> --scope "..." # declare work intent before starting
|
||||
smriti claim create <space> --agent <name> --scope "..." --intent-type review
|
||||
|
|
|
|||
|
|
@ -192,6 +192,16 @@ class SmritiClient:
|
|||
params=params if params else None,
|
||||
)
|
||||
|
||||
def get_current_state(self, space_id: str) -> dict:
|
||||
"""GET /api/v5/current/spaces/{space_id} — Project Current State.
|
||||
|
||||
This endpoint is the backend-owned compact operational payload for
|
||||
founder-facing and agent-facing current-state surfaces. CLI callers
|
||||
may fall back to composing the same contract from existing endpoints
|
||||
while older backends are still deployed.
|
||||
"""
|
||||
return self._request("GET", f"/api/v5/current/spaces/{space_id}")
|
||||
|
||||
# ── Work claims ──────────────────────────────────────────────────
|
||||
|
||||
def create_claim(
|
||||
|
|
|
|||
|
|
@ -100,6 +100,13 @@ def _short_hash(commit_hash: str | None) -> str:
|
|||
return commit_hash[:7] if commit_hash else "?"
|
||||
|
||||
|
||||
def _truncate_text(text: str, limit: int = 180) -> str:
|
||||
"""Keep compact surfaces compact without hiding that content exists."""
|
||||
if len(text) <= limit:
|
||||
return text
|
||||
return text[: limit - 1].rstrip() + "…"
|
||||
|
||||
|
||||
def _list_section(heading: str, items: list[str]) -> str:
|
||||
if not items:
|
||||
return ""
|
||||
|
|
@ -478,6 +485,220 @@ def format_state_brief(
|
|||
return result
|
||||
|
||||
|
||||
def _direction_text(value) -> str:
|
||||
"""Normalize current_direction to a readable paragraph.
|
||||
|
||||
The backend contract may keep `current_direction` as a string or a richer
|
||||
object. Keep the formatter tolerant so the CLI can ship in parallel with
|
||||
the backend implementation.
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
return value.strip()
|
||||
if isinstance(value, dict):
|
||||
for key in ("text", "summary", "objective", "message"):
|
||||
raw = value.get(key)
|
||||
if isinstance(raw, str) and raw.strip():
|
||||
return raw.strip()
|
||||
return ""
|
||||
|
||||
|
||||
def _format_current_counts(counts: dict | None) -> str:
|
||||
if not counts:
|
||||
return "## Counts\nNo count data available.\n"
|
||||
|
||||
labels = {
|
||||
"checkpoints": "checkpoints",
|
||||
"active_claims": "active claims",
|
||||
"active_branches": "active branches",
|
||||
"open_tasks": "open tasks",
|
||||
"milestones": "milestones",
|
||||
"attention": "attention signals",
|
||||
}
|
||||
ordered = [
|
||||
"checkpoints",
|
||||
"active_claims",
|
||||
"active_branches",
|
||||
"open_tasks",
|
||||
"milestones",
|
||||
"attention",
|
||||
]
|
||||
bits: list[str] = []
|
||||
for key in ordered:
|
||||
if key in counts and counts.get(key) is not None:
|
||||
bits.append(f"{labels[key]}: {counts[key]}")
|
||||
for key in sorted(k for k in counts.keys() if k not in labels):
|
||||
bits.append(f"{key.replace('_', ' ')}: {counts[key]}")
|
||||
return "## Counts\n" + (" · ".join(bits) if bits else "No count data available.") + "\n"
|
||||
|
||||
|
||||
def _format_attention(attention: list | None) -> str:
|
||||
lines = ["## Needs attention"]
|
||||
if not attention:
|
||||
lines.append("- none")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
for raw in attention:
|
||||
if isinstance(raw, str):
|
||||
lines.append(f"- {raw}")
|
||||
continue
|
||||
if isinstance(raw, dict):
|
||||
severity = raw.get("severity") or raw.get("kind")
|
||||
message = raw.get("message") or raw.get("text") or raw.get("label")
|
||||
if not message:
|
||||
message = str(raw)
|
||||
prefix = f"[{severity}] " if severity else ""
|
||||
lines.append(f"- {prefix}{message}")
|
||||
continue
|
||||
lines.append(f"- {raw}")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def _format_current_active_work(active_work: list | None) -> str:
|
||||
lines = ["## Active work"]
|
||||
if not active_work:
|
||||
lines.append("No active work claims.")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
for item in active_work:
|
||||
if not isinstance(item, dict):
|
||||
lines.append(f"- {item}")
|
||||
continue
|
||||
agent = item.get("agent") or "unknown"
|
||||
intent = item.get("intent_type") or item.get("intent") or "implement"
|
||||
branch = item.get("branch_name") or item.get("branch") or "main"
|
||||
scope = item.get("scope") or item.get("message") or "(no scope)"
|
||||
task_id = item.get("task_id")
|
||||
created = item.get("claimed_at") or item.get("created_at")
|
||||
rel = f" · {_relative_time(created)}" if created else ""
|
||||
task = f" task `{task_id}`" if task_id else ""
|
||||
lines.append(
|
||||
f"- `{agent}` [{intent}]{task} on `{branch}`{rel} — {scope}"
|
||||
)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def _format_recent_milestones(milestones: list | None) -> str:
|
||||
lines = ["## Recent milestones"]
|
||||
if not milestones:
|
||||
lines.append("No recent milestones.")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
for item in milestones:
|
||||
if not isinstance(item, dict):
|
||||
lines.append(f"- {item}")
|
||||
continue
|
||||
h = item.get("commit_hash") or item.get("checkpoint_hash") or ""
|
||||
hash_part = f"`{_short_hash(h)}`"
|
||||
created = item.get("created_at")
|
||||
rel = f" · {_relative_time(created)}" if created else ""
|
||||
text = (
|
||||
item.get("note")
|
||||
or item.get("text")
|
||||
or item.get("message")
|
||||
or "(milestone)"
|
||||
)
|
||||
author = item.get("author") or item.get("author_agent")
|
||||
author_part = f" · `{author}`" if author else ""
|
||||
lines.append(f"- {hash_part}{author_part}{rel} — {_truncate_text(str(text))}")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def _format_open_tasks_by_intent(open_tasks_by_intent: dict | None) -> str:
|
||||
lines = ["## Open tasks by intent"]
|
||||
if not open_tasks_by_intent:
|
||||
lines.append("No open tasks.")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
rendered_any = False
|
||||
for intent in sorted(open_tasks_by_intent.keys()):
|
||||
tasks = open_tasks_by_intent.get(intent) or []
|
||||
if not tasks:
|
||||
continue
|
||||
rendered_any = True
|
||||
lines.append(f"### {intent}")
|
||||
for raw in tasks:
|
||||
task = _normalize_task_item(raw)
|
||||
text = task.get("text", "")
|
||||
task_id = task.get("id")
|
||||
blocked = task.get("blocked_by")
|
||||
id_part = f"`{task_id}` " if task_id else ""
|
||||
blocked_part = f" → blocked by: {blocked}" if blocked else ""
|
||||
lines.append(f"- {id_part}{text}{blocked_part}")
|
||||
|
||||
if not rendered_any:
|
||||
lines.append("No open tasks.")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def _format_recent_activity(activity: list | None) -> str:
|
||||
lines = ["## Recent activity"]
|
||||
if not activity:
|
||||
lines.append("No recent activity.")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
for item in activity:
|
||||
if not isinstance(item, dict):
|
||||
lines.append(f"- {item}")
|
||||
continue
|
||||
h = item.get("commit_hash") or item.get("checkpoint_hash") or ""
|
||||
author = item.get("author_agent") or item.get("author") or "unknown"
|
||||
branch = item.get("branch_name") or item.get("branch")
|
||||
created = item.get("created_at")
|
||||
rel = f" · {_relative_time(created)}" if created else ""
|
||||
branch_part = f" on `{branch}`" if branch and branch != "main" else ""
|
||||
msg = item.get("message") or item.get("title") or "(no message)"
|
||||
lines.append(
|
||||
f"- `{_short_hash(h)}` · `{author}`{branch_part}{rel} — {_truncate_text(str(msg), 140)}"
|
||||
)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def format_project_current(data: dict) -> str:
|
||||
"""Readable Project Current State surface.
|
||||
|
||||
Contract keys:
|
||||
space_id, name, description, current_direction, counts, attention,
|
||||
active_work, recent_milestones, open_tasks_by_intent, recent_activity.
|
||||
"""
|
||||
name = data.get("name") or data.get("space_name") or "Untitled space"
|
||||
parts: list[str] = [f"# {name} — current state\n"]
|
||||
|
||||
if data.get("description"):
|
||||
parts.append(str(data["description"]).rstrip() + "\n")
|
||||
|
||||
direction = _direction_text(data.get("current_direction"))
|
||||
if direction:
|
||||
parts.append(direction + "\n")
|
||||
else:
|
||||
parts.append("No current direction recorded.\n")
|
||||
|
||||
latest = data.get("latest_checkpoint")
|
||||
if isinstance(data.get("current_direction"), dict):
|
||||
latest = latest or data["current_direction"].get("latest_checkpoint")
|
||||
if not latest and data.get("recent_activity"):
|
||||
latest = data["recent_activity"][0]
|
||||
if isinstance(latest, dict):
|
||||
h = latest.get("commit_hash") or latest.get("checkpoint_hash") or ""
|
||||
msg = latest.get("message") or "(no message)"
|
||||
author = latest.get("author_agent") or latest.get("author")
|
||||
created = latest.get("created_at")
|
||||
meta = [f"Latest checkpoint: `{_short_hash(h)}`"]
|
||||
if author:
|
||||
meta.append(f"by `{author}`")
|
||||
if created:
|
||||
meta.append(_relative_time(created))
|
||||
parts.append(" · ".join(meta) + f" — {msg}\n")
|
||||
|
||||
parts.append(_format_current_counts(data.get("counts")))
|
||||
parts.append(_format_attention(data.get("attention")))
|
||||
parts.append(_format_current_active_work(data.get("active_work")))
|
||||
parts.append(_format_recent_milestones(data.get("recent_milestones")))
|
||||
parts.append(_format_open_tasks_by_intent(data.get("open_tasks_by_intent")))
|
||||
parts.append(_format_recent_activity(data.get("recent_activity")))
|
||||
|
||||
return "\n".join(p for p in parts if p).rstrip() + "\n"
|
||||
|
||||
|
||||
def format_checkpoint(commit: dict, *, full_artifacts: bool = False) -> str:
|
||||
"""Readable markdown for a single checkpoint."""
|
||||
parts: list[str] = []
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ Commands for agent and programmatic use:
|
|||
smriti space delete <space> [-y]
|
||||
smriti doctor
|
||||
smriti state <space> [--preview]
|
||||
smriti current <space>
|
||||
smriti fork <checkpoint-id> [--branch <name>]
|
||||
smriti restore <checkpoint-id>
|
||||
smriti compare <checkpoint-a> <checkpoint-b>
|
||||
|
|
@ -62,6 +63,7 @@ from .formatters import (
|
|||
format_doctor,
|
||||
format_fork_result,
|
||||
format_metrics,
|
||||
format_project_current,
|
||||
format_restore_brief,
|
||||
format_review,
|
||||
format_space_list,
|
||||
|
|
@ -427,6 +429,175 @@ def _print_no_checkpoints(space: dict, args: argparse.Namespace) -> None:
|
|||
print("No checkpoints yet. Create one with `smriti checkpoint create`.")
|
||||
|
||||
|
||||
def _current_task_item(raw: Any) -> dict:
|
||||
if isinstance(raw, str):
|
||||
return {"text": raw}
|
||||
if isinstance(raw, dict):
|
||||
return raw
|
||||
return {"text": str(raw)}
|
||||
|
||||
|
||||
def _current_open_tasks_by_intent(tasks: list) -> dict[str, list[dict]]:
|
||||
grouped: dict[str, list[dict]] = {}
|
||||
for raw in tasks:
|
||||
task = _current_task_item(raw)
|
||||
status = task.get("status", "open")
|
||||
if status and status != "open":
|
||||
continue
|
||||
intent = task.get("intent_hint") or task.get("intent_type") or "other"
|
||||
grouped.setdefault(intent, []).append(task)
|
||||
return grouped
|
||||
|
||||
|
||||
def _current_milestones(commits: list[dict], limit: int = 5) -> list[dict]:
|
||||
milestones: list[dict] = []
|
||||
for commit in commits:
|
||||
metadata = commit.get("metadata") or commit.get("metadata_") or {}
|
||||
notes = metadata.get("notes") or []
|
||||
for note in notes:
|
||||
if note.get("kind") != "milestone":
|
||||
continue
|
||||
milestones.append({
|
||||
"commit_hash": commit.get("commit_hash"),
|
||||
"message": commit.get("message"),
|
||||
"note": note.get("text") or commit.get("message"),
|
||||
"author": note.get("author") or commit.get("author_agent"),
|
||||
"created_at": note.get("created_at") or commit.get("created_at"),
|
||||
})
|
||||
if len(milestones) >= limit:
|
||||
return milestones
|
||||
|
||||
# Lineage/current endpoints may expose only note kind summaries. When
|
||||
# the note text is unavailable, still surface the checkpoint as a
|
||||
# milestone so the current-state view does not hide the marker.
|
||||
note_kinds = commit.get("note_kinds") or []
|
||||
if "milestone" in note_kinds:
|
||||
milestones.append({
|
||||
"commit_hash": commit.get("commit_hash"),
|
||||
"message": commit.get("message"),
|
||||
"note": commit.get("message"),
|
||||
"author_agent": commit.get("author_agent"),
|
||||
"created_at": commit.get("created_at"),
|
||||
})
|
||||
if len(milestones) >= limit:
|
||||
return milestones
|
||||
return milestones
|
||||
|
||||
|
||||
def _current_activity(commits: list[dict], limit: int = 5) -> list[dict]:
|
||||
return [
|
||||
{
|
||||
"id": c.get("id"),
|
||||
"commit_hash": c.get("commit_hash"),
|
||||
"message": c.get("message"),
|
||||
"author_agent": c.get("author_agent"),
|
||||
"branch_name": c.get("branch_name"),
|
||||
"created_at": c.get("created_at"),
|
||||
}
|
||||
for c in commits[:limit]
|
||||
]
|
||||
|
||||
|
||||
def _build_current_payload_from_existing(client: SmritiClient, space: dict) -> dict:
|
||||
"""Compose the Project Current State contract from shipped endpoints.
|
||||
|
||||
This keeps the CLI usable while the backend-owned compact endpoint rolls
|
||||
out in parallel. Once the endpoint is present, `cmd_current` will prefer
|
||||
it and skip this compatibility path.
|
||||
"""
|
||||
space_id = space["id"]
|
||||
state = client.get_space_state(space_id)
|
||||
commits = client.list_commits(space_id)
|
||||
try:
|
||||
metrics = client.get_space_metrics(space_id)
|
||||
except SmritiError as e:
|
||||
if e.status not in (404, 405):
|
||||
raise
|
||||
metrics = {}
|
||||
|
||||
commit = state.get("commit") or {}
|
||||
active_work = state.get("active_claims") or []
|
||||
active_branches = state.get("active_branches") or []
|
||||
open_tasks = _current_open_tasks_by_intent(commit.get("tasks") or [])
|
||||
open_task_count = sum(len(items) for items in open_tasks.values())
|
||||
milestones = _current_milestones(commits)
|
||||
|
||||
attention: list[dict] = []
|
||||
if not commit:
|
||||
attention.append({
|
||||
"severity": "setup",
|
||||
"message": "No checkpoints yet; create the first checkpoint to establish project direction.",
|
||||
})
|
||||
divergence = state.get("divergence") or {}
|
||||
if divergence.get("pairs"):
|
||||
attention.append({
|
||||
"severity": "risk",
|
||||
"message": "Active branch divergence detected; run `smriti compare` before reconciling.",
|
||||
})
|
||||
if active_branches:
|
||||
attention.append({
|
||||
"severity": "branch",
|
||||
"message": f"{len(active_branches)} active branch(es) need disposition when resolved.",
|
||||
})
|
||||
open_questions = commit.get("open_questions") or []
|
||||
if open_questions:
|
||||
attention.append({
|
||||
"severity": "question",
|
||||
"message": f"{len(open_questions)} open question(s) on the latest checkpoint.",
|
||||
})
|
||||
if open_task_count > 0 and not active_work:
|
||||
attention.append({
|
||||
"severity": "next",
|
||||
"message": f"{open_task_count} open task(s) are available with no active claim.",
|
||||
})
|
||||
|
||||
coord = metrics.get("coordination") or {}
|
||||
state_quality = metrics.get("state_quality") or {}
|
||||
branch_metrics = metrics.get("branches") or {}
|
||||
counts = {
|
||||
"checkpoints": coord.get("total_checkpoints", len(commits)),
|
||||
"active_claims": len(active_work),
|
||||
"active_branches": branch_metrics.get("active", len(active_branches)),
|
||||
"open_tasks": open_task_count,
|
||||
"milestones": state_quality.get("milestone_count", len(milestones)),
|
||||
"attention": len(attention),
|
||||
}
|
||||
|
||||
return {
|
||||
"space_id": space_id,
|
||||
"name": space.get("name"),
|
||||
"description": space.get("description") or "",
|
||||
"current_direction": (
|
||||
commit.get("objective")
|
||||
or commit.get("summary")
|
||||
or commit.get("message")
|
||||
or ""
|
||||
),
|
||||
"counts": counts,
|
||||
"attention": attention,
|
||||
"active_work": active_work,
|
||||
"recent_milestones": milestones,
|
||||
"open_tasks_by_intent": open_tasks,
|
||||
"recent_activity": _current_activity(commits),
|
||||
}
|
||||
|
||||
|
||||
def cmd_current(client: SmritiClient, args: argparse.Namespace) -> None:
|
||||
"""Print the compact Project Current State surface for a space."""
|
||||
space = client.resolve_space(args.space)
|
||||
try:
|
||||
data = client.get_current_state(space["id"])
|
||||
except SmritiError as e:
|
||||
if e.status not in (404, 405):
|
||||
raise
|
||||
data = _build_current_payload_from_existing(client, space)
|
||||
|
||||
if args.json:
|
||||
_print_json(data)
|
||||
else:
|
||||
print(format_project_current(data), end="")
|
||||
|
||||
|
||||
def cmd_checkpoint_create(client: SmritiClient, args: argparse.Namespace) -> None:
|
||||
space = client.resolve_space(args.space)
|
||||
|
||||
|
|
@ -1229,6 +1400,15 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||
state_parser.add_argument("--json", action="store_true", help="Output structured JSON")
|
||||
state_parser.set_defaults(func=cmd_state)
|
||||
|
||||
# current — compact founder/agent current-state surface
|
||||
current_parser = subparsers.add_parser(
|
||||
"current",
|
||||
help="Print the compact Project Current State surface for a space",
|
||||
)
|
||||
current_parser.add_argument("space", help="Space name or UUID")
|
||||
current_parser.add_argument("--json", action="store_true", help="Output structured JSON")
|
||||
current_parser.set_defaults(func=cmd_current)
|
||||
|
||||
# checkpoint
|
||||
cp_parser = subparsers.add_parser("checkpoint", help="Manage checkpoints")
|
||||
cp_sub = cp_parser.add_subparsers(dest="subcommand", required=True)
|
||||
|
|
|
|||
241
cli/tests/test_current_cli.py
Normal file
241
cli/tests/test_current_cli.py
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from smriti_cli import main as cli_main
|
||||
from smriti_cli.client import SmritiClient, SmritiError
|
||||
from smriti_cli.formatters import format_project_current
|
||||
|
||||
|
||||
def _space() -> dict:
|
||||
return {
|
||||
"id": "space-uuid",
|
||||
"name": "smriti-dev",
|
||||
"description": "Shared reasoning backend.",
|
||||
}
|
||||
|
||||
|
||||
def _commit(**overrides) -> dict:
|
||||
base = {
|
||||
"id": "checkpoint-uuid",
|
||||
"repo_id": "space-uuid",
|
||||
"commit_hash": "abcdef1234567890",
|
||||
"branch_name": "main",
|
||||
"author_agent": "codex-local",
|
||||
"message": "Project Current State direction",
|
||||
"objective": "Make current project operation legible.",
|
||||
"summary": "Current-state surface is the next product packaging layer.",
|
||||
"tasks": [
|
||||
{
|
||||
"id": "current-cli",
|
||||
"text": "Add Project Current State CLI surface",
|
||||
"intent_hint": "implement",
|
||||
},
|
||||
{
|
||||
"id": "current-review",
|
||||
"text": "Review the current-state UI",
|
||||
"intent_hint": "review",
|
||||
"blocked_by": "current-ui",
|
||||
},
|
||||
],
|
||||
"open_questions": ["Should this live in state or current?"],
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
base.update(overrides)
|
||||
return base
|
||||
|
||||
|
||||
def _current_payload() -> dict:
|
||||
return {
|
||||
"space_id": "space-uuid",
|
||||
"name": "smriti-dev",
|
||||
"description": "Shared reasoning backend.",
|
||||
"current_direction": "Make current project operation legible.",
|
||||
"latest_checkpoint": {
|
||||
"commit_hash": "abcdef1234567890",
|
||||
"message": "Project Current State direction",
|
||||
"author_agent": "codex-local",
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
},
|
||||
"counts": {
|
||||
"checkpoints": 74,
|
||||
"active_claims": 1,
|
||||
"active_branches": 0,
|
||||
"open_tasks": 2,
|
||||
"milestones": 2,
|
||||
"attention": 1,
|
||||
},
|
||||
"attention": [
|
||||
{"severity": "question", "message": "1 open question on latest checkpoint."}
|
||||
],
|
||||
"active_work": [
|
||||
{
|
||||
"agent": "codex-local",
|
||||
"intent_type": "implement",
|
||||
"task_id": "current-cli",
|
||||
"branch_name": "codex/project-current-state-cli",
|
||||
"scope": "Add Project Current State CLI surface",
|
||||
"claimed_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
],
|
||||
"recent_milestones": [
|
||||
{
|
||||
"commit_hash": "1111222233334444",
|
||||
"note": "First autonomous task selection worked.",
|
||||
"author": "founder",
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
],
|
||||
"open_tasks_by_intent": {
|
||||
"implement": [
|
||||
{
|
||||
"id": "current-cli",
|
||||
"text": "Add Project Current State CLI surface",
|
||||
"intent_hint": "implement",
|
||||
}
|
||||
],
|
||||
"review": [
|
||||
{
|
||||
"id": "current-review",
|
||||
"text": "Review the current-state UI",
|
||||
"intent_hint": "review",
|
||||
"blocked_by": "current-ui",
|
||||
}
|
||||
],
|
||||
},
|
||||
"recent_activity": [
|
||||
{
|
||||
"commit_hash": "abcdef1234567890",
|
||||
"author_agent": "codex-local",
|
||||
"message": "Project Current State direction",
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_current_parser_wiring():
|
||||
parser = cli_main._build_parser()
|
||||
|
||||
args = parser.parse_args(["current", "smriti-dev"])
|
||||
|
||||
assert args.command == "current"
|
||||
assert args.space == "smriti-dev"
|
||||
assert args.func is cli_main.cmd_current
|
||||
|
||||
|
||||
def test_format_project_current_renders_contract_sections():
|
||||
out = format_project_current(_current_payload())
|
||||
|
||||
assert "# smriti-dev — current state" in out
|
||||
assert "## Counts" in out
|
||||
assert "active claims: 1" in out
|
||||
assert "## Needs attention" in out
|
||||
assert "[question] 1 open question" in out
|
||||
assert "## Active work" in out
|
||||
assert "task `current-cli`" in out
|
||||
assert "## Recent milestones" in out
|
||||
assert "First autonomous task selection worked." in out
|
||||
assert "## Open tasks by intent" in out
|
||||
assert "### implement" in out
|
||||
assert "`current-review` Review the current-state UI → blocked by: current-ui" in out
|
||||
assert "## Recent activity" in out
|
||||
|
||||
|
||||
def test_cmd_current_prefers_backend_payload(capsys: pytest.CaptureFixture[str]):
|
||||
client = MagicMock(spec=SmritiClient)
|
||||
client.resolve_space.return_value = _space()
|
||||
client.get_current_state.return_value = _current_payload()
|
||||
args = argparse.Namespace(space="smriti-dev", json=False)
|
||||
|
||||
cli_main.cmd_current(client, args)
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert "# smriti-dev — current state" in out
|
||||
assert "Add Project Current State CLI surface" in out
|
||||
client.resolve_space.assert_called_once_with("smriti-dev")
|
||||
client.get_current_state.assert_called_once_with("space-uuid")
|
||||
client.get_space_state.assert_not_called()
|
||||
|
||||
|
||||
def test_cmd_current_json_outputs_backend_payload():
|
||||
client = MagicMock(spec=SmritiClient)
|
||||
client.resolve_space.return_value = _space()
|
||||
payload = _current_payload()
|
||||
client.get_current_state.return_value = payload
|
||||
args = argparse.Namespace(space="smriti-dev", json=True)
|
||||
captured: list[dict] = []
|
||||
original = cli_main._print_json
|
||||
cli_main._print_json = captured.append
|
||||
try:
|
||||
cli_main.cmd_current(client, args)
|
||||
finally:
|
||||
cli_main._print_json = original
|
||||
|
||||
assert captured == [payload]
|
||||
|
||||
|
||||
def test_cmd_current_falls_back_to_shipped_endpoints(capsys: pytest.CaptureFixture[str]):
|
||||
client = MagicMock(spec=SmritiClient)
|
||||
client.resolve_space.return_value = _space()
|
||||
client.get_current_state.side_effect = SmritiError(
|
||||
"endpoint missing", status=404
|
||||
)
|
||||
commit = _commit()
|
||||
client.get_space_state.return_value = {
|
||||
"commit": commit,
|
||||
"head": {"commit_id": "checkpoint-uuid"},
|
||||
"active_claims": [
|
||||
{
|
||||
"agent": "codex-local",
|
||||
"intent_type": "implement",
|
||||
"task_id": "current-cli",
|
||||
"branch_name": "codex/project-current-state-cli",
|
||||
"scope": "Add Project Current State CLI surface",
|
||||
"claimed_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
],
|
||||
"active_branches": [],
|
||||
"divergence": None,
|
||||
}
|
||||
client.list_commits.return_value = [
|
||||
{
|
||||
**commit,
|
||||
"metadata": {
|
||||
"notes": [
|
||||
{
|
||||
"kind": "milestone",
|
||||
"text": "Current-state direction chosen.",
|
||||
"author": "founder",
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
client.get_space_metrics.return_value = {
|
||||
"coordination": {"total_checkpoints": 74},
|
||||
"state_quality": {"milestone_count": 3},
|
||||
"branches": {"active": 0},
|
||||
}
|
||||
args = argparse.Namespace(space="smriti-dev", json=False)
|
||||
|
||||
cli_main.cmd_current(client, args)
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert "Make current project operation legible." in out
|
||||
assert "checkpoints: 74" in out
|
||||
assert "active claims: 1" in out
|
||||
assert "open tasks: 2" in out
|
||||
assert "[question] 1 open question" in out
|
||||
assert "Current-state direction chosen." in out
|
||||
assert "### implement" in out
|
||||
assert "`current-cli` Add Project Current State CLI surface" in out
|
||||
client.get_space_state.assert_called_once_with("space-uuid")
|
||||
client.list_commits.assert_called_once_with("space-uuid")
|
||||
client.get_space_metrics.assert_called_once_with("space-uuid")
|
||||
|
||||
Loading…
Add table
Reference in a new issue