142 lines
4.5 KiB
Python
142 lines
4.5 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
import typer
|
|
from rich.console import Console
|
|
from rich.table import Table
|
|
|
|
from .discovery import append_discovery_answer, core_questions
|
|
from .handoff import build_handoff
|
|
from .status import project_stage, task_counts
|
|
from .tasks import generate_agent_prompt, get_next_task, update_task_status
|
|
from .templates import CONTEXT_FILES, write_file_if_missing
|
|
|
|
app = typer.Typer(help="RDB discovery and delivery workflow CLI.")
|
|
console = Console()
|
|
|
|
|
|
def root_path() -> Path:
|
|
return Path.cwd()
|
|
|
|
|
|
def append_run_log(root: Path, event: str, task_id: str = "", notes: str = "") -> None:
|
|
path = root / "RUN_LOG.md"
|
|
if not path.exists():
|
|
path.write_text("# RUN_LOG.md\n\n| Date | Event | Task | Notes |\n|---|---|---|---|\n", encoding="utf-8")
|
|
line = f"| {datetime.now().isoformat(timespec='seconds')} | {event} | {task_id} | {notes} |\n"
|
|
with path.open("a", encoding="utf-8") as handle:
|
|
handle.write(line)
|
|
|
|
|
|
@app.command()
|
|
def init() -> None:
|
|
"""Create the initial markdown project structure."""
|
|
root = root_path()
|
|
created: list[str] = []
|
|
skipped: list[str] = []
|
|
|
|
for relative_path, content in CONTEXT_FILES.items():
|
|
if write_file_if_missing(root, relative_path, content):
|
|
created.append(relative_path)
|
|
else:
|
|
skipped.append(relative_path)
|
|
|
|
console.print("[bold green]Initialised rdb-discovery files.[/bold green]")
|
|
if created:
|
|
console.print("\nCreated:")
|
|
for item in created:
|
|
console.print(f"- {item}")
|
|
if skipped:
|
|
console.print("\nSkipped existing files:")
|
|
for item in skipped:
|
|
console.print(f"- {item}")
|
|
|
|
|
|
@app.command()
|
|
def discover() -> None:
|
|
"""Ask the core discovery questions and append answers to the discovery ledger."""
|
|
root = root_path()
|
|
write_file_if_missing(root, "context/discovery-log.md", CONTEXT_FILES["context/discovery-log.md"])
|
|
|
|
for index, question in enumerate(core_questions(), start=1):
|
|
qid = f"Q-{index:03d}"
|
|
answer = typer.prompt(question)
|
|
confidence = typer.prompt("Confidence?", default="Medium")
|
|
follow_up = typer.prompt("Follow-up needed?", default="No")
|
|
append_discovery_answer(root, qid, question, answer, confidence, follow_up)
|
|
|
|
append_run_log(root, "Discovery completed", notes="Core questions answered")
|
|
console.print("[bold green]Discovery complete.[/bold green]")
|
|
|
|
|
|
@app.command()
|
|
def status() -> None:
|
|
"""Show current project stage and task counts."""
|
|
root = root_path()
|
|
console.print(f"[bold]Project stage:[/bold] {project_stage(root)}")
|
|
|
|
counts = task_counts(root)
|
|
table = Table(title="Task Counts")
|
|
table.add_column("Status")
|
|
table.add_column("Count")
|
|
|
|
if counts:
|
|
for status_name, count in counts.items():
|
|
table.add_row(status_name, str(count))
|
|
else:
|
|
table.add_row("None", "0")
|
|
|
|
console.print(table)
|
|
|
|
|
|
@app.command(name="next")
|
|
def next_task() -> None:
|
|
"""Show the next Todo task."""
|
|
task = get_next_task(root_path())
|
|
if not task:
|
|
console.print("[yellow]No Todo task found.[/yellow]")
|
|
raise typer.Exit(code=0)
|
|
|
|
console.print(f"[bold]{task.task_id}[/bold] - {task.title}")
|
|
console.print(f"Status: {task.status}")
|
|
console.print(task.body)
|
|
|
|
|
|
@app.command()
|
|
def start(task_id: str) -> None:
|
|
"""Mark a task as In Progress."""
|
|
root = root_path()
|
|
if not update_task_status(root, task_id, "In Progress"):
|
|
console.print(f"[red]Task not found:[/red] {task_id}")
|
|
raise typer.Exit(code=1)
|
|
append_run_log(root, "Task started", task_id=task_id)
|
|
console.print(f"[green]Started {task_id}.[/green]")
|
|
|
|
|
|
@app.command()
|
|
def complete(task_id: str) -> None:
|
|
"""Mark a task as Done and record validation notes."""
|
|
root = root_path()
|
|
notes = typer.prompt("Validation notes", default="Not tested")
|
|
if not update_task_status(root, task_id, "Done"):
|
|
console.print(f"[red]Task not found:[/red] {task_id}")
|
|
raise typer.Exit(code=1)
|
|
append_run_log(root, "Task completed", task_id=task_id, notes=notes)
|
|
console.print(f"[green]Completed {task_id}.[/green]")
|
|
|
|
|
|
@app.command()
|
|
def handoff() -> None:
|
|
"""Print an AI-agent handoff summary."""
|
|
console.print(build_handoff(root_path()))
|
|
|
|
|
|
@app.command()
|
|
def prompt() -> None:
|
|
"""Generate a ready-to-paste implementation prompt for the next Todo task."""
|
|
root = root_path()
|
|
result = generate_agent_prompt(root)
|
|
console.print(result)
|