feat(cya): T01-T07 core console-native MVP slice (CYA-WP-0001)

- T01: Python + Typer/rich + pyproject.toml + full src/ layout + working `cya` CLI entrypoint
- T02: Bounded transparent context collector (top-level only, provenance, ignores) + --explain-context
- T03: Genuine rule-based risk classifier (primary) + mandatory terminal confirmation, no auto-execute
- T04: LLMAdapter Protocol + deterministic FakeLLMAdapter seam (llm-connect boundary, zero bypass)
- T05: Strictly minimal phase-memory no-op ports (loud markers, per operator direction 2026-05-26)
- T06: Orchestrator coordinating the full flow; CLI is thin delegation
- T07: pytest harness + safety-focused tests (risk invariants + collector)

All changes verified by running the installed `cya` binary and `pytest tests/`.

Workplan updated with status. State Hub progress event logged (workstream 0a1233fd...).

Refs: CYA-WP-0001, Decision a644364b-11c4-49a9-bf17-99063382e27b
This commit is contained in:
2026-05-26 02:19:13 +02:00
parent da6c7acfc9
commit 637919dd8c
16 changed files with 1308 additions and 8 deletions

5
src/cya/cli/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
"""CLI surface and entrypoint (T01).
The primary user-facing commands live here. Later tasks will extend with
context-aware behavior while keeping this the stable surface.
"""

122
src/cya/cli/main.py Normal file
View File

@@ -0,0 +1,122 @@
"""T01 — Project scaffolding and Typer CLI entrypoint.
Implements the minimal runnable package per CYA-WP-0001-T01 acceptance criteria:
- pyproject.toml + src/ layout with clean separation (cli, context, safety, llm, memory)
- `cya --help`, `cya --version`, `cya "<natural language request>"` all work after `pip install -e .`
- Rich, structured, human-readable output even before LLM / collector wiring.
- Graceful fallback message pointing to the remaining workplan tasks.
This module will evolve in T06 (orchestrator) but the surface contract stays stable.
"""
from __future__ import annotations
import sys
import typer
from rich.console import Console
from rich.panel import Panel
from rich.text import Text
from cya import __version__
from cya.context.collector import collect, render_explanation
app = typer.Typer(
name="cya",
help=(
"[bold cyan]cya[/bold cyan] — console-native assistant for local work.\n\n"
"Express intent in natural language from your terminal.\n"
"MVP T01 (scaffolding): this is the skeleton only. "
"Real context collection (T02), rule-based safety (T03), llm-connect boundary (T04), "
"and orchestration (T06) are added in subsequent tasks.\n\n"
"Usage: [bold]cya \"your request in plain English\"[/bold]"
),
rich_markup_mode="rich",
add_completion=False,
invoke_without_command=True,
)
console = Console()
def version_callback(value: bool) -> None:
"""Print version and exit (eager)."""
if value:
console.print(f"[bold]cya[/bold] version [green]{__version__}[/green] (MVP T01 scaffolding)")
raise typer.Exit()
@app.callback()
def main(
ctx: typer.Context,
request: str | None = typer.Argument(
None,
help="Your natural-language request or intent (e.g. 'explain the recent git log for this repo').",
),
explain_context: bool = typer.Option(
False,
"--explain-context",
"-C",
help="Show exactly what local context would be collected (real implementation in T02).",
),
dry_run: bool = typer.Option(
False,
"--dry-run",
"-n",
help="Preview mode — do not perform any actions (stub in T01).",
),
version: bool = typer.Option(
None,
"--version",
"-V",
callback=version_callback,
is_eager=True,
help="Show cya version and exit.",
),
) -> None:
"""Root entry: supports bare `cya "request"` as the primary one-shot UX."""
if ctx.invoked_subcommand is not None:
# A real subcommand was given; let Typer handle it.
return
if request is None:
# No request and no subcommand — show friendly guidance instead of raw error.
console.print(
Panel(
Text.from_markup(
"No request provided.\n\n"
"Try:\n"
" [bold]cya \"what changed since the last commit?\"[/bold]\n"
" [bold]cya --help[/bold] for all options and examples\n\n"
"[dim]This is T01 scaffolding. Full behavior arrives after T02T06.[/dim]"
),
title="cya (MVP)",
border_style="yellow",
padding=(1, 2),
)
)
raise typer.Exit(0)
# Delegate the entire coordinated flow (T02T04) to the orchestrator (T06).
# This keeps the Typer surface thin and makes the core logic testable.
from cya.orchestrator import handle_request
handle_request(
request,
explain_context=explain_context,
dry_run=dry_run,
)
if __name__ == "__main__":
app()
def run() -> None:
"""Primary console-script entry point (no-arg callable expected by setuptools/pip).
The generated `cya` wrapper in bin/ does `sys.exit(run())`.
Using a thin wrapper around app() lets us keep the full-featured
@app.callback(invoke_without_command=True) + ctx signature for Typer/Click.
"""
app()