feat: align ramble defaults with shared ai config
This commit is contained in:
parent
57255a7e09
commit
3eca60148c
|
|
@ -18,6 +18,11 @@ import argparse, datetime, json, os, re, subprocess, sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Tuple
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
|
try:
|
||||||
|
from automation.ai_config import load_ai_settings
|
||||||
|
except ImportError:
|
||||||
|
load_ai_settings = None # type: ignore
|
||||||
|
|
||||||
# --------- helpers ---------
|
# --------- helpers ---------
|
||||||
def say(msg: str) -> None:
|
def say(msg: str) -> None:
|
||||||
print(msg, flush=True)
|
print(msg, flush=True)
|
||||||
|
|
@ -204,14 +209,47 @@ def main():
|
||||||
ap.add_argument("--dir", help="Repo root (defaults to git root or CWD)")
|
ap.add_argument("--dir", help="Repo root (defaults to git root or CWD)")
|
||||||
ap.add_argument("--title", help="Feature title (useful without Ramble)")
|
ap.add_argument("--title", help="Feature title (useful without Ramble)")
|
||||||
ap.add_argument("--no-ramble", action="store_true", help="Disable Ramble UI")
|
ap.add_argument("--no-ramble", action="store_true", help="Disable Ramble UI")
|
||||||
ap.add_argument("--provider", choices=["mock", "claude"], default="mock")
|
ap.add_argument("--provider", help="Override Ramble provider (defaults to config)")
|
||||||
ap.add_argument("--claude-cmd", default="claude")
|
ap.add_argument("--claude-cmd", help="Override claude CLI path")
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
|
||||||
start = Path(args.dir).expanduser().resolve() if args.dir else Path.cwd()
|
start = Path(args.dir).expanduser().resolve() if args.dir else Path.cwd()
|
||||||
repo = git_root_or_cwd(start)
|
repo = git_root_or_cwd(start)
|
||||||
say(f"[=] Using repository: {repo}")
|
say(f"[=] Using repository: {repo}")
|
||||||
|
|
||||||
|
provider = args.provider
|
||||||
|
claude_cmd = args.claude_cmd
|
||||||
|
provider_map: Dict[str, Dict[str, object]] = {}
|
||||||
|
default_provider = "mock"
|
||||||
|
|
||||||
|
if load_ai_settings is not None:
|
||||||
|
try:
|
||||||
|
settings = load_ai_settings(repo)
|
||||||
|
provider_map = dict(settings.ramble.providers)
|
||||||
|
candidate_default = settings.ramble.default_provider
|
||||||
|
if isinstance(candidate_default, str) and candidate_default.strip():
|
||||||
|
default_provider = candidate_default.strip()
|
||||||
|
except Exception:
|
||||||
|
provider_map = {}
|
||||||
|
|
||||||
|
if "mock" not in provider_map:
|
||||||
|
provider_map["mock"] = {"kind": "mock"}
|
||||||
|
|
||||||
|
if not provider:
|
||||||
|
provider = default_provider if default_provider in provider_map else "mock"
|
||||||
|
elif provider not in provider_map:
|
||||||
|
say(f"[WARN] Unknown Ramble provider '{provider}', defaulting to {default_provider}")
|
||||||
|
provider = default_provider if default_provider in provider_map else "mock"
|
||||||
|
|
||||||
|
if not claude_cmd:
|
||||||
|
selected = provider_map.get(provider, {})
|
||||||
|
maybe_cmd = selected.get("command") if isinstance(selected, dict) else None
|
||||||
|
if isinstance(maybe_cmd, str) and maybe_cmd.strip():
|
||||||
|
claude_cmd = maybe_cmd.strip()
|
||||||
|
|
||||||
|
if not claude_cmd:
|
||||||
|
claude_cmd = "claude"
|
||||||
|
|
||||||
tmpl_path = repo / "process" / "templates" / "feature_request.md"
|
tmpl_path = repo / "process" / "templates" / "feature_request.md"
|
||||||
tmpl = read_text(tmpl_path)
|
tmpl = read_text(tmpl_path)
|
||||||
parsed_fields = find_template_fields(tmpl) or [(f, "") for f in default_fields()]
|
parsed_fields = find_template_fields(tmpl) or [(f, "") for f in default_fields()]
|
||||||
|
|
@ -222,7 +260,7 @@ def main():
|
||||||
# Try Ramble unless disabled
|
# Try Ramble unless disabled
|
||||||
fields: Dict[str, str] | None = None
|
fields: Dict[str, str] | None = None
|
||||||
if not args.no_ramble:
|
if not args.no_ramble:
|
||||||
fields = try_ramble(repo, field_labels, provider=args.provider, claude_cmd=args.claude_cmd)
|
fields = try_ramble(repo, field_labels, provider=provider, claude_cmd=claude_cmd)
|
||||||
|
|
||||||
# Terminal prompts fallback
|
# Terminal prompts fallback
|
||||||
if not fields:
|
if not fields:
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ Requirements
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional, Any, Tuple, Protocol, runtime_checkable, Mapping, cast
|
from typing import Dict, List, Optional, Any, Tuple, Protocol, runtime_checkable, Mapping, cast
|
||||||
import os, sys, json, textwrap, base64, re, time, shutil, subprocess, argparse, threading
|
import os, sys, json, textwrap, base64, re, time, shutil, subprocess, argparse, threading
|
||||||
|
|
||||||
|
|
@ -43,6 +44,11 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
requests = None
|
requests = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
from automation.ai_config import load_ai_settings
|
||||||
|
except ImportError:
|
||||||
|
load_ai_settings = None # type: ignore
|
||||||
|
|
||||||
# ── Qt (PySide6 preferred; PyQt5 fallback) ────────────────────────────────────
|
# ── Qt (PySide6 preferred; PyQt5 fallback) ────────────────────────────────────
|
||||||
QT_LIB = None
|
QT_LIB = None
|
||||||
try:
|
try:
|
||||||
|
|
@ -690,38 +696,116 @@ def open_ramble_dialog(
|
||||||
|
|
||||||
|
|
||||||
# ── CLI demo ──────────────────────────────────────────────────────────────────
|
# ── CLI demo ──────────────────────────────────────────────────────────────────
|
||||||
def parse_args():
|
def load_ramble_preferences(repo_root: Path) -> Tuple[List[str], str, Dict[str, Any], Dict[str, Dict[str, Any]]]:
|
||||||
|
"""Return (choices, default_provider, claude_defaults, provider_map)."""
|
||||||
|
provider_map: Dict[str, Dict[str, Any]] = {}
|
||||||
|
default_provider = "mock"
|
||||||
|
|
||||||
|
if load_ai_settings is not None:
|
||||||
|
try:
|
||||||
|
settings = load_ai_settings(repo_root)
|
||||||
|
provider_map = dict(settings.ramble.providers)
|
||||||
|
candidate_default = settings.ramble.default_provider
|
||||||
|
if isinstance(candidate_default, str) and candidate_default.strip():
|
||||||
|
default_provider = candidate_default.strip()
|
||||||
|
except Exception:
|
||||||
|
provider_map = {}
|
||||||
|
|
||||||
|
if "mock" not in provider_map:
|
||||||
|
provider_map["mock"] = {"kind": "mock"}
|
||||||
|
|
||||||
|
provider_choices = sorted(provider_map.keys())
|
||||||
|
if default_provider not in provider_choices:
|
||||||
|
default_provider = "mock"
|
||||||
|
|
||||||
|
claude_defaults = provider_map.get("claude", {})
|
||||||
|
if not isinstance(claude_defaults, dict):
|
||||||
|
claude_defaults = {}
|
||||||
|
|
||||||
|
return provider_choices, default_provider, claude_defaults, provider_map
|
||||||
|
|
||||||
|
|
||||||
|
def build_provider(name: str, args: "argparse.Namespace", providers: Dict[str, Dict[str, Any]]) -> RambleProvider:
|
||||||
|
meta = providers.get(name, {})
|
||||||
|
if not isinstance(meta, dict):
|
||||||
|
meta = {}
|
||||||
|
|
||||||
|
kind = meta.get("kind")
|
||||||
|
if not isinstance(kind, str) or not kind:
|
||||||
|
kind = "mock" if name == "mock" else name
|
||||||
|
|
||||||
|
if kind == "mock":
|
||||||
|
return cast(RambleProvider, MockProvider())
|
||||||
|
|
||||||
|
if kind in {"claude", "claude_cli"}:
|
||||||
|
cmd = args.claude_cmd or meta.get("command") or "claude"
|
||||||
|
extra_args = meta.get("args", []) or []
|
||||||
|
if isinstance(extra_args, str):
|
||||||
|
extra_args = [extra_args]
|
||||||
|
extra_args = [str(x) for x in extra_args]
|
||||||
|
|
||||||
|
use_arg_p = bool(meta.get("use_arg_p", True))
|
||||||
|
log_path = str(meta.get("log_path", "/tmp/ramble_claude.log"))
|
||||||
|
timeout_s = int(args.timeout)
|
||||||
|
tail_chars = int(args.tail)
|
||||||
|
debug_flag = bool(args.debug or meta.get("debug", False))
|
||||||
|
|
||||||
|
return cast(
|
||||||
|
RambleProvider,
|
||||||
|
ClaudeCLIProvider(
|
||||||
|
cmd=cmd,
|
||||||
|
extra_args=extra_args,
|
||||||
|
timeout_s=timeout_s,
|
||||||
|
tail_chars=tail_chars,
|
||||||
|
use_arg_p=use_arg_p,
|
||||||
|
debug=debug_flag,
|
||||||
|
log_path=log_path,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
raise ValueError(f"Unknown Ramble provider kind: {kind}")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args(
|
||||||
|
provider_choices: List[str],
|
||||||
|
default_provider: str,
|
||||||
|
claude_defaults: Dict[str, Any],
|
||||||
|
):
|
||||||
p = argparse.ArgumentParser(description="Ramble → Generate (PlantUML + optional images)")
|
p = argparse.ArgumentParser(description="Ramble → Generate (PlantUML + optional images)")
|
||||||
p.add_argument("--provider", choices=["mock", "claude"], default="mock")
|
p.add_argument("--provider", choices=provider_choices, default=default_provider)
|
||||||
p.add_argument("--claude-cmd", default="claude", help="Path to claude CLI")
|
|
||||||
|
claude_cmd_default = str(claude_defaults.get("command", "claude"))
|
||||||
|
try:
|
||||||
|
timeout_default = int(claude_defaults.get("timeout_s", 90))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
timeout_default = 90
|
||||||
|
try:
|
||||||
|
tail_default = int(claude_defaults.get("tail_chars", 6000))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
tail_default = 6000
|
||||||
|
|
||||||
|
p.add_argument("--claude-cmd", default=claude_cmd_default, help="Path to claude CLI")
|
||||||
p.add_argument("--stability", action="store_true", help="Enable Stability AI images (needs STABILITY_API_KEY)")
|
p.add_argument("--stability", action="store_true", help="Enable Stability AI images (needs STABILITY_API_KEY)")
|
||||||
p.add_argument("--pexels", action="store_true", help="Enable Pexels images (needs PEXELS_API_KEY); ignored if --stability set")
|
p.add_argument("--pexels", action="store_true", help="Enable Pexels images (needs PEXELS_API_KEY); ignored if --stability set")
|
||||||
p.add_argument("--prompt", default="Explain your new feature idea")
|
p.add_argument("--prompt", default="Explain your new feature idea")
|
||||||
p.add_argument("--fields", nargs="+", default=["Summary","Title","Intent","ProblemItSolves","BriefOverview"])
|
p.add_argument("--fields", nargs="+", default=["Summary","Title","Intent","ProblemItSolves","BriefOverview"])
|
||||||
p.add_argument("--criteria", default="", help="JSON mapping of field -> criteria")
|
p.add_argument("--criteria", default="", help="JSON mapping of field -> criteria")
|
||||||
p.add_argument("--hints", default="", help="JSON list of hint strings")
|
p.add_argument("--hints", default="", help="JSON list of hint strings")
|
||||||
p.add_argument("--timeout", type=int, default=90)
|
p.add_argument("--timeout", type=int, default=timeout_default)
|
||||||
p.add_argument("--tail", type=int, default=6000)
|
p.add_argument("--tail", type=int, default=tail_default)
|
||||||
p.add_argument("--debug", action="store_true")
|
p.add_argument("--debug", action="store_true")
|
||||||
return p.parse_args()
|
return p.parse_args()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
args = parse_args()
|
repo_root = Path.cwd()
|
||||||
|
provider_choices, default_provider, claude_defaults, provider_map = load_ramble_preferences(repo_root)
|
||||||
|
args = parse_args(provider_choices, default_provider, claude_defaults)
|
||||||
|
|
||||||
# Field criteria parsing
|
# Provider selection with fallback
|
||||||
criteria: Dict[str, str] = {}
|
|
||||||
if args.criteria.strip():
|
|
||||||
try:
|
try:
|
||||||
criteria = json.loads(args.criteria)
|
provider = build_provider(args.provider, args, provider_map)
|
||||||
except Exception as e:
|
except Exception as exc:
|
||||||
print(f"[WARN] Could not parse --criteria JSON: {e}", file=sys.stderr)
|
print(f"[WARN] Ramble provider '{args.provider}' unavailable ({exc}); falling back to mock", file=sys.stderr)
|
||||||
|
|
||||||
# Provider
|
|
||||||
if args.provider == "claude":
|
|
||||||
provider = cast(RambleProvider, ClaudeCLIProvider(
|
|
||||||
cmd=args.claude_cmd, use_arg_p=True, timeout_s=args.timeout, tail_chars=args.tail, debug=args.debug,
|
|
||||||
))
|
|
||||||
else:
|
|
||||||
provider = cast(RambleProvider, MockProvider())
|
provider = cast(RambleProvider, MockProvider())
|
||||||
|
|
||||||
# Ensure PlantUML
|
# Ensure PlantUML
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ ramble:
|
||||||
providers:
|
providers:
|
||||||
mock:
|
mock:
|
||||||
kind: mock
|
kind: mock
|
||||||
claude_cli:
|
claude:
|
||||||
kind: claude_cli
|
kind: claude_cli
|
||||||
command: "claude"
|
command: "claude"
|
||||||
args: []
|
args: []
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue