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,441 @@
1
+ """Subprocess wrappers for external tools.
2
+
3
+ This module provides typed interfaces for external tools used by raise-cli:
4
+ - git: Version control operations
5
+ - ast-grep (sg): AST-based code search
6
+ - ripgrep (rg): Fast text search
7
+
8
+ Each wrapper checks tool availability and raises DependencyError if missing.
9
+
10
+ Example:
11
+ >>> from raise_cli.core.tools import git_root, check_tool
12
+ >>> if check_tool("git"):
13
+ ... root = git_root()
14
+ ... print(f"Git root: {root}")
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import shutil
20
+ import subprocess
21
+ from dataclasses import dataclass, field
22
+ from pathlib import Path
23
+
24
+ from raise_cli.exceptions import DependencyError
25
+
26
+
27
+ @dataclass
28
+ class ToolResult:
29
+ """Result from running an external tool.
30
+
31
+ Attributes:
32
+ returncode: Process exit code.
33
+ stdout: Standard output (stripped).
34
+ stderr: Standard error (stripped).
35
+ """
36
+
37
+ returncode: int
38
+ stdout: str
39
+ stderr: str
40
+
41
+ @property
42
+ def success(self) -> bool:
43
+ """Check if command succeeded."""
44
+ return self.returncode == 0
45
+
46
+
47
+ @dataclass
48
+ class GitStatus:
49
+ """Parsed git status output.
50
+
51
+ Attributes:
52
+ staged: Files staged for commit.
53
+ modified: Modified but unstaged files.
54
+ untracked: Untracked files.
55
+ branch: Current branch name.
56
+ """
57
+
58
+ staged: list[str] = field(default_factory=lambda: list[str]())
59
+ modified: list[str] = field(default_factory=lambda: list[str]())
60
+ untracked: list[str] = field(default_factory=lambda: list[str]())
61
+ branch: str = ""
62
+
63
+
64
+ @dataclass
65
+ class SearchMatch:
66
+ """A search match from rg or sg.
67
+
68
+ Attributes:
69
+ path: File path containing the match.
70
+ line: Line number (1-indexed).
71
+ text: Matched line text.
72
+ """
73
+
74
+ path: Path
75
+ line: int
76
+ text: str
77
+
78
+
79
+ def check_tool(name: str) -> bool:
80
+ """Check if an external tool is available.
81
+
82
+ Args:
83
+ name: Tool name (git, sg, rg).
84
+
85
+ Returns:
86
+ True if tool is available, False otherwise.
87
+
88
+ Example:
89
+ >>> check_tool("git")
90
+ True
91
+ """
92
+ return shutil.which(name) is not None
93
+
94
+
95
+ def require_tool(name: str) -> None:
96
+ """Require an external tool, raising if unavailable.
97
+
98
+ Args:
99
+ name: Tool name (git, sg, rg).
100
+
101
+ Raises:
102
+ DependencyError: If tool is not available.
103
+
104
+ Example:
105
+ >>> require_tool("nonexistent")
106
+ Traceback (most recent call last):
107
+ ...
108
+ raise_cli.exceptions.DependencyError: ...
109
+ """
110
+ if not check_tool(name):
111
+ hints = {
112
+ "git": "Install git: https://git-scm.com/downloads",
113
+ "sg": "Install ast-grep: https://ast-grep.github.io/",
114
+ "rg": "Install ripgrep: https://github.com/BurntSushi/ripgrep",
115
+ }
116
+ raise DependencyError(
117
+ f"Required tool '{name}' is not installed",
118
+ hint=hints.get(name, f"Install {name} and ensure it's in PATH"),
119
+ )
120
+
121
+
122
+ def run_tool(
123
+ args: list[str],
124
+ *,
125
+ cwd: Path | None = None,
126
+ check: bool = False,
127
+ ) -> ToolResult:
128
+ """Run an external tool and capture output.
129
+
130
+ Args:
131
+ args: Command and arguments.
132
+ cwd: Working directory. Defaults to current directory.
133
+ check: If True, raise on non-zero exit code.
134
+
135
+ Returns:
136
+ ToolResult with returncode, stdout, stderr.
137
+
138
+ Raises:
139
+ DependencyError: If tool is not available.
140
+ subprocess.CalledProcessError: If check=True and command fails.
141
+
142
+ Example:
143
+ >>> result = run_tool(["git", "status", "--porcelain"])
144
+ >>> print(result.stdout)
145
+ """
146
+ if not args:
147
+ raise ValueError("args must not be empty")
148
+
149
+ tool_name = args[0]
150
+ require_tool(tool_name)
151
+
152
+ result = subprocess.run(
153
+ args,
154
+ cwd=cwd,
155
+ capture_output=True,
156
+ text=True,
157
+ check=check,
158
+ )
159
+
160
+ return ToolResult(
161
+ returncode=result.returncode,
162
+ stdout=result.stdout.strip(),
163
+ stderr=result.stderr.strip(),
164
+ )
165
+
166
+
167
+ # -----------------------------------------------------------------------------
168
+ # Git Operations
169
+ # -----------------------------------------------------------------------------
170
+
171
+
172
+ def git_root(cwd: Path | None = None) -> Path:
173
+ """Get the root directory of the git repository.
174
+
175
+ Args:
176
+ cwd: Working directory. Defaults to current directory.
177
+
178
+ Returns:
179
+ Path to the git repository root.
180
+
181
+ Raises:
182
+ DependencyError: If git is not available or not in a repo.
183
+
184
+ Example:
185
+ >>> root = git_root()
186
+ >>> (root / ".git").exists()
187
+ True
188
+ """
189
+ result = run_tool(["git", "rev-parse", "--show-toplevel"], cwd=cwd)
190
+ if not result.success:
191
+ raise DependencyError(
192
+ "Not in a git repository",
193
+ hint="Run this command from within a git repository",
194
+ )
195
+ return Path(result.stdout)
196
+
197
+
198
+ def git_branch(cwd: Path | None = None) -> str:
199
+ """Get the current git branch name.
200
+
201
+ Args:
202
+ cwd: Working directory. Defaults to current directory.
203
+
204
+ Returns:
205
+ Current branch name.
206
+
207
+ Raises:
208
+ DependencyError: If git is not available or not in a repo.
209
+
210
+ Example:
211
+ >>> branch = git_branch()
212
+ >>> isinstance(branch, str)
213
+ True
214
+ """
215
+ result = run_tool(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=cwd)
216
+ if not result.success:
217
+ raise DependencyError(
218
+ "Cannot determine git branch",
219
+ hint="Ensure you're in a git repository with at least one commit",
220
+ )
221
+ return result.stdout
222
+
223
+
224
+ def git_status(cwd: Path | None = None) -> GitStatus:
225
+ """Get structured git status.
226
+
227
+ Args:
228
+ cwd: Working directory. Defaults to current directory.
229
+
230
+ Returns:
231
+ GitStatus with staged, modified, untracked files and branch.
232
+
233
+ Raises:
234
+ DependencyError: If git is not available.
235
+
236
+ Example:
237
+ >>> status = git_status()
238
+ >>> isinstance(status.staged, list)
239
+ True
240
+ """
241
+ status = GitStatus()
242
+
243
+ # Get branch
244
+ try:
245
+ status.branch = git_branch(cwd)
246
+ except DependencyError:
247
+ status.branch = ""
248
+
249
+ # Get file status with porcelain format
250
+ result = run_tool(["git", "status", "--porcelain"], cwd=cwd)
251
+ if not result.success:
252
+ return status
253
+
254
+ for line in result.stdout.splitlines():
255
+ if len(line) < 3:
256
+ continue
257
+
258
+ index_status = line[0]
259
+ worktree_status = line[1]
260
+ filepath = line[3:]
261
+
262
+ # Staged changes (index has changes)
263
+ if index_status in "MADRC":
264
+ status.staged.append(filepath)
265
+
266
+ # Modified in worktree (not staged)
267
+ if worktree_status == "M":
268
+ status.modified.append(filepath)
269
+
270
+ # Untracked
271
+ if index_status == "?" and worktree_status == "?":
272
+ status.untracked.append(filepath)
273
+
274
+ return status
275
+
276
+
277
+ def git_diff(staged: bool = False, cwd: Path | None = None) -> str:
278
+ """Get git diff output.
279
+
280
+ Args:
281
+ staged: If True, show staged changes only. Defaults to False.
282
+ cwd: Working directory. Defaults to current directory.
283
+
284
+ Returns:
285
+ Diff output as string.
286
+
287
+ Raises:
288
+ DependencyError: If git is not available.
289
+
290
+ Example:
291
+ >>> diff = git_diff(staged=True)
292
+ >>> isinstance(diff, str)
293
+ True
294
+ """
295
+ args = ["git", "diff"]
296
+ if staged:
297
+ args.append("--staged")
298
+
299
+ result = run_tool(args, cwd=cwd)
300
+ return result.stdout
301
+
302
+
303
+ # -----------------------------------------------------------------------------
304
+ # Ripgrep Operations
305
+ # -----------------------------------------------------------------------------
306
+
307
+
308
+ def rg_search(
309
+ pattern: str,
310
+ path: Path | None = None,
311
+ *,
312
+ glob: str | None = None,
313
+ ignore_case: bool = False,
314
+ ) -> list[SearchMatch]:
315
+ """Search files using ripgrep.
316
+
317
+ Args:
318
+ pattern: Regex pattern to search for.
319
+ path: Directory or file to search. Defaults to current directory.
320
+ glob: Glob pattern to filter files (e.g., "*.py").
321
+ ignore_case: If True, search case-insensitively.
322
+
323
+ Returns:
324
+ List of SearchMatch results.
325
+
326
+ Raises:
327
+ DependencyError: If ripgrep is not available.
328
+
329
+ Example:
330
+ >>> matches = rg_search("def ", Path("."), glob="*.py")
331
+ >>> all(isinstance(m, SearchMatch) for m in matches)
332
+ True
333
+ """
334
+ args = ["rg", "--line-number", "--no-heading", pattern]
335
+
336
+ if glob:
337
+ args.extend(["--glob", glob])
338
+
339
+ if ignore_case:
340
+ args.append("--ignore-case")
341
+
342
+ if path:
343
+ args.append(str(path))
344
+
345
+ result = run_tool(args)
346
+
347
+ matches: list[SearchMatch] = []
348
+ if not result.success:
349
+ return matches
350
+
351
+ for line in result.stdout.splitlines():
352
+ # Format: path:line:text
353
+ parts = line.split(":", 2)
354
+ if len(parts) >= 3:
355
+ matches.append(
356
+ SearchMatch(
357
+ path=Path(parts[0]),
358
+ line=int(parts[1]),
359
+ text=parts[2],
360
+ )
361
+ )
362
+
363
+ return matches
364
+
365
+
366
+ # -----------------------------------------------------------------------------
367
+ # ast-grep Operations
368
+ # -----------------------------------------------------------------------------
369
+
370
+
371
+ def sg_search(
372
+ pattern: str,
373
+ path: Path | None = None,
374
+ *,
375
+ lang: str | None = None,
376
+ ) -> list[SearchMatch]:
377
+ """Search files using ast-grep.
378
+
379
+ Args:
380
+ pattern: AST pattern to search for.
381
+ path: Directory or file to search. Defaults to current directory.
382
+ lang: Language to parse (e.g., "python", "typescript").
383
+
384
+ Returns:
385
+ List of SearchMatch results.
386
+
387
+ Raises:
388
+ DependencyError: If ast-grep is not available.
389
+
390
+ Example:
391
+ >>> matches = sg_search("def $NAME($$$ARGS)", Path("."), lang="python")
392
+ >>> all(isinstance(m, SearchMatch) for m in matches)
393
+ True
394
+ """
395
+ args = ["sg", "--pattern", pattern]
396
+
397
+ if lang:
398
+ args.extend(["--lang", lang])
399
+
400
+ if path:
401
+ args.append(str(path))
402
+
403
+ result = run_tool(args)
404
+
405
+ matches: list[SearchMatch] = []
406
+ if not result.success:
407
+ return matches
408
+
409
+ # ast-grep default output: path:line:column: matched text
410
+ for line in result.stdout.splitlines():
411
+ parts = line.split(":", 3)
412
+ if len(parts) >= 3:
413
+ try:
414
+ line_num = int(parts[1])
415
+ matches.append(
416
+ SearchMatch(
417
+ path=Path(parts[0]),
418
+ line=line_num,
419
+ text=parts[3] if len(parts) > 3 else "",
420
+ )
421
+ )
422
+ except ValueError:
423
+ continue
424
+
425
+ return matches
426
+
427
+
428
+ __all__ = [
429
+ "ToolResult",
430
+ "GitStatus",
431
+ "SearchMatch",
432
+ "check_tool",
433
+ "require_tool",
434
+ "run_tool",
435
+ "git_root",
436
+ "git_branch",
437
+ "git_status",
438
+ "git_diff",
439
+ "rg_search",
440
+ "sg_search",
441
+ ]
@@ -0,0 +1,50 @@
1
+ """Discovery module for codebase analysis.
2
+
3
+ This module provides tools to scan codebases and extract structural
4
+ information (classes, functions, modules) for the unified context graph.
5
+
6
+ Supports Python, TypeScript, and JavaScript via ast (Python) and
7
+ tree-sitter (TS/JS).
8
+
9
+ Architecture: Epic E13 Discovery
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from raise_cli.discovery.drift import (
15
+ DriftSeverity,
16
+ DriftWarning,
17
+ detect_drift,
18
+ )
19
+ from raise_cli.discovery.scanner import (
20
+ EXTENSION_TO_LANGUAGE,
21
+ Language,
22
+ ScanResult,
23
+ Symbol,
24
+ SymbolKind,
25
+ detect_language,
26
+ extract_javascript_symbols,
27
+ extract_python_symbols,
28
+ extract_symbols,
29
+ extract_typescript_symbols,
30
+ scan_directory,
31
+ )
32
+
33
+ __all__ = [
34
+ # Drift detection
35
+ "DriftWarning",
36
+ "DriftSeverity",
37
+ "detect_drift",
38
+ # Scanner
39
+ "Symbol",
40
+ "SymbolKind",
41
+ "Language",
42
+ "ScanResult",
43
+ "EXTENSION_TO_LANGUAGE",
44
+ "detect_language",
45
+ "extract_symbols",
46
+ "extract_python_symbols",
47
+ "extract_typescript_symbols",
48
+ "extract_javascript_symbols",
49
+ "scan_directory",
50
+ ]