day 1: Kin from zero to production - agents, GUI, autopilot, 352 tests

This commit is contained in:
Gros Frumos 2026-03-15 23:22:49 +02:00
parent 8d9facda4f
commit 8a6f280cbd
22 changed files with 1907 additions and 103 deletions

View file

@ -141,6 +141,7 @@ def project_show(ctx, id):
click.echo(f" Path: {p['path']}")
click.echo(f" Status: {p['status']}")
click.echo(f" Priority: {p['priority']}")
click.echo(f" Mode: {p.get('execution_mode') or 'review'}")
if p.get("tech_stack"):
click.echo(f" Tech stack: {', '.join(p['tech_stack'])}")
if p.get("forgejo_repo"):
@ -148,6 +149,21 @@ def project_show(ctx, id):
click.echo(f" Created: {p['created_at']}")
@project.command("set-mode")
@click.option("--project", "project_id", required=True, help="Project ID")
@click.argument("mode", type=click.Choice(["auto", "review"]))
@click.pass_context
def project_set_mode(ctx, project_id, mode):
"""Set execution mode for a project (auto|review)."""
conn = ctx.obj["conn"]
p = models.get_project(conn, project_id)
if not p:
click.echo(f"Project '{project_id}' not found.", err=True)
raise SystemExit(1)
models.update_project(conn, project_id, execution_mode=mode)
click.echo(f"Project '{project_id}' execution_mode set to '{mode}'.")
# ===========================================================================
# task
# ===========================================================================
@ -204,11 +220,15 @@ def task_show(ctx, id):
if not t:
click.echo(f"Task '{id}' not found.", err=True)
raise SystemExit(1)
effective_mode = models.get_effective_mode(conn, t["project_id"], t["id"])
task_mode = t.get("execution_mode")
mode_label = f"{effective_mode} (overridden)" if task_mode else f"{effective_mode} (inherited)"
click.echo(f"Task: {t['id']}")
click.echo(f" Project: {t['project_id']}")
click.echo(f" Title: {t['title']}")
click.echo(f" Status: {t['status']}")
click.echo(f" Priority: {t['priority']}")
click.echo(f" Mode: {mode_label}")
if t.get("assigned_role"):
click.echo(f" Role: {t['assigned_role']}")
if t.get("parent_task_id"):
@ -223,13 +243,14 @@ def task_show(ctx, id):
@task.command("update")
@click.argument("task_id")
@click.option("--status", type=click.Choice(
["pending", "in_progress", "review", "done", "blocked", "decomposed", "cancelled"]),
@click.option("--status", type=click.Choice(models.VALID_TASK_STATUSES),
default=None, help="New status")
@click.option("--priority", type=int, default=None, help="New priority (1-10)")
@click.option("--mode", "mode", type=click.Choice(["auto", "review"]),
default=None, help="Override execution mode for this task")
@click.pass_context
def task_update(ctx, task_id, status, priority):
"""Update a task's status or priority."""
def task_update(ctx, task_id, status, priority, mode):
"""Update a task's status, priority, or execution mode."""
conn = ctx.obj["conn"]
t = models.get_task(conn, task_id)
if not t:
@ -240,11 +261,13 @@ def task_update(ctx, task_id, status, priority):
fields["status"] = status
if priority is not None:
fields["priority"] = priority
if mode is not None:
fields["execution_mode"] = mode
if not fields:
click.echo("Nothing to update. Use --status or --priority.", err=True)
click.echo("Nothing to update. Use --status, --priority, or --mode.", err=True)
raise SystemExit(1)
updated = models.update_task(conn, task_id, **fields)
click.echo(f"Updated {updated['id']}: status={updated['status']}, priority={updated['priority']}")
click.echo(f"Updated {updated['id']}: status={updated['status']}, priority={updated['priority']}, mode={updated.get('execution_mode') or '(inherited)'}")
# ===========================================================================
@ -816,7 +839,8 @@ def hook_logs(ctx, project_id, limit):
def hook_setup(ctx, project_id, scripts_dir):
"""Register standard hooks for a project.
Currently registers: rebuild-frontend (fires on web/frontend/* changes).
Registers: rebuild-frontend (fires on web/frontend/* changes),
auto-commit (fires on task_done git add -A && git commit).
Idempotent skips hooks that already exist.
"""
conn = ctx.obj["conn"]
@ -838,7 +862,6 @@ def hook_setup(ctx, project_id, scripts_dir):
name="rebuild-frontend",
event="pipeline_completed",
command=rebuild_cmd,
trigger_module_path="web/frontend/*",
working_dir=p.get("path"),
timeout_seconds=300,
)
@ -846,6 +869,20 @@ def hook_setup(ctx, project_id, scripts_dir):
else:
click.echo("Hook 'rebuild-frontend' already exists, skipping.")
if "auto-commit" not in existing_names:
project_path = str(Path(p.get("path", ".")).expanduser())
hooks_module.create_hook(
conn, project_id,
name="auto-commit",
event="task_done",
command='git add -A && git commit -m "kin: {task_id} {title}"',
working_dir=project_path,
timeout_seconds=30,
)
created.append("auto-commit")
else:
click.echo("Hook 'auto-commit' already exists, skipping.")
if created:
click.echo(f"Registered hooks: {', '.join(created)}")