Encode intent/decision/plan workflow for Snowflake platform delivery so engagements share a durable recipe instead of one-off LLM chats. Co-authored-by: Cursor <cursoragent@cursor.com>
165 lines
6.2 KiB
Python
165 lines
6.2 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import typer
|
|
from rich.console import Console
|
|
from rich.prompt import Prompt
|
|
|
|
from skylattice.core.decisions import create_decision
|
|
from skylattice.core.intent import load_intent, mark_wizard_answered, save_intent
|
|
from skylattice.core.paths import blueprint_dir, find_customer_root, load_yaml, set_by_path
|
|
|
|
console = Console()
|
|
|
|
|
|
def _parse_list(raw: str) -> list[str]:
|
|
if not raw or raw.strip().lower() == "none":
|
|
return []
|
|
return [p.strip() for p in raw.split(",") if p.strip()]
|
|
|
|
|
|
def _slug_decision(prefix: str, name: str) -> str:
|
|
day = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
safe = re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-")
|
|
return f"{day}-{prefix}-{safe}"
|
|
|
|
|
|
def _apply_answer(
|
|
intent: dict[str, Any],
|
|
customer_root: Path,
|
|
q: dict[str, Any],
|
|
raw: str,
|
|
*,
|
|
answer_map: dict[str, Any] | None = None,
|
|
) -> None:
|
|
qtype = q.get("type")
|
|
qid = q["id"]
|
|
answer_map = answer_map or {}
|
|
|
|
def _rationale_for(name: str) -> str:
|
|
key = f"{qid}_rationale"
|
|
if key in answer_map:
|
|
return str(answer_map[key]).format(name=name)
|
|
per_name = answer_map.get(f"{qid}_rationales") or {}
|
|
if isinstance(per_name, dict) and name in per_name:
|
|
return str(per_name[name])
|
|
if answer_map:
|
|
# Non-interactive answers file: do not block on Prompt
|
|
return str(
|
|
answer_map.get("default_rationale")
|
|
or f"Deferred via wizard answers for {name}"
|
|
)
|
|
return Prompt.ask(q.get("decision_prompt", "Rationale?").format(name=name))
|
|
|
|
if qtype in ("string",) and q.get("intent_path"):
|
|
set_by_path(intent, q["intent_path"], raw.strip())
|
|
elif qtype == "choice" and q.get("intent_path"):
|
|
set_by_path(intent, q["intent_path"], raw.strip())
|
|
elif qtype == "list" and q.get("intent_path"):
|
|
set_by_path(intent, q["intent_path"], _parse_list(raw) or q.get("default"))
|
|
elif qtype == "domain_list" and q.get("intent_path"):
|
|
names = _parse_list(raw)
|
|
profile = q.get("default_access_profile") or "standard"
|
|
intent["domains"] = [
|
|
{"name": n, "access_profile": profile, "zones": ["landing", "raw", "curated", "marts"]}
|
|
for n in names
|
|
]
|
|
elif qtype == "restricted_domains":
|
|
names = _parse_list(raw)
|
|
for d in intent.get("domains") or []:
|
|
if d["name"] in names:
|
|
d["access_profile"] = "restricted"
|
|
elif qtype == "override_with_decision":
|
|
names = _parse_list(raw)
|
|
overrides = list(intent.get("overrides") or [])
|
|
for name in names:
|
|
rationale = _rationale_for(name)
|
|
did = _slug_decision("override", name)
|
|
create_decision(
|
|
customer_root,
|
|
decision_id=did,
|
|
applies_to=q.get("override_path_template", "domains.{name}").format(name=name),
|
|
rationale=rationale,
|
|
)
|
|
overrides.append(
|
|
{
|
|
"path": q.get("override_path_template", "domains.{name}.prod_curated_read").format(name=name),
|
|
"value": True,
|
|
"decision_id": did,
|
|
}
|
|
)
|
|
intent["overrides"] = overrides
|
|
elif qtype == "unmanaged_list":
|
|
names = _parse_list(raw)
|
|
unmanaged = list(intent.get("unmanaged") or [])
|
|
kind = q.get("kind") or "role"
|
|
for name in names:
|
|
rationale = _rationale_for(name)
|
|
did = _slug_decision("unmanaged", name)
|
|
create_decision(
|
|
customer_root,
|
|
decision_id=did,
|
|
applies_to=f"unmanaged.{kind}.{name}",
|
|
rationale=rationale,
|
|
)
|
|
unmanaged.append({"kind": kind, "name": name, "reason": rationale, "decision_id": did})
|
|
intent["unmanaged"] = unmanaged
|
|
else:
|
|
console.print(f"[yellow]Unknown question type {qtype}; skipped[/yellow]")
|
|
|
|
mark_wizard_answered(intent, qid)
|
|
|
|
|
|
def run(
|
|
mode: str | None = typer.Option(None, "--mode", help="greenfield|brownfield (default: from intent)"),
|
|
resume: bool = typer.Option(False, "--resume", help="Only ask unanswered questions"),
|
|
path: Path | None = typer.Option(None, "--path", help="Customer project path"),
|
|
answers: Path | None = typer.Option(None, "--answers", help="Non-interactive answers YAML"),
|
|
) -> None:
|
|
"""Guided interview that writes intent.yaml and decisions/."""
|
|
customer_root = path.resolve() if path else find_customer_root()
|
|
intent = load_intent(customer_root)
|
|
mode = mode or intent.get("mode") or "greenfield"
|
|
catalog_name = f"catalog.{mode}.yaml"
|
|
catalog = load_yaml(blueprint_dir() / "wizard" / catalog_name)
|
|
questions = catalog.get("questions") or []
|
|
answered = set((intent.get("wizard") or {}).get("answered") or [])
|
|
|
|
answer_map: dict[str, Any] = {}
|
|
if answers:
|
|
answer_map = load_yaml(answers) or {}
|
|
|
|
console.print(f"[bold]Sky Lattice wizard[/bold] ({mode}) — {customer_root}")
|
|
if "discover" in (catalog.get("prerequisites") or []):
|
|
console.print("[dim]Tip: run platformctl discover before brownfield adoption.[/dim]")
|
|
|
|
for q in questions:
|
|
qid = q["id"]
|
|
if resume and qid in answered:
|
|
continue
|
|
|
|
if qid in answer_map:
|
|
raw = answer_map[qid]
|
|
if isinstance(raw, list):
|
|
raw = ",".join(str(x) for x in raw)
|
|
else:
|
|
raw = str(raw)
|
|
console.print(f"{q['prompt']} → {raw}")
|
|
else:
|
|
default = q.get("default")
|
|
if isinstance(default, list):
|
|
default_s = ",".join(str(x) for x in default)
|
|
else:
|
|
default_s = "" if default is None else str(default)
|
|
raw = Prompt.ask(q["prompt"], default=default_s)
|
|
|
|
_apply_answer(intent, customer_root, q, raw, answer_map=answer_map)
|
|
|
|
intent["mode"] = mode
|
|
save_intent(customer_root, intent)
|
|
console.print("[green]Wizard complete.[/green] Run: platformctl validate && platformctl plan")
|