day 1: Kin from zero to production - agents, GUI, autopilot, 352 tests
This commit is contained in:
parent
8d9facda4f
commit
8a6f280cbd
22 changed files with 1907 additions and 103 deletions
53
cli/main.py
53
cli/main.py
|
|
@ -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)}")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue