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
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Configuration settings for raise-cli using Pydantic Settings.
|
|
2
|
+
|
|
3
|
+
Implements configuration cascade with proper precedence:
|
|
4
|
+
1. CLI arguments (highest priority)
|
|
5
|
+
2. Environment variables (RAI_* prefix)
|
|
6
|
+
3. Project config (pyproject.toml)
|
|
7
|
+
4. User config (~/.config/rai/config.toml)
|
|
8
|
+
5. Defaults (lowest priority)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Literal
|
|
16
|
+
|
|
17
|
+
from pydantic import Field
|
|
18
|
+
from pydantic_settings import (
|
|
19
|
+
BaseSettings,
|
|
20
|
+
PydanticBaseSettingsSource,
|
|
21
|
+
SettingsConfigDict,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
if sys.version_info >= (3, 11): # noqa: UP036
|
|
25
|
+
import tomllib
|
|
26
|
+
else:
|
|
27
|
+
import tomli as tomllib
|
|
28
|
+
|
|
29
|
+
from raise_cli.config.paths import get_config_dir
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TomlConfigSource(PydanticBaseSettingsSource):
|
|
33
|
+
"""Custom settings source for TOML configuration files."""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
settings_cls: type[BaseSettings],
|
|
38
|
+
toml_file: Path | None = None,
|
|
39
|
+
toml_table: str = "rai",
|
|
40
|
+
):
|
|
41
|
+
"""Initialize TOML config source.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
settings_cls: The settings class
|
|
45
|
+
toml_file: Path to TOML file (None = auto-detect)
|
|
46
|
+
toml_table: Table name in TOML file (default: "rai")
|
|
47
|
+
"""
|
|
48
|
+
super().__init__(settings_cls)
|
|
49
|
+
self.toml_file = toml_file
|
|
50
|
+
self.toml_table = toml_table
|
|
51
|
+
|
|
52
|
+
def get_field_value(self, field: Any, field_name: str) -> tuple[Any, str, bool]:
|
|
53
|
+
"""Get field value from TOML file."""
|
|
54
|
+
# Not used in newer pydantic-settings
|
|
55
|
+
return None, "", False
|
|
56
|
+
|
|
57
|
+
def __call__(self) -> dict[str, Any]:
|
|
58
|
+
"""Load settings from TOML file."""
|
|
59
|
+
if self.toml_file is None or not self.toml_file.exists():
|
|
60
|
+
return {}
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
with open(self.toml_file, "rb") as f:
|
|
64
|
+
data = tomllib.load(f)
|
|
65
|
+
|
|
66
|
+
# Handle pyproject.toml with [tool.rai] section
|
|
67
|
+
if self.toml_file.name == "pyproject.toml":
|
|
68
|
+
return data.get("tool", {}).get(self.toml_table, {})
|
|
69
|
+
# Handle user config with [rai] section
|
|
70
|
+
else:
|
|
71
|
+
return data.get(self.toml_table, {})
|
|
72
|
+
except Exception:
|
|
73
|
+
# Silently ignore TOML parsing errors (graceful degradation)
|
|
74
|
+
return {}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class RaiSettings(BaseSettings):
|
|
78
|
+
"""Configuration settings for raise-cli with proper cascade precedence.
|
|
79
|
+
|
|
80
|
+
Settings are loaded in order of precedence (highest to lowest):
|
|
81
|
+
1. Constructor arguments (from CLI)
|
|
82
|
+
2. Environment variables (RAI_* prefix)
|
|
83
|
+
3. Project pyproject.toml [tool.rai] section
|
|
84
|
+
4. User config file (~/.config/rai/config.toml)
|
|
85
|
+
5. Field defaults
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
>>> # Load with defaults and env vars
|
|
89
|
+
>>> settings = RaiSettings()
|
|
90
|
+
>>> # Override specific values (e.g., from CLI)
|
|
91
|
+
>>> settings = RaiSettings(output_format="json", verbosity=2)
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
model_config = SettingsConfigDict(
|
|
95
|
+
env_prefix="RAI_",
|
|
96
|
+
extra="ignore",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Output settings
|
|
100
|
+
output_format: Literal["human", "json", "table"] = Field(
|
|
101
|
+
default="human",
|
|
102
|
+
description="Output format for CLI commands",
|
|
103
|
+
)
|
|
104
|
+
color: bool = Field(
|
|
105
|
+
default=True,
|
|
106
|
+
description="Enable colored output",
|
|
107
|
+
)
|
|
108
|
+
verbosity: int = Field(
|
|
109
|
+
default=0,
|
|
110
|
+
ge=-1,
|
|
111
|
+
le=3,
|
|
112
|
+
description="Verbosity level: -1=quiet, 0=normal, 1-3=verbose",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Paths (project-level)
|
|
116
|
+
raise_dir: Path = Field(
|
|
117
|
+
default=Path(".raise"),
|
|
118
|
+
description="Directory containing RaiSE framework files",
|
|
119
|
+
)
|
|
120
|
+
governance_dir: Path = Field(
|
|
121
|
+
default=Path("governance"),
|
|
122
|
+
description="Directory containing governance artifacts",
|
|
123
|
+
)
|
|
124
|
+
work_dir: Path = Field(
|
|
125
|
+
default=Path("work"),
|
|
126
|
+
description="Directory containing active work",
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# External tools (graceful degradation handled by core utilities)
|
|
130
|
+
ast_grep_path: str | None = Field(
|
|
131
|
+
default=None,
|
|
132
|
+
description="Path to ast-grep binary (auto-detected if None)",
|
|
133
|
+
)
|
|
134
|
+
ripgrep_path: str | None = Field(
|
|
135
|
+
default=None,
|
|
136
|
+
description="Path to ripgrep binary (auto-detected if None)",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Feature flags
|
|
140
|
+
interactive: bool = Field(
|
|
141
|
+
default=False,
|
|
142
|
+
description="Enable interactive prompts",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def settings_customise_sources(
|
|
147
|
+
cls,
|
|
148
|
+
settings_cls: type[BaseSettings],
|
|
149
|
+
init_settings: PydanticBaseSettingsSource,
|
|
150
|
+
env_settings: PydanticBaseSettingsSource,
|
|
151
|
+
dotenv_settings: PydanticBaseSettingsSource,
|
|
152
|
+
file_secret_settings: PydanticBaseSettingsSource,
|
|
153
|
+
) -> tuple[PydanticBaseSettingsSource, ...]:
|
|
154
|
+
"""Customize settings sources to include TOML files.
|
|
155
|
+
|
|
156
|
+
Order (highest to lowest priority):
|
|
157
|
+
1. init_settings (constructor args / CLI)
|
|
158
|
+
2. env_settings (environment variables)
|
|
159
|
+
3. project_toml (pyproject.toml)
|
|
160
|
+
4. user_toml (~/.config/rai/config.toml)
|
|
161
|
+
5. file defaults (from Field definitions)
|
|
162
|
+
"""
|
|
163
|
+
# Project-level config
|
|
164
|
+
project_toml = TomlConfigSource(
|
|
165
|
+
settings_cls, toml_file=Path("pyproject.toml"), toml_table="rai"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# User-level config
|
|
169
|
+
user_config_file = get_config_dir() / "config.toml"
|
|
170
|
+
user_toml = TomlConfigSource(
|
|
171
|
+
settings_cls, toml_file=user_config_file, toml_table="rai"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
init_settings, # CLI args (highest priority)
|
|
176
|
+
env_settings, # Environment variables
|
|
177
|
+
project_toml, # pyproject.toml
|
|
178
|
+
user_toml, # User config
|
|
179
|
+
# Field defaults are always last (implicit)
|
|
180
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Context module for cross-domain knowledge retrieval.
|
|
2
|
+
|
|
3
|
+
Re-exports graph domain types from raise_core and local context components.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from raise_cli.context.builder import GraphBuilder
|
|
9
|
+
from raise_cli.context.diff import GraphDiff, NodeChange, diff_graphs
|
|
10
|
+
from raise_core.graph.engine import Graph
|
|
11
|
+
from raise_core.graph.models import (
|
|
12
|
+
CoreEdgeTypes,
|
|
13
|
+
EdgeType,
|
|
14
|
+
GraphEdge,
|
|
15
|
+
GraphNode,
|
|
16
|
+
NodeType,
|
|
17
|
+
)
|
|
18
|
+
from raise_core.graph.query import (
|
|
19
|
+
Query,
|
|
20
|
+
QueryEngine,
|
|
21
|
+
QueryMetadata,
|
|
22
|
+
QueryResult,
|
|
23
|
+
QueryStrategy,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"CoreEdgeTypes",
|
|
28
|
+
"EdgeType",
|
|
29
|
+
"Graph",
|
|
30
|
+
"GraphDiff",
|
|
31
|
+
"GraphEdge",
|
|
32
|
+
"GraphNode",
|
|
33
|
+
"NodeChange",
|
|
34
|
+
"NodeType",
|
|
35
|
+
"Query",
|
|
36
|
+
"QueryEngine",
|
|
37
|
+
"QueryMetadata",
|
|
38
|
+
"QueryResult",
|
|
39
|
+
"QueryStrategy",
|
|
40
|
+
"GraphBuilder",
|
|
41
|
+
"diff_graphs",
|
|
42
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Code analyzers for extracting module structure from source code.
|
|
2
|
+
|
|
3
|
+
This package provides language-specific analyzers that extract imports,
|
|
4
|
+
exports, and component counts from source modules. Results enrich the
|
|
5
|
+
unified graph with real code data alongside manually-maintained frontmatter.
|
|
6
|
+
|
|
7
|
+
Architecture: S16.1 — Code-Aware Graph
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from raise_cli.context.analyzers.models import ModuleInfo
|
|
13
|
+
from raise_cli.context.analyzers.protocol import CodeAnalyzer
|
|
14
|
+
from raise_cli.context.analyzers.python import PythonAnalyzer
|
|
15
|
+
|
|
16
|
+
__all__ = ["CodeAnalyzer", "ModuleInfo", "PythonAnalyzer"]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Pydantic models for code analysis results.
|
|
2
|
+
|
|
3
|
+
Architecture: S16.1 — Code-Aware Graph
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ModuleInfo(BaseModel):
|
|
12
|
+
"""Language-agnostic module analysis result.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
name: Module name (e.g., 'memory', 'config').
|
|
16
|
+
language: Programming language (e.g., 'python').
|
|
17
|
+
source_path: Relative path to the module directory.
|
|
18
|
+
imports: Other modules this one imports from.
|
|
19
|
+
exports: Public API names exported by this module.
|
|
20
|
+
component_count: Number of classes + top-level functions.
|
|
21
|
+
entry_points: CLI commands or other entry points, if detectable.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
name: str = Field(..., description="Module name")
|
|
25
|
+
language: str = Field(..., description="Programming language")
|
|
26
|
+
source_path: str = Field(..., description="Relative path to module directory")
|
|
27
|
+
imports: list[str] = Field(
|
|
28
|
+
default_factory=lambda: list[str](), description="Imported module names"
|
|
29
|
+
)
|
|
30
|
+
exports: list[str] = Field(
|
|
31
|
+
default_factory=lambda: list[str](), description="Exported public API names"
|
|
32
|
+
)
|
|
33
|
+
component_count: int = Field(..., description="Classes + top-level functions")
|
|
34
|
+
entry_points: list[str] = Field(
|
|
35
|
+
default_factory=lambda: list[str](), description="CLI commands or entry points"
|
|
36
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Protocol definition for code analyzers.
|
|
2
|
+
|
|
3
|
+
Architecture: S16.1 — Code-Aware Graph
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Protocol, runtime_checkable
|
|
10
|
+
|
|
11
|
+
from raise_cli.context.analyzers.models import ModuleInfo
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@runtime_checkable
|
|
15
|
+
class CodeAnalyzer(Protocol):
|
|
16
|
+
"""Contract for language-specific code analyzers.
|
|
17
|
+
|
|
18
|
+
Implementations must provide:
|
|
19
|
+
- detect(): Check if the project uses this language.
|
|
20
|
+
- analyze_modules(): Extract module-level structure.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def detect(self, project_root: Path) -> bool:
|
|
24
|
+
"""Check if this analyzer applies to the given project.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
project_root: Root directory of the project.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
True if the project uses this language.
|
|
31
|
+
"""
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
def analyze_modules(self, project_root: Path) -> list[ModuleInfo]:
|
|
35
|
+
"""Extract module-level structure from source code.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
project_root: Root directory of the project.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
List of ModuleInfo for each detected module.
|
|
42
|
+
"""
|
|
43
|
+
...
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Python-specific code analyzer using ast module.
|
|
2
|
+
|
|
3
|
+
Extracts imports, exports, and component counts from Python modules
|
|
4
|
+
by parsing source files with ast.parse().
|
|
5
|
+
|
|
6
|
+
Architecture: S16.1 — Code-Aware Graph
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import ast
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from raise_cli.compat import portable_path
|
|
15
|
+
from raise_cli.context.analyzers.models import ModuleInfo
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PythonAnalyzer:
|
|
19
|
+
"""Analyzes Python modules using ast to extract structure.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
src_dir: Relative path to the source directory (e.g., 'src/raise_cli').
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, src_dir: str) -> None:
|
|
26
|
+
self.src_dir = src_dir
|
|
27
|
+
self._package_name = Path(src_dir).name
|
|
28
|
+
|
|
29
|
+
def detect(self, project_root: Path) -> bool:
|
|
30
|
+
"""Check if the project is a Python project.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
project_root: Root directory of the project.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
True if pyproject.toml or setup.py exists.
|
|
37
|
+
"""
|
|
38
|
+
return (project_root / "pyproject.toml").exists() or (
|
|
39
|
+
project_root / "setup.py"
|
|
40
|
+
).exists()
|
|
41
|
+
|
|
42
|
+
def analyze_modules(self, project_root: Path) -> list[ModuleInfo]:
|
|
43
|
+
"""Extract module-level structure from all Python modules.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
project_root: Root directory of the project.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
List of ModuleInfo for each module directory.
|
|
50
|
+
"""
|
|
51
|
+
src_path = project_root / self.src_dir
|
|
52
|
+
if not src_path.exists():
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
modules: list[ModuleInfo] = []
|
|
56
|
+
for entry in sorted(src_path.iterdir()):
|
|
57
|
+
if not entry.is_dir():
|
|
58
|
+
continue
|
|
59
|
+
if entry.name.startswith("__"):
|
|
60
|
+
continue
|
|
61
|
+
if not (entry / "__init__.py").exists():
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
info = self._analyze_module(entry, project_root)
|
|
65
|
+
modules.append(info)
|
|
66
|
+
|
|
67
|
+
return modules
|
|
68
|
+
|
|
69
|
+
def _analyze_module(self, module_dir: Path, project_root: Path) -> ModuleInfo:
|
|
70
|
+
"""Analyze a single module directory.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
module_dir: Path to the module directory.
|
|
74
|
+
project_root: Root directory of the project.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
ModuleInfo with extracted data.
|
|
78
|
+
"""
|
|
79
|
+
module_name = module_dir.name
|
|
80
|
+
py_files = sorted(module_dir.rglob("*.py"))
|
|
81
|
+
|
|
82
|
+
imports: set[str] = set()
|
|
83
|
+
component_count = 0
|
|
84
|
+
|
|
85
|
+
for py_file in py_files:
|
|
86
|
+
tree = self._parse_file(py_file)
|
|
87
|
+
if tree is None:
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
# Skip __init__.py for imports (handled separately for exports)
|
|
91
|
+
if py_file.name != "__init__.py":
|
|
92
|
+
file_imports = self._extract_imports(tree, module_name)
|
|
93
|
+
imports.update(file_imports)
|
|
94
|
+
else:
|
|
95
|
+
# __init__.py imports from siblings also count as module deps
|
|
96
|
+
file_imports = self._extract_imports(tree, module_name)
|
|
97
|
+
imports.update(file_imports)
|
|
98
|
+
|
|
99
|
+
component_count += self._count_components(tree)
|
|
100
|
+
|
|
101
|
+
exports = self._extract_exports(module_dir)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
source_path = portable_path(module_dir, project_root)
|
|
105
|
+
except ValueError:
|
|
106
|
+
source_path = str(module_dir)
|
|
107
|
+
|
|
108
|
+
return ModuleInfo(
|
|
109
|
+
name=module_name,
|
|
110
|
+
language="python",
|
|
111
|
+
source_path=source_path,
|
|
112
|
+
imports=sorted(imports),
|
|
113
|
+
exports=sorted(exports),
|
|
114
|
+
component_count=component_count,
|
|
115
|
+
entry_points=[],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def _parse_file(self, py_file: Path) -> ast.Module | None:
|
|
119
|
+
"""Parse a Python file into AST.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
py_file: Path to the .py file.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Parsed AST or None on failure.
|
|
126
|
+
"""
|
|
127
|
+
try:
|
|
128
|
+
source = py_file.read_text(encoding="utf-8")
|
|
129
|
+
return ast.parse(source, filename=str(py_file))
|
|
130
|
+
except (SyntaxError, UnicodeDecodeError):
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
def _extract_imports(self, tree: ast.Module, module_name: str) -> set[str]:
|
|
134
|
+
"""Extract internal module imports, skipping TYPE_CHECKING blocks.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
tree: Parsed AST.
|
|
138
|
+
module_name: Name of the current module (to exclude self-imports).
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Set of imported module names (siblings only).
|
|
142
|
+
"""
|
|
143
|
+
visitor = _ImportVisitor(self._package_name, module_name)
|
|
144
|
+
visitor.visit(tree)
|
|
145
|
+
return visitor.imports
|
|
146
|
+
|
|
147
|
+
def _extract_exports(self, module_dir: Path) -> list[str]:
|
|
148
|
+
"""Extract public API from __init__.py.
|
|
149
|
+
|
|
150
|
+
Uses __all__ if present, otherwise imported names.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
module_dir: Path to the module directory.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
List of exported names.
|
|
157
|
+
"""
|
|
158
|
+
init_file = module_dir / "__init__.py"
|
|
159
|
+
if not init_file.exists():
|
|
160
|
+
return []
|
|
161
|
+
|
|
162
|
+
tree = self._parse_file(init_file)
|
|
163
|
+
if tree is None:
|
|
164
|
+
return []
|
|
165
|
+
|
|
166
|
+
# Check for __all__
|
|
167
|
+
for node in ast.walk(tree):
|
|
168
|
+
if isinstance(node, ast.Assign):
|
|
169
|
+
for target in node.targets:
|
|
170
|
+
if isinstance(target, ast.Name) and target.id == "__all__":
|
|
171
|
+
return self._extract_all_list(node.value)
|
|
172
|
+
|
|
173
|
+
# Fallback: extract imported names from __init__.py
|
|
174
|
+
names: list[str] = []
|
|
175
|
+
for node in ast.iter_child_nodes(tree):
|
|
176
|
+
if isinstance(node, ast.ImportFrom) and node.names:
|
|
177
|
+
for alias in node.names:
|
|
178
|
+
name = alias.asname if alias.asname else alias.name
|
|
179
|
+
if not name.startswith("_"):
|
|
180
|
+
names.append(name)
|
|
181
|
+
return names
|
|
182
|
+
|
|
183
|
+
def _extract_all_list(self, node: ast.expr) -> list[str]:
|
|
184
|
+
"""Extract string values from an __all__ assignment.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
node: The value node of the __all__ assignment.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
List of names from __all__.
|
|
191
|
+
"""
|
|
192
|
+
names: list[str] = []
|
|
193
|
+
if isinstance(node, (ast.List, ast.Tuple)):
|
|
194
|
+
for elt in node.elts:
|
|
195
|
+
if isinstance(elt, ast.Constant) and isinstance(elt.value, str):
|
|
196
|
+
names.append(elt.value)
|
|
197
|
+
return names
|
|
198
|
+
|
|
199
|
+
def _count_components(self, tree: ast.Module) -> int:
|
|
200
|
+
"""Count top-level classes and functions in a module.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
tree: Parsed AST.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Count of top-level class and function definitions.
|
|
207
|
+
"""
|
|
208
|
+
count = 0
|
|
209
|
+
for node in ast.iter_child_nodes(tree):
|
|
210
|
+
if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
211
|
+
count += 1
|
|
212
|
+
return count
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class _ImportVisitor(ast.NodeVisitor):
|
|
216
|
+
"""AST visitor that extracts imports while skipping TYPE_CHECKING blocks.
|
|
217
|
+
|
|
218
|
+
Attributes:
|
|
219
|
+
package_name: Top-level package name (e.g., 'raise_cli').
|
|
220
|
+
module_name: Current module name (to exclude self-imports).
|
|
221
|
+
imports: Set of discovered sibling module names.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
def __init__(self, package_name: str, module_name: str) -> None:
|
|
225
|
+
self.package_name = package_name
|
|
226
|
+
self.module_name = module_name
|
|
227
|
+
self.imports: set[str] = set()
|
|
228
|
+
self._in_type_checking = False
|
|
229
|
+
|
|
230
|
+
def visit_If(self, node: ast.If) -> None: # noqa: N802
|
|
231
|
+
"""Skip imports inside TYPE_CHECKING blocks."""
|
|
232
|
+
if self._is_type_checking(node.test):
|
|
233
|
+
# Don't visit the body — skip TYPE_CHECKING imports
|
|
234
|
+
for child in node.orelse:
|
|
235
|
+
self.visit(child)
|
|
236
|
+
else:
|
|
237
|
+
self.generic_visit(node)
|
|
238
|
+
|
|
239
|
+
def visit_ImportFrom(self, node: ast.ImportFrom) -> None: # noqa: N802
|
|
240
|
+
"""Extract module name from 'from X import Y' statements."""
|
|
241
|
+
if self._in_type_checking:
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
if node.module and node.level == 0:
|
|
245
|
+
# Absolute import: from pkg.sibling.foo import bar
|
|
246
|
+
self._resolve_absolute(node.module)
|
|
247
|
+
elif node.level > 0 and node.module:
|
|
248
|
+
# Relative import: from ..sibling import bar
|
|
249
|
+
self._resolve_relative(node.module, node.level)
|
|
250
|
+
elif node.level > 0 and node.names:
|
|
251
|
+
# Relative import without module: from .. import sibling
|
|
252
|
+
for alias in node.names:
|
|
253
|
+
if node.level >= 2:
|
|
254
|
+
# from ..sibling means the name IS the sibling module
|
|
255
|
+
imported = alias.name
|
|
256
|
+
if imported != self.module_name:
|
|
257
|
+
self.imports.add(imported)
|
|
258
|
+
|
|
259
|
+
def visit_Import(self, node: ast.Import) -> None: # noqa: N802
|
|
260
|
+
"""Extract module name from 'import X' statements."""
|
|
261
|
+
if self._in_type_checking:
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
for alias in node.names:
|
|
265
|
+
self._resolve_absolute(alias.name)
|
|
266
|
+
|
|
267
|
+
def _resolve_absolute(self, module_path: str) -> None:
|
|
268
|
+
"""Resolve an absolute import to a sibling module name."""
|
|
269
|
+
parts = module_path.split(".")
|
|
270
|
+
# Must start with our package name
|
|
271
|
+
if parts[0] != self.package_name:
|
|
272
|
+
return
|
|
273
|
+
if len(parts) < 2:
|
|
274
|
+
return
|
|
275
|
+
sibling = parts[1]
|
|
276
|
+
if sibling != self.module_name:
|
|
277
|
+
self.imports.add(sibling)
|
|
278
|
+
|
|
279
|
+
def _resolve_relative(self, module_path: str, level: int) -> None:
|
|
280
|
+
"""Resolve a relative import to a sibling module name."""
|
|
281
|
+
if level >= 2:
|
|
282
|
+
# from ..sibling.foo import bar → sibling is the module
|
|
283
|
+
parts = module_path.split(".")
|
|
284
|
+
sibling = parts[0]
|
|
285
|
+
if sibling != self.module_name:
|
|
286
|
+
self.imports.add(sibling)
|
|
287
|
+
|
|
288
|
+
def _is_type_checking(self, test: ast.expr) -> bool:
|
|
289
|
+
"""Check if an if-test is TYPE_CHECKING."""
|
|
290
|
+
return (isinstance(test, ast.Name) and test.id == "TYPE_CHECKING") or (
|
|
291
|
+
isinstance(test, ast.Attribute) and test.attr == "TYPE_CHECKING"
|
|
292
|
+
)
|