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,443 @@
1
+ """Governance generation and scaffolding.
2
+
3
+ Two capabilities:
4
+ 1. Generate guardrails.md from detected conventions (brownfield, --detect).
5
+ 2. Scaffold governance/ from bundled rai_base templates (all projects).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ from collections.abc import Callable
12
+ from datetime import date
13
+ from enum import StrEnum
14
+ from importlib.resources import files
15
+ from pathlib import Path
16
+
17
+ from pydantic import BaseModel
18
+
19
+ from raise_cli.onboarding.conventions import (
20
+ Confidence,
21
+ ConventionResult,
22
+ )
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class GuardrailLevel(StrEnum):
28
+ """Guardrail enforcement level.
29
+
30
+ Maps to MoSCoW prioritization:
31
+ - MUST: Required, enforced by tooling
32
+ - SHOULD: Recommended, reviewed manually
33
+ - COULD: Optional, noted for awareness
34
+ """
35
+
36
+ MUST = "MUST"
37
+ SHOULD = "SHOULD"
38
+ COULD = "COULD"
39
+
40
+
41
+ class GeneratedGuardrail(BaseModel):
42
+ """A guardrail generated from detected conventions.
43
+
44
+ Attributes:
45
+ id: Unique identifier (e.g., "MUST-STYLE-001").
46
+ level: Enforcement level (MUST/SHOULD/COULD).
47
+ category: Category for grouping (e.g., "Code Style").
48
+ description: Human-readable description of the rule.
49
+ verification: Optional command or method to verify compliance.
50
+ """
51
+
52
+ id: str
53
+ level: GuardrailLevel
54
+ category: str
55
+ description: str
56
+ verification: str | None = None
57
+
58
+
59
+ class GuardrailGenerator:
60
+ """Generates guardrails from detected conventions.
61
+
62
+ Maps confidence levels to guardrail enforcement levels:
63
+ - HIGH confidence → MUST (enforced)
64
+ - MEDIUM confidence → SHOULD (recommended)
65
+ - LOW confidence → COULD (optional)
66
+ """
67
+
68
+ def confidence_to_level(self, confidence: Confidence) -> GuardrailLevel:
69
+ """Map confidence to guardrail level.
70
+
71
+ Args:
72
+ confidence: Detection confidence level.
73
+
74
+ Returns:
75
+ Corresponding guardrail enforcement level.
76
+ """
77
+ mapping = {
78
+ Confidence.HIGH: GuardrailLevel.MUST,
79
+ Confidence.MEDIUM: GuardrailLevel.SHOULD,
80
+ Confidence.LOW: GuardrailLevel.COULD,
81
+ }
82
+ return mapping[confidence]
83
+
84
+ def _generate_style_guardrails(
85
+ self,
86
+ conventions: ConventionResult,
87
+ next_id: Callable[[GuardrailLevel, str], str],
88
+ ) -> list[GeneratedGuardrail]:
89
+ """Generate style-related guardrails (indentation, quotes, line length)."""
90
+ guardrails: list[GeneratedGuardrail] = []
91
+
92
+ # Indentation
93
+ indent = conventions.style.indentation
94
+ indent_level = self.confidence_to_level(indent.confidence)
95
+ if indent.style == "spaces" and indent.width:
96
+ indent_desc = f"Use {indent.width}-space indentation"
97
+ elif indent.style == "tabs":
98
+ indent_desc = "Use tab indentation"
99
+ else:
100
+ indent_desc = "Use consistent indentation (mixed detected)"
101
+
102
+ guardrails.append(
103
+ GeneratedGuardrail(
104
+ id=next_id(indent_level, "STYLE"),
105
+ level=indent_level,
106
+ category="Code Style",
107
+ description=indent_desc,
108
+ verification="ruff check ."
109
+ if indent_level == GuardrailLevel.MUST
110
+ else None,
111
+ )
112
+ )
113
+
114
+ # Quote style
115
+ quotes = conventions.style.quote_style
116
+ quote_level = self.confidence_to_level(quotes.confidence)
117
+ guardrails.append(
118
+ GeneratedGuardrail(
119
+ id=next_id(quote_level, "STYLE"),
120
+ level=quote_level,
121
+ category="Code Style",
122
+ description=f"Use {quotes.style} quotes for strings",
123
+ verification="ruff check ."
124
+ if quote_level == GuardrailLevel.MUST
125
+ else None,
126
+ )
127
+ )
128
+
129
+ # Line length
130
+ line_length = conventions.style.line_length
131
+ line_level = self.confidence_to_level(line_length.confidence)
132
+ guardrails.append(
133
+ GeneratedGuardrail(
134
+ id=next_id(line_level, "STYLE"),
135
+ level=line_level,
136
+ category="Code Style",
137
+ description=f"Maximum line length: {line_length.max_length} characters",
138
+ verification="ruff check ."
139
+ if line_level == GuardrailLevel.MUST
140
+ else None,
141
+ )
142
+ )
143
+
144
+ return guardrails
145
+
146
+ def _generate_naming_guardrails(
147
+ self,
148
+ conventions: ConventionResult,
149
+ next_id: Callable[[GuardrailLevel, str], str],
150
+ ) -> list[GeneratedGuardrail]:
151
+ """Generate naming convention guardrails."""
152
+ guardrails: list[GeneratedGuardrail] = []
153
+
154
+ # Functions
155
+ func_naming = conventions.naming.functions
156
+ func_level = self.confidence_to_level(func_naming.confidence)
157
+ guardrails.append(
158
+ GeneratedGuardrail(
159
+ id=next_id(func_level, "NAMING"),
160
+ level=func_level,
161
+ category="Naming",
162
+ description=f"Function names: {func_naming.pattern}",
163
+ )
164
+ )
165
+
166
+ # Classes
167
+ class_naming = conventions.naming.classes
168
+ class_level = self.confidence_to_level(class_naming.confidence)
169
+ guardrails.append(
170
+ GeneratedGuardrail(
171
+ id=next_id(class_level, "NAMING"),
172
+ level=class_level,
173
+ category="Naming",
174
+ description=f"Class names: {class_naming.pattern}",
175
+ )
176
+ )
177
+
178
+ # Constants (only if meaningful samples)
179
+ const_naming = conventions.naming.constants
180
+ if const_naming.sample_count >= 3:
181
+ const_level = self.confidence_to_level(const_naming.confidence)
182
+ guardrails.append(
183
+ GeneratedGuardrail(
184
+ id=next_id(const_level, "NAMING"),
185
+ level=const_level,
186
+ category="Naming",
187
+ description=f"Constant names: {const_naming.pattern}",
188
+ )
189
+ )
190
+
191
+ return guardrails
192
+
193
+ def _generate_structure_guardrails(
194
+ self,
195
+ conventions: ConventionResult,
196
+ next_id: Callable[[GuardrailLevel, str], str],
197
+ ) -> list[GeneratedGuardrail]:
198
+ """Generate project structure guardrails."""
199
+ guardrails: list[GeneratedGuardrail] = []
200
+ structure = conventions.structure
201
+
202
+ if structure.has_src_layout and structure.source_dir:
203
+ guardrails.append(
204
+ GeneratedGuardrail(
205
+ id=next_id(GuardrailLevel.SHOULD, "STRUCTURE"),
206
+ level=GuardrailLevel.SHOULD,
207
+ category="Structure",
208
+ description=f"Source code in src/ layout ({structure.source_dir})",
209
+ )
210
+ )
211
+
212
+ if structure.test_dir:
213
+ guardrails.append(
214
+ GeneratedGuardrail(
215
+ id=next_id(GuardrailLevel.SHOULD, "STRUCTURE"),
216
+ level=GuardrailLevel.SHOULD,
217
+ category="Structure",
218
+ description=f"Tests in {structure.test_dir}/ directory",
219
+ )
220
+ )
221
+
222
+ return guardrails
223
+
224
+ def generate(self, conventions: ConventionResult) -> list[GeneratedGuardrail]:
225
+ """Generate guardrails from conventions.
226
+
227
+ Args:
228
+ conventions: Detected conventions from analysis.
229
+
230
+ Returns:
231
+ List of generated guardrails.
232
+ """
233
+ counters: dict[str, int] = {}
234
+
235
+ def next_id(level: GuardrailLevel, category: str) -> str:
236
+ """Generate next ID for a category."""
237
+ key = f"{level.value}-{category.upper()}"
238
+ counters[key] = counters.get(key, 0) + 1
239
+ return f"{key}-{counters[key]:03d}"
240
+
241
+ guardrails: list[GeneratedGuardrail] = []
242
+ guardrails.extend(self._generate_style_guardrails(conventions, next_id))
243
+ guardrails.extend(self._generate_naming_guardrails(conventions, next_id))
244
+ guardrails.extend(self._generate_structure_guardrails(conventions, next_id))
245
+ return guardrails
246
+
247
+ def to_markdown(
248
+ self,
249
+ conventions: ConventionResult,
250
+ project_name: str | None = None,
251
+ ) -> str:
252
+ """Generate markdown guardrails document.
253
+
254
+ Args:
255
+ conventions: Detected conventions.
256
+ project_name: Optional project name for context.
257
+
258
+ Returns:
259
+ Markdown content for guardrails.md.
260
+ """
261
+ guardrails = self.generate(conventions)
262
+ by_category = self._group_by_category(guardrails)
263
+
264
+ lines: list[str] = []
265
+ self._add_md_header(lines, project_name)
266
+ self._add_md_context(lines, conventions)
267
+ self._add_md_guardrail_tables(lines, by_category)
268
+ self._add_md_footer(lines)
269
+ return "\n".join(lines)
270
+
271
+ def _group_by_category(
272
+ self, guardrails: list[GeneratedGuardrail]
273
+ ) -> dict[str, list[GeneratedGuardrail]]:
274
+ """Group guardrails by category."""
275
+ by_category: dict[str, list[GeneratedGuardrail]] = {}
276
+ for g in guardrails:
277
+ by_category.setdefault(g.category, []).append(g)
278
+ return by_category
279
+
280
+ def _add_md_header(self, lines: list[str], project_name: str | None) -> None:
281
+ """Add markdown document header with YAML frontmatter.
282
+
283
+ Frontmatter is required by the guardrails parser for type identification.
284
+ """
285
+ lines.append("---")
286
+ lines.append("type: guardrails")
287
+ lines.append('version: "1.0.0"')
288
+ lines.append("---")
289
+ lines.append("")
290
+ title = f"Guardrails: {project_name}" if project_name else "Guardrails"
291
+ lines.append(f"# {title}")
292
+ lines.append("")
293
+ lines.append("> Auto-generated from detected conventions")
294
+ lines.append("")
295
+
296
+ def _add_md_context(self, lines: list[str], conventions: ConventionResult) -> None:
297
+ """Add context section with confidence info."""
298
+ lines.append("## Context")
299
+ lines.append("")
300
+ lines.append(f"- **Files analyzed:** {conventions.files_analyzed}")
301
+ lines.append(
302
+ f"- **Overall confidence:** {conventions.overall_confidence.value}"
303
+ )
304
+ lines.append(f"- **Generated:** {date.today().isoformat()}")
305
+ lines.append("")
306
+
307
+ if conventions.overall_confidence == Confidence.LOW:
308
+ lines.append(
309
+ "> ⚠️ **Low confidence:** Limited samples analyzed. "
310
+ "Review guardrails carefully before adopting."
311
+ )
312
+ lines.append("")
313
+
314
+ def _add_md_guardrail_tables(
315
+ self, lines: list[str], by_category: dict[str, list[GeneratedGuardrail]]
316
+ ) -> None:
317
+ """Add guardrail tables grouped by category.
318
+
319
+ Uses ### headings and 5-column tables to match the guardrails
320
+ parser contract (parser looks for ``^###`` sections and extracts
321
+ the ``Derived from`` column).
322
+ """
323
+ for category, cat_guardrails in by_category.items():
324
+ lines.append(f"### {category}")
325
+ lines.append("")
326
+ lines.append("| ID | Level | Guardrail | Verification | Derived from |")
327
+ lines.append("|----|-------|-----------|--------------|--------------|")
328
+
329
+ for g in cat_guardrails:
330
+ verification = g.verification or "Manual review"
331
+ guardrail_id = g.id.lower()
332
+ lines.append(
333
+ f"| {guardrail_id} | {g.level.value} | "
334
+ f"{g.description} | {verification} | Convention |"
335
+ )
336
+ lines.append("")
337
+
338
+ def _add_md_footer(self, lines: list[str]) -> None:
339
+ """Add document footer."""
340
+ lines.append("---")
341
+ lines.append("")
342
+ lines.append(
343
+ "*Generated by `raise init --detect`. Review and adjust as needed.*"
344
+ )
345
+ lines.append("")
346
+
347
+
348
+ def generate_guardrails(
349
+ conventions: ConventionResult,
350
+ project_name: str | None = None,
351
+ ) -> str:
352
+ """Convenience function to generate guardrails markdown.
353
+
354
+ Args:
355
+ conventions: Detected conventions from analysis.
356
+ project_name: Optional project name for context.
357
+
358
+ Returns:
359
+ Markdown content for guardrails.md.
360
+
361
+ Example:
362
+ >>> result = detect_conventions(Path("/my/project"))
363
+ >>> markdown = generate_guardrails(result, project_name="my-api")
364
+ >>> Path("guardrails.md").write_text(markdown)
365
+ """
366
+ generator = GuardrailGenerator()
367
+ return generator.to_markdown(conventions, project_name=project_name)
368
+
369
+
370
+ # =============================================================================
371
+ # Governance Scaffolding (from bundled templates)
372
+ # =============================================================================
373
+
374
+ # Template files in rai_base/governance/, relative to package root.
375
+ # Tuples of (source_path_in_package, dest_path_in_governance).
376
+ _GOVERNANCE_TEMPLATES: list[tuple[str, str]] = [
377
+ ("prd.md", "prd.md"),
378
+ ("vision.md", "vision.md"),
379
+ ("guardrails.md", "guardrails.md"),
380
+ ("backlog.md", "backlog.md"),
381
+ ("architecture/system-context.md", "architecture/system-context.md"),
382
+ ("architecture/system-design.md", "architecture/system-design.md"),
383
+ ("architecture/domain-model.md", "architecture/domain-model.md"),
384
+ ]
385
+
386
+
387
+ class GovernanceScaffoldResult(BaseModel):
388
+ """Result of governance scaffolding.
389
+
390
+ Attributes:
391
+ already_existed: True if all files already existed (nothing created).
392
+ files_created: Number of template files created.
393
+ files_skipped: Number of files skipped (already existed).
394
+ path: Path to the governance/ directory.
395
+ """
396
+
397
+ already_existed: bool = False
398
+ files_created: int = 0
399
+ files_skipped: int = 0
400
+ path: Path
401
+
402
+
403
+ def scaffold_governance(
404
+ project_path: Path,
405
+ project_name: str,
406
+ ) -> GovernanceScaffoldResult:
407
+ """Scaffold governance/ from bundled rai_base templates.
408
+
409
+ Copies template files via importlib.resources, rendering
410
+ ``{project_name}`` placeholders. Per-file idempotency: existing
411
+ files are never overwritten.
412
+
413
+ Follows bootstrap.py pattern for asset distribution.
414
+
415
+ Args:
416
+ project_path: Project root directory.
417
+ project_name: Project name for template rendering.
418
+
419
+ Returns:
420
+ GovernanceScaffoldResult with details of what was created.
421
+ """
422
+ gov_dir = project_path / "governance"
423
+ base = files("raise_cli.rai_base") / "governance"
424
+ result = GovernanceScaffoldResult(path=gov_dir)
425
+
426
+ for src_rel, dest_rel in _GOVERNANCE_TEMPLATES:
427
+ dest = gov_dir / dest_rel
428
+ if dest.exists():
429
+ result.files_skipped += 1
430
+ logger.debug("Skipped (exists): %s", dest)
431
+ continue
432
+
433
+ # Read bundled template and render placeholders
434
+ content = (base / src_rel).read_text(encoding="utf-8")
435
+ content = content.replace("{project_name}", project_name)
436
+
437
+ dest.parent.mkdir(parents=True, exist_ok=True)
438
+ dest.write_text(content, encoding="utf-8")
439
+ result.files_created += 1
440
+ logger.debug("Created: %s", dest)
441
+
442
+ result.already_existed = result.files_created == 0
443
+ return result