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,177 @@
|
|
|
1
|
+
"""rai doctor — self-diagnostic and bug reporting.
|
|
2
|
+
|
|
3
|
+
Architecture: ADR-045 (DoctorCheck protocol, E352).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Annotated
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
|
|
14
|
+
from raise_cli.doctor.models import CheckStatus, DoctorContext
|
|
15
|
+
from raise_cli.doctor.registry import CheckRegistry
|
|
16
|
+
from raise_cli.doctor.runner import PIPELINE_ORDER, run_checks, summarize
|
|
17
|
+
|
|
18
|
+
doctor_app = typer.Typer(
|
|
19
|
+
name="doctor",
|
|
20
|
+
help="Diagnose RaiSE setup, report problems, and auto-fix common issues.",
|
|
21
|
+
invoke_without_command=True,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
console = Console(stderr=True)
|
|
25
|
+
|
|
26
|
+
_STATUS_STYLE: dict[CheckStatus, tuple[str, str]] = {
|
|
27
|
+
CheckStatus.PASS: ("[green]OK[/green]", " "),
|
|
28
|
+
CheckStatus.WARN: ("[yellow]!![/yellow]", "[yellow]!![/yellow]"),
|
|
29
|
+
CheckStatus.ERROR: ("[red]XX[/red]", "[red]XX[/red]"),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _format_result_line(category: str, message: str, status: CheckStatus) -> str:
|
|
34
|
+
"""Format a single result line for human output."""
|
|
35
|
+
_, prefix = _STATUS_STYLE[status]
|
|
36
|
+
return f" {prefix} {category:14s} {message}"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@doctor_app.callback()
|
|
40
|
+
def doctor(
|
|
41
|
+
ctx: typer.Context,
|
|
42
|
+
verbose: Annotated[
|
|
43
|
+
bool,
|
|
44
|
+
typer.Option("--verbose", "-v", help="Show all checks including passing"),
|
|
45
|
+
] = False,
|
|
46
|
+
json_output: Annotated[
|
|
47
|
+
bool,
|
|
48
|
+
typer.Option("--json", help="JSON output for CI"),
|
|
49
|
+
] = False,
|
|
50
|
+
fix: Annotated[
|
|
51
|
+
bool,
|
|
52
|
+
typer.Option("--fix", help="Auto-fix common issues (with backup)"),
|
|
53
|
+
] = False,
|
|
54
|
+
online: Annotated[
|
|
55
|
+
bool,
|
|
56
|
+
typer.Option("--online", help="Include online checks (MCP, adapter connectivity)"),
|
|
57
|
+
] = False,
|
|
58
|
+
category: Annotated[
|
|
59
|
+
str | None,
|
|
60
|
+
typer.Option("--category", "-c", help="Run only this check category"),
|
|
61
|
+
] = None,
|
|
62
|
+
) -> None:
|
|
63
|
+
"""Run diagnostic checks on your RaiSE setup."""
|
|
64
|
+
if ctx.invoked_subcommand is not None:
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
registry = CheckRegistry()
|
|
68
|
+
registry.discover()
|
|
69
|
+
|
|
70
|
+
categories = [category] if category else None
|
|
71
|
+
if category and category not in PIPELINE_ORDER:
|
|
72
|
+
available = ", ".join(PIPELINE_ORDER)
|
|
73
|
+
console.print(f"Unknown category '{category}'. Available: {available}")
|
|
74
|
+
raise typer.Exit(1)
|
|
75
|
+
|
|
76
|
+
context = DoctorContext(
|
|
77
|
+
working_dir=Path.cwd(),
|
|
78
|
+
online=online,
|
|
79
|
+
verbose=verbose,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
results = run_checks(registry, context, categories)
|
|
83
|
+
passes, warns, errors = summarize(results)
|
|
84
|
+
|
|
85
|
+
if json_output:
|
|
86
|
+
import json
|
|
87
|
+
|
|
88
|
+
out = Console()
|
|
89
|
+
data = {
|
|
90
|
+
"results": [
|
|
91
|
+
{
|
|
92
|
+
"check_id": r.check_id,
|
|
93
|
+
"category": r.category,
|
|
94
|
+
"status": r.status.value,
|
|
95
|
+
"message": r.message,
|
|
96
|
+
"fix_hint": r.fix_hint,
|
|
97
|
+
"details": list(r.details),
|
|
98
|
+
}
|
|
99
|
+
for r in results
|
|
100
|
+
],
|
|
101
|
+
"summary": {"pass": passes, "warn": warns, "error": errors},
|
|
102
|
+
}
|
|
103
|
+
out.print_json(json.dumps(data))
|
|
104
|
+
else:
|
|
105
|
+
out = Console()
|
|
106
|
+
if not results:
|
|
107
|
+
out.print("No checks registered. Install raise-cli with extras for diagnostics.")
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
for r in results:
|
|
111
|
+
if not verbose and r.status == CheckStatus.PASS:
|
|
112
|
+
continue
|
|
113
|
+
out.print(_format_result_line(r.category, r.message, r.status))
|
|
114
|
+
if r.fix_hint:
|
|
115
|
+
from rich.markup import escape
|
|
116
|
+
|
|
117
|
+
out.print(f" hint: {escape(r.fix_hint)}")
|
|
118
|
+
|
|
119
|
+
if warns == 0 and errors == 0:
|
|
120
|
+
out.print("\n[green]All checks passed.[/green]")
|
|
121
|
+
else:
|
|
122
|
+
parts: list[str] = []
|
|
123
|
+
if warns:
|
|
124
|
+
parts.append(f"{warns} warning{'s' if warns > 1 else ''}")
|
|
125
|
+
if errors:
|
|
126
|
+
parts.append(f"{errors} error{'s' if errors > 1 else ''}")
|
|
127
|
+
out.print(f"\n{', '.join(parts)}.")
|
|
128
|
+
|
|
129
|
+
if fix:
|
|
130
|
+
fixable = [r for r in results if r.fix_id and r.status != CheckStatus.PASS]
|
|
131
|
+
if fixable:
|
|
132
|
+
from raise_cli.doctor.fix import run_fixes
|
|
133
|
+
|
|
134
|
+
out = Console()
|
|
135
|
+
outcomes = run_fixes(fixable, Path.cwd())
|
|
136
|
+
for fix_id, success in outcomes:
|
|
137
|
+
status_label = "[green]fixed[/green]" if success else "[red]failed[/red]"
|
|
138
|
+
out.print(f" fix: {fix_id} -- {status_label}")
|
|
139
|
+
|
|
140
|
+
if errors > 0:
|
|
141
|
+
raise typer.Exit(1)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@doctor_app.command()
|
|
145
|
+
def report(
|
|
146
|
+
send: Annotated[
|
|
147
|
+
bool, typer.Option("--send", help="Open email client with report"),
|
|
148
|
+
] = False,
|
|
149
|
+
) -> None:
|
|
150
|
+
"""Generate diagnostic report, optionally send via email."""
|
|
151
|
+
from raise_cli.doctor.report import (
|
|
152
|
+
SUPPORT_EMAIL,
|
|
153
|
+
generate_report,
|
|
154
|
+
open_mailto,
|
|
155
|
+
save_report,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
registry = CheckRegistry()
|
|
159
|
+
registry.discover()
|
|
160
|
+
context = DoctorContext(working_dir=Path.cwd())
|
|
161
|
+
results = run_checks(registry, context)
|
|
162
|
+
|
|
163
|
+
report_data = generate_report(results, Path.cwd())
|
|
164
|
+
saved = save_report(report_data, Path.cwd())
|
|
165
|
+
out = Console()
|
|
166
|
+
out.print(f"Report saved to {saved}")
|
|
167
|
+
|
|
168
|
+
if send:
|
|
169
|
+
opened = open_mailto(report_data)
|
|
170
|
+
if opened:
|
|
171
|
+
out.print("Email client opened. Review and send.")
|
|
172
|
+
else:
|
|
173
|
+
out.print("Could not open email client.")
|
|
174
|
+
out.print(f"Email to: {SUPPORT_EMAIL}")
|
|
175
|
+
out.print(f"Attach or paste the report from: {saved}")
|
|
176
|
+
else:
|
|
177
|
+
out.print("To send: rai doctor report --send")
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""CLI commands for workflow gate discovery and execution.
|
|
2
|
+
|
|
3
|
+
Provides ``rai gate check`` and ``rai gate list`` for discovering
|
|
4
|
+
and invoking registered WorkflowGate implementations.
|
|
5
|
+
|
|
6
|
+
Architecture: ADR-039 §1 (WorkflowGate Protocol), §5 (Standalone gates)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Annotated
|
|
14
|
+
|
|
15
|
+
import typer
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
|
|
18
|
+
from raise_cli.gates.models import GateContext, GateResult
|
|
19
|
+
from raise_cli.gates.protocol import WorkflowGate
|
|
20
|
+
from raise_cli.gates.registry import GateRegistry
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
gate_app = typer.Typer(
|
|
25
|
+
name="gate",
|
|
26
|
+
help="Discover and run workflow gates",
|
|
27
|
+
no_args_is_help=True,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
console = Console()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _get_registry() -> GateRegistry:
|
|
34
|
+
"""Create and populate a gate registry from entry points."""
|
|
35
|
+
reg = GateRegistry()
|
|
36
|
+
reg.discover()
|
|
37
|
+
return reg
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _run_gate(gate: WorkflowGate, context: GateContext) -> GateResult:
|
|
41
|
+
"""Run a single gate with error isolation.
|
|
42
|
+
|
|
43
|
+
Gate exceptions are caught and converted to a failed GateResult.
|
|
44
|
+
Gates never crash the CLI.
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
return gate.evaluate(context)
|
|
48
|
+
except Exception as exc: # noqa: BLE001
|
|
49
|
+
msg = f"{type(exc).__name__}: {exc}"
|
|
50
|
+
logger.warning("Gate '%s' raised: %s", context.gate_id, msg)
|
|
51
|
+
return GateResult(
|
|
52
|
+
passed=False,
|
|
53
|
+
gate_id=context.gate_id,
|
|
54
|
+
message=msg,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _print_result(result: GateResult) -> None:
|
|
59
|
+
"""Print a single gate result in human format."""
|
|
60
|
+
marker = "[green]✓[/green]" if result.passed else "[red]✗[/red]"
|
|
61
|
+
console.print(f" {marker} {result.gate_id}: {result.message}")
|
|
62
|
+
for detail in result.details:
|
|
63
|
+
console.print(f" {detail}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@gate_app.command("list")
|
|
67
|
+
def list_command(
|
|
68
|
+
format: Annotated[
|
|
69
|
+
str,
|
|
70
|
+
typer.Option("--format", "-f", help="Output format: human or json"),
|
|
71
|
+
] = "human",
|
|
72
|
+
) -> None:
|
|
73
|
+
"""List all discovered workflow gates.
|
|
74
|
+
|
|
75
|
+
Shows each gate's ID, description, and workflow point.
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
$ rai gate list
|
|
79
|
+
$ rai gate list --format json
|
|
80
|
+
"""
|
|
81
|
+
registry = _get_registry()
|
|
82
|
+
gates = registry.gates
|
|
83
|
+
|
|
84
|
+
if not gates:
|
|
85
|
+
if format == "json":
|
|
86
|
+
typer.echo(json.dumps({"gates": []}, indent=2))
|
|
87
|
+
else:
|
|
88
|
+
console.print("No gates discovered.")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
if format == "json":
|
|
92
|
+
data = [
|
|
93
|
+
{
|
|
94
|
+
"gate_id": g.gate_id,
|
|
95
|
+
"description": g.description,
|
|
96
|
+
"workflow_point": g.workflow_point,
|
|
97
|
+
}
|
|
98
|
+
for g in gates
|
|
99
|
+
]
|
|
100
|
+
typer.echo(json.dumps({"gates": data}, indent=2))
|
|
101
|
+
else:
|
|
102
|
+
console.print("[bold]Discovered gates:[/bold]\n")
|
|
103
|
+
for g in gates:
|
|
104
|
+
console.print(f" {g.gate_id:<20s} {g.description:<30s} {g.workflow_point}")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@gate_app.command("check")
|
|
108
|
+
def check_command(
|
|
109
|
+
gate_id: Annotated[
|
|
110
|
+
str | None,
|
|
111
|
+
typer.Argument(help="Gate ID to check (omit for --all)"),
|
|
112
|
+
] = None,
|
|
113
|
+
all_gates: Annotated[
|
|
114
|
+
bool,
|
|
115
|
+
typer.Option("--all", "-a", help="Run all discovered gates"),
|
|
116
|
+
] = False,
|
|
117
|
+
format: Annotated[
|
|
118
|
+
str,
|
|
119
|
+
typer.Option("--format", "-f", help="Output format: human or json"),
|
|
120
|
+
] = "human",
|
|
121
|
+
) -> None:
|
|
122
|
+
"""Run workflow gates and report results.
|
|
123
|
+
|
|
124
|
+
Check a specific gate by ID, or use --all to run every discovered gate.
|
|
125
|
+
Exit code 0 when all pass, 1 when any fail.
|
|
126
|
+
|
|
127
|
+
Examples:
|
|
128
|
+
$ rai gate check gate-tests
|
|
129
|
+
$ rai gate check --all
|
|
130
|
+
$ rai gate check --all --format json
|
|
131
|
+
"""
|
|
132
|
+
registry = _get_registry()
|
|
133
|
+
|
|
134
|
+
if gate_id and all_gates:
|
|
135
|
+
console.print("[red]Error:[/red] Provide gate_id OR --all, not both.")
|
|
136
|
+
raise typer.Exit(1)
|
|
137
|
+
|
|
138
|
+
if not gate_id and not all_gates:
|
|
139
|
+
console.print("[red]Error:[/red] Provide a gate_id or use --all.")
|
|
140
|
+
raise typer.Exit(1)
|
|
141
|
+
|
|
142
|
+
if gate_id:
|
|
143
|
+
_check_single(registry, gate_id, format)
|
|
144
|
+
else:
|
|
145
|
+
_check_all(registry, format)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _check_single(registry: GateRegistry, gate_id: str, fmt: str) -> None:
|
|
149
|
+
"""Check a single gate by ID."""
|
|
150
|
+
gate = registry.get_gate(gate_id)
|
|
151
|
+
if gate is None:
|
|
152
|
+
if fmt == "json":
|
|
153
|
+
typer.echo(json.dumps({"error": f"Gate '{gate_id}' not found"}))
|
|
154
|
+
else:
|
|
155
|
+
console.print(f"[red]Error:[/red] Gate '{gate_id}' not found.")
|
|
156
|
+
raise typer.Exit(1)
|
|
157
|
+
|
|
158
|
+
context = GateContext(gate_id=gate_id)
|
|
159
|
+
result = _run_gate(gate, context)
|
|
160
|
+
|
|
161
|
+
if fmt == "json":
|
|
162
|
+
typer.echo(
|
|
163
|
+
json.dumps(
|
|
164
|
+
{
|
|
165
|
+
"gate_id": result.gate_id,
|
|
166
|
+
"passed": result.passed,
|
|
167
|
+
"message": result.message,
|
|
168
|
+
"details": list(result.details),
|
|
169
|
+
},
|
|
170
|
+
indent=2,
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
else:
|
|
174
|
+
_print_result(result)
|
|
175
|
+
|
|
176
|
+
raise typer.Exit(0 if result.passed else 1)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _check_all(registry: GateRegistry, fmt: str) -> None:
|
|
180
|
+
"""Check all discovered gates."""
|
|
181
|
+
gates = registry.gates
|
|
182
|
+
if not gates:
|
|
183
|
+
if fmt == "json":
|
|
184
|
+
typer.echo(json.dumps({"gates": [], "summary": "No gates discovered"}))
|
|
185
|
+
else:
|
|
186
|
+
console.print("No gates discovered.")
|
|
187
|
+
raise typer.Exit(0)
|
|
188
|
+
|
|
189
|
+
results: list[GateResult] = []
|
|
190
|
+
for gate in gates:
|
|
191
|
+
context = GateContext(gate_id=gate.gate_id)
|
|
192
|
+
result = _run_gate(gate, context)
|
|
193
|
+
results.append(result)
|
|
194
|
+
|
|
195
|
+
failed = [r for r in results if not r.passed]
|
|
196
|
+
|
|
197
|
+
if fmt == "json":
|
|
198
|
+
data = [
|
|
199
|
+
{
|
|
200
|
+
"gate_id": r.gate_id,
|
|
201
|
+
"passed": r.passed,
|
|
202
|
+
"message": r.message,
|
|
203
|
+
"details": list(r.details),
|
|
204
|
+
}
|
|
205
|
+
for r in results
|
|
206
|
+
]
|
|
207
|
+
typer.echo(
|
|
208
|
+
json.dumps({"gates": data, "all_passed": len(failed) == 0}, indent=2)
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
for r in results:
|
|
212
|
+
_print_result(r)
|
|
213
|
+
console.print()
|
|
214
|
+
if failed:
|
|
215
|
+
console.print(
|
|
216
|
+
f"[red bold]FAILED:[/red bold] {len(failed)} of {len(results)} gates failed"
|
|
217
|
+
)
|
|
218
|
+
else:
|
|
219
|
+
console.print(
|
|
220
|
+
f"[green bold]PASSED:[/green bold] {len(results)} gates passed"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
raise typer.Exit(1 if failed else 0)
|