Add bootstrap command — auto-detect project stack, modules, decisions

kin bootstrap <path> --id <id> --name <name> [--vault <path>]

Detects: package.json, requirements.txt, go.mod, config files → tech_stack.
Scans src/app/lib/frontend/backend dirs → modules with type detection.
Parses CLAUDE.md for GOTCHA/WORKAROUND/FIXME/ВАЖНО → decisions.
Scans Obsidian vault for kanban tasks, checkboxes, and decisions.
Preview before save, -y to skip confirmation.
18 bootstrap tests, 57 total passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
johnfrum1234 2026-03-15 13:29:01 +02:00
parent 432cfd55d4
commit da4a8aae72
4 changed files with 957 additions and 0 deletions

View file

@ -14,6 +14,10 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
from core.db import init_db
from core import models
from agents.bootstrap import (
detect_tech_stack, detect_modules, extract_decisions_from_claude_md,
find_vault_root, scan_obsidian, format_preview, save_to_db,
)
DEFAULT_DB = Path.home() / ".kin" / "kin.db"
@ -404,6 +408,71 @@ def cost(ctx, period):
click.echo(f"\nTotal: ${total:.4f}")
# ===========================================================================
# bootstrap
# ===========================================================================
@cli.command("bootstrap")
@click.argument("path", type=click.Path(exists=True))
@click.option("--id", "project_id", required=True, help="Short project ID (e.g. vdol)")
@click.option("--name", required=True, help="Project display name")
@click.option("--vault", "vault_path", type=click.Path(), default=None,
help="Obsidian vault path (auto-detected if omitted)")
@click.option("-y", "--yes", is_flag=True, help="Skip confirmation")
@click.pass_context
def bootstrap(ctx, path, project_id, name, vault_path, yes):
"""Auto-detect project stack, modules, decisions and import into Kin."""
conn = ctx.obj["conn"]
project_path = Path(path).expanduser().resolve()
# Check if project already exists
existing = models.get_project(conn, project_id)
if existing:
click.echo(f"Project '{project_id}' already exists. Use 'kin project show {project_id}'.", err=True)
raise SystemExit(1)
# Detect everything
click.echo(f"Scanning {project_path} ...")
tech_stack = detect_tech_stack(project_path)
modules = detect_modules(project_path)
decisions = extract_decisions_from_claude_md(project_path)
# Obsidian
obsidian = None
vault_root = find_vault_root(Path(vault_path) if vault_path else None)
if vault_root:
dir_name = project_path.name
obsidian = scan_obsidian(vault_root, project_id, name, dir_name)
if not obsidian["tasks"] and not obsidian["decisions"]:
obsidian = None # Nothing found, don't clutter output
# Preview
click.echo("")
click.echo(format_preview(
project_id, name, str(project_path), tech_stack,
modules, decisions, obsidian,
))
click.echo("")
if not yes:
if not click.confirm("Save to kin.db?"):
click.echo("Aborted.")
return
save_to_db(conn, project_id, name, str(project_path),
tech_stack, modules, decisions, obsidian)
# Summary
task_count = 0
dec_count = len(decisions)
if obsidian:
task_count += len(obsidian.get("tasks", []))
dec_count += len(obsidian.get("decisions", []))
click.echo(f"Saved: 1 project, {len(modules)} modules, "
f"{dec_count} decisions, {task_count} tasks.")
# ===========================================================================
# Entry point
# ===========================================================================