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,672 @@
1
+ """Instructions file generation for project onboarding.
2
+
3
+ Generates project-specific instructions content (CLAUDE.md, .cursor/rules/raise.mdc,
4
+ .windsurf/rules/raise.md, etc.) from .raise/ canonical sources. A .raise/ directory
5
+ must exist before generation — ``rai init`` always creates it first.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ import yaml
15
+
16
+ from raise_cli.config.agents import AgentConfig
17
+ from raise_cli.onboarding.conventions import ConventionResult
18
+ from raise_cli.onboarding.detection import DetectionResult
19
+
20
+
21
+ def _str_from_nested(data: dict[str, Any], key1: str, key2: str) -> str | None:
22
+ """Safely extract a string from a nested dict (YAML-loaded data).
23
+
24
+ Works with YAML-loaded dicts which have ``Any``-typed values.
25
+ """
26
+ outer = data.get(key1)
27
+ if not isinstance(outer, dict):
28
+ return None
29
+ typed: dict[str, object] = {str(k): v for k, v in outer.items()} # type: ignore[union-attr]
30
+ raw = typed.get(key2)
31
+ return raw if isinstance(raw, str) else None
32
+
33
+
34
+ class InstructionsGenerator:
35
+ """Generates agent instructions file content from .raise/ sources.
36
+
37
+ Reads identity, methodology, manifest, and optional integration configs
38
+ to produce a comprehensive instructions file (e.g. CLAUDE.md).
39
+ """
40
+
41
+ def generate(
42
+ self,
43
+ project_name: str,
44
+ detection: DetectionResult,
45
+ conventions: ConventionResult | None = None,
46
+ *,
47
+ project_path: Path,
48
+ ) -> str:
49
+ """Generate CLAUDE.md content from .raise/ canonical sources.
50
+
51
+ Args:
52
+ project_name: Name of the project.
53
+ detection: Project detection result (type, file count).
54
+ conventions: Optional convention detection result (unused, kept
55
+ for backward compatibility).
56
+ project_path: Project root path containing .raise/ directory.
57
+
58
+ Returns:
59
+ Markdown content for CLAUDE.md.
60
+ """
61
+ raise_dir = project_path / ".raise"
62
+ return self._generate_raise_project(project_name, raise_dir)
63
+
64
+ # =========================================================================
65
+ # RaiSE Project Generation
66
+ # =========================================================================
67
+
68
+ def _generate_raise_project(
69
+ self,
70
+ project_name: str,
71
+ raise_dir: Path,
72
+ ) -> str:
73
+ """Generate CLAUDE.md for a RaiSE project from .raise/ sources.
74
+
75
+ Reads identity, methodology, manifest, and optional integration
76
+ configs to produce a comprehensive CLAUDE.md.
77
+ """
78
+ lines: list[str] = []
79
+
80
+ # Header comment
81
+ lines.append(
82
+ "<!-- Generated from .raise/ canonical source. "
83
+ "Do not edit manually. Regenerate with: rai init -->"
84
+ )
85
+ lines.append("")
86
+
87
+ # Project title + session start
88
+ lines.append("# RaiSE Project")
89
+ lines.append("")
90
+ lines.append(
91
+ "Run `/rai-session-start` at the beginning of each session "
92
+ "to load full context (patterns, coaching, session continuity)."
93
+ )
94
+ lines.append("")
95
+
96
+ # Load sources
97
+ identity_path = raise_dir / "rai" / "identity" / "core.md"
98
+ methodology_path = raise_dir / "rai" / "framework" / "methodology.yaml"
99
+ manifest_path = raise_dir / "manifest.yaml"
100
+ jira_path = raise_dir / "jira.yaml"
101
+
102
+ methodology: dict[str, Any] = {}
103
+ if methodology_path.is_file():
104
+ methodology = yaml.safe_load(methodology_path.read_text(encoding="utf-8"))
105
+
106
+ manifest: dict[str, Any] = {}
107
+ if manifest_path.is_file():
108
+ manifest = yaml.safe_load(manifest_path.read_text(encoding="utf-8"))
109
+
110
+ dev_branch = _str_from_nested(manifest, "branches", "development") or "dev"
111
+
112
+ # Identity section
113
+ if identity_path.is_file():
114
+ identity_text = identity_path.read_text(encoding="utf-8")
115
+ self._add_identity_section(lines, identity_text)
116
+
117
+ # Process Rules section
118
+ if methodology:
119
+ self._add_process_rules_section(lines, methodology)
120
+
121
+ # Branch Model section
122
+ if methodology.get("branches"):
123
+ self._add_branch_model_section(lines, methodology, dev_branch)
124
+
125
+ # CLI Quick Reference (static content)
126
+ self._add_cli_reference_section(lines)
127
+
128
+ # External Integrations (only if jira.yaml exists)
129
+ if jira_path.is_file():
130
+ self._add_integrations_section(lines)
131
+
132
+ # File Operations (static)
133
+ self._add_file_operations_section(lines)
134
+
135
+ # Post-Compaction (static)
136
+ self._add_post_compaction_section(lines)
137
+
138
+ return "\n".join(lines)
139
+
140
+ def _add_identity_section(self, lines: list[str], identity_text: str) -> None:
141
+ """Extract and add Rai Identity section from core.md."""
142
+ lines.append("## Rai Identity")
143
+ lines.append("")
144
+
145
+ # Extract Values
146
+ values = self._extract_values(identity_text)
147
+ if values:
148
+ lines.append("### Values")
149
+ for i, (name, summary) in enumerate(values, 1):
150
+ lines.append(f"{i}. {name} \u2014 {summary}")
151
+ lines.append("")
152
+
153
+ # Extract Boundaries
154
+ i_will, i_wont = self._extract_boundaries(identity_text)
155
+ if i_will or i_wont:
156
+ lines.append("### Boundaries")
157
+ if i_will:
158
+ lines.append("I Will: " + ", ".join(i_will))
159
+ if i_wont:
160
+ lines.append("I Won't: " + ", ".join(i_wont))
161
+ lines.append("")
162
+
163
+ # Extract Principles (from the Internalized Philosophy table if present)
164
+ principles = self._extract_principles_from_identity(identity_text)
165
+ if principles:
166
+ lines.append("### Principles")
167
+ for i, (name, summary) in enumerate(principles, 1):
168
+ lines.append(f"{i}. {name} \u2014 {summary}")
169
+ lines.append("")
170
+
171
+ def _extract_values(self, text: str) -> list[tuple[str, str]]:
172
+ """Extract value name + summary pairs from identity core.md.
173
+
174
+ Looks for ### N. Name headings followed by bullet points.
175
+ Condenses all bullets into a comma-separated summary.
176
+ """
177
+ values: list[tuple[str, str]] = []
178
+ # Match ### N. Name followed by bullet list (within Values section)
179
+ pattern = r"###\s+\d+\.\s+(.+)\n((?:- .+\n)*)"
180
+ for match in re.finditer(pattern, text):
181
+ name = match.group(1).strip()
182
+ bullets = match.group(2).strip()
183
+ # Condense ALL bullets into a comma-separated short summary
184
+ bullet_texts = [
185
+ b.lstrip("- ").strip() for b in bullets.split("\n") if b.strip()
186
+ ]
187
+ if bullet_texts:
188
+ # Strip leading pronouns and clean up each bullet
189
+ cleaned: list[str] = []
190
+ for bt in bullet_texts:
191
+ # Remove "I'll " / "I'm " prefix for conciseness
192
+ bt = re.sub(r"^I'll\s+", "", bt)
193
+ bt = re.sub(r"^I'm not .+ — I'm ", "", bt)
194
+ bt = bt.rstrip(".")
195
+ # Remove surrounding quotes (only if both present)
196
+ if bt.startswith('"') and bt.endswith('"'):
197
+ bt = bt[1:-1]
198
+ # Lower-case the first char for inline style
199
+ if bt and bt[0].isupper():
200
+ bt = bt[0].lower() + bt[1:]
201
+ cleaned.append(bt)
202
+ summary = ", ".join(cleaned)
203
+ else:
204
+ summary = name
205
+ values.append((name, summary))
206
+ return values
207
+
208
+ def _extract_boundaries(self, text: str) -> tuple[list[str], list[str]]:
209
+ """Extract I Will / I Won't lists from identity core.md."""
210
+ i_will: list[str] = []
211
+ i_wont: list[str] = []
212
+
213
+ # Find "### I Will" section
214
+ will_match = re.search(
215
+ r"###\s+I Will\s*\n((?:- .+\n)*)", text, re.IGNORECASE
216
+ )
217
+ if will_match:
218
+ for line in will_match.group(1).strip().split("\n"):
219
+ item = line.lstrip("- ").strip()
220
+ if item:
221
+ # Lower-case and remove trailing periods for compact format
222
+ item = item[0].lower() + item[1:] if item else item
223
+ item = item.rstrip(".")
224
+ i_will.append(item)
225
+
226
+ # Find "### I Won't" section
227
+ wont_match = re.search(
228
+ r"###\s+I Won't\s*\n((?:- .+\n)*)", text, re.IGNORECASE
229
+ )
230
+ if wont_match:
231
+ for line in wont_match.group(1).strip().split("\n"):
232
+ item = line.lstrip("- ").strip()
233
+ if item:
234
+ item = item[0].lower() + item[1:] if item else item
235
+ item = item.rstrip(".")
236
+ i_wont.append(item)
237
+
238
+ return i_will, i_wont
239
+
240
+ def _extract_principles_from_identity(
241
+ self, text: str
242
+ ) -> list[tuple[str, str]]:
243
+ """Extract principles from the Internalized Philosophy table."""
244
+ principles: list[tuple[str, str]] = []
245
+ # Find the "Internalized Philosophy" section first to avoid
246
+ # matching other tables (e.g. comparison tables) in the file
247
+ philosophy_match = re.search(
248
+ r"##\s+Internalized Philosophy\s*\n(.*?)(?=\n---|\n##|\Z)",
249
+ text,
250
+ re.DOTALL,
251
+ )
252
+ if not philosophy_match:
253
+ return principles
254
+
255
+ section_text = philosophy_match.group(1)
256
+
257
+ # Look for table rows with | **Name** | description |
258
+ pattern = r"\|\s*\*\*(.+?)\*\*\s*\|\s*(.+?)\s*\|"
259
+ for match in re.finditer(pattern, section_text):
260
+ name = match.group(1).strip()
261
+ desc = match.group(2).strip()
262
+ # Skip table header separators
263
+ if name.startswith("-"):
264
+ continue
265
+ # Clean up description
266
+ desc = desc.rstrip(".")
267
+ if desc.startswith("I "):
268
+ # Condense: "I push back on over-engineering..." -> short form
269
+ desc = desc[0].lower() + desc[1:]
270
+ principles.append((name, desc))
271
+ return principles
272
+
273
+ def _add_process_rules_section(
274
+ self, lines: list[str], methodology: dict[str, Any]
275
+ ) -> None:
276
+ """Add Process Rules section from methodology.yaml."""
277
+ lines.append("## Process Rules")
278
+ lines.append("")
279
+
280
+ # Work Lifecycle
281
+ lifecycle = methodology.get("lifecycle", {})
282
+ if lifecycle:
283
+ lines.append("### Work Lifecycle")
284
+ for work_type in ["epic", "story", "session"]:
285
+ cfg = lifecycle.get(work_type, {})
286
+ flow = cfg.get("flow", "")
287
+ if flow:
288
+ lines.append(f"{work_type.upper()}: {flow}")
289
+ lines.append("")
290
+
291
+ # Gates
292
+ gates = methodology.get("gates", {})
293
+ blocking = gates.get("blocking", [])
294
+ quality = gates.get("quality", [])
295
+ if blocking or quality:
296
+ lines.append("### Gates")
297
+ for gate in blocking:
298
+ require = gate.get("require", "")
299
+ before = gate.get("before", "")
300
+ if require and before:
301
+ lines.append(f"- {require} before {before.lower()}")
302
+ for gate in quality:
303
+ gate_name = gate.get("gate", "")
304
+ when = gate.get("when", "")
305
+ if gate_name:
306
+ desc = f"- {gate_name}"
307
+ if when:
308
+ when_lower = when[0].lower() + when[1:] if when else when
309
+ desc += f" {when_lower}"
310
+ lines.append(desc)
311
+ lines.append("")
312
+
313
+ # Critical Rules from principles
314
+ principles = methodology.get("principles", {})
315
+ if principles:
316
+ lines.append("### Critical Rules")
317
+ for category in ["process", "collaboration", "technical"]:
318
+ category_principles = principles.get(category, [])
319
+ for p in category_principles:
320
+ name = p.get("name", "")
321
+ rule = p.get("rule", "")
322
+ rationale = p.get("rationale", "")
323
+ if name and rule:
324
+ line = f"- {name} \u2014 {rule}"
325
+ if rationale:
326
+ line += f" ({rationale.rstrip('.')})"
327
+ lines.append(line)
328
+ lines.append("")
329
+
330
+ def _add_branch_model_section(
331
+ self,
332
+ lines: list[str],
333
+ methodology: dict[str, Any],
334
+ dev_branch: str,
335
+ ) -> None:
336
+ """Add Branch Model section."""
337
+ lines.append("## Branch Model")
338
+
339
+ branches = methodology.get("branches", {})
340
+ flow_items = branches.get("flow", [])
341
+
342
+ # Build the one-liner: main (stable) -> dev (development) -> story/...
343
+ lines.append(
344
+ f"main (stable) \u2192 {dev_branch} (development) "
345
+ "\u2192 story/s{N}.{M}/{name}"
346
+ )
347
+
348
+ # Flow description
349
+ for flow_item in flow_items:
350
+ resolved = flow_item.replace("{development_branch}", dev_branch)
351
+ lines.append(resolved)
352
+ lines.append("")
353
+
354
+ def _add_cli_reference_section(self, lines: list[str]) -> None:
355
+ """Add CLI Quick Reference section (static content)."""
356
+ lines.append("## CLI Quick Reference")
357
+ lines.append("")
358
+ lines.append("### Core")
359
+ lines.append(
360
+ "- cmd: rai init | sig: [--name TEXT] [--path PATH] [--detect] "
361
+ "| notes: --detect analyzes conventions"
362
+ )
363
+ lines.append("")
364
+ lines.append("### Session")
365
+ lines.append(
366
+ "- cmd: rai session start | sig: [--name TEXT] [--project TEXT] "
367
+ "[--agent TEXT] [--context] | notes: --name first-time only, --context for bundle"
368
+ )
369
+ lines.append(
370
+ "- cmd: rai session close | sig: [--summary TEXT] [--type TEXT] "
371
+ "[--pattern TEXT] [--state-file TEXT] [--session TEXT] "
372
+ "| notes: --state-file for structured close, --pattern repeatable"
373
+ )
374
+ lines.append(
375
+ "- cmd: rai session context | sig: --sections/-s TEXT --project/-p TEXT "
376
+ "| notes: sections: governance,behavioral,coaching,deadlines,progress"
377
+ )
378
+ lines.append(
379
+ "- cmd: rai session journal add | sig: TEXT [--type TYPE] "
380
+ "| notes: add decision/insight/task to session"
381
+ )
382
+ lines.append(
383
+ "- cmd: rai session journal show | sig: [--compact] [--project TEXT] "
384
+ "| notes: --compact for post-compaction restore"
385
+ )
386
+ lines.append("")
387
+ lines.append("### Graph")
388
+ lines.append(
389
+ "- cmd: rai graph build | sig: [--output PATH] [--no-diff] "
390
+ "| notes: NO --project flag, runs from CWD"
391
+ )
392
+ lines.append(
393
+ "- cmd: rai graph query | sig: QUERY_STR [--types TYPE] "
394
+ "[--strategy keyword_search|concept_lookup] [--limit N] "
395
+ "[--format human|json|compact] | notes: QUERY_STR positional"
396
+ )
397
+ lines.append(
398
+ "- cmd: rai graph context | sig: MODULE_ID [--format human|json] "
399
+ "| notes: MODULE_ID positional (e.g. mod-memory)"
400
+ )
401
+ lines.append("")
402
+ lines.append("### Pattern")
403
+ lines.append(
404
+ "- cmd: rai pattern add | sig: CONTENT [--context KEYWORDS] "
405
+ "[--type TYPE] [--from STORY_ID] [--scope SCOPE] "
406
+ "| notes: CONTENT positional, --from NOT --source"
407
+ )
408
+ lines.append("")
409
+ lines.append("### Signal")
410
+ lines.append(
411
+ "- cmd: rai signal emit-work | sig: WORK_TYPE WORK_ID "
412
+ "[--event EVENT] [--phase PHASE] "
413
+ "| notes: WORK_TYPE=epic|story, EVENT=start|complete|blocked"
414
+ )
415
+ lines.append("")
416
+ lines.append("### Discovery")
417
+ lines.append(
418
+ "- cmd: rai discover scan | sig: [PATH] [--language LANG] "
419
+ "[--output human|json|summary] [--exclude PATTERN] "
420
+ "| notes: PATH positional, --exclude repeatable"
421
+ )
422
+ lines.append("")
423
+ lines.append("### Skill")
424
+ lines.append(
425
+ "- cmd: rai skill list|validate|check-name|scaffold "
426
+ "| sig: [SKILL_NAME] | notes: validate checks skill structure"
427
+ )
428
+ lines.append(
429
+ "- cmd: rai skill set create|list|diff "
430
+ "| sig: [SET_NAME] | notes: manage skill sets"
431
+ )
432
+ lines.append("")
433
+ lines.append("### Backlog (requires -a jira when multiple adapters)")
434
+ lines.append(
435
+ "- cmd: rai backlog create | sig: SUMMARY -p PROJECT [-t TYPE] "
436
+ "[-d DESC] [-l LABELS] [--parent KEY] "
437
+ "| notes: SUMMARY positional, -p required"
438
+ )
439
+ lines.append(
440
+ "- cmd: rai backlog search | sig: QUERY [-n LIMIT] [-a ADAPTER] "
441
+ "[-f FORMAT] | notes: QUERY positional, JQL for Jira"
442
+ )
443
+ lines.append(
444
+ "- cmd: rai backlog get | sig: KEY [-a ADAPTER] "
445
+ "| notes: single issue details"
446
+ )
447
+ lines.append(
448
+ "- cmd: rai backlog get-comments | sig: KEY [-a ADAPTER] "
449
+ "| notes: issue comments"
450
+ )
451
+ lines.append(
452
+ "- cmd: rai backlog transition | sig: KEY STATUS [-a ADAPTER] "
453
+ "| notes: both positional"
454
+ )
455
+ lines.append(
456
+ "- cmd: rai backlog batch-transition | sig: KEYS STATUS [-a ADAPTER] "
457
+ "| notes: KEYS comma-separated"
458
+ )
459
+ lines.append(
460
+ "- cmd: rai backlog comment | sig: KEY BODY [-a ADAPTER] "
461
+ "| notes: both positional"
462
+ )
463
+ lines.append(
464
+ "- cmd: rai backlog link | sig: SOURCE TARGET LINK_TYPE [-a ADAPTER] "
465
+ "| notes: all 3 positional"
466
+ )
467
+ lines.append(
468
+ "- cmd: rai backlog update | sig: KEY [-s SUMMARY] [-l LABELS] "
469
+ "[--priority TEXT] [--assignee TEXT] "
470
+ "| notes: KEY positional, named flags for fields"
471
+ )
472
+ lines.append("")
473
+ lines.append("### Docs (documentation targets \u2014 Confluence etc.)")
474
+ lines.append(
475
+ "- cmd: rai docs publish | sig: ARTIFACT_TYPE [--title TEXT] "
476
+ "[-t TARGET] | notes: ARTIFACT_TYPE positional (roadmap, adr, etc.)"
477
+ )
478
+ lines.append(
479
+ "- cmd: rai docs get | sig: IDENTIFIER [-t TARGET] "
480
+ "| notes: page ID on remote target"
481
+ )
482
+ lines.append(
483
+ "- cmd: rai docs search | sig: QUERY [-n LIMIT] [-t TARGET] "
484
+ "| notes: QUERY positional"
485
+ )
486
+ lines.append("")
487
+ lines.append("### MCP")
488
+ lines.append(
489
+ "- cmd: rai mcp list | notes: registered servers in .raise/mcp/"
490
+ )
491
+ lines.append(
492
+ "- cmd: rai mcp health | sig: SERVER | notes: SERVER positional"
493
+ )
494
+ lines.append(
495
+ "- cmd: rai mcp tools | sig: SERVER | notes: list tools on server"
496
+ )
497
+ lines.append(
498
+ "- cmd: rai mcp call | sig: SERVER TOOL [--args JSON] [--verbose] "
499
+ "| notes: both positional"
500
+ )
501
+ lines.append(
502
+ "- cmd: rai mcp install | sig: PACKAGE --type uvx|npx|pip --name TEXT "
503
+ "[--env TEXT] [--module TEXT] | notes: PACKAGE positional"
504
+ )
505
+ lines.append(
506
+ "- cmd: rai mcp scaffold | sig: NAME --command TEXT [--args TEXT] "
507
+ "[--env TEXT] | notes: NAME positional"
508
+ )
509
+ lines.append("")
510
+ lines.append("### Gate")
511
+ lines.append(
512
+ "- cmd: rai gate list | sig: [-f FORMAT] "
513
+ "| notes: discovered workflow gates"
514
+ )
515
+ lines.append(
516
+ "- cmd: rai gate check | sig: [GATE_ID] [--all/-a] [-f FORMAT] "
517
+ "| notes: exit 0 all pass, 1 any fail"
518
+ )
519
+ lines.append("")
520
+ lines.append("### Adapter")
521
+ lines.append(
522
+ "- cmd: rai adapter list | sig: [-f FORMAT] "
523
+ "| notes: registered adapters by entry point"
524
+ )
525
+ lines.append(
526
+ "- cmd: rai adapter check | sig: [-f FORMAT] "
527
+ "| notes: validate against Protocol contracts"
528
+ )
529
+ lines.append(
530
+ "- cmd: rai adapter validate | sig: FILE "
531
+ "| notes: validate declarative YAML adapter config"
532
+ )
533
+ lines.append("")
534
+ lines.append("### Release")
535
+ lines.append(
536
+ "- cmd: rai release check | sig: [-p PATH] "
537
+ "| notes: run 10 quality gates"
538
+ )
539
+ lines.append(
540
+ "- cmd: rai release publish | sig: --bump/-b "
541
+ "major|minor|patch|alpha|beta|rc|release "
542
+ "[--version/-v TEXT] [--dry-run] [--skip-check] "
543
+ "| notes: --bump or --version required"
544
+ )
545
+ lines.append("")
546
+ lines.append("### Common Mistakes")
547
+ lines.append(
548
+ "- wrong: rai graph build --project . | right: rai graph build "
549
+ "| why: no --project flag"
550
+ )
551
+ lines.append(
552
+ "- wrong: rai pattern add --content \"...\" | right: rai pattern add \"...\" "
553
+ "| why: CONTENT positional"
554
+ )
555
+ lines.append(
556
+ "- wrong: rai pattern add --source F1 | right: --from F1 "
557
+ "| why: flag is --from"
558
+ )
559
+ lines.append(
560
+ "- wrong: rai discover scan --input dir | right: rai discover scan dir "
561
+ "| why: PATH positional"
562
+ )
563
+ lines.append(
564
+ "- wrong: rai backlog create RAISE --summary \"Title\" "
565
+ "| right: rai backlog create \"Title\" -p RAISE "
566
+ "| why: SUMMARY positional, project is -p flag"
567
+ )
568
+ lines.append(
569
+ "- wrong: rai backlog link X Y --type blocks "
570
+ "| right: rai backlog link X Y blocks "
571
+ "| why: LINK_TYPE positional"
572
+ )
573
+ lines.append(
574
+ "- wrong: rai backlog update KEY --field summary=\"X\" "
575
+ "| right: rai backlog update KEY -s \"X\" "
576
+ "| why: named flags (-s, -l, --priority, --assignee)"
577
+ )
578
+ lines.append("")
579
+
580
+ def _add_integrations_section(self, lines: list[str]) -> None:
581
+ """Add External Integrations section."""
582
+ lines.append("## External Integrations")
583
+ lines.append(
584
+ "- Jira config: `.raise/jira.yaml` \u2014 team identifiers, workflows, "
585
+ "transition IDs. Read just-in-time via `rai backlog` CLI."
586
+ )
587
+ lines.append(
588
+ "- MCP servers: `.raise/mcp/*.yaml` \u2014 managed via "
589
+ "`rai mcp install|scaffold|list|health`."
590
+ )
591
+ lines.append(
592
+ "- Documentation targets: configured per adapter. "
593
+ "Use `rai docs publish|get|search`."
594
+ )
595
+ lines.append("")
596
+
597
+ def _add_file_operations_section(self, lines: list[str]) -> None:
598
+ """Add File Operations section (static content)."""
599
+ lines.append("## File Operations")
600
+ lines.append("- ALWAYS read files explicitly before editing them")
601
+ lines.append("- Use read tool first, then edit/write tools")
602
+ lines.append("- Never assume file context is loaded from previous turns")
603
+ lines.append("- After `/clear`, re-read all files you need to modify")
604
+ lines.append("")
605
+
606
+ def _add_post_compaction_section(self, lines: list[str]) -> None:
607
+ """Add Post-Compaction Context Restoration section (static content)."""
608
+ lines.append("## Post-Compaction Context Restoration")
609
+ lines.append(
610
+ "When you detect context was compacted "
611
+ "(continuation summary present), restore working state:"
612
+ )
613
+ lines.append(
614
+ "1. Read the session journal: "
615
+ "`uv run rai session journal show --compact --project .`"
616
+ )
617
+ lines.append(
618
+ "2. Read the current epic/story scope doc if referenced in journal"
619
+ )
620
+ lines.append("3. Summarize: where we are, what was decided, what's next")
621
+ lines.append(
622
+ "4. Continue work \u2014 do NOT re-run "
623
+ "`/rai-session-start` (session is already active)"
624
+ )
625
+ lines.append("")
626
+ lines.append(
627
+ "The PreCompact hook logs journal state "
628
+ "before compaction (side-effect only)."
629
+ )
630
+ lines.append(
631
+ "Post-compaction injection via hooks is broken "
632
+ "(Claude Code bugs #12671, #15174)."
633
+ )
634
+ lines.append("")
635
+
636
+
637
+ def generate_instructions(
638
+ project_name: str,
639
+ detection: DetectionResult,
640
+ conventions: ConventionResult | None = None,
641
+ *,
642
+ agent_config: AgentConfig | None = None,
643
+ project_path: Path,
644
+ ) -> str:
645
+ """Convenience function to generate agent instructions file content.
646
+
647
+ Args:
648
+ project_name: Name of the project.
649
+ detection: Project detection result.
650
+ conventions: Optional convention detection result (unused, kept
651
+ for backward compatibility).
652
+ agent_config: Agent configuration (unused, kept for backward
653
+ compatibility).
654
+ project_path: Project root path containing .raise/ directory.
655
+
656
+ Returns:
657
+ Markdown content for the agent's instructions file.
658
+
659
+ Example:
660
+ >>> detection = detect_project_type(Path("/my/project"))
661
+ >>> content = generate_instructions("my-api", detection, project_path=Path("/my/project"))
662
+ >>> Path("CLAUDE.md").write_text(content)
663
+ """
664
+ generator = InstructionsGenerator()
665
+ return generator.generate(
666
+ project_name, detection, conventions, project_path=project_path
667
+ )
668
+
669
+
670
+ # Backward-compat alias
671
+ ClaudeMdGenerator = InstructionsGenerator
672
+ generate_claude_md = generate_instructions