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.
Files changed (264) hide show
  1. raise_cli/__init__.py +38 -0
  2. raise_cli/__main__.py +30 -0
  3. raise_cli/adapters/__init__.py +91 -0
  4. raise_cli/adapters/declarative/__init__.py +26 -0
  5. raise_cli/adapters/declarative/adapter.py +267 -0
  6. raise_cli/adapters/declarative/discovery.py +94 -0
  7. raise_cli/adapters/declarative/expressions.py +150 -0
  8. raise_cli/adapters/declarative/reference/__init__.py +1 -0
  9. raise_cli/adapters/declarative/reference/github.yaml +143 -0
  10. raise_cli/adapters/declarative/schema.py +98 -0
  11. raise_cli/adapters/filesystem.py +299 -0
  12. raise_cli/adapters/mcp_bridge.py +10 -0
  13. raise_cli/adapters/mcp_confluence.py +246 -0
  14. raise_cli/adapters/mcp_jira.py +405 -0
  15. raise_cli/adapters/models.py +205 -0
  16. raise_cli/adapters/protocols.py +180 -0
  17. raise_cli/adapters/registry.py +90 -0
  18. raise_cli/adapters/sync.py +149 -0
  19. raise_cli/agents/__init__.py +14 -0
  20. raise_cli/agents/antigravity.yaml +8 -0
  21. raise_cli/agents/claude.yaml +8 -0
  22. raise_cli/agents/copilot.yaml +8 -0
  23. raise_cli/agents/copilot_plugin.py +124 -0
  24. raise_cli/agents/cursor.yaml +7 -0
  25. raise_cli/agents/roo.yaml +8 -0
  26. raise_cli/agents/windsurf.yaml +8 -0
  27. raise_cli/artifacts/__init__.py +30 -0
  28. raise_cli/artifacts/models.py +43 -0
  29. raise_cli/artifacts/reader.py +55 -0
  30. raise_cli/artifacts/renderer.py +104 -0
  31. raise_cli/artifacts/story_design.py +69 -0
  32. raise_cli/artifacts/writer.py +45 -0
  33. raise_cli/backlog/__init__.py +1 -0
  34. raise_cli/backlog/sync.py +115 -0
  35. raise_cli/cli/__init__.py +3 -0
  36. raise_cli/cli/commands/__init__.py +3 -0
  37. raise_cli/cli/commands/_resolve.py +153 -0
  38. raise_cli/cli/commands/adapters.py +362 -0
  39. raise_cli/cli/commands/artifact.py +137 -0
  40. raise_cli/cli/commands/backlog.py +333 -0
  41. raise_cli/cli/commands/base.py +31 -0
  42. raise_cli/cli/commands/discover.py +551 -0
  43. raise_cli/cli/commands/docs.py +130 -0
  44. raise_cli/cli/commands/doctor.py +177 -0
  45. raise_cli/cli/commands/gate.py +223 -0
  46. raise_cli/cli/commands/graph.py +1086 -0
  47. raise_cli/cli/commands/info.py +81 -0
  48. raise_cli/cli/commands/init.py +746 -0
  49. raise_cli/cli/commands/journal.py +167 -0
  50. raise_cli/cli/commands/mcp.py +524 -0
  51. raise_cli/cli/commands/memory.py +467 -0
  52. raise_cli/cli/commands/pattern.py +348 -0
  53. raise_cli/cli/commands/profile.py +59 -0
  54. raise_cli/cli/commands/publish.py +80 -0
  55. raise_cli/cli/commands/release.py +338 -0
  56. raise_cli/cli/commands/session.py +528 -0
  57. raise_cli/cli/commands/signal.py +410 -0
  58. raise_cli/cli/commands/skill.py +350 -0
  59. raise_cli/cli/commands/skill_set.py +145 -0
  60. raise_cli/cli/error_handler.py +158 -0
  61. raise_cli/cli/main.py +163 -0
  62. raise_cli/compat.py +66 -0
  63. raise_cli/config/__init__.py +41 -0
  64. raise_cli/config/agent_plugin.py +105 -0
  65. raise_cli/config/agent_registry.py +233 -0
  66. raise_cli/config/agents.py +120 -0
  67. raise_cli/config/ide.py +32 -0
  68. raise_cli/config/paths.py +379 -0
  69. raise_cli/config/settings.py +180 -0
  70. raise_cli/context/__init__.py +42 -0
  71. raise_cli/context/analyzers/__init__.py +16 -0
  72. raise_cli/context/analyzers/models.py +36 -0
  73. raise_cli/context/analyzers/protocol.py +43 -0
  74. raise_cli/context/analyzers/python.py +292 -0
  75. raise_cli/context/builder.py +1569 -0
  76. raise_cli/context/diff.py +213 -0
  77. raise_cli/context/extractors/__init__.py +13 -0
  78. raise_cli/context/extractors/skills.py +121 -0
  79. raise_cli/core/__init__.py +37 -0
  80. raise_cli/core/files.py +66 -0
  81. raise_cli/core/text.py +174 -0
  82. raise_cli/core/tools.py +441 -0
  83. raise_cli/discovery/__init__.py +50 -0
  84. raise_cli/discovery/analyzer.py +691 -0
  85. raise_cli/discovery/drift.py +355 -0
  86. raise_cli/discovery/scanner.py +1687 -0
  87. raise_cli/doctor/__init__.py +4 -0
  88. raise_cli/doctor/checks/__init__.py +1 -0
  89. raise_cli/doctor/checks/environment.py +110 -0
  90. raise_cli/doctor/checks/project.py +238 -0
  91. raise_cli/doctor/fix.py +80 -0
  92. raise_cli/doctor/models.py +56 -0
  93. raise_cli/doctor/protocol.py +43 -0
  94. raise_cli/doctor/registry.py +100 -0
  95. raise_cli/doctor/report.py +141 -0
  96. raise_cli/doctor/runner.py +95 -0
  97. raise_cli/engines/__init__.py +3 -0
  98. raise_cli/exceptions.py +215 -0
  99. raise_cli/gates/__init__.py +19 -0
  100. raise_cli/gates/builtin/__init__.py +1 -0
  101. raise_cli/gates/builtin/coverage.py +52 -0
  102. raise_cli/gates/builtin/lint.py +48 -0
  103. raise_cli/gates/builtin/tests.py +48 -0
  104. raise_cli/gates/builtin/types.py +48 -0
  105. raise_cli/gates/models.py +40 -0
  106. raise_cli/gates/protocol.py +41 -0
  107. raise_cli/gates/registry.py +141 -0
  108. raise_cli/governance/__init__.py +11 -0
  109. raise_cli/governance/extractor.py +412 -0
  110. raise_cli/governance/models.py +134 -0
  111. raise_cli/governance/parsers/__init__.py +35 -0
  112. raise_cli/governance/parsers/_convert.py +38 -0
  113. raise_cli/governance/parsers/adr.py +274 -0
  114. raise_cli/governance/parsers/backlog.py +356 -0
  115. raise_cli/governance/parsers/constitution.py +119 -0
  116. raise_cli/governance/parsers/epic.py +323 -0
  117. raise_cli/governance/parsers/glossary.py +316 -0
  118. raise_cli/governance/parsers/guardrails.py +345 -0
  119. raise_cli/governance/parsers/prd.py +112 -0
  120. raise_cli/governance/parsers/roadmap.py +118 -0
  121. raise_cli/governance/parsers/vision.py +116 -0
  122. raise_cli/graph/__init__.py +1 -0
  123. raise_cli/graph/backends/__init__.py +57 -0
  124. raise_cli/graph/backends/api.py +137 -0
  125. raise_cli/graph/backends/dual.py +139 -0
  126. raise_cli/graph/backends/pending.py +84 -0
  127. raise_cli/handlers/__init__.py +3 -0
  128. raise_cli/hooks/__init__.py +54 -0
  129. raise_cli/hooks/builtin/__init__.py +1 -0
  130. raise_cli/hooks/builtin/backlog.py +216 -0
  131. raise_cli/hooks/builtin/gate_bridge.py +83 -0
  132. raise_cli/hooks/builtin/jira_sync.py +127 -0
  133. raise_cli/hooks/builtin/memory.py +117 -0
  134. raise_cli/hooks/builtin/telemetry.py +72 -0
  135. raise_cli/hooks/emitter.py +184 -0
  136. raise_cli/hooks/events.py +262 -0
  137. raise_cli/hooks/protocol.py +38 -0
  138. raise_cli/hooks/registry.py +117 -0
  139. raise_cli/mcp/__init__.py +33 -0
  140. raise_cli/mcp/bridge.py +218 -0
  141. raise_cli/mcp/models.py +43 -0
  142. raise_cli/mcp/registry.py +77 -0
  143. raise_cli/mcp/schema.py +41 -0
  144. raise_cli/memory/__init__.py +58 -0
  145. raise_cli/memory/loader.py +247 -0
  146. raise_cli/memory/migration.py +241 -0
  147. raise_cli/memory/models.py +169 -0
  148. raise_cli/memory/writer.py +598 -0
  149. raise_cli/onboarding/__init__.py +103 -0
  150. raise_cli/onboarding/bootstrap.py +324 -0
  151. raise_cli/onboarding/claudemd.py +17 -0
  152. raise_cli/onboarding/conventions.py +742 -0
  153. raise_cli/onboarding/detection.py +374 -0
  154. raise_cli/onboarding/governance.py +443 -0
  155. raise_cli/onboarding/instructions.py +672 -0
  156. raise_cli/onboarding/manifest.py +201 -0
  157. raise_cli/onboarding/memory_md.py +399 -0
  158. raise_cli/onboarding/migration.py +207 -0
  159. raise_cli/onboarding/profile.py +624 -0
  160. raise_cli/onboarding/skill_conflict.py +100 -0
  161. raise_cli/onboarding/skill_manifest.py +176 -0
  162. raise_cli/onboarding/skills.py +437 -0
  163. raise_cli/onboarding/workflows.py +101 -0
  164. raise_cli/output/__init__.py +28 -0
  165. raise_cli/output/console.py +394 -0
  166. raise_cli/output/formatters/__init__.py +9 -0
  167. raise_cli/output/formatters/adapters.py +135 -0
  168. raise_cli/output/formatters/discover.py +439 -0
  169. raise_cli/output/formatters/skill.py +298 -0
  170. raise_cli/publish/__init__.py +3 -0
  171. raise_cli/publish/changelog.py +80 -0
  172. raise_cli/publish/check.py +179 -0
  173. raise_cli/publish/version.py +172 -0
  174. raise_cli/rai_base/__init__.py +22 -0
  175. raise_cli/rai_base/framework/__init__.py +7 -0
  176. raise_cli/rai_base/framework/methodology.yaml +233 -0
  177. raise_cli/rai_base/governance/__init__.py +1 -0
  178. raise_cli/rai_base/governance/architecture/__init__.py +1 -0
  179. raise_cli/rai_base/governance/architecture/domain-model.md +20 -0
  180. raise_cli/rai_base/governance/architecture/system-context.md +34 -0
  181. raise_cli/rai_base/governance/architecture/system-design.md +24 -0
  182. raise_cli/rai_base/governance/backlog.md +8 -0
  183. raise_cli/rai_base/governance/guardrails.md +17 -0
  184. raise_cli/rai_base/governance/prd.md +25 -0
  185. raise_cli/rai_base/governance/vision.md +16 -0
  186. raise_cli/rai_base/identity/__init__.py +8 -0
  187. raise_cli/rai_base/identity/core.md +119 -0
  188. raise_cli/rai_base/identity/perspective.md +119 -0
  189. raise_cli/rai_base/memory/__init__.py +7 -0
  190. raise_cli/rai_base/memory/patterns-base.jsonl +55 -0
  191. raise_cli/schemas/__init__.py +3 -0
  192. raise_cli/schemas/journal.py +49 -0
  193. raise_cli/schemas/session_state.py +117 -0
  194. raise_cli/session/__init__.py +5 -0
  195. raise_cli/session/bundle.py +820 -0
  196. raise_cli/session/close.py +268 -0
  197. raise_cli/session/journal.py +119 -0
  198. raise_cli/session/resolver.py +126 -0
  199. raise_cli/session/state.py +187 -0
  200. raise_cli/skills/__init__.py +44 -0
  201. raise_cli/skills/locator.py +141 -0
  202. raise_cli/skills/name_checker.py +199 -0
  203. raise_cli/skills/parser.py +145 -0
  204. raise_cli/skills/scaffold.py +212 -0
  205. raise_cli/skills/schema.py +132 -0
  206. raise_cli/skills/skillsets.py +195 -0
  207. raise_cli/skills/validator.py +197 -0
  208. raise_cli/skills_base/__init__.py +80 -0
  209. raise_cli/skills_base/contract-template.md +60 -0
  210. raise_cli/skills_base/preamble.md +37 -0
  211. raise_cli/skills_base/rai-architecture-review/SKILL.md +137 -0
  212. raise_cli/skills_base/rai-debug/SKILL.md +171 -0
  213. raise_cli/skills_base/rai-discover/SKILL.md +167 -0
  214. raise_cli/skills_base/rai-discover-document/SKILL.md +128 -0
  215. raise_cli/skills_base/rai-discover-scan/SKILL.md +147 -0
  216. raise_cli/skills_base/rai-discover-start/SKILL.md +145 -0
  217. raise_cli/skills_base/rai-discover-validate/SKILL.md +142 -0
  218. raise_cli/skills_base/rai-docs-update/SKILL.md +142 -0
  219. raise_cli/skills_base/rai-doctor/SKILL.md +120 -0
  220. raise_cli/skills_base/rai-epic-close/SKILL.md +165 -0
  221. raise_cli/skills_base/rai-epic-close/templates/retrospective.md +68 -0
  222. raise_cli/skills_base/rai-epic-design/SKILL.md +146 -0
  223. raise_cli/skills_base/rai-epic-design/templates/design.md +24 -0
  224. raise_cli/skills_base/rai-epic-design/templates/scope.md +76 -0
  225. raise_cli/skills_base/rai-epic-plan/SKILL.md +153 -0
  226. raise_cli/skills_base/rai-epic-plan/_references/sequencing-strategies.md +67 -0
  227. raise_cli/skills_base/rai-epic-plan/templates/plan-section.md +49 -0
  228. raise_cli/skills_base/rai-epic-run/SKILL.md +208 -0
  229. raise_cli/skills_base/rai-epic-start/SKILL.md +136 -0
  230. raise_cli/skills_base/rai-epic-start/templates/brief.md +34 -0
  231. raise_cli/skills_base/rai-mcp-add/SKILL.md +176 -0
  232. raise_cli/skills_base/rai-mcp-remove/SKILL.md +120 -0
  233. raise_cli/skills_base/rai-mcp-status/SKILL.md +147 -0
  234. raise_cli/skills_base/rai-problem-shape/SKILL.md +138 -0
  235. raise_cli/skills_base/rai-project-create/SKILL.md +144 -0
  236. raise_cli/skills_base/rai-project-onboard/SKILL.md +162 -0
  237. raise_cli/skills_base/rai-quality-review/SKILL.md +189 -0
  238. raise_cli/skills_base/rai-research/SKILL.md +143 -0
  239. raise_cli/skills_base/rai-research/references/research-prompt-template.md +317 -0
  240. raise_cli/skills_base/rai-session-close/SKILL.md +176 -0
  241. raise_cli/skills_base/rai-session-start/SKILL.md +110 -0
  242. raise_cli/skills_base/rai-story-close/SKILL.md +198 -0
  243. raise_cli/skills_base/rai-story-design/SKILL.md +203 -0
  244. raise_cli/skills_base/rai-story-design/references/tech-design-story-v2.md +293 -0
  245. raise_cli/skills_base/rai-story-implement/SKILL.md +115 -0
  246. raise_cli/skills_base/rai-story-plan/SKILL.md +135 -0
  247. raise_cli/skills_base/rai-story-review/SKILL.md +178 -0
  248. raise_cli/skills_base/rai-story-run/SKILL.md +282 -0
  249. raise_cli/skills_base/rai-story-start/SKILL.md +166 -0
  250. raise_cli/skills_base/rai-story-start/templates/story.md +38 -0
  251. raise_cli/skills_base/rai-welcome/SKILL.md +134 -0
  252. raise_cli/telemetry/__init__.py +42 -0
  253. raise_cli/telemetry/schemas.py +285 -0
  254. raise_cli/telemetry/writer.py +217 -0
  255. raise_cli/tier/__init__.py +0 -0
  256. raise_cli/tier/context.py +134 -0
  257. raise_cli/viz/__init__.py +7 -0
  258. raise_cli/viz/generator.py +406 -0
  259. raise_cli-2.2.1.dist-info/METADATA +433 -0
  260. raise_cli-2.2.1.dist-info/RECORD +264 -0
  261. raise_cli-2.2.1.dist-info/WHEEL +4 -0
  262. raise_cli-2.2.1.dist-info/entry_points.txt +40 -0
  263. raise_cli-2.2.1.dist-info/licenses/LICENSE +190 -0
  264. raise_cli-2.2.1.dist-info/licenses/NOTICE +4 -0
@@ -0,0 +1,153 @@
1
+ """Generic entry-point resolver for adapters and targets.
2
+
3
+ Parametrized auto-detect logic:
4
+ - 1 registered → auto-select
5
+ - 0 registered → error with install guidance
6
+ - 2+ registered → error listing names, request flag
7
+ - flag → select by name (override)
8
+
9
+ Auto-wraps async implementations with the provided sync wrapper.
10
+
11
+ Replaces ``_adapter_resolve.py`` (S301.4, D7 — DRY).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import inspect
17
+ import logging
18
+ import sys
19
+ from collections.abc import Callable
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ from rich.console import Console
24
+
25
+ from raise_cli.adapters.protocols import DocumentationTarget, ProjectManagementAdapter
26
+ from raise_cli.adapters.registry import get_doc_targets, get_pm_adapters
27
+ from raise_cli.adapters.sync import SyncDocsAdapter, SyncPMAdapter
28
+ from raise_cli.onboarding.manifest import load_manifest
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+ console = Console()
33
+
34
+
35
+ def resolve_entrypoint(
36
+ discover: Callable[[], dict[str, Callable[[], Any]]],
37
+ sync_wrapper: type | None,
38
+ async_check_method: str,
39
+ group_label: str,
40
+ flag_name: str,
41
+ selected: str | None,
42
+ ) -> Any:
43
+ """Resolve and instantiate an adapter/target from entry points and YAML configs.
44
+
45
+ Args:
46
+ discover: Function returning {name: factory} from entry points and/or YAML.
47
+ sync_wrapper: Wrapper class for async→sync bridging (or None).
48
+ async_check_method: Method name to check for async (e.g., "get_issue").
49
+ group_label: Human label for error messages (e.g., "PM adapter").
50
+ flag_name: CLI flag name for error messages (e.g., "--adapter").
51
+ selected: Explicit selection by name, or None for auto-detect.
52
+
53
+ Returns:
54
+ An instantiated adapter/target, wrapped if async.
55
+
56
+ Raises:
57
+ SystemExit: If resolution fails.
58
+ """
59
+ entries = discover()
60
+
61
+ if selected is not None:
62
+ cls = entries.get(selected)
63
+ if cls is None:
64
+ available = ", ".join(sorted(entries)) if entries else "none"
65
+ console.print(
66
+ f"[red]Error:[/red] {group_label} '{selected}' not found. "
67
+ f"Available: {available}"
68
+ )
69
+ sys.exit(1)
70
+ elif len(entries) == 0:
71
+ console.print(
72
+ f"[red]Error:[/red] No {group_label} installed.\n"
73
+ f"Install one or register via entry points. Use {flag_name} to select."
74
+ )
75
+ sys.exit(1)
76
+ elif len(entries) == 1:
77
+ cls = next(iter(entries.values()))
78
+ else:
79
+ names = ", ".join(sorted(entries))
80
+ console.print(
81
+ f"[red]Error:[/red] Multiple {group_label}s found: {names}.\n"
82
+ f"Use {flag_name} <name> to select."
83
+ )
84
+ sys.exit(1)
85
+
86
+ try:
87
+ instance = cls()
88
+ except Exception as exc:
89
+ name = selected or next(iter(entries))
90
+ console.print(
91
+ f"[red]Error:[/red] Failed to instantiate {group_label} '{name}': {exc}"
92
+ )
93
+ sys.exit(1)
94
+
95
+ # Auto-wrap async implementations for sync CLI consumption
96
+ if sync_wrapper and inspect.iscoroutinefunction(
97
+ getattr(instance, async_check_method, None)
98
+ ):
99
+ instance = sync_wrapper(instance)
100
+
101
+ return instance
102
+
103
+
104
+ def _discover_pm() -> dict[str, Callable[[], Any]]:
105
+ """Merge YAML and entry point PM adapters. EP wins on name collision."""
106
+ from raise_cli.adapters.declarative.discovery import discover_yaml_adapters
107
+
108
+ return {**discover_yaml_adapters("pm"), **get_pm_adapters()}
109
+
110
+
111
+ def _discover_docs() -> dict[str, Callable[[], Any]]:
112
+ """Merge YAML and entry point docs targets. EP wins on name collision."""
113
+ from raise_cli.adapters.declarative.discovery import discover_yaml_adapters
114
+
115
+ return {**discover_yaml_adapters("docs"), **get_doc_targets()}
116
+
117
+
118
+ def resolve_adapter(adapter_name: str | None) -> ProjectManagementAdapter:
119
+ """Resolve a ProjectManagementAdapter from entry points and YAML configs.
120
+
121
+ Resolution priority:
122
+ 1. Explicit adapter_name (from -a/--adapter flag) → use it
123
+ 2. Manifest backlog.adapter_default → use it
124
+ 3. Auto-detect (single adapter) or error (0 / 2+)
125
+ """
126
+ effective = adapter_name
127
+
128
+ if effective is None:
129
+ manifest = load_manifest(Path.cwd())
130
+ if manifest and manifest.backlog and manifest.backlog.adapter_default:
131
+ effective = manifest.backlog.adapter_default
132
+ logger.debug("Using manifest default adapter: %s", effective)
133
+
134
+ return resolve_entrypoint(
135
+ discover=_discover_pm,
136
+ sync_wrapper=SyncPMAdapter,
137
+ async_check_method="get_issue",
138
+ group_label="PM adapter",
139
+ flag_name="--adapter",
140
+ selected=effective,
141
+ )
142
+
143
+
144
+ def resolve_docs_target(target_name: str | None) -> DocumentationTarget:
145
+ """Resolve a DocumentationTarget from entry points and YAML configs."""
146
+ return resolve_entrypoint(
147
+ discover=_discover_docs,
148
+ sync_wrapper=SyncDocsAdapter,
149
+ async_check_method="get_page",
150
+ group_label="docs target",
151
+ flag_name="--target",
152
+ selected=target_name,
153
+ )
@@ -0,0 +1,362 @@
1
+ """CLI commands for adapter discovery and validation.
2
+
3
+ Provides `rai adapter list`, `rai adapter check`, `rai adapter validate`,
4
+ and `rai adapter status` for inspecting, checking, and validating adapter
5
+ configurations.
6
+
7
+ Architecture: ADR-033 (PM), ADR-034 (Governance), ADR-036 (Graph Backend), ADR-041 (Declarative)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import inspect
13
+ import os
14
+ from importlib.metadata import entry_points
15
+ from pathlib import Path
16
+ from typing import Annotated, Any
17
+
18
+ import typer
19
+ from rich.console import Console
20
+
21
+ from raise_cli.adapters.protocols import (
22
+ DocumentationTarget,
23
+ GovernanceParser,
24
+ GovernanceSchemaProvider,
25
+ ProjectManagementAdapter,
26
+ )
27
+ from raise_cli.adapters.registry import (
28
+ EP_DOC_TARGETS,
29
+ EP_GOVERNANCE_PARSERS,
30
+ EP_GOVERNANCE_SCHEMAS,
31
+ EP_GRAPH_BACKENDS,
32
+ EP_PM_ADAPTERS,
33
+ )
34
+ from raise_cli.hooks.emitter import create_emitter
35
+ from raise_cli.hooks.events import AdapterFailedEvent, AdapterLoadedEvent
36
+ from raise_cli.output.formatters.adapters import (
37
+ format_check_human,
38
+ format_check_json,
39
+ format_list_human,
40
+ format_list_json,
41
+ format_validate_human,
42
+ )
43
+ from raise_cli.tier.context import TierContext
44
+ from raise_core.graph.backends.protocol import KnowledgeGraphBackend
45
+
46
+ adapters_app = typer.Typer(
47
+ name="adapter",
48
+ help="Inspect and validate registered adapters",
49
+ no_args_is_help=True,
50
+ )
51
+
52
+ console = Console()
53
+
54
+ # Group → (Protocol display name, Protocol class).
55
+ # Only consumer of this mapping — lives here per arch review Q1.
56
+ ADAPTER_GROUPS: dict[str, tuple[str, type]] = {
57
+ EP_GOVERNANCE_PARSERS: ("GovernanceParser", GovernanceParser),
58
+ EP_GRAPH_BACKENDS: ("KnowledgeGraphBackend", KnowledgeGraphBackend),
59
+ EP_PM_ADAPTERS: ("ProjectManagementAdapter", ProjectManagementAdapter),
60
+ EP_GOVERNANCE_SCHEMAS: ("GovernanceSchemaProvider", GovernanceSchemaProvider),
61
+ EP_DOC_TARGETS: ("DocumentationTarget", DocumentationTarget),
62
+ }
63
+
64
+
65
+ def _get_dist_name(ep: Any) -> str:
66
+ """Best-effort extraction of distribution name from an entry point."""
67
+ try:
68
+ return ep.dist.name # type: ignore[union-attr]
69
+ except AttributeError:
70
+ return "unknown"
71
+
72
+
73
+ def _collect_groups() -> list[dict[str, Any]]:
74
+ """Collect adapter info for all entry point groups."""
75
+ groups: list[dict[str, Any]] = []
76
+ for group, (proto_name, _proto_cls) in ADAPTER_GROUPS.items():
77
+ adapters: list[dict[str, str]] = []
78
+ for ep in entry_points(group=group):
79
+ adapters.append({"name": ep.name, "package": _get_dist_name(ep)})
80
+ groups.append(
81
+ {
82
+ "group": group,
83
+ "protocol_name": proto_name,
84
+ "adapters": adapters,
85
+ }
86
+ )
87
+ return groups
88
+
89
+
90
+ def _get_tier() -> str:
91
+ """Get current tier level string."""
92
+ ctx = TierContext.from_manifest(Path.cwd())
93
+ return ctx.tier.value
94
+
95
+
96
+ @adapters_app.command("list")
97
+ def list_command(
98
+ format: Annotated[
99
+ str,
100
+ typer.Option(
101
+ "--format",
102
+ "-f",
103
+ help="Output format: human or json",
104
+ ),
105
+ ] = "human",
106
+ ) -> None:
107
+ """List all registered adapters by entry point group.
108
+
109
+ Shows each entry point group with its registered adapters and
110
+ their source package.
111
+
112
+ Examples:
113
+ $ rai adapter list
114
+ $ rai adapter list --format json
115
+ """
116
+ tier = _get_tier()
117
+ groups = _collect_groups()
118
+
119
+ if format == "json":
120
+ typer.echo(format_list_json(tier, groups))
121
+ else:
122
+ format_list_human(tier, groups, console)
123
+
124
+
125
+ @adapters_app.command("check")
126
+ def check_command(
127
+ format: Annotated[
128
+ str,
129
+ typer.Option(
130
+ "--format",
131
+ "-f",
132
+ help="Output format: human or json",
133
+ ),
134
+ ] = "human",
135
+ ) -> None:
136
+ """Validate adapters against their Protocol contracts.
137
+
138
+ Loads each registered adapter and checks compliance via isinstance()
139
+ against its corresponding @runtime_checkable Protocol.
140
+
141
+ Examples:
142
+ $ rai adapter check
143
+ $ rai adapter check --format json
144
+ """
145
+ results: list[dict[str, Any]] = []
146
+ emitter = create_emitter()
147
+
148
+ for group, (proto_name, proto_cls) in ADAPTER_GROUPS.items():
149
+ for ep in entry_points(group=group):
150
+ try:
151
+ loaded: Any = ep.load()
152
+ except Exception as exc: # noqa: BLE001
153
+ emitter.emit(
154
+ AdapterFailedEvent(
155
+ adapter_name=ep.name,
156
+ group=group,
157
+ error=str(exc),
158
+ )
159
+ )
160
+ results.append(
161
+ {
162
+ "group": group,
163
+ "name": ep.name,
164
+ "package": _get_dist_name(ep),
165
+ "protocol_name": proto_name,
166
+ "compliant": False,
167
+ "error": f"Failed to load: {exc}",
168
+ }
169
+ )
170
+ continue
171
+
172
+ compliant = inspect.isclass(loaded) and issubclass(loaded, proto_cls)
173
+ error = None if compliant else f"Not a {proto_name} subclass"
174
+ if compliant:
175
+ emitter.emit(
176
+ AdapterLoadedEvent(
177
+ adapter_name=ep.name,
178
+ group=group,
179
+ adapter_type=type(loaded).__name__,
180
+ )
181
+ )
182
+ else:
183
+ emitter.emit(
184
+ AdapterFailedEvent(
185
+ adapter_name=ep.name,
186
+ group=group,
187
+ error=error or "",
188
+ )
189
+ )
190
+ results.append(
191
+ {
192
+ "group": group,
193
+ "name": ep.name,
194
+ "package": _get_dist_name(ep),
195
+ "protocol_name": proto_name,
196
+ "compliant": compliant,
197
+ "error": error,
198
+ }
199
+ )
200
+
201
+ if format == "json":
202
+ typer.echo(format_check_json(results))
203
+ else:
204
+ format_check_human(results, console)
205
+
206
+
207
+ @adapters_app.command("validate")
208
+ def validate_command(
209
+ file: Annotated[
210
+ Path,
211
+ typer.Argument(help="Path to YAML adapter config file"),
212
+ ],
213
+ ) -> None:
214
+ """Validate a declarative YAML adapter config.
215
+
216
+ Checks that the YAML file conforms to the DeclarativeAdapterConfig
217
+ schema. Reports adapter name, protocol, and method counts on success,
218
+ or specific field errors on failure.
219
+
220
+ Examples:
221
+ $ rai adapter validate .raise/adapters/github.yaml
222
+ $ rai adapter validate my-adapter.yaml
223
+ """
224
+ import yaml
225
+ from pydantic import ValidationError
226
+
227
+ from raise_cli.adapters.declarative.schema import DeclarativeAdapterConfig
228
+
229
+ if not file.exists():
230
+ console.print(f"[red]Error:[/red] File not found: {file}")
231
+ raise typer.Exit(1)
232
+
233
+ try:
234
+ raw = yaml.safe_load(file.read_text(encoding="utf-8"))
235
+ except yaml.YAMLError as exc:
236
+ console.print(f"[red]✗ Invalid adapter config:[/red] {file.name}")
237
+ console.print(f" Cannot parse YAML: {exc}")
238
+ raise typer.Exit(1) from None
239
+
240
+ if not isinstance(raw, dict):
241
+ console.print(f"[red]✗ Invalid adapter config:[/red] {file.name}")
242
+ console.print(" YAML content is not a mapping")
243
+ raise typer.Exit(1)
244
+
245
+ try:
246
+ config = DeclarativeAdapterConfig.model_validate(raw)
247
+ except ValidationError as exc:
248
+ console.print(f"[red]✗ Invalid adapter config:[/red] {file.name}")
249
+ for err in exc.errors():
250
+ loc = ".".join(str(p) for p in err["loc"])
251
+ console.print(f" {loc}")
252
+ console.print(f" {err['msg']}")
253
+ raise typer.Exit(1) from None
254
+
255
+ format_validate_human(config, console)
256
+
257
+
258
+ # ---------------------------------------------------------------------------
259
+ # rai adapter status — show configuration status for known adapters
260
+ # ---------------------------------------------------------------------------
261
+
262
+ # Jira env var names expected by McpJiraAdapter._create_bridge
263
+ _JIRA_ENV_VARS: list[tuple[str, str]] = [
264
+ ("JIRA_URL", "Jira instance URL"),
265
+ ("JIRA_USERNAME", "Jira user email"),
266
+ ("JIRA_API_TOKEN", "Jira API token (or JIRA_TOKEN)"),
267
+ ]
268
+
269
+
270
+ def _check_jira_config(project_root: Path) -> dict[str, Any]:
271
+ """Collect Jira adapter configuration status.
272
+
273
+ Returns a dict with keys: yaml_path, yaml_exists, env_vars (list of
274
+ dicts with name, set, description), and ready (bool).
275
+ """
276
+ yaml_path = project_root / ".raise" / "jira.yaml"
277
+ env_results: list[dict[str, Any]] = []
278
+ for var_name, description in _JIRA_ENV_VARS:
279
+ if var_name == "JIRA_API_TOKEN":
280
+ is_set = bool(os.environ.get("JIRA_API_TOKEN") or os.environ.get("JIRA_TOKEN"))
281
+ else:
282
+ is_set = bool(os.environ.get(var_name))
283
+ env_results.append({"name": var_name, "set": is_set, "description": description})
284
+
285
+ all_env_set = all(e["set"] for e in env_results)
286
+ return {
287
+ "yaml_path": str(yaml_path),
288
+ "yaml_exists": yaml_path.exists(),
289
+ "env_vars": env_results,
290
+ "ready": yaml_path.exists() and all_env_set,
291
+ }
292
+
293
+
294
+ @adapters_app.command("status")
295
+ def status_command(
296
+ format: Annotated[
297
+ str,
298
+ typer.Option(
299
+ "--format",
300
+ "-f",
301
+ help="Output format: human or json",
302
+ ),
303
+ ] = "human",
304
+ ) -> None:
305
+ """Show configuration status for known adapters.
306
+
307
+ Checks that required config files exist and environment variables
308
+ are set. Useful for verifying setup after configuring an adapter.
309
+
310
+ Examples:
311
+ $ rai adapter status
312
+ $ rai adapter status --format json
313
+ """
314
+ import json as json_mod
315
+
316
+ project_root = Path.cwd()
317
+ jira_status = _check_jira_config(project_root)
318
+
319
+ if format == "json":
320
+ typer.echo(json_mod.dumps({"jira": jira_status}, indent=2))
321
+ return
322
+
323
+ console.print("[bold]Adapter Configuration Status[/bold]\n")
324
+
325
+ # --- Jira ---
326
+ console.print("[bold]Jira[/bold]")
327
+
328
+ yaml_path = jira_status["yaml_path"]
329
+ if jira_status["yaml_exists"]:
330
+ console.print(f" [green]\u2713[/green] Config: {yaml_path}")
331
+ else:
332
+ console.print(f" [red]\u2717[/red] Config: {yaml_path} [red](not found)[/red]")
333
+ console.print(
334
+ " [dim]Create .raise/jira.yaml with status_mapping and project config.[/dim]"
335
+ )
336
+
337
+ for env_var in jira_status["env_vars"]:
338
+ if env_var["set"]:
339
+ console.print(f" [green]\u2713[/green] {env_var['name']}: set")
340
+ else:
341
+ console.print(
342
+ f" [red]\u2717[/red] {env_var['name']}: [red]not set[/red]"
343
+ f" [dim]({env_var['description']})[/dim]"
344
+ )
345
+
346
+ console.print()
347
+ if jira_status["ready"]:
348
+ console.print("[green]Jira adapter is fully configured.[/green]")
349
+ else:
350
+ missing: list[str] = []
351
+ if not jira_status["yaml_exists"]:
352
+ missing.append(".raise/jira.yaml")
353
+ for env_var in jira_status["env_vars"]:
354
+ if not env_var["set"]:
355
+ missing.append(env_var["name"])
356
+ console.print(
357
+ f"[yellow]Jira adapter is not ready.[/yellow] Missing: {', '.join(missing)}"
358
+ )
359
+ console.print(
360
+ "\n[dim]Set env vars in .env or shell. "
361
+ "See CLAUDE.md 'Jira Access' section for details.[/dim]"
362
+ )
@@ -0,0 +1,137 @@
1
+ """CLI commands for artifact validation.
2
+
3
+ Provides ``rai artifact validate`` for checking YAML artifacts
4
+ against the Pydantic type registry.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ from pathlib import Path
12
+ from typing import Annotated
13
+
14
+ import typer
15
+ from pydantic import ValidationError
16
+ from rich.console import Console
17
+
18
+ from raise_cli.artifacts.reader import read_artifact
19
+
20
+ artifact_app = typer.Typer(
21
+ name="artifact",
22
+ help="Manage skill artifacts",
23
+ no_args_is_help=True,
24
+ )
25
+
26
+ console = Console()
27
+
28
+
29
+ def _resolve_project_root() -> Path:
30
+ """Resolve the project root from env or cwd."""
31
+ env_root = os.environ.get("RAI_PROJECT_ROOT")
32
+ if env_root:
33
+ return Path(env_root)
34
+ return Path.cwd()
35
+
36
+
37
+ @artifact_app.command("validate")
38
+ def validate_command(
39
+ file: Annotated[
40
+ str | None,
41
+ typer.Option("--file", help="Path to a single artifact file to validate"),
42
+ ] = None,
43
+ format: Annotated[
44
+ str,
45
+ typer.Option("--format", "-f", help="Output format: human or json"),
46
+ ] = "human",
47
+ ) -> None:
48
+ """Validate YAML artifacts against their Pydantic schemas.
49
+
50
+ Reads all .yaml files from .raise/artifacts/ (or a single file with --file)
51
+ and validates each against the type registry.
52
+
53
+ Exit code 0 if all pass, 1 if any fail.
54
+
55
+ Examples:
56
+ $ rai artifact validate
57
+ $ rai artifact validate --file .raise/artifacts/s354.1-design.yaml
58
+ $ rai artifact validate --format json
59
+ """
60
+ if file:
61
+ paths = _resolve_single_file(Path(file), format)
62
+ else:
63
+ paths = _resolve_all_artifacts(format)
64
+
65
+ if paths is None:
66
+ return # Already handled (exit raised)
67
+
68
+ results: list[dict[str, object]] = []
69
+ for path in paths:
70
+ try:
71
+ read_artifact(path)
72
+ results.append({"file": path.name, "passed": True, "error": None})
73
+ except (ValidationError, Exception) as exc: # noqa: BLE001
74
+ results.append({"file": path.name, "passed": False, "error": str(exc)})
75
+
76
+ failed = [r for r in results if not r["passed"]]
77
+
78
+ if format == "json":
79
+ typer.echo(
80
+ json.dumps(
81
+ {"results": results, "all_passed": len(failed) == 0},
82
+ indent=2,
83
+ )
84
+ )
85
+ else:
86
+ for r in results:
87
+ marker = "[green]✓[/green]" if r["passed"] else "[red]✗[/red]"
88
+ console.print(f" {marker} {r['file']}")
89
+ if r["error"]:
90
+ # Show first line of error only
91
+ first_line = str(r["error"]).split("\n")[0]
92
+ console.print(f" {first_line}")
93
+ console.print()
94
+ if failed:
95
+ console.print(
96
+ f"[red bold]FAILED:[/red bold] {len(failed)} of {len(results)} artifacts invalid"
97
+ )
98
+ else:
99
+ console.print(
100
+ f"[green bold]PASSED:[/green bold] {len(results)} artifact(s) valid"
101
+ )
102
+
103
+ raise typer.Exit(1 if failed else 0)
104
+
105
+
106
+ def _resolve_single_file(path: Path, fmt: str) -> list[Path] | None:
107
+ """Resolve a single file path, handling not-found."""
108
+ if not path.exists():
109
+ if fmt == "json":
110
+ typer.echo(json.dumps({"error": f"File not found: {path}"}))
111
+ else:
112
+ console.print(f"[red]Error:[/red] File not found: {path}")
113
+ raise typer.Exit(1)
114
+ return [path]
115
+
116
+
117
+ def _resolve_all_artifacts(fmt: str) -> list[Path] | None:
118
+ """Resolve all artifact files from .raise/artifacts/."""
119
+ root = _resolve_project_root()
120
+ artifacts_dir = root / ".raise" / "artifacts"
121
+
122
+ if not artifacts_dir.is_dir():
123
+ if fmt == "json":
124
+ typer.echo(json.dumps({"results": [], "all_passed": True, "message": "No artifacts found"}))
125
+ else:
126
+ console.print("No artifacts found in .raise/artifacts/")
127
+ raise typer.Exit(0)
128
+
129
+ paths = sorted(artifacts_dir.glob("*.yaml"))
130
+ if not paths:
131
+ if fmt == "json":
132
+ typer.echo(json.dumps({"results": [], "all_passed": True, "message": "No artifacts found"}))
133
+ else:
134
+ console.print("No artifacts found in .raise/artifacts/")
135
+ raise typer.Exit(0)
136
+
137
+ return paths