raise-cli 2.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- raise_cli/__init__.py +38 -0
- raise_cli/__main__.py +30 -0
- raise_cli/adapters/__init__.py +91 -0
- raise_cli/adapters/declarative/__init__.py +26 -0
- raise_cli/adapters/declarative/adapter.py +267 -0
- raise_cli/adapters/declarative/discovery.py +94 -0
- raise_cli/adapters/declarative/expressions.py +150 -0
- raise_cli/adapters/declarative/reference/__init__.py +1 -0
- raise_cli/adapters/declarative/reference/github.yaml +143 -0
- raise_cli/adapters/declarative/schema.py +98 -0
- raise_cli/adapters/filesystem.py +299 -0
- raise_cli/adapters/mcp_bridge.py +10 -0
- raise_cli/adapters/mcp_confluence.py +246 -0
- raise_cli/adapters/mcp_jira.py +405 -0
- raise_cli/adapters/models.py +205 -0
- raise_cli/adapters/protocols.py +180 -0
- raise_cli/adapters/registry.py +90 -0
- raise_cli/adapters/sync.py +149 -0
- raise_cli/agents/__init__.py +14 -0
- raise_cli/agents/antigravity.yaml +8 -0
- raise_cli/agents/claude.yaml +8 -0
- raise_cli/agents/copilot.yaml +8 -0
- raise_cli/agents/copilot_plugin.py +124 -0
- raise_cli/agents/cursor.yaml +7 -0
- raise_cli/agents/roo.yaml +8 -0
- raise_cli/agents/windsurf.yaml +8 -0
- raise_cli/artifacts/__init__.py +30 -0
- raise_cli/artifacts/models.py +43 -0
- raise_cli/artifacts/reader.py +55 -0
- raise_cli/artifacts/renderer.py +104 -0
- raise_cli/artifacts/story_design.py +69 -0
- raise_cli/artifacts/writer.py +45 -0
- raise_cli/backlog/__init__.py +1 -0
- raise_cli/backlog/sync.py +115 -0
- raise_cli/cli/__init__.py +3 -0
- raise_cli/cli/commands/__init__.py +3 -0
- raise_cli/cli/commands/_resolve.py +153 -0
- raise_cli/cli/commands/adapters.py +362 -0
- raise_cli/cli/commands/artifact.py +137 -0
- raise_cli/cli/commands/backlog.py +333 -0
- raise_cli/cli/commands/base.py +31 -0
- raise_cli/cli/commands/discover.py +551 -0
- raise_cli/cli/commands/docs.py +130 -0
- raise_cli/cli/commands/doctor.py +177 -0
- raise_cli/cli/commands/gate.py +223 -0
- raise_cli/cli/commands/graph.py +1086 -0
- raise_cli/cli/commands/info.py +81 -0
- raise_cli/cli/commands/init.py +746 -0
- raise_cli/cli/commands/journal.py +167 -0
- raise_cli/cli/commands/mcp.py +524 -0
- raise_cli/cli/commands/memory.py +467 -0
- raise_cli/cli/commands/pattern.py +348 -0
- raise_cli/cli/commands/profile.py +59 -0
- raise_cli/cli/commands/publish.py +80 -0
- raise_cli/cli/commands/release.py +338 -0
- raise_cli/cli/commands/session.py +528 -0
- raise_cli/cli/commands/signal.py +410 -0
- raise_cli/cli/commands/skill.py +350 -0
- raise_cli/cli/commands/skill_set.py +145 -0
- raise_cli/cli/error_handler.py +158 -0
- raise_cli/cli/main.py +163 -0
- raise_cli/compat.py +66 -0
- raise_cli/config/__init__.py +41 -0
- raise_cli/config/agent_plugin.py +105 -0
- raise_cli/config/agent_registry.py +233 -0
- raise_cli/config/agents.py +120 -0
- raise_cli/config/ide.py +32 -0
- raise_cli/config/paths.py +379 -0
- raise_cli/config/settings.py +180 -0
- raise_cli/context/__init__.py +42 -0
- raise_cli/context/analyzers/__init__.py +16 -0
- raise_cli/context/analyzers/models.py +36 -0
- raise_cli/context/analyzers/protocol.py +43 -0
- raise_cli/context/analyzers/python.py +292 -0
- raise_cli/context/builder.py +1569 -0
- raise_cli/context/diff.py +213 -0
- raise_cli/context/extractors/__init__.py +13 -0
- raise_cli/context/extractors/skills.py +121 -0
- raise_cli/core/__init__.py +37 -0
- raise_cli/core/files.py +66 -0
- raise_cli/core/text.py +174 -0
- raise_cli/core/tools.py +441 -0
- raise_cli/discovery/__init__.py +50 -0
- raise_cli/discovery/analyzer.py +691 -0
- raise_cli/discovery/drift.py +355 -0
- raise_cli/discovery/scanner.py +1687 -0
- raise_cli/doctor/__init__.py +4 -0
- raise_cli/doctor/checks/__init__.py +1 -0
- raise_cli/doctor/checks/environment.py +110 -0
- raise_cli/doctor/checks/project.py +238 -0
- raise_cli/doctor/fix.py +80 -0
- raise_cli/doctor/models.py +56 -0
- raise_cli/doctor/protocol.py +43 -0
- raise_cli/doctor/registry.py +100 -0
- raise_cli/doctor/report.py +141 -0
- raise_cli/doctor/runner.py +95 -0
- raise_cli/engines/__init__.py +3 -0
- raise_cli/exceptions.py +215 -0
- raise_cli/gates/__init__.py +19 -0
- raise_cli/gates/builtin/__init__.py +1 -0
- raise_cli/gates/builtin/coverage.py +52 -0
- raise_cli/gates/builtin/lint.py +48 -0
- raise_cli/gates/builtin/tests.py +48 -0
- raise_cli/gates/builtin/types.py +48 -0
- raise_cli/gates/models.py +40 -0
- raise_cli/gates/protocol.py +41 -0
- raise_cli/gates/registry.py +141 -0
- raise_cli/governance/__init__.py +11 -0
- raise_cli/governance/extractor.py +412 -0
- raise_cli/governance/models.py +134 -0
- raise_cli/governance/parsers/__init__.py +35 -0
- raise_cli/governance/parsers/_convert.py +38 -0
- raise_cli/governance/parsers/adr.py +274 -0
- raise_cli/governance/parsers/backlog.py +356 -0
- raise_cli/governance/parsers/constitution.py +119 -0
- raise_cli/governance/parsers/epic.py +323 -0
- raise_cli/governance/parsers/glossary.py +316 -0
- raise_cli/governance/parsers/guardrails.py +345 -0
- raise_cli/governance/parsers/prd.py +112 -0
- raise_cli/governance/parsers/roadmap.py +118 -0
- raise_cli/governance/parsers/vision.py +116 -0
- raise_cli/graph/__init__.py +1 -0
- raise_cli/graph/backends/__init__.py +57 -0
- raise_cli/graph/backends/api.py +137 -0
- raise_cli/graph/backends/dual.py +139 -0
- raise_cli/graph/backends/pending.py +84 -0
- raise_cli/handlers/__init__.py +3 -0
- raise_cli/hooks/__init__.py +54 -0
- raise_cli/hooks/builtin/__init__.py +1 -0
- raise_cli/hooks/builtin/backlog.py +216 -0
- raise_cli/hooks/builtin/gate_bridge.py +83 -0
- raise_cli/hooks/builtin/jira_sync.py +127 -0
- raise_cli/hooks/builtin/memory.py +117 -0
- raise_cli/hooks/builtin/telemetry.py +72 -0
- raise_cli/hooks/emitter.py +184 -0
- raise_cli/hooks/events.py +262 -0
- raise_cli/hooks/protocol.py +38 -0
- raise_cli/hooks/registry.py +117 -0
- raise_cli/mcp/__init__.py +33 -0
- raise_cli/mcp/bridge.py +218 -0
- raise_cli/mcp/models.py +43 -0
- raise_cli/mcp/registry.py +77 -0
- raise_cli/mcp/schema.py +41 -0
- raise_cli/memory/__init__.py +58 -0
- raise_cli/memory/loader.py +247 -0
- raise_cli/memory/migration.py +241 -0
- raise_cli/memory/models.py +169 -0
- raise_cli/memory/writer.py +598 -0
- raise_cli/onboarding/__init__.py +103 -0
- raise_cli/onboarding/bootstrap.py +324 -0
- raise_cli/onboarding/claudemd.py +17 -0
- raise_cli/onboarding/conventions.py +742 -0
- raise_cli/onboarding/detection.py +374 -0
- raise_cli/onboarding/governance.py +443 -0
- raise_cli/onboarding/instructions.py +672 -0
- raise_cli/onboarding/manifest.py +201 -0
- raise_cli/onboarding/memory_md.py +399 -0
- raise_cli/onboarding/migration.py +207 -0
- raise_cli/onboarding/profile.py +624 -0
- raise_cli/onboarding/skill_conflict.py +100 -0
- raise_cli/onboarding/skill_manifest.py +176 -0
- raise_cli/onboarding/skills.py +437 -0
- raise_cli/onboarding/workflows.py +101 -0
- raise_cli/output/__init__.py +28 -0
- raise_cli/output/console.py +394 -0
- raise_cli/output/formatters/__init__.py +9 -0
- raise_cli/output/formatters/adapters.py +135 -0
- raise_cli/output/formatters/discover.py +439 -0
- raise_cli/output/formatters/skill.py +298 -0
- raise_cli/publish/__init__.py +3 -0
- raise_cli/publish/changelog.py +80 -0
- raise_cli/publish/check.py +179 -0
- raise_cli/publish/version.py +172 -0
- raise_cli/rai_base/__init__.py +22 -0
- raise_cli/rai_base/framework/__init__.py +7 -0
- raise_cli/rai_base/framework/methodology.yaml +233 -0
- raise_cli/rai_base/governance/__init__.py +1 -0
- raise_cli/rai_base/governance/architecture/__init__.py +1 -0
- raise_cli/rai_base/governance/architecture/domain-model.md +20 -0
- raise_cli/rai_base/governance/architecture/system-context.md +34 -0
- raise_cli/rai_base/governance/architecture/system-design.md +24 -0
- raise_cli/rai_base/governance/backlog.md +8 -0
- raise_cli/rai_base/governance/guardrails.md +17 -0
- raise_cli/rai_base/governance/prd.md +25 -0
- raise_cli/rai_base/governance/vision.md +16 -0
- raise_cli/rai_base/identity/__init__.py +8 -0
- raise_cli/rai_base/identity/core.md +119 -0
- raise_cli/rai_base/identity/perspective.md +119 -0
- raise_cli/rai_base/memory/__init__.py +7 -0
- raise_cli/rai_base/memory/patterns-base.jsonl +55 -0
- raise_cli/schemas/__init__.py +3 -0
- raise_cli/schemas/journal.py +49 -0
- raise_cli/schemas/session_state.py +117 -0
- raise_cli/session/__init__.py +5 -0
- raise_cli/session/bundle.py +820 -0
- raise_cli/session/close.py +268 -0
- raise_cli/session/journal.py +119 -0
- raise_cli/session/resolver.py +126 -0
- raise_cli/session/state.py +187 -0
- raise_cli/skills/__init__.py +44 -0
- raise_cli/skills/locator.py +141 -0
- raise_cli/skills/name_checker.py +199 -0
- raise_cli/skills/parser.py +145 -0
- raise_cli/skills/scaffold.py +212 -0
- raise_cli/skills/schema.py +132 -0
- raise_cli/skills/skillsets.py +195 -0
- raise_cli/skills/validator.py +197 -0
- raise_cli/skills_base/__init__.py +80 -0
- raise_cli/skills_base/contract-template.md +60 -0
- raise_cli/skills_base/preamble.md +37 -0
- raise_cli/skills_base/rai-architecture-review/SKILL.md +137 -0
- raise_cli/skills_base/rai-debug/SKILL.md +171 -0
- raise_cli/skills_base/rai-discover/SKILL.md +167 -0
- raise_cli/skills_base/rai-discover-document/SKILL.md +128 -0
- raise_cli/skills_base/rai-discover-scan/SKILL.md +147 -0
- raise_cli/skills_base/rai-discover-start/SKILL.md +145 -0
- raise_cli/skills_base/rai-discover-validate/SKILL.md +142 -0
- raise_cli/skills_base/rai-docs-update/SKILL.md +142 -0
- raise_cli/skills_base/rai-doctor/SKILL.md +120 -0
- raise_cli/skills_base/rai-epic-close/SKILL.md +165 -0
- raise_cli/skills_base/rai-epic-close/templates/retrospective.md +68 -0
- raise_cli/skills_base/rai-epic-design/SKILL.md +146 -0
- raise_cli/skills_base/rai-epic-design/templates/design.md +24 -0
- raise_cli/skills_base/rai-epic-design/templates/scope.md +76 -0
- raise_cli/skills_base/rai-epic-plan/SKILL.md +153 -0
- raise_cli/skills_base/rai-epic-plan/_references/sequencing-strategies.md +67 -0
- raise_cli/skills_base/rai-epic-plan/templates/plan-section.md +49 -0
- raise_cli/skills_base/rai-epic-run/SKILL.md +208 -0
- raise_cli/skills_base/rai-epic-start/SKILL.md +136 -0
- raise_cli/skills_base/rai-epic-start/templates/brief.md +34 -0
- raise_cli/skills_base/rai-mcp-add/SKILL.md +176 -0
- raise_cli/skills_base/rai-mcp-remove/SKILL.md +120 -0
- raise_cli/skills_base/rai-mcp-status/SKILL.md +147 -0
- raise_cli/skills_base/rai-problem-shape/SKILL.md +138 -0
- raise_cli/skills_base/rai-project-create/SKILL.md +144 -0
- raise_cli/skills_base/rai-project-onboard/SKILL.md +162 -0
- raise_cli/skills_base/rai-quality-review/SKILL.md +189 -0
- raise_cli/skills_base/rai-research/SKILL.md +143 -0
- raise_cli/skills_base/rai-research/references/research-prompt-template.md +317 -0
- raise_cli/skills_base/rai-session-close/SKILL.md +176 -0
- raise_cli/skills_base/rai-session-start/SKILL.md +110 -0
- raise_cli/skills_base/rai-story-close/SKILL.md +198 -0
- raise_cli/skills_base/rai-story-design/SKILL.md +203 -0
- raise_cli/skills_base/rai-story-design/references/tech-design-story-v2.md +293 -0
- raise_cli/skills_base/rai-story-implement/SKILL.md +115 -0
- raise_cli/skills_base/rai-story-plan/SKILL.md +135 -0
- raise_cli/skills_base/rai-story-review/SKILL.md +178 -0
- raise_cli/skills_base/rai-story-run/SKILL.md +282 -0
- raise_cli/skills_base/rai-story-start/SKILL.md +166 -0
- raise_cli/skills_base/rai-story-start/templates/story.md +38 -0
- raise_cli/skills_base/rai-welcome/SKILL.md +134 -0
- raise_cli/telemetry/__init__.py +42 -0
- raise_cli/telemetry/schemas.py +285 -0
- raise_cli/telemetry/writer.py +217 -0
- raise_cli/tier/__init__.py +0 -0
- raise_cli/tier/context.py +134 -0
- raise_cli/viz/__init__.py +7 -0
- raise_cli/viz/generator.py +406 -0
- raise_cli-2.2.1.dist-info/METADATA +433 -0
- raise_cli-2.2.1.dist-info/RECORD +264 -0
- raise_cli-2.2.1.dist-info/WHEEL +4 -0
- raise_cli-2.2.1.dist-info/entry_points.txt +40 -0
- raise_cli-2.2.1.dist-info/licenses/LICENSE +190 -0
- raise_cli-2.2.1.dist-info/licenses/NOTICE +4 -0
raise_cli/core/tools.py
ADDED
|
@@ -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
|
+
]
|