Skip to content

Commit e5e11aa

Browse files
authored
Merge pull request #121 from cyberfabric/fix-whatsnew-and-up-kit-1.3.0
refactor(update): extract whatsnew handling from kit update flow
2 parents 5c5cbf3 + 3dc0993 commit e5e11aa

File tree

36 files changed

+2011
-424
lines changed

36 files changed

+2011
-424
lines changed

.bootstrap/.core/skills/cypilot/cypilot.clispec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ RELATED:
334334

335335
COMMAND generate-agents
336336
SYNOPSIS: python3 scripts/cypilot.py generate-agents --agent <name> [options]
337-
DESCRIPTION: Generate/update agent-specific workflow proxies and skill outputs. Creates unified proxy files for workflows (cypilot-generate, cypilot-analyze) and the cypilot skill entry point. Supports windsurf, cursor, claude, copilot, openai.
337+
DESCRIPTION: Generate/update agent-specific workflow proxies and skill outputs. Creates unified proxy files for workflows (cypilot-generate, cypilot-analyze, cypilot-plan, cypilot-workspace) and the cypilot skill entry point. Supports windsurf, cursor, claude, copilot, openai.
338338
ARGUMENTS:
339339

340340
OPTIONS:

.bootstrap/.core/skills/cypilot/scripts/cypilot/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def main(argv: Optional[List[str]] = None) -> int:
1717
from .cli import main as _main
1818
return _main(argv)
1919

20-
__version__ = "v3.2.1-beta"
20+
__version__ = "v3.2.2-beta"
2121

2222
__all__ = [
2323
# Main entry point

.bootstrap/.core/skills/cypilot/scripts/cypilot/commands/agents.py

Lines changed: 243 additions & 35 deletions
Large diffs are not rendered by default.

.bootstrap/.core/skills/cypilot/scripts/cypilot/commands/kit.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from typing import Any, Dict, List, Optional, Tuple
1919

2020
from ..utils.ui import ui
21+
from ..utils.whatsnew import show_kit_whatsnew
2122

2223

2324
# ---------------------------------------------------------------------------
@@ -1154,6 +1155,30 @@ def cmd_kit_update(argv: List[str]) -> int:
11541155
errors: List[str] = []
11551156

11561157
for kit_slug, kit_source, github_source, tmp_dir in update_targets:
1158+
# @cpt-begin:cpt-cypilot-flow-kit-update-cli:p1:inst-show-whatsnew
1159+
# Show whatsnew entries before file-level diff (if whatsnew.toml exists)
1160+
if not args.dry_run:
1161+
installed_version = _read_kit_version_from_core(config_dir, kit_slug)
1162+
ack = show_kit_whatsnew(
1163+
kit_source,
1164+
installed_version,
1165+
kit_slug,
1166+
interactive=interactive and not args.yes,
1167+
)
1168+
if not ack:
1169+
# User aborted — skip this kit
1170+
all_results.append({
1171+
"kit": kit_slug,
1172+
"action": "aborted",
1173+
"accepted": [],
1174+
"declined": [],
1175+
"files_written": 0,
1176+
})
1177+
if tmp_dir:
1178+
shutil.rmtree(tmp_dir, ignore_errors=True)
1179+
continue
1180+
# @cpt-end:cpt-cypilot-flow-kit-update-cli:p1:inst-show-whatsnew
1181+
11571182
try:
11581183
# @cpt-begin:cpt-cypilot-flow-kit-update-cli:p1:inst-legacy-migration
11591184
# Legacy manifest migration is handled inside update_kit() when
@@ -1196,7 +1221,7 @@ def cmd_kit_update(argv: List[str]) -> int:
11961221
# @cpt-end:cpt-cypilot-flow-kit-update-cli:p1:inst-regen-gen
11971222

11981223
# @cpt-begin:cpt-cypilot-flow-kit-update-cli:p1:inst-format-output
1199-
n_updated = sum(1 for r in all_results if r["action"] not in ("current", "dry_run", "ERROR"))
1224+
n_updated = sum(1 for r in all_results if r["action"] not in ("current", "dry_run", "ERROR", "aborted"))
12001225
output: Dict[str, Any] = {
12011226
"status": "PASS" if not errors else "WARN",
12021227
"kits_updated": n_updated,

.bootstrap/.core/skills/cypilot/scripts/cypilot/commands/update.py

Lines changed: 25 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
# @cpt-begin:cpt-cypilot-flow-version-config-update:p1:inst-update-imports
2727
import argparse
2828
import json
29-
import re
3029
import shutil
3130
import sys
3231
from pathlib import Path
@@ -45,6 +44,7 @@
4544
_inject_root_claude,
4645
)
4746
from ..utils.ui import ui
47+
from ..utils.whatsnew import read_whatsnew, show_core_whatsnew, show_kit_whatsnew
4848
# @cpt-end:cpt-cypilot-flow-version-config-update:p1:inst-update-imports
4949

5050
def cmd_update(argv: List[str]) -> int:
@@ -134,10 +134,10 @@ def cmd_update(argv: List[str]) -> int:
134134

135135
# ── Show core whatsnew (before .core/ is replaced) ────────────────────
136136
if not args.dry_run:
137-
cache_whatsnew = _read_core_whatsnew(CACHE_DIR / "whatsnew.toml")
138-
core_whatsnew = _read_core_whatsnew(core_dir / "whatsnew.toml")
137+
cache_whatsnew = read_whatsnew(CACHE_DIR / "whatsnew.toml")
138+
core_whatsnew = read_whatsnew(core_dir / "whatsnew.toml")
139139
if cache_whatsnew:
140-
ack = _show_core_whatsnew(
140+
ack = show_core_whatsnew(
141141
cache_whatsnew, core_whatsnew,
142142
interactive=not args.no_interactive and not args.yes and sys.stdin.isatty(),
143143
)
@@ -225,6 +225,7 @@ def cmd_update(argv: List[str]) -> int:
225225
from .kit import (
226226
update_kit, regenerate_gen_aggregates,
227227
_read_kits_from_core_toml, _parse_github_source, _download_kit_from_github,
228+
_read_kit_version_from_core,
228229
migrate_legacy_kit_to_manifest,
229230
)
230231

@@ -258,6 +259,24 @@ def cmd_update(argv: List[str]) -> int:
258259
if kit_src is None:
259260
continue
260261

262+
if not args.dry_run:
263+
installed_version = _read_kit_version_from_core(config_dir, kit_slug)
264+
ack = show_kit_whatsnew(
265+
kit_src,
266+
installed_version,
267+
kit_slug,
268+
interactive=interactive and not args.yes,
269+
)
270+
if not ack:
271+
kit_r = {
272+
"kit": kit_slug,
273+
"version": {"status": "aborted"},
274+
"gen": {"files_written": 0},
275+
"gen_rejected": [],
276+
}
277+
kit_results[kit_slug] = kit_r
278+
continue
279+
261280
try:
262281
kit_r = update_kit(
263282
kit_slug, kit_src, cypilot_dir,
@@ -337,6 +356,8 @@ def cmd_update(argv: List[str]) -> int:
337356
ui.substep(f" ~ {fp}")
338357
for fp in rejected:
339358
ui.substep(f" ✗ {fp} (declined)")
359+
elif ver_status == "aborted":
360+
ui.substep(f"{kit_slug}: skipped by user")
340361
elif ver_status == "current":
341362
ui.substep(f"{kit_slug}: up to date")
342363

@@ -802,89 +823,6 @@ def _migrate_kit_sources(config_dir: Path) -> Dict[str, str]:
802823

803824
# Re-exported from kit.py — tests import it from here
804825
from .kit import _read_conf_version as _read_conf_version # noqa: F401
805-
806-
def _read_core_whatsnew(path: Path) -> Dict[str, Dict[str, str]]:
807-
"""Read a standalone whatsnew.toml file.
808-
809-
Returns dict mapping version string to {summary, details}.
810-
"""
811-
if not path.is_file():
812-
return {}
813-
try:
814-
import tomllib
815-
with open(path, "rb") as f:
816-
data = tomllib.load(f)
817-
except Exception:
818-
return {}
819-
result: Dict[str, Dict[str, str]] = {}
820-
for key, entry in data.items():
821-
if isinstance(entry, dict):
822-
result[key] = {
823-
"summary": str(entry.get("summary", "")),
824-
"details": str(entry.get("details", "")),
825-
}
826-
return result
827-
828-
829-
def _stderr_supports_ansi() -> bool:
830-
return hasattr(sys.stderr, "isatty") and sys.stderr.isatty()
831-
832-
833-
def _format_whatsnew_text(text: str, *, use_ansi: bool) -> str:
834-
if use_ansi:
835-
formatted = re.sub(r"\*\*(.+?)\*\*", r"\033[1m\1\033[0m", text)
836-
return re.sub(r"`(.+?)`", r"\033[36m\1\033[0m", formatted)
837-
plain = re.sub(r"\*\*(.+?)\*\*", r"\1", text)
838-
return re.sub(r"`(.+?)`", r"\1", plain)
839-
840-
841-
def _show_core_whatsnew(
842-
ref_whatsnew: Dict[str, Dict[str, str]],
843-
core_whatsnew: Dict[str, Dict[str, str]],
844-
*,
845-
interactive: bool = True,
846-
) -> bool:
847-
"""Display core whatsnew entries present in cache but missing from .core/.
848-
849-
Returns True if user acknowledged (or non-interactive), False if declined.
850-
"""
851-
missing = sorted(
852-
(v, ref_whatsnew[v]) for v in ref_whatsnew
853-
if v not in core_whatsnew
854-
)
855-
if not missing:
856-
return True
857-
858-
sys.stderr.write(f"\n{'=' * 60}\n")
859-
sys.stderr.write(f" What's new in Cypilot\n")
860-
sys.stderr.write(f"{'=' * 60}\n")
861-
862-
use_ansi = _stderr_supports_ansi()
863-
for ver, entry in missing:
864-
summary = _format_whatsnew_text(entry["summary"], use_ansi=use_ansi)
865-
if use_ansi and summary == entry["summary"]:
866-
sys.stderr.write(f"\n \033[1m{ver}: {entry['summary']}\033[0m\n")
867-
else:
868-
version_label = f"\033[1m{ver}:\033[0m" if use_ansi else f"{ver}:"
869-
sys.stderr.write(f"\n {version_label} {summary}\n")
870-
if entry["details"]:
871-
for line in entry["details"].splitlines():
872-
sys.stderr.write(
873-
f" {_format_whatsnew_text(line, use_ansi=use_ansi)}\n"
874-
)
875-
876-
sys.stderr.write(f"\n{'=' * 60}\n")
877-
878-
if not interactive:
879-
return True
880-
881-
sys.stderr.write(" Press Enter to continue with update (or 'q' to abort): ")
882-
sys.stderr.flush()
883-
try:
884-
response = input().strip().lower()
885-
except EOFError:
886-
return False
887-
return response != "q"
888826
# @cpt-end:cpt-cypilot-flow-version-config-update:p1:inst-update-helpers
889827

890828
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)