raise-cli 2.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- raise_cli/__init__.py +38 -0
- raise_cli/__main__.py +30 -0
- raise_cli/adapters/__init__.py +91 -0
- raise_cli/adapters/declarative/__init__.py +26 -0
- raise_cli/adapters/declarative/adapter.py +267 -0
- raise_cli/adapters/declarative/discovery.py +94 -0
- raise_cli/adapters/declarative/expressions.py +150 -0
- raise_cli/adapters/declarative/reference/__init__.py +1 -0
- raise_cli/adapters/declarative/reference/github.yaml +143 -0
- raise_cli/adapters/declarative/schema.py +98 -0
- raise_cli/adapters/filesystem.py +299 -0
- raise_cli/adapters/mcp_bridge.py +10 -0
- raise_cli/adapters/mcp_confluence.py +246 -0
- raise_cli/adapters/mcp_jira.py +405 -0
- raise_cli/adapters/models.py +205 -0
- raise_cli/adapters/protocols.py +180 -0
- raise_cli/adapters/registry.py +90 -0
- raise_cli/adapters/sync.py +149 -0
- raise_cli/agents/__init__.py +14 -0
- raise_cli/agents/antigravity.yaml +8 -0
- raise_cli/agents/claude.yaml +8 -0
- raise_cli/agents/copilot.yaml +8 -0
- raise_cli/agents/copilot_plugin.py +124 -0
- raise_cli/agents/cursor.yaml +7 -0
- raise_cli/agents/roo.yaml +8 -0
- raise_cli/agents/windsurf.yaml +8 -0
- raise_cli/artifacts/__init__.py +30 -0
- raise_cli/artifacts/models.py +43 -0
- raise_cli/artifacts/reader.py +55 -0
- raise_cli/artifacts/renderer.py +104 -0
- raise_cli/artifacts/story_design.py +69 -0
- raise_cli/artifacts/writer.py +45 -0
- raise_cli/backlog/__init__.py +1 -0
- raise_cli/backlog/sync.py +115 -0
- raise_cli/cli/__init__.py +3 -0
- raise_cli/cli/commands/__init__.py +3 -0
- raise_cli/cli/commands/_resolve.py +153 -0
- raise_cli/cli/commands/adapters.py +362 -0
- raise_cli/cli/commands/artifact.py +137 -0
- raise_cli/cli/commands/backlog.py +333 -0
- raise_cli/cli/commands/base.py +31 -0
- raise_cli/cli/commands/discover.py +551 -0
- raise_cli/cli/commands/docs.py +130 -0
- raise_cli/cli/commands/doctor.py +177 -0
- raise_cli/cli/commands/gate.py +223 -0
- raise_cli/cli/commands/graph.py +1086 -0
- raise_cli/cli/commands/info.py +81 -0
- raise_cli/cli/commands/init.py +746 -0
- raise_cli/cli/commands/journal.py +167 -0
- raise_cli/cli/commands/mcp.py +524 -0
- raise_cli/cli/commands/memory.py +467 -0
- raise_cli/cli/commands/pattern.py +348 -0
- raise_cli/cli/commands/profile.py +59 -0
- raise_cli/cli/commands/publish.py +80 -0
- raise_cli/cli/commands/release.py +338 -0
- raise_cli/cli/commands/session.py +528 -0
- raise_cli/cli/commands/signal.py +410 -0
- raise_cli/cli/commands/skill.py +350 -0
- raise_cli/cli/commands/skill_set.py +145 -0
- raise_cli/cli/error_handler.py +158 -0
- raise_cli/cli/main.py +163 -0
- raise_cli/compat.py +66 -0
- raise_cli/config/__init__.py +41 -0
- raise_cli/config/agent_plugin.py +105 -0
- raise_cli/config/agent_registry.py +233 -0
- raise_cli/config/agents.py +120 -0
- raise_cli/config/ide.py +32 -0
- raise_cli/config/paths.py +379 -0
- raise_cli/config/settings.py +180 -0
- raise_cli/context/__init__.py +42 -0
- raise_cli/context/analyzers/__init__.py +16 -0
- raise_cli/context/analyzers/models.py +36 -0
- raise_cli/context/analyzers/protocol.py +43 -0
- raise_cli/context/analyzers/python.py +292 -0
- raise_cli/context/builder.py +1569 -0
- raise_cli/context/diff.py +213 -0
- raise_cli/context/extractors/__init__.py +13 -0
- raise_cli/context/extractors/skills.py +121 -0
- raise_cli/core/__init__.py +37 -0
- raise_cli/core/files.py +66 -0
- raise_cli/core/text.py +174 -0
- raise_cli/core/tools.py +441 -0
- raise_cli/discovery/__init__.py +50 -0
- raise_cli/discovery/analyzer.py +691 -0
- raise_cli/discovery/drift.py +355 -0
- raise_cli/discovery/scanner.py +1687 -0
- raise_cli/doctor/__init__.py +4 -0
- raise_cli/doctor/checks/__init__.py +1 -0
- raise_cli/doctor/checks/environment.py +110 -0
- raise_cli/doctor/checks/project.py +238 -0
- raise_cli/doctor/fix.py +80 -0
- raise_cli/doctor/models.py +56 -0
- raise_cli/doctor/protocol.py +43 -0
- raise_cli/doctor/registry.py +100 -0
- raise_cli/doctor/report.py +141 -0
- raise_cli/doctor/runner.py +95 -0
- raise_cli/engines/__init__.py +3 -0
- raise_cli/exceptions.py +215 -0
- raise_cli/gates/__init__.py +19 -0
- raise_cli/gates/builtin/__init__.py +1 -0
- raise_cli/gates/builtin/coverage.py +52 -0
- raise_cli/gates/builtin/lint.py +48 -0
- raise_cli/gates/builtin/tests.py +48 -0
- raise_cli/gates/builtin/types.py +48 -0
- raise_cli/gates/models.py +40 -0
- raise_cli/gates/protocol.py +41 -0
- raise_cli/gates/registry.py +141 -0
- raise_cli/governance/__init__.py +11 -0
- raise_cli/governance/extractor.py +412 -0
- raise_cli/governance/models.py +134 -0
- raise_cli/governance/parsers/__init__.py +35 -0
- raise_cli/governance/parsers/_convert.py +38 -0
- raise_cli/governance/parsers/adr.py +274 -0
- raise_cli/governance/parsers/backlog.py +356 -0
- raise_cli/governance/parsers/constitution.py +119 -0
- raise_cli/governance/parsers/epic.py +323 -0
- raise_cli/governance/parsers/glossary.py +316 -0
- raise_cli/governance/parsers/guardrails.py +345 -0
- raise_cli/governance/parsers/prd.py +112 -0
- raise_cli/governance/parsers/roadmap.py +118 -0
- raise_cli/governance/parsers/vision.py +116 -0
- raise_cli/graph/__init__.py +1 -0
- raise_cli/graph/backends/__init__.py +57 -0
- raise_cli/graph/backends/api.py +137 -0
- raise_cli/graph/backends/dual.py +139 -0
- raise_cli/graph/backends/pending.py +84 -0
- raise_cli/handlers/__init__.py +3 -0
- raise_cli/hooks/__init__.py +54 -0
- raise_cli/hooks/builtin/__init__.py +1 -0
- raise_cli/hooks/builtin/backlog.py +216 -0
- raise_cli/hooks/builtin/gate_bridge.py +83 -0
- raise_cli/hooks/builtin/jira_sync.py +127 -0
- raise_cli/hooks/builtin/memory.py +117 -0
- raise_cli/hooks/builtin/telemetry.py +72 -0
- raise_cli/hooks/emitter.py +184 -0
- raise_cli/hooks/events.py +262 -0
- raise_cli/hooks/protocol.py +38 -0
- raise_cli/hooks/registry.py +117 -0
- raise_cli/mcp/__init__.py +33 -0
- raise_cli/mcp/bridge.py +218 -0
- raise_cli/mcp/models.py +43 -0
- raise_cli/mcp/registry.py +77 -0
- raise_cli/mcp/schema.py +41 -0
- raise_cli/memory/__init__.py +58 -0
- raise_cli/memory/loader.py +247 -0
- raise_cli/memory/migration.py +241 -0
- raise_cli/memory/models.py +169 -0
- raise_cli/memory/writer.py +598 -0
- raise_cli/onboarding/__init__.py +103 -0
- raise_cli/onboarding/bootstrap.py +324 -0
- raise_cli/onboarding/claudemd.py +17 -0
- raise_cli/onboarding/conventions.py +742 -0
- raise_cli/onboarding/detection.py +374 -0
- raise_cli/onboarding/governance.py +443 -0
- raise_cli/onboarding/instructions.py +672 -0
- raise_cli/onboarding/manifest.py +201 -0
- raise_cli/onboarding/memory_md.py +399 -0
- raise_cli/onboarding/migration.py +207 -0
- raise_cli/onboarding/profile.py +624 -0
- raise_cli/onboarding/skill_conflict.py +100 -0
- raise_cli/onboarding/skill_manifest.py +176 -0
- raise_cli/onboarding/skills.py +437 -0
- raise_cli/onboarding/workflows.py +101 -0
- raise_cli/output/__init__.py +28 -0
- raise_cli/output/console.py +394 -0
- raise_cli/output/formatters/__init__.py +9 -0
- raise_cli/output/formatters/adapters.py +135 -0
- raise_cli/output/formatters/discover.py +439 -0
- raise_cli/output/formatters/skill.py +298 -0
- raise_cli/publish/__init__.py +3 -0
- raise_cli/publish/changelog.py +80 -0
- raise_cli/publish/check.py +179 -0
- raise_cli/publish/version.py +172 -0
- raise_cli/rai_base/__init__.py +22 -0
- raise_cli/rai_base/framework/__init__.py +7 -0
- raise_cli/rai_base/framework/methodology.yaml +233 -0
- raise_cli/rai_base/governance/__init__.py +1 -0
- raise_cli/rai_base/governance/architecture/__init__.py +1 -0
- raise_cli/rai_base/governance/architecture/domain-model.md +20 -0
- raise_cli/rai_base/governance/architecture/system-context.md +34 -0
- raise_cli/rai_base/governance/architecture/system-design.md +24 -0
- raise_cli/rai_base/governance/backlog.md +8 -0
- raise_cli/rai_base/governance/guardrails.md +17 -0
- raise_cli/rai_base/governance/prd.md +25 -0
- raise_cli/rai_base/governance/vision.md +16 -0
- raise_cli/rai_base/identity/__init__.py +8 -0
- raise_cli/rai_base/identity/core.md +119 -0
- raise_cli/rai_base/identity/perspective.md +119 -0
- raise_cli/rai_base/memory/__init__.py +7 -0
- raise_cli/rai_base/memory/patterns-base.jsonl +55 -0
- raise_cli/schemas/__init__.py +3 -0
- raise_cli/schemas/journal.py +49 -0
- raise_cli/schemas/session_state.py +117 -0
- raise_cli/session/__init__.py +5 -0
- raise_cli/session/bundle.py +820 -0
- raise_cli/session/close.py +268 -0
- raise_cli/session/journal.py +119 -0
- raise_cli/session/resolver.py +126 -0
- raise_cli/session/state.py +187 -0
- raise_cli/skills/__init__.py +44 -0
- raise_cli/skills/locator.py +141 -0
- raise_cli/skills/name_checker.py +199 -0
- raise_cli/skills/parser.py +145 -0
- raise_cli/skills/scaffold.py +212 -0
- raise_cli/skills/schema.py +132 -0
- raise_cli/skills/skillsets.py +195 -0
- raise_cli/skills/validator.py +197 -0
- raise_cli/skills_base/__init__.py +80 -0
- raise_cli/skills_base/contract-template.md +60 -0
- raise_cli/skills_base/preamble.md +37 -0
- raise_cli/skills_base/rai-architecture-review/SKILL.md +137 -0
- raise_cli/skills_base/rai-debug/SKILL.md +171 -0
- raise_cli/skills_base/rai-discover/SKILL.md +167 -0
- raise_cli/skills_base/rai-discover-document/SKILL.md +128 -0
- raise_cli/skills_base/rai-discover-scan/SKILL.md +147 -0
- raise_cli/skills_base/rai-discover-start/SKILL.md +145 -0
- raise_cli/skills_base/rai-discover-validate/SKILL.md +142 -0
- raise_cli/skills_base/rai-docs-update/SKILL.md +142 -0
- raise_cli/skills_base/rai-doctor/SKILL.md +120 -0
- raise_cli/skills_base/rai-epic-close/SKILL.md +165 -0
- raise_cli/skills_base/rai-epic-close/templates/retrospective.md +68 -0
- raise_cli/skills_base/rai-epic-design/SKILL.md +146 -0
- raise_cli/skills_base/rai-epic-design/templates/design.md +24 -0
- raise_cli/skills_base/rai-epic-design/templates/scope.md +76 -0
- raise_cli/skills_base/rai-epic-plan/SKILL.md +153 -0
- raise_cli/skills_base/rai-epic-plan/_references/sequencing-strategies.md +67 -0
- raise_cli/skills_base/rai-epic-plan/templates/plan-section.md +49 -0
- raise_cli/skills_base/rai-epic-run/SKILL.md +208 -0
- raise_cli/skills_base/rai-epic-start/SKILL.md +136 -0
- raise_cli/skills_base/rai-epic-start/templates/brief.md +34 -0
- raise_cli/skills_base/rai-mcp-add/SKILL.md +176 -0
- raise_cli/skills_base/rai-mcp-remove/SKILL.md +120 -0
- raise_cli/skills_base/rai-mcp-status/SKILL.md +147 -0
- raise_cli/skills_base/rai-problem-shape/SKILL.md +138 -0
- raise_cli/skills_base/rai-project-create/SKILL.md +144 -0
- raise_cli/skills_base/rai-project-onboard/SKILL.md +162 -0
- raise_cli/skills_base/rai-quality-review/SKILL.md +189 -0
- raise_cli/skills_base/rai-research/SKILL.md +143 -0
- raise_cli/skills_base/rai-research/references/research-prompt-template.md +317 -0
- raise_cli/skills_base/rai-session-close/SKILL.md +176 -0
- raise_cli/skills_base/rai-session-start/SKILL.md +110 -0
- raise_cli/skills_base/rai-story-close/SKILL.md +198 -0
- raise_cli/skills_base/rai-story-design/SKILL.md +203 -0
- raise_cli/skills_base/rai-story-design/references/tech-design-story-v2.md +293 -0
- raise_cli/skills_base/rai-story-implement/SKILL.md +115 -0
- raise_cli/skills_base/rai-story-plan/SKILL.md +135 -0
- raise_cli/skills_base/rai-story-review/SKILL.md +178 -0
- raise_cli/skills_base/rai-story-run/SKILL.md +282 -0
- raise_cli/skills_base/rai-story-start/SKILL.md +166 -0
- raise_cli/skills_base/rai-story-start/templates/story.md +38 -0
- raise_cli/skills_base/rai-welcome/SKILL.md +134 -0
- raise_cli/telemetry/__init__.py +42 -0
- raise_cli/telemetry/schemas.py +285 -0
- raise_cli/telemetry/writer.py +217 -0
- raise_cli/tier/__init__.py +0 -0
- raise_cli/tier/context.py +134 -0
- raise_cli/viz/__init__.py +7 -0
- raise_cli/viz/generator.py +406 -0
- raise_cli-2.2.1.dist-info/METADATA +433 -0
- raise_cli-2.2.1.dist-info/RECORD +264 -0
- raise_cli-2.2.1.dist-info/WHEEL +4 -0
- raise_cli-2.2.1.dist-info/entry_points.txt +40 -0
- raise_cli-2.2.1.dist-info/licenses/LICENSE +190 -0
- raise_cli-2.2.1.dist-info/licenses/NOTICE +4 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""Release CLI commands — release management, quality gates, and release workflow.
|
|
2
|
+
|
|
3
|
+
Provides commands for:
|
|
4
|
+
- Listing releases from the memory graph
|
|
5
|
+
- Running pre-publish quality checks
|
|
6
|
+
- Orchestrating full releases (bump, changelog, commit, tag, push)
|
|
7
|
+
|
|
8
|
+
The check and publish commands were absorbed from the `publish` group (RAISE-247/S5).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
import subprocess # nosec B404 - subprocess required for git operations in CLI tool
|
|
15
|
+
from datetime import date
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Annotated
|
|
18
|
+
|
|
19
|
+
import typer
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
from rich.table import Table
|
|
22
|
+
|
|
23
|
+
from raise_cli.cli.error_handler import cli_error
|
|
24
|
+
from raise_cli.graph.backends import get_active_backend
|
|
25
|
+
from raise_cli.publish.check import CheckResult, run_checks
|
|
26
|
+
from raise_cli.publish.version import (
|
|
27
|
+
BumpType,
|
|
28
|
+
bump_version,
|
|
29
|
+
is_pep440,
|
|
30
|
+
sync_version_files,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
release_app = typer.Typer(help="Release management commands")
|
|
34
|
+
console = Console()
|
|
35
|
+
|
|
36
|
+
# Graph path relative to project root
|
|
37
|
+
GRAPH_REL_PATH = Path(".raise") / "rai" / "memory" / "index.json"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# =============================================================================
|
|
41
|
+
# Helpers (moved from publish.py)
|
|
42
|
+
# =============================================================================
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _find_project_paths(project: Path) -> tuple[Path, Path, Path]:
|
|
46
|
+
"""Find pyproject.toml, __init__.py, and CHANGELOG.md paths."""
|
|
47
|
+
pyproject_path = project / "pyproject.toml"
|
|
48
|
+
changelog_path = project / "CHANGELOG.md"
|
|
49
|
+
init_path = project / "src" / "raise_cli" / "__init__.py"
|
|
50
|
+
return pyproject_path, init_path, changelog_path
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _read_current_version(pyproject_path: Path) -> str:
|
|
54
|
+
"""Read current version from pyproject.toml."""
|
|
55
|
+
if not pyproject_path.exists():
|
|
56
|
+
console.print("[red]pyproject.toml not found[/red]")
|
|
57
|
+
raise typer.Exit(1)
|
|
58
|
+
content = pyproject_path.read_text(encoding="utf-8")
|
|
59
|
+
match = re.search(r'version\s*=\s*"([^"]*)"', content)
|
|
60
|
+
if not match:
|
|
61
|
+
console.print("[red]Could not find version in pyproject.toml[/red]")
|
|
62
|
+
raise typer.Exit(1)
|
|
63
|
+
return match.group(1)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _display_results(results: list[CheckResult]) -> bool:
|
|
67
|
+
"""Display check results with Rich formatting."""
|
|
68
|
+
console.print()
|
|
69
|
+
console.print("[bold]Pre-publish Quality Check[/bold]")
|
|
70
|
+
console.print("─" * 40)
|
|
71
|
+
|
|
72
|
+
passed_count = 0
|
|
73
|
+
for r in results:
|
|
74
|
+
icon = "[green]✓[/green]" if r.passed else "[red]✗[/red]"
|
|
75
|
+
console.print(f" {icon} {r.gate}: {r.message}")
|
|
76
|
+
if r.passed:
|
|
77
|
+
passed_count += 1
|
|
78
|
+
|
|
79
|
+
total = len(results)
|
|
80
|
+
console.print()
|
|
81
|
+
if passed_count == total:
|
|
82
|
+
console.print(f"[green]All {total} checks passed[/green]")
|
|
83
|
+
else:
|
|
84
|
+
console.print(
|
|
85
|
+
f"[red]{passed_count}/{total} checks passed, "
|
|
86
|
+
f"{total - passed_count} FAILED[/red]"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return passed_count == total
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# =============================================================================
|
|
93
|
+
# Commands
|
|
94
|
+
# =============================================================================
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@release_app.command("list")
|
|
98
|
+
def list_releases(
|
|
99
|
+
project: Annotated[
|
|
100
|
+
Path,
|
|
101
|
+
typer.Option("--project", "-p", help="Project root path"),
|
|
102
|
+
] = Path("."),
|
|
103
|
+
) -> None:
|
|
104
|
+
"""List releases from the memory graph.
|
|
105
|
+
|
|
106
|
+
Shows all release nodes with their status, target date, and associated epics.
|
|
107
|
+
|
|
108
|
+
Examples:
|
|
109
|
+
$ rai release list
|
|
110
|
+
$ rai release list --project /path/to/project
|
|
111
|
+
"""
|
|
112
|
+
graph_path = project / GRAPH_REL_PATH
|
|
113
|
+
if not graph_path.exists():
|
|
114
|
+
cli_error(
|
|
115
|
+
f"Memory index not found: {graph_path}",
|
|
116
|
+
hint="Run 'rai memory build' first to create the index",
|
|
117
|
+
exit_code=4,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
graph = get_active_backend(graph_path).load()
|
|
122
|
+
except Exception as e:
|
|
123
|
+
cli_error(f"Error loading memory index: {e}")
|
|
124
|
+
|
|
125
|
+
# Find all release nodes
|
|
126
|
+
releases = [n for n in graph.iter_concepts() if n.type == "release"]
|
|
127
|
+
|
|
128
|
+
if not releases:
|
|
129
|
+
console.print("\nNo release nodes found in graph.")
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
# Find epics linked to each release via part_of edges
|
|
133
|
+
release_epics: dict[str, list[str]] = {}
|
|
134
|
+
for node in graph.iter_concepts():
|
|
135
|
+
if node.type == "epic":
|
|
136
|
+
neighbors = graph.get_neighbors(node.id, depth=1, edge_types=["part_of"])
|
|
137
|
+
for neighbor in neighbors:
|
|
138
|
+
if neighbor.type == "release":
|
|
139
|
+
release_epics.setdefault(neighbor.id, []).append(
|
|
140
|
+
node.id.replace("epic-", "").upper()
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Build table
|
|
144
|
+
table = Table(title="Releases")
|
|
145
|
+
table.add_column("ID", style="cyan")
|
|
146
|
+
table.add_column("Name")
|
|
147
|
+
table.add_column("Status", style="yellow")
|
|
148
|
+
table.add_column("Target", style="green")
|
|
149
|
+
table.add_column("Epics", style="dim")
|
|
150
|
+
|
|
151
|
+
for rel in sorted(releases, key=lambda r: r.metadata.get("target", "")):
|
|
152
|
+
release_id = rel.metadata.get("release_id", rel.id)
|
|
153
|
+
name = rel.metadata.get("name", "")
|
|
154
|
+
status = rel.metadata.get("status", "")
|
|
155
|
+
target = rel.metadata.get("target", "")
|
|
156
|
+
epics = ", ".join(sorted(release_epics.get(rel.id, [])))
|
|
157
|
+
|
|
158
|
+
table.add_row(release_id, name, status, target, epics)
|
|
159
|
+
|
|
160
|
+
console.print()
|
|
161
|
+
console.print(table)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@release_app.command("check")
|
|
165
|
+
def check_command(
|
|
166
|
+
project: Annotated[
|
|
167
|
+
Path,
|
|
168
|
+
typer.Option("--project", "-p", help="Project root path"),
|
|
169
|
+
] = Path("."),
|
|
170
|
+
) -> None:
|
|
171
|
+
"""Run all quality gates before publishing.
|
|
172
|
+
|
|
173
|
+
Runs 9 quality checks: tests (with coverage diagnostic), types, lint,
|
|
174
|
+
security, build, package validation, changelog, PEP 440 version, and
|
|
175
|
+
version sync. Coverage is reported but not a blocking gate.
|
|
176
|
+
|
|
177
|
+
Exits with code 0 if all pass, 1 if any fail.
|
|
178
|
+
|
|
179
|
+
Examples:
|
|
180
|
+
$ rai release check
|
|
181
|
+
$ rai release check --project /path/to/project
|
|
182
|
+
"""
|
|
183
|
+
pyproject_path, init_path, changelog_path = _find_project_paths(project)
|
|
184
|
+
|
|
185
|
+
results = run_checks(
|
|
186
|
+
project_root=project,
|
|
187
|
+
pyproject_path=pyproject_path,
|
|
188
|
+
init_path=init_path,
|
|
189
|
+
changelog_path=changelog_path,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
all_passed = _display_results(results)
|
|
193
|
+
if not all_passed:
|
|
194
|
+
raise typer.Exit(1)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@release_app.command("publish")
|
|
198
|
+
def publish_command(
|
|
199
|
+
bump: Annotated[
|
|
200
|
+
BumpType | None,
|
|
201
|
+
typer.Option("--bump", "-b", help="Version bump type"),
|
|
202
|
+
] = None,
|
|
203
|
+
version: Annotated[
|
|
204
|
+
str | None,
|
|
205
|
+
typer.Option("--version", "-v", help="Explicit version (overrides --bump)"),
|
|
206
|
+
] = None,
|
|
207
|
+
dry_run: Annotated[
|
|
208
|
+
bool,
|
|
209
|
+
typer.Option("--dry-run", help="Show what would happen without executing"),
|
|
210
|
+
] = False,
|
|
211
|
+
skip_check: Annotated[
|
|
212
|
+
bool,
|
|
213
|
+
typer.Option("--skip-check", help="Skip quality gates (dangerous)"),
|
|
214
|
+
] = False,
|
|
215
|
+
project: Annotated[
|
|
216
|
+
Path,
|
|
217
|
+
typer.Option("--project", "-p", help="Project root path"),
|
|
218
|
+
] = Path("."),
|
|
219
|
+
) -> None:
|
|
220
|
+
"""Orchestrate a full release: check, bump, changelog, commit, tag, push.
|
|
221
|
+
|
|
222
|
+
Either --bump or --version is required.
|
|
223
|
+
|
|
224
|
+
Examples:
|
|
225
|
+
rai release publish --bump alpha
|
|
226
|
+
rai release publish --bump minor --dry-run
|
|
227
|
+
rai release publish --version 2.1.0
|
|
228
|
+
"""
|
|
229
|
+
if bump is None and version is None:
|
|
230
|
+
console.print("[red]Either --bump or --version is required[/red]")
|
|
231
|
+
raise typer.Exit(1)
|
|
232
|
+
|
|
233
|
+
pyproject_path, init_path, changelog_path = _find_project_paths(project)
|
|
234
|
+
|
|
235
|
+
# Run checks unless skipped
|
|
236
|
+
if not skip_check:
|
|
237
|
+
results = run_checks(
|
|
238
|
+
project_root=project,
|
|
239
|
+
pyproject_path=pyproject_path,
|
|
240
|
+
init_path=init_path,
|
|
241
|
+
changelog_path=changelog_path,
|
|
242
|
+
)
|
|
243
|
+
all_passed = _display_results(results)
|
|
244
|
+
if not all_passed:
|
|
245
|
+
console.print(
|
|
246
|
+
"\n[red]Quality gates failed. Fix issues or use --skip-check.[/red]"
|
|
247
|
+
)
|
|
248
|
+
raise typer.Exit(1)
|
|
249
|
+
console.print()
|
|
250
|
+
|
|
251
|
+
# Determine new version
|
|
252
|
+
current = _read_current_version(pyproject_path)
|
|
253
|
+
|
|
254
|
+
if version:
|
|
255
|
+
if not is_pep440(version):
|
|
256
|
+
console.print(f"[red]'{version}' is not valid PEP 440[/red]")
|
|
257
|
+
raise typer.Exit(1)
|
|
258
|
+
new_version = version
|
|
259
|
+
else:
|
|
260
|
+
assert bump is not None # nosec B101 - validated at line above
|
|
261
|
+
new_version = bump_version(current, bump)
|
|
262
|
+
|
|
263
|
+
today = date.today().isoformat()
|
|
264
|
+
|
|
265
|
+
# Display plan
|
|
266
|
+
console.print("[bold]Release Plan[/bold]")
|
|
267
|
+
console.print(f" Current version: {current}")
|
|
268
|
+
console.print(f" New version: {new_version}")
|
|
269
|
+
console.print(f" Date: {today}")
|
|
270
|
+
console.print()
|
|
271
|
+
console.print(" Steps:")
|
|
272
|
+
console.print(f" 1. Update pyproject.toml: {current} → {new_version}")
|
|
273
|
+
console.print(f" 2. Update __init__.py: {current} → {new_version}")
|
|
274
|
+
console.print(
|
|
275
|
+
f" 3. Update CHANGELOG.md: [Unreleased] → [{new_version}] - {today}"
|
|
276
|
+
)
|
|
277
|
+
console.print(f" 4. Commit: release: v{new_version}")
|
|
278
|
+
console.print(f" 5. Tag: v{new_version}")
|
|
279
|
+
console.print(" 6. Push commit + tag → triggers GitHub Actions release")
|
|
280
|
+
|
|
281
|
+
if dry_run:
|
|
282
|
+
console.print("\n[yellow]Dry run — no changes made[/yellow]")
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
# Confirm
|
|
286
|
+
console.print()
|
|
287
|
+
if not typer.confirm("Proceed?"):
|
|
288
|
+
console.print("[yellow]Aborted[/yellow]")
|
|
289
|
+
raise typer.Exit(0)
|
|
290
|
+
|
|
291
|
+
# Execute
|
|
292
|
+
# 1-2: Bump version files
|
|
293
|
+
sync_version_files(new_version, pyproject_path=pyproject_path, init_path=init_path)
|
|
294
|
+
console.print("[green]✓ Version bumped[/green]")
|
|
295
|
+
|
|
296
|
+
# 3: Update changelog
|
|
297
|
+
if changelog_path.exists():
|
|
298
|
+
from raise_cli.publish.changelog import promote_unreleased
|
|
299
|
+
|
|
300
|
+
content = changelog_path.read_text(encoding="utf-8")
|
|
301
|
+
try:
|
|
302
|
+
content = promote_unreleased(content, new_version, today)
|
|
303
|
+
changelog_path.write_text(content, encoding="utf-8")
|
|
304
|
+
console.print("[green]✓ Changelog updated[/green]")
|
|
305
|
+
except ValueError:
|
|
306
|
+
console.print("[yellow]⚠ No unreleased entries to promote[/yellow]")
|
|
307
|
+
|
|
308
|
+
# 4: Commit
|
|
309
|
+
subprocess.run( # nosec B603,B607 - controlled git commands, no untrusted input
|
|
310
|
+
["git", "add", str(pyproject_path), str(init_path), str(changelog_path)],
|
|
311
|
+
cwd=project,
|
|
312
|
+
check=True,
|
|
313
|
+
)
|
|
314
|
+
subprocess.run( # nosec B603,B607 - controlled git commands, no untrusted input
|
|
315
|
+
["git", "commit", "-m", f"release: v{new_version}"],
|
|
316
|
+
cwd=project,
|
|
317
|
+
check=True,
|
|
318
|
+
)
|
|
319
|
+
console.print(f"[green]✓ Committed: release: v{new_version}[/green]")
|
|
320
|
+
|
|
321
|
+
# 5: Tag
|
|
322
|
+
subprocess.run( # nosec B603,B607 - controlled git commands, no untrusted input
|
|
323
|
+
["git", "tag", f"v{new_version}"],
|
|
324
|
+
cwd=project,
|
|
325
|
+
check=True,
|
|
326
|
+
)
|
|
327
|
+
console.print(f"[green]✓ Tagged: v{new_version}[/green]")
|
|
328
|
+
|
|
329
|
+
# 6: Push (with confirmation already given)
|
|
330
|
+
subprocess.run( # nosec B603,B607 - controlled git commands, no untrusted input
|
|
331
|
+
["git", "push", "--follow-tags"],
|
|
332
|
+
cwd=project,
|
|
333
|
+
check=True,
|
|
334
|
+
)
|
|
335
|
+
console.print("[green]✓ Pushed to origin[/green]")
|
|
336
|
+
|
|
337
|
+
console.print(f"\n[bold green]Release v{new_version} published.[/bold green]")
|
|
338
|
+
console.print("GitHub Actions will handle PyPI upload.")
|